9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
520 lines
22 KiB
Python
520 lines
22 KiB
Python
"""Mutation harness for candidate B (two-counter) model.
|
|
|
|
Each mutant: subclass DeviceB with a weakened rule, run the BFS explorer,
|
|
require a recorded counterexample. A model that stays green under a real
|
|
weakening is worthless.
|
|
|
|
Mutants:
|
|
M1: drop baseline dominance (frontier > B no longer clears stale set)
|
|
M2: drop max(S,C)+1 bump (use S+1 or C+1 — counter can regress)
|
|
M3: flip tie policy (verify the model distinguishes them)
|
|
M4: revert to delete-on-dominance compaction (drops the tombstone floor
|
|
entirely instead of zeroing S and keeping max(S,C) as C) — reproduces
|
|
Thufir's pass-3 CRITICAL: stale-replay resurrection after counter reuse
|
|
M5: uint32 overflow bypass (legacy sanitization disabled)
|
|
M6: componentwise-max -> last-write-wins merge (convergence breaks)
|
|
M7: publish without canonicalization (serialize raw registers instead
|
|
of the compact-at-publish canonical form) — reproduces Thufir's
|
|
pass-1/2 CRITICAL: dead+dead merge resurrection
|
|
M8: revert split_blob_into_slots to per-entry splitting (violates the
|
|
atomic-grouping rule) — reproduces Thufir's pass-2/2 CRITICAL:
|
|
partial-slot reconstruction of a live RegB creates a false tombstone
|
|
that permanently suppresses the override after eventual full delivery
|
|
M9: revert split_blob_into_slots to escaped-key grouping (groups frontier
|
|
by wire key instead of unescaped logical ID) — reproduces Thufir's
|
|
round-2 CRITICAL: for a context whose raw ID starts with a reserved
|
|
prefix (e.g. "ov_s:evil"), the frontier's escaped wire key
|
|
("esc:ov_s:evil") and the ov_* siblings (keyed by raw suffix "ov_s:evil")
|
|
resolve to different groups → register split across slots →
|
|
old/new slot-coordinate mixture produces partial reconstruction →
|
|
false tombstone → permanent false clear across publication cycles
|
|
|
|
Each mutant is injected into the model via DeviceB subclass, then the
|
|
explorer or invariant suite is rerun. The counterexample (first violation)
|
|
is recorded and printed.
|
|
"""
|
|
from copy import deepcopy
|
|
from model import (
|
|
RegB, merge_reg_b, override_set_b, compact_b,
|
|
DeviceB, legacy_sanitize_blob,
|
|
escape_context_key,
|
|
SET, CLEAR,
|
|
)
|
|
from exhaustive import (
|
|
explore_b, test_concurrent_stability,
|
|
test_compaction_register_exhaustive, test_deep_history_compaction,
|
|
test_published_merge_closure, test_interleaved_delivery_grouping,
|
|
test_escaped_context_slot_grouping,
|
|
CONTEXTS,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M1: drop baseline dominance
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class M1_NoBaselineDominance(DeviceB):
|
|
def _override_set(self, reg, frontier_val, tie_policy):
|
|
if reg is None:
|
|
return False
|
|
if reg.s > reg.c:
|
|
return True
|
|
if reg.s == reg.c and reg.s > 0:
|
|
return tie_policy == SET
|
|
return False
|
|
|
|
def _compact(self, reg, frontier_val, tie_policy):
|
|
if reg.s == 0 and reg.c == 0:
|
|
return None
|
|
if self._override_set(reg, frontier_val, tie_policy):
|
|
return reg
|
|
if reg.c > reg.s:
|
|
return RegB(s=0, c=reg.c, b=0)
|
|
if reg.c == reg.s and tie_policy == CLEAR:
|
|
return RegB(s=0, c=reg.c, b=0)
|
|
return reg
|
|
|
|
|
|
def mutant_m1():
|
|
"""M1: without baseline dominance, a stale set persists after frontier
|
|
advance past baseline. Verify by constructing the scenario directly:
|
|
mark-unread at frontier=10, then advance frontier to 100. The correct
|
|
model clears the override; the mutant keeps it live."""
|
|
violations = []
|
|
for ctx in CONTEXTS:
|
|
dev = M1_NoBaselineDominance("d0")
|
|
dev.frontier[ctx] = 10
|
|
dev.do_mark_unread(ctx)
|
|
dev.do_advance_frontier(ctx, 100)
|
|
|
|
correct = override_set_b(dev.overrides[ctx], 100, CLEAR)
|
|
mutant_result = dev.override_is_set(ctx, CLEAR)
|
|
|
|
if correct != mutant_result:
|
|
violations.append((
|
|
"baseline-dominance-missing", ctx,
|
|
dev.overrides[ctx], 100,
|
|
f"correct={correct}", f"mutant={mutant_result}",
|
|
))
|
|
|
|
if not violations:
|
|
_, violations = explore_b(max_depth=3, tie_policy=CLEAR,
|
|
device_cls=M1_NoBaselineDominance)
|
|
return violations
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M2: drop max(S,C)+1 bump
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class M2_NoBump(DeviceB):
|
|
"""Each counter bumps only itself: mark_unread does S := S+1,
|
|
mark_read does C := C+1. When S > C from a prior set, a clear
|
|
at C+1 can produce C < S even though the clear is causally later."""
|
|
def do_mark_unread(self, ctx):
|
|
if self.is_legacy:
|
|
return
|
|
cur = self.overrides.get(ctx, RegB())
|
|
self.overrides[ctx] = RegB(s=cur.s + 1, c=cur.c,
|
|
b=self.effective_frontier(ctx))
|
|
|
|
def do_mark_read(self, ctx, frontier_ts):
|
|
self.frontier[ctx] = max(self.frontier.get(ctx, 0), frontier_ts)
|
|
if not self.is_legacy:
|
|
cur = self.overrides.get(ctx, RegB())
|
|
self.overrides[ctx] = RegB(s=cur.s, c=cur.c + 1, b=cur.b)
|
|
|
|
|
|
def mutant_m2():
|
|
"""M2: each counter bumps independently. After set→set→clear at
|
|
the SAME frontier (no advance past baseline): correct clear has
|
|
C=3 > S=2, mutant clear has C=1 < S=2 — a causally later clear
|
|
fails to dominate.
|
|
|
|
Use mark_read at the current frontier (not advancing past baseline)
|
|
so baseline dominance doesn't mask the counter discrepancy.
|
|
"""
|
|
violations = []
|
|
for ctx in CONTEXTS:
|
|
front = 10
|
|
dev_correct = DeviceB("d0")
|
|
dev_correct.frontier[ctx] = front
|
|
dev_correct.do_mark_unread(ctx)
|
|
dev_correct.do_mark_unread(ctx)
|
|
dev_correct.do_mark_read(ctx, front)
|
|
|
|
dev_mutant = M2_NoBump("d0")
|
|
dev_mutant.frontier[ctx] = front
|
|
dev_mutant.do_mark_unread(ctx)
|
|
dev_mutant.do_mark_unread(ctx)
|
|
dev_mutant.do_mark_read(ctx, front)
|
|
|
|
correct_set = dev_correct.override_is_set(ctx, CLEAR)
|
|
mutant_set = dev_mutant.override_is_set(ctx, CLEAR)
|
|
|
|
if correct_set != mutant_set:
|
|
violations.append((
|
|
"bump-independent", ctx,
|
|
f"correct={dev_correct.overrides[ctx]}",
|
|
f"mutant={dev_mutant.overrides[ctx]}",
|
|
f"correct_set={correct_set}", f"mutant_set={mutant_set}",
|
|
))
|
|
|
|
if not violations:
|
|
_, violations = explore_b(max_depth=4, tie_policy=CLEAR,
|
|
device_cls=M2_NoBump)
|
|
return violations
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M3: tie policy distinguishable
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def mutant_m3():
|
|
"""M3: tie policy is load-bearing — S==C must produce different verdicts.
|
|
Not a DeviceB mutation; tests the model function directly."""
|
|
reg = RegB(s=1, c=1, b=10)
|
|
frontier = 10
|
|
v_clear = override_set_b(reg, frontier, CLEAR)
|
|
v_set = override_set_b(reg, frontier, SET)
|
|
if v_clear == v_set:
|
|
return []
|
|
return [("tie-distinguishable", v_clear, v_set, reg, frontier)]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M4: revert to delete-on-dominance compaction (drops the tombstone floor)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class M4_DeleteOnDominance(DeviceB):
|
|
"""The pre-fix compaction rule: any dead/dominated register is deleted
|
|
entirely rather than reduced to the tombstone floor RegB(0, max(S,C), 0).
|
|
This makes counters reusable — a later local set/clear pair restarts
|
|
from S=0/C=0, so a delayed stale peer snapshot can dominate it on
|
|
replay. This is exactly the rule Thufir's pass-3 CRITICAL found live
|
|
at e453b3945."""
|
|
def _compact(self, reg, frontier_val, tie_policy):
|
|
if reg.s == 0 and reg.c == 0:
|
|
return None
|
|
if self._override_set(reg, frontier_val, tie_policy):
|
|
return reg
|
|
if frontier_val > reg.b:
|
|
return None
|
|
if reg.c > reg.s:
|
|
return RegB(s=0, c=reg.c, b=0)
|
|
if reg.c == reg.s and tie_policy == CLEAR:
|
|
return RegB(s=0, c=reg.c, b=0)
|
|
return reg
|
|
|
|
|
|
def mutant_m4():
|
|
"""M4: without the tombstone floor, compaction deletes the counter
|
|
ceiling instead of preserving it. Reproduce Thufir's exact witness
|
|
directly: RegB(3,0,10) at frontier=20 compacts to None under the old
|
|
rule (vs. RegB(0,3,0) under the fix); a subsequent local set+clear
|
|
reuses counters from zero; the stale ancestor then replays and
|
|
resurrects (S>C) under both tie policies.
|
|
|
|
Then confirm the explorer/deep-history suite also catches it (defense
|
|
in depth — a mutant that only fails a hand-built scenario would still
|
|
be a real bug, but the directed check is what's supposed to catch this
|
|
class per T2/T3)."""
|
|
violations = []
|
|
stale = RegB(s=3, c=0, b=10)
|
|
frontier_after = 20
|
|
|
|
for tie_policy in (CLEAR, SET):
|
|
dev = M4_DeleteOnDominance("d0")
|
|
dev.frontier["c0"] = 10
|
|
dev.overrides["c0"] = stale
|
|
dev.do_advance_frontier("c0", frontier_after)
|
|
dev.do_compact("c0", tie_policy)
|
|
if "c0" in dev.overrides:
|
|
continue # old rule didn't drop it here; not the witness shape
|
|
|
|
dev.do_mark_unread("c0") # S := 1, B := 20
|
|
dev.do_mark_read("c0", frontier_after) # C := 2
|
|
|
|
stale_blob = {"contexts": {"ov_s:c0": stale.s, "ov_c:c0": stale.c, "ov_b:c0": stale.b}}
|
|
dev.receive_merge(stale_blob)
|
|
resurrected = dev.override_is_set("c0", tie_policy)
|
|
|
|
if resurrected:
|
|
violations.append((
|
|
"M4-delete-on-dominance-resurrection", tie_policy,
|
|
f"stale_ancestor={stale}", f"post_compact_reuse=(set,clear)",
|
|
f"final_reg={dev.overrides['c0']}", f"override_is_set={resurrected}",
|
|
))
|
|
|
|
if not violations:
|
|
_, violations = test_deep_history_compaction(device_cls=M4_DeleteOnDominance)
|
|
return violations
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M5: uint32 overflow bypass
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def mutant_m5():
|
|
"""M5: values outside uint32 range must fail legacy sanitization."""
|
|
blob = {"v": 1, "client_id": "x", "contexts": {
|
|
"ov_s:c0": 4294967296,
|
|
"ov_c:c0": 0,
|
|
"ov_b:c0": 10,
|
|
}}
|
|
sanitized = legacy_sanitize_blob(blob)
|
|
if "ov_s:c0" in sanitized["contexts"]:
|
|
return []
|
|
return [("overflow-rejected", blob["contexts"]["ov_s:c0"],
|
|
sanitized["contexts"])]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M6: last-write-wins merge (breaks convergence)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class M6_LastWriteWins(DeviceB):
|
|
def _merge_reg(self, a, b):
|
|
if a is None:
|
|
return b
|
|
if b is None:
|
|
return a
|
|
return b
|
|
|
|
|
|
def mutant_m6():
|
|
"""M6: replace componentwise max with last-write-wins. Convergence must
|
|
break — different delivery orders produce different final states."""
|
|
_, violations = explore_b(max_depth=3, tie_policy=CLEAR,
|
|
device_cls=M6_LastWriteWins)
|
|
return violations
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M7: publish without canonicalization (reproduces Thufir's pass-1/2
|
|
# CRITICAL — dead+dead merge resurrection)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class M7_PublishWithoutCanonicalization(DeviceB):
|
|
"""Reverts `publish_blob` to serialize raw, uncompacted registers —
|
|
the exact pre-fix behavior Thufir's pass-1/2 CRITICAL exploited:
|
|
a dead register's baseline-relative death (or clear-count-relative
|
|
death) never gets folded into a globally-comparable ceiling before
|
|
hitting the wire, so two individually-dead registers can
|
|
componentwise-max-merge into a live join."""
|
|
def publish_blob(self, tie_policy=CLEAR):
|
|
blob_ctx = {escape_context_key(k): v for k, v in self.frontier.items()}
|
|
if not self.is_legacy:
|
|
for k, reg in self.overrides.items():
|
|
blob_ctx[f"ov_s:{k}"] = reg.s
|
|
blob_ctx[f"ov_c:{k}"] = reg.c
|
|
blob_ctx[f"ov_b:{k}"] = reg.b
|
|
return {"v": 1, "client_id": self.client_id, "contexts": blob_ctx}
|
|
|
|
|
|
def mutant_m7():
|
|
"""M7: publish-without-canonicalization must be caught by the
|
|
published-state merge-closure invariant — proving that invariant
|
|
has teeth. Reproduce Thufir's exact witness directly first (fast,
|
|
deterministic); fall back to the full search if the hand-built
|
|
scenario doesn't trigger under a given tie policy."""
|
|
violations = []
|
|
for tie_policy in (CLEAR, SET):
|
|
dev_a = M7_PublishWithoutCanonicalization("a")
|
|
dev_a.frontier["c0"] = 50
|
|
dev_a.overrides["c0"] = RegB(s=3, c=2, b=0)
|
|
dev_b = M7_PublishWithoutCanonicalization("b")
|
|
dev_b.frontier["c0"] = 100
|
|
dev_b.overrides["c0"] = RegB(s=1, c=2, b=100)
|
|
|
|
blob_a = dev_a.publish_blob(tie_policy)
|
|
blob_b = dev_b.publish_blob(tie_policy)
|
|
|
|
for first, second in [(blob_a, blob_b), (blob_b, blob_a)]:
|
|
recv = M7_PublishWithoutCanonicalization("recv")
|
|
recv.receive_merge(first)
|
|
recv.receive_merge(second)
|
|
if recv.override_is_set("c0", tie_policy):
|
|
violations.append((
|
|
"M7-publish-without-canonicalization-resurrection",
|
|
tie_policy, blob_a, blob_b, recv.overrides["c0"],
|
|
))
|
|
|
|
if not violations:
|
|
_, violations = test_published_merge_closure(
|
|
device_cls=M7_PublishWithoutCanonicalization
|
|
)
|
|
return violations
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M8: revert split_blob_into_slots to per-entry splitting
|
|
# (violates the atomic-grouping rule — reproduces Thufir's pass-2/2 CRITICAL)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class M8_PerEntrySplit(DeviceB):
|
|
"""Reverts `split_blob_into_slots` to a per-entry split that violates the
|
|
atomic-grouping rule by separating `ov_s:` + frontier from `ov_b:` + `ov_c:`.
|
|
|
|
This reproduces Thufir's exact transport witness:
|
|
- Slot 0: frontier key + `ov_s:` entry (the "partial set" slot)
|
|
- Slot 1: `ov_c:` + `ov_b:` entries
|
|
|
|
An observer receiving only slot 0 reconstructs `RegB(s=1, c=0, b=0)` at
|
|
`frontier=10`. Because `frontier(10) > b(0)`, the override is baseline-dead.
|
|
Canonical re-publication emits tombstone `RegB(0, 1, 0)`. After full
|
|
eventual delivery (both original slots + transient tombstone), the merged
|
|
result is `RegB(s=1, c=1, b=10)` — dead under clear-wins — permanently
|
|
suppressing a live override.
|
|
"""
|
|
|
|
def split_blob_into_slots(self, tie_policy=CLEAR, n_slots=2):
|
|
"""Split by key type: frontier + ov_s: in slot 0, ov_b: + ov_c: in slot 1.
|
|
Violates the atomic-grouping rule by separating ov_s: from ov_b:."""
|
|
blob = self.publish_blob(tie_policy)
|
|
slots = [{"v": blob["v"], "client_id": blob["client_id"], "contexts": {}}
|
|
for _ in range(n_slots)]
|
|
for wire_key, value in blob["contexts"].items():
|
|
if wire_key.startswith("ov_b:") or wire_key.startswith("ov_c:"):
|
|
# ov_b and ov_c go to slot 1 — separated from their ov_s: sibling
|
|
slots[1]["contexts"][wire_key] = value
|
|
else:
|
|
# frontier keys and ov_s: go to slot 0
|
|
slots[0]["contexts"][wire_key] = value
|
|
return slots
|
|
|
|
|
|
def mutant_m8():
|
|
"""M8: per-entry splitting must be caught by test_interleaved_delivery_grouping —
|
|
proving that the new interleaved-delivery test has teeth.
|
|
|
|
Reproduce Thufir's exact transport witness directly: source live
|
|
`RegB(1,0,10)` at frontier=10. Per-entry split puts frontier+`ov_s:c0`
|
|
in slot 0 and `ov_c:c0`+`ov_b:c0` in slot 1. An observer receiving only
|
|
slot 0 reconstructs `RegB(1,0,0)`, re-publishes tombstone `RegB(0,1,0)`.
|
|
Full merge including the transient: `RegB(1,1,10)` → inactive.
|
|
|
|
Confirmed by running test_interleaved_delivery_grouping with M8_PerEntrySplit;
|
|
the witness must be caught before resorting to the full suite."""
|
|
return test_interleaved_delivery_grouping(device_cls=M8_PerEntrySplit)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M9: revert split_blob_into_slots to escaped-key grouping
|
|
# (groups frontier by wire key instead of unescaped logical ID —
|
|
# reproduces Thufir's round-2 CRITICAL)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class M9_EscapedKeyGrouping(DeviceB):
|
|
"""Reverts `split_blob_into_slots` to group the frontier key by its
|
|
ESCAPED wire key rather than the unescaped logical context ID.
|
|
|
|
For a normal context like "c0", this is a no-op (escape_context_key("c0")
|
|
== "c0"), so M9 is identical to the correct model on normal contexts.
|
|
The defect only manifests when the raw context ID starts with a reserved
|
|
prefix — e.g. raw "ov_s:evil" escapes to frontier wire key "esc:ov_s:evil".
|
|
The ov_* sibling keys are keyed by the RAW suffix ("ov_s:evil"), while
|
|
the frontier is keyed by the escaped wire key ("esc:ov_s:evil") — two
|
|
identities for one logical context, so they land in different slots.
|
|
|
|
This reproduces Thufir's round-2 CRITICAL: across publication cycles an
|
|
observer can receive the new frontier slot (esc:ov_s:evil=10) plus the
|
|
stale old-cycle override slot (ov_s/ov_c/ov_b at b=0), reconstructing
|
|
RegB(s=1,c=0,b=0) at frontier=10 — baseline-dead — and emitting tombstone
|
|
RegB(0,1,0). Full eventual delivery merges to RegB(1,1,10) — dead under
|
|
clear-wins — permanently suppressing a live override.
|
|
"""
|
|
|
|
def split_blob_into_slots(self, tie_policy=CLEAR, n_slots=2):
|
|
"""Split by original (escaped) wire key identity — does not unescape
|
|
frontier keys before grouping, so escaped contexts split incorrectly."""
|
|
blob = self.publish_blob(tie_policy)
|
|
contexts = blob["contexts"]
|
|
|
|
groups = {} # wire_key -> list of (wire_key, value)
|
|
for wire_key, value in contexts.items():
|
|
if wire_key.startswith("ov_s:"):
|
|
ctx = wire_key[5:]
|
|
elif wire_key.startswith("ov_c:"):
|
|
ctx = wire_key[5:]
|
|
elif wire_key.startswith("ov_b:"):
|
|
ctx = wire_key[5:]
|
|
else:
|
|
ctx = wire_key # frontier: use escaped wire key as group ID (BUG)
|
|
groups.setdefault(ctx, []).append((wire_key, value))
|
|
|
|
slots = [{"v": blob["v"], "client_id": blob["client_id"], "contexts": {}}
|
|
for _ in range(n_slots)]
|
|
for i, (_ctx, pairs) in enumerate(sorted(groups.items())):
|
|
slot = slots[i % n_slots]
|
|
for wire_key, value in pairs:
|
|
slot["contexts"][wire_key] = value
|
|
return slots
|
|
|
|
|
|
def mutant_m9():
|
|
"""M9: escaped-key grouping must be caught by test_escaped_context_slot_grouping —
|
|
proving that the escaped-context regression test has teeth.
|
|
|
|
For a context whose raw ID starts with a reserved prefix ("ov_s:evil"),
|
|
the frontier wire key is "esc:ov_s:evil" and the ov_* sibling keys are
|
|
"ov_s:ov_s:evil", "ov_c:ov_s:evil", "ov_b:ov_s:evil". The escaped-key
|
|
grouping treats "esc:ov_s:evil" (frontier) and "ov_s:evil" (ov_* suffix)
|
|
as different groups, splitting the register across slots.
|
|
|
|
Old/new slot-coordinate mixture across publication cycles then reproduces
|
|
the round-1 transport poison: partial reconstruction → false tombstone →
|
|
permanent false clear of a live override.
|
|
|
|
The test is parameterized to route through the "mismatched grouping" path
|
|
(else branch) when the M9 split puts frontier and siblings in different slots,
|
|
and the witness must be caught."""
|
|
return test_escaped_context_slot_grouping(device_cls=M9_EscapedKeyGrouping)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Runner
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def run_mutations():
|
|
mutants = [
|
|
("M1: drop baseline dominance", mutant_m1),
|
|
("M2: drop max(S,C)+1 bump", mutant_m2),
|
|
("M3: tie policy distinguishable", mutant_m3),
|
|
("M4: revert to delete-on-dominance compaction (reproduces pass-3 CRITICAL)", mutant_m4),
|
|
("M5: uint32 overflow bypass", mutant_m5),
|
|
("M6: last-write-wins merge", mutant_m6),
|
|
("M7: publish without canonicalization (reproduces pass-1/2 CRITICAL)", mutant_m7),
|
|
("M8: per-entry split violates atomic-grouping rule (reproduces pass-2/2 CRITICAL)", mutant_m8),
|
|
("M9: escaped-key grouping splits escaped-ctx register across slots (reproduces round-2 CRITICAL)", mutant_m9),
|
|
]
|
|
|
|
print("=" * 60)
|
|
print("Mutation harness — candidate B")
|
|
print("=" * 60)
|
|
|
|
caught = []
|
|
missed = []
|
|
for name, fn in mutants:
|
|
violations = fn()
|
|
if violations:
|
|
caught.append(name)
|
|
v = violations[0]
|
|
detail = str(v)[:200]
|
|
print(f" CAUGHT: {name}")
|
|
print(f" counterexample: {detail}")
|
|
else:
|
|
missed.append(name)
|
|
print(f" MISSED: {name}")
|
|
|
|
print(f"\nCaught {len(caught)}/{len(mutants)} mutants")
|
|
if missed:
|
|
print(f"MISSED: {missed}")
|
|
print("=" * 60)
|
|
return len(missed) == 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
sys.exit(0 if run_mutations() else 1)
|