Добавление канонического графа M4.7

This commit is contained in:
DCCONSTRUCTIONS
2026-08-23 20:03:40 +03:00
parent 51bb1369eb
commit d50d3bb3d3
18 changed files with 2538 additions and 110 deletions
@@ -0,0 +1,126 @@
from __future__ import annotations
import hashlib
import importlib.util
import json
import tarfile
from pathlib import Path
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
BUILDER_PATH = REPOSITORY_ROOT / "scripts/build_m47_worker_graph_shadow_artifact.py"
SPEC = importlib.util.spec_from_file_location("m47_worker_graph_shadow_builder", BUILDER_PATH)
assert SPEC is not None and SPEC.loader is not None
BUILDER = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(BUILDER)
def _sha256(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def _regular_files(archive: tarfile.TarFile) -> dict[str, bytes]:
result: dict[str, bytes] = {}
for member in archive.getmembers():
if not member.isfile():
continue
extracted = archive.extractfile(member)
assert extracted is not None
result[member.name] = extracted.read()
return result
def test_m47_worker_artifact_is_deterministic_and_self_contained(tmp_path: Path) -> None:
patch_id = "mission-core-m47-graph-shadow-unit-001"
revision = "d" * 40
first = BUILDER.build_artifact(patch_id, tmp_path / "first", revision=revision)
second = BUILDER.build_artifact(patch_id, tmp_path / "second", revision=revision)
first_bytes = Path(first["artifact"]).read_bytes()
second_bytes = Path(second["artifact"]).read_bytes()
assert first_bytes == second_bytes
assert first["sha256"] == _sha256(first_bytes)
assert first["transition"] == "m47-canonical-graph-shadow-v1"
with tarfile.open(first["artifact"], "r:gz") as archive:
regular = _regular_files(archive)
payload_names = sorted(
name.removeprefix("payload/")
for name in regular
if name.startswith("payload/")
)
assert payload_names == first["payload_files"]
assert regular["files.txt"].decode().splitlines() == first["payload_files"]
assert _sha256(regular[f"payload/{BUILDER.WHEEL_NAME}"]) == first["wheel_sha256"]
assert regular[f"payload/{BUILDER.RUNNER.name}"] == BUILDER.RUNNER.read_bytes()
for relative in BUILDER.CONFIG_PATHS:
assert regular[f"payload/{relative.name}"] == (
BUILDER.REPOSITORY_ROOT / relative
).read_bytes()
def test_m47_descriptor_preserves_predecessor_and_separates_readiness() -> None:
descriptor = json.loads(
BUILDER.render_descriptor(
"mission-core-m47-graph-shadow-unit-002",
"e" * 40,
wheel_sha256="f" * 64,
)
)
assert descriptor["schema_version"] == "nodedc.mission-core-worker.shadow-release/v2"
assert descriptor["transition"] == "m47-canonical-graph-shadow-v1"
assert descriptor["boundary"]["external_deploy_registry"] is False
assert descriptor["container"]["public_ports"] is False
assert descriptor["inputs"]["local_surface"]["sha256"] == (
"f57eb2485b6cef47f2a97a2d9ff1aa9fd9265fe1eb69cd5852d12f39e13b8bc6"
)
assert descriptor["inputs"]["accepted_temporal_frames"]["sha256"] == (
"e83b80ea06b3462c2d04c5a1b74289a0ec401596c7150ae90f5a1b748b639c3a"
)
assert descriptor["inputs"]["accepted_threat_frames"]["sha256"] == (
"b57be1839f5915e3b80b54355b694e0bd8c9ac318d0cbe6de2bff713082cfa4e"
)
assert descriptor["predecessor"]["durable_worker"]["name"] == (
"ndc-mission-core-perception-worker"
)
assert descriptor["rollback"] == {
"durable_worker_action": "none",
"preserve_failed_evidence": True,
"remove_candidate_container": True,
"remove_unaccepted_release": True,
"triton_action": "none",
}
assert descriptor["acceptance"] == {
"run_mode": "lossless-replay",
"expected_frames": 4489,
"delivered_frames": 4489,
"failed_frames": 0,
"stale_frames": 0,
"superseded_frames": 0,
"accepted_parity": True,
"class_routing_used": False,
}
assert descriptor["readiness"]["graph"] == {
"graph_id": "reference-perception-graph/v2",
"terminal_accounting_required": True,
"actuation_allowed": False,
}
assert set(descriptor["release"]["configs"]) == {
path.name for path in BUILDER.CONFIG_PATHS
}
serialized = json.dumps(descriptor).encode()
assert b"PRIVATE KEY" not in serialized
assert b"password=" not in serialized.lower()
def test_m47_runner_calls_only_the_canonical_graph_entrypoint() -> None:
runner = BUILDER.RUNNER.read_text("utf-8")
assert "k1link.perception.reference_graph_cli" in runner
assert "k1link.perception.detector_replay_cli" not in runner
assert '"--mode", $descriptor.acceptance.run_mode' in runner
assert '"--temporal-parity-frames"' in runner
assert '"--threat-parity-frames"' in runner
assert 'Write-Output "PROVIDER_READINESS=accepted"' in runner
assert 'Write-Output "GRAPH_READINESS=accepted"' in runner
assert 'Write-Output "DURABLE_WORKER_ACTION=none"' in runner
assert 'Write-Output "TRITON_ACTION=none"' in runner
+5 -91
View File
@@ -1,11 +1,11 @@
from __future__ import annotations
import hashlib
import importlib.util
import json
import tarfile
from pathlib import Path
import pytest
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
BUILDER_PATH = REPOSITORY_ROOT / "scripts/build_m4_worker_shadow_artifact.py"
SPEC = importlib.util.spec_from_file_location("m4_worker_shadow_builder", BUILDER_PATH)
@@ -13,98 +13,12 @@ assert SPEC is not None and SPEC.loader is not None
BUILDER = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(BUILDER)
def _sha256(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def _regular_files(archive: tarfile.TarFile) -> dict[str, bytes]:
result: dict[str, bytes] = {}
for member in archive.getmembers():
if not member.isfile():
continue
extracted = archive.extractfile(member)
assert extracted is not None
result[member.name] = extracted.read()
return result
def test_worker_shadow_artifact_is_deterministic_narrow_and_self_contained(
def test_historical_worker_shadow_refuses_runtime_relabeling(
tmp_path: Path,
) -> None:
patch_id = "mission-core-m4-detector-shadow-unit-001"
first = BUILDER.build_artifact(patch_id, tmp_path / "first")
second = BUILDER.build_artifact(patch_id, tmp_path / "second")
first_bytes = Path(first["artifact"]).read_bytes()
second_bytes = Path(second["artifact"]).read_bytes()
assert first_bytes == second_bytes
assert first["sha256"] == _sha256(first_bytes)
assert first["sha256"] == second["sha256"]
assert first["payload_files"] == list(BUILDER.PAYLOAD_FILES)
with tarfile.open(first["artifact"], "r:gz") as archive:
members = archive.getmembers()
names = [member.name for member in members]
regular = _regular_files(archive)
assert names == [
"manifest.env",
"files.txt",
"payload",
"payload/Invoke-M4DetectorShadow.ps1",
"payload/m4-recorded-realtime-baseline-v1.json",
"payload/mission-core-worker-shadow-v1.json",
f"payload/{BUILDER.WHEEL_NAME}",
]
assert (
regular["manifest.env"]
== (f"id={patch_id}\ncomponent=mission-core-worker\ntype=shadow-release\n").encode()
)
assert regular["files.txt"].decode().splitlines() == list(BUILDER.PAYLOAD_FILES)
assert regular[f"payload/{BUILDER.RUNNER_NAME}"] == BUILDER.RUNNER.read_bytes()
assert _sha256(regular[f"payload/{BUILDER.WHEEL_NAME}"]) == (BUILDER.EXPECTED_WHEEL_SHA256)
assert _sha256(regular["payload/m4-recorded-realtime-baseline-v1.json"]) == (
BUILDER.EXPECTED_BASELINE_SHA256
)
descriptor = json.loads(regular["payload/mission-core-worker-shadow-v1.json"])
assert descriptor["patch_id"] == patch_id
assert descriptor["code_revision"] == first["code_revision"]
assert descriptor["boundary"] == {
"external_deploy_registry": False,
"nodedc_platform_repository": False,
"repository": "NODEDC_MISSION_CORE",
"server_docker_runtime": False,
}
assert descriptor["dependency_tree_identity"] == (
"relative-path-tab-size-tab-file-sha256-lf/v1"
)
assert descriptor["release"]["runner"] == {
"name": BUILDER.RUNNER_NAME,
"sha256": _sha256(BUILDER.RUNNER.read_bytes()),
}
assert descriptor["release"]["wheel"] == {
"name": BUILDER.WHEEL_NAME,
"sha256": BUILDER.EXPECTED_WHEEL_SHA256,
}
assert descriptor["container"]["public_ports"] is False
assert descriptor["rollback"] == {
"durable_worker_action": "none",
"preserve_failed_evidence": True,
"remove_candidate_container": True,
"remove_unaccepted_release": True,
"triton_action": "none",
}
all_bytes = b"\n".join(regular.values())
assert b"PRIVATE KEY" not in all_bytes
assert b"password=" not in all_bytes.lower()
assert not any(
Path(name).name.startswith(".env")
or "/secrets/" in name
or "/runtime/" in name
or "/recordings/" in name
for name in names
)
with pytest.raises(BUILDER.ArtifactBuildError, match="runtime wheel digest changed"):
BUILDER.build_artifact(patch_id, tmp_path)
def test_worker_shadow_descriptor_pins_external_inputs_and_preserves_e15() -> None:
+1
View File
@@ -320,6 +320,7 @@ def test_reference_graph_config_pins_all_roles_and_queue_bounds() -> None:
providers=tuple(
ProviderPin(role, f"{role.value}-provider", "v1", "78a3dc2", "a" * 64)
for role in ProviderRole
if role is not ProviderRole.ROLLING
),
queues=tuple(
QueuePolicy(stage, 2, 80_000_000, 200_000_000)
+180
View File
@@ -5,6 +5,7 @@ import threading
from collections.abc import Iterator
from dataclasses import replace
from pathlib import Path
from queue import Queue
from threading import Event
import numpy as np
@@ -36,9 +37,12 @@ from k1link.perception.contracts import (
TimestampBundle,
)
from k1link.perception.graph import (
GraphExecutionError,
GraphRunMode,
GraphRunResult,
GraphState,
ReferencePerceptionGraphV1,
ReferencePerceptionGraphV2,
TerminalOutcomeType,
)
from k1link.perception.providers import (
@@ -47,6 +51,7 @@ from k1link.perception.providers import (
ProviderRole,
QueuePolicy,
ReferencePerceptionGraphConfig,
ReferencePerceptionGraphConfigV2,
SourcePacket,
)
from k1link.perception.recorded_source import (
@@ -230,6 +235,44 @@ class _Motion:
return obstacles
class _Rolling:
provider_id = "test-rolling/v1"
def update(
self,
packet: SourcePacket,
obstacles: tuple[TemporalObstacle, ...],
) -> tuple[TemporalObstacle, ...]:
if packet.envelope.sequence == 0:
return ()
return (
TemporalObstacle(
component_id=f"rolling-{packet.envelope.sequence}",
identity_scope="ephemeral",
state=TemporalState.RETAINED,
ttl_ns=3_000_000_000,
last_hit_ns=packet.envelope.timestamps.source_ns - 100_000_000,
age_ns=100_000_000,
association_basis="registered-map-increment-retention",
history=(
HistorySample(
frame_id=f"frame-{packet.envelope.sequence - 1:06d}",
evidence_time_ns=(
packet.envelope.timestamps.source_ns - 100_000_000
),
centroid_xyz_m=(3.0, 0.5, 0.5),
),
),
cells=(GridCell(99, packet.envelope.sequence, 0),),
coordinate_frame="map",
last_centroid_xyz_m=(3.0, 0.5, 0.5),
motion=MotionState.UNKNOWN,
motion_confidence=0.0,
motion_reason="retained-map-increment-no-current-motion",
),
)
class _Threat:
provider_id = "test-threat/v1"
@@ -312,6 +355,39 @@ def _config(
)
def _config_v2(
capacity: int = 8,
terminal_timeout_ns: int = 500_000_000,
) -> ReferencePerceptionGraphConfigV2:
ids = {
ProviderRole.SOURCE: _Source.provider_id,
ProviderRole.DETECTOR: _Detector.provider_id,
ProviderRole.GEOMETRY: _Geometry.provider_id,
ProviderRole.TEMPORAL: _Temporal.provider_id,
ProviderRole.MOTION: _Motion.provider_id,
ProviderRole.ROLLING: _Rolling.provider_id,
ProviderRole.THREAT: _Threat.provider_id,
}
return ReferencePerceptionGraphConfigV2(
graph_id="reference-perception-graph/v2",
source_profile_id=BASELINE_PROFILE_ID,
providers=tuple(
ProviderPin(role, provider_id, "v1", "test-revision", "b" * 64)
for role, provider_id in ids.items()
),
queues=tuple(
QueuePolicy(
stage,
capacity,
min(80_000_000, terminal_timeout_ns),
terminal_timeout_ns,
)
for stage in ("detector", "geometry", "temporal", "rolling", "threat")
),
authority=GraphAuthority(),
)
class _MemoryTelemetry:
def __init__(self) -> None:
self.records: list[dict[str, object]] = []
@@ -358,6 +434,28 @@ def _graph(
)
def _graph_v2(
source: object,
*,
detector: object | None = None,
capacity: int = 8,
run_mode: GraphRunMode = GraphRunMode.LOSSLESS_REPLAY,
terminal_timeout_ns: int = 500_000_000,
) -> ReferencePerceptionGraphV2:
return ReferencePerceptionGraphV2(
config=_config_v2(capacity, terminal_timeout_ns),
source=source,
detector=detector or _Detector(),
geometry=_Geometry(),
temporal=_Temporal(),
motion=_Motion(),
rolling=_Rolling(),
threat=_Threat(),
run_mode=run_mode,
clock_ns=lambda: 10_000,
)
def test_reference_graph_closes_accounting_telemetry_and_deterministic_digest() -> None:
telemetry = _MemoryTelemetry()
graph = _graph(_Source((_packet(0), _packet(1))), telemetry=telemetry)
@@ -386,6 +484,88 @@ def test_reference_graph_closes_accounting_telemetry_and_deterministic_digest()
assert {"detector", "geometry", "temporal", "threat"}.issubset(stage_ids)
def test_reference_graph_v2_publishes_current_and_retained_occupancy() -> None:
result = _graph_v2(_Source((_packet(0), _packet(1)))).run()
assert result.graph_id == "reference-perception-graph/v2"
assert result.run_mode is GraphRunMode.LOSSLESS_REPLAY
assert [item.outcome for item in result.terminal_outcomes] == [
TerminalOutcomeType.DELIVERED,
TerminalOutcomeType.DELIVERED,
]
second = result.deliveries[1].obstacle_map
assert [item.state for item in second.occupied] == [
TemporalState.CURRENT,
TemporalState.RETAINED,
]
assert second.unknown == ()
assert set(dict(result.queue_high_watermarks)) == {
"detector",
"geometry",
"temporal",
"rolling",
"threat",
}
def test_reference_graph_v2_lossless_mode_applies_bounded_backpressure() -> None:
release = Event()
class BlockingDetector(_Detector):
def detect(self, packet: SourcePacket) -> tuple[ObjectProposal2D, ...]:
if packet.envelope.sequence == 0:
assert release.wait(2)
return super().detect(packet)
BlockingDetector.provider_id = _Detector.provider_id
graph = _graph_v2(
_Source(tuple(_packet(index) for index in range(4))),
detector=BlockingDetector(),
capacity=1,
)
holder: list[GraphRunResult] = []
runner = threading.Thread(target=lambda: holder.append(graph.run()))
runner.start()
release.set()
runner.join(3)
assert not runner.is_alive()
assert len(holder[0].terminal_outcomes) == 4
assert all(
item.outcome is TerminalOutcomeType.DELIVERED
for item in holder[0].terminal_outcomes
)
def test_reference_graph_v2_restart_requires_fresh_stateful_providers() -> None:
graph = _graph_v2(_Source((_packet(0),)))
assert graph.run().state is GraphState.STOPPED
with pytest.raises(
GraphExecutionError,
match="restart requires freshly instantiated providers",
):
graph.run()
def test_reference_graph_v2_terminal_timeout_fails_stranded_packet() -> None:
packet = _packet(0)
graph = _graph_v2(
_Source(()),
capacity=1,
terminal_timeout_ns=1,
)
queue = Queue(maxsize=1)
queue.put_nowait(packet)
graph._put_stop(queue, "detector")
outcome = graph._outcomes[0]
assert outcome.outcome is TerminalOutcomeType.FAILED
assert outcome.reason == "terminal-queue-timeout"
assert queue.qsize() == 1
def test_reference_graph_marks_unavailable_stale_and_provider_failure() -> None:
class FailingDetector(_Detector):
def detect(self, packet: SourcePacket) -> tuple[ObjectProposal2D, ...]:
+121
View File
@@ -0,0 +1,121 @@
from __future__ import annotations
import json
from pathlib import Path
from k1link.perception.contracts import LocalObstacleMap, SourceAccounting
from k1link.perception.graph_contracts import (
REFERENCE_GRAPH_ID_V2,
DeliveredFrame,
GraphRunMode,
GraphState,
TerminalOutcome,
TerminalOutcomeType,
build_graph_run_result_v2,
)
from k1link.perception.reference_graph_parity import (
compare_reference_graph_to_accepted_ledgers,
)
def _result():
obstacle_map = LocalObstacleMap(
source_id="RAVNOVES00",
session_id="20260720T065719Z_viewer_live",
frame_id="frame-000000",
graph_id=REFERENCE_GRAPH_ID_V2,
generated_monotonic_ns=1,
output_age_ns=0,
occupied=(),
unknown=(),
camera_uncertainty=(),
accounting=SourceAccounting(1, 1, 0, 0),
)
return build_graph_run_result_v2(
graph_id=REFERENCE_GRAPH_ID_V2,
source_profile_id="m4-ravnoves00-recorded-realtime/v1",
run_mode=GraphRunMode.LOSSLESS_REPLAY,
state=GraphState.STOPPED,
admitted_count=1,
outcomes=(
TerminalOutcome(
source_id="RAVNOVES00",
session_id="20260720T065719Z_viewer_live",
frame_id="frame-000000",
sequence=0,
outcome=TerminalOutcomeType.DELIVERED,
stage_id="threat",
reason="object-payload-delivered",
),
),
deliveries=(DeliveredFrame(0, obstacle_map, ()),),
queue_high_watermarks=(("detector", 1),),
)
def _write_ledgers(root: Path, *, threat_assessments: list[dict[str, object]]) -> tuple[Path, Path]:
temporal = root / "temporal.jsonl"
threat = root / "threat.jsonl"
temporal.write_text(
json.dumps(
{
"sequence": 0,
"frame_id": "frame-000000",
"current": [],
"rolling_retained": [],
"held": [],
"expired": [],
}
)
+ "\n",
"utf-8",
)
threat.write_text(
json.dumps(
{
"sequence": 0,
"frame_id": "frame-000000",
"camera_proposals": [],
"assessments": threat_assessments,
}
)
+ "\n",
"utf-8",
)
return temporal, threat
def test_reference_graph_parity_accepts_exact_frame_semantics(tmp_path: Path) -> None:
temporal, threat = _write_ledgers(tmp_path, threat_assessments=[])
report = compare_reference_graph_to_accepted_ledgers(
_result(),
temporal_frames_path=temporal,
threat_frames_path=threat,
expected_frames=1,
)
assert report.accepted is True
assert dict(report.mismatch_counts) == {
"camera_uncertainty": 0,
"current": 0,
"expired": 0,
"held": 0,
"rolling_retained": 0,
"source_binding": 0,
"threat_assessments": 0,
}
def test_reference_graph_parity_reports_threat_drift(tmp_path: Path) -> None:
temporal, threat = _write_ledgers(tmp_path, threat_assessments=[{"component_id": "x"}])
report = compare_reference_graph_to_accepted_ledgers(
_result(),
temporal_frames_path=temporal,
threat_frames_path=threat,
expected_frames=1,
)
assert report.accepted is False
assert dict(report.mismatch_counts)["threat_assessments"] == 1
+152
View File
@@ -0,0 +1,152 @@
from __future__ import annotations
import json
from pathlib import Path
from k1link.perception.contracts import LocalObstacleMap, SourceAccounting
from k1link.perception.graph_contracts import (
REFERENCE_GRAPH_ID_V2,
DeliveredFrame,
GraphRunMode,
GraphState,
TerminalOutcome,
TerminalOutcomeType,
build_graph_run_result_v2,
)
from k1link.perception.providers import ReferencePerceptionGraphConfigV2
from k1link.perception.reference_graph_parity import ReferenceGraphParityReport
from k1link.perception.reference_graph_result import seal_reference_graph_result
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
def _parity(accepted: bool = True) -> ReferenceGraphParityReport:
return ReferenceGraphParityReport(
expected_frames=1,
compared_frames=1,
temporal_frames_sha256="a" * 64,
threat_frames_sha256="b" * 64,
mismatch_counts=(("threat_assessments", 0 if accepted else 1),),
accepted=accepted,
)
def _result(outcome: TerminalOutcomeType = TerminalOutcomeType.DELIVERED):
terminal = TerminalOutcome(
source_id="RAVNOVES00",
session_id="20260720T065719Z_viewer_live",
frame_id="frame-000000",
sequence=0,
outcome=outcome,
stage_id="threat" if outcome is TerminalOutcomeType.DELIVERED else "detector",
reason=(
"object-payload-delivered"
if outcome is TerminalOutcomeType.DELIVERED
else "bounded-queue-latest-wins"
),
)
deliveries = (
(
DeliveredFrame(
sequence=0,
obstacle_map=LocalObstacleMap(
source_id="RAVNOVES00",
session_id="20260720T065719Z_viewer_live",
frame_id="frame-000000",
graph_id=REFERENCE_GRAPH_ID_V2,
generated_monotonic_ns=123,
output_age_ns=45,
occupied=(),
unknown=(),
camera_uncertainty=(),
accounting=SourceAccounting(1, 1, 0, 0),
),
threats=(),
),
)
if outcome is TerminalOutcomeType.DELIVERED
else ()
)
return build_graph_run_result_v2(
graph_id=REFERENCE_GRAPH_ID_V2,
source_profile_id="m4-ravnoves00-recorded-realtime/v1",
run_mode=GraphRunMode.LOSSLESS_REPLAY,
state=GraphState.STOPPED,
admitted_count=1,
outcomes=(terminal,),
deliveries=deliveries,
queue_high_watermarks=(("detector", 1), ("threat", 1)),
)
def test_m47_graph_config_round_trips_and_pins_rolling_stage() -> None:
path = REPOSITORY_ROOT / "config/perception/m4-reference-graph-v2.json"
document = json.loads(path.read_text("utf-8"))
config = ReferencePerceptionGraphConfigV2.from_dict(document)
assert config.to_dict() == document
assert config.graph_id == REFERENCE_GRAPH_ID_V2
assert {provider.role.value for provider in config.providers} == {
"source",
"detector",
"geometry",
"temporal",
"motion",
"rolling",
"threat",
}
assert {queue.stage_id for queue in config.queues} == {
"detector",
"geometry",
"temporal",
"rolling",
"threat",
}
def test_reference_graph_result_is_content_addressed_and_reproducible(tmp_path: Path) -> None:
first = seal_reference_graph_result(
_result(),
output_root=tmp_path / "one",
expected_frames=1,
parity=_parity(),
)
second = seal_reference_graph_result(
_result(),
output_root=tmp_path / "two",
expected_frames=1,
parity=_parity(),
)
assert first.accepted is True
assert first.result_id == second.result_id
assert first.report["gates"] == {
"lossless_replay_mode": True,
"graph_stopped": True,
"admitted_frame_count": True,
"terminal_accounting_closed": True,
"delivery_count": True,
"delivery_payload_count": True,
"no_failed_frames": True,
"no_stale_frames": True,
"no_superseded_frames": True,
"no_rejected_frames": True,
"no_unavailable_frames": True,
"accepted_m45r_m46_parity": True,
}
assert {
path.name: path.read_bytes() for path in first.result_root.iterdir()
} == {path.name: path.read_bytes() for path in second.result_root.iterdir()}
def test_reference_graph_result_fails_closed_on_supersession(tmp_path: Path) -> None:
sealed = seal_reference_graph_result(
_result(TerminalOutcomeType.SUPERSEDED),
output_root=tmp_path,
expected_frames=1,
parity=_parity(),
)
assert sealed.accepted is False
assert sealed.report["gates"]["no_superseded_frames"] is False
assert sealed.report["gates"]["delivery_count"] is False