From 6c93d2c3fa1c7dd30e95bca857e2d15e72ad8920 Mon Sep 17 00:00:00 2001 From: Christopher <131207452+PetrefiedThunder@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:47:38 -0700 Subject: [PATCH 1/4] QA: agent-middleware gateway/failure_lab/ Audit fix by Muse Code fleet instance qa5-am-gateway-failure_lab (Grok Bot reviews before PR). --- gateway/failure_lab/claims.py | 3 +- gateway/failure_lab/effect_ledger.py | 12 +- gateway/failure_lab/evidence.py | 42 ++++++- gateway/failure_lab/refund_tool.py | 11 +- gateway/failure_lab/report.py | 76 +++++++++---- gateway/failure_lab/verifier.py | 7 +- gateway/tests/test_failure_lab_claims.py | 19 ++++ gateway/tests/test_failure_lab_evidence.py | 64 +++++++++++ gateway/tests/test_failure_lab_harness.py | 122 +++++++++++++++++++++ gateway/tests/test_failure_lab_report.py | 83 ++++++++++++++ gateway/tests/test_failure_lab_verifier.py | 18 +++ 11 files changed, 428 insertions(+), 29 deletions(-) diff --git a/gateway/failure_lab/claims.py b/gateway/failure_lab/claims.py index 4e2c3892..d736ebbb 100644 --- a/gateway/failure_lab/claims.py +++ b/gateway/failure_lab/claims.py @@ -163,7 +163,8 @@ def from_dict(cls, document: Mapping[str, Any]) -> ClaimRecord: if reported is None: matches = bool(expected) and not rows_diverge else: - matches = bool(reported) and not rows_diverge + # Only a real boolean true counts. The string "false" is truthy. + matches = reported is True and not rows_diverge status, status_lines = _observed_status( str(document.get("status", "")), observed ) diff --git a/gateway/failure_lab/effect_ledger.py b/gateway/failure_lab/effect_ledger.py index add62e68..dac5d7f5 100644 --- a/gateway/failure_lab/effect_ledger.py +++ b/gateway/failure_lab/effect_ledger.py @@ -281,9 +281,15 @@ def execution_count(self, operation_id: str | None = None) -> int: return int(connection.execute(sql, params).fetchone()[0]) def snapshot(self) -> list[dict[str, Any]]: - """The PRD's ledger shape: one entry per effect with its execution count.""" + """The PRD's ledger shape: one entry per effect with its execution count. + + Counts and rows come from one read. A second read can contain an + operation the first read never counted, and the snapshot would then + raise or mix two moments. + """ + records = self.effects() counts: dict[str, int] = {} - for record in self.effects(): + for record in records: counts[record.operation_id] = counts.get(record.operation_id, 0) + 1 return [ { @@ -299,7 +305,7 @@ def snapshot(self) -> list[dict[str, Any]]: "worker_pid": record.worker_pid, "configuration": record.configuration, } - for record in self.effects() + for record in records ] def reset(self) -> None: diff --git a/gateway/failure_lab/evidence.py b/gateway/failure_lab/evidence.py index 643b0c53..5330ec54 100644 --- a/gateway/failure_lab/evidence.py +++ b/gateway/failure_lab/evidence.py @@ -62,7 +62,7 @@ from dataclasses import dataclass from dataclasses import fields as dataclass_fields from datetime import datetime, timezone -from pathlib import Path +from pathlib import Path, PurePosixPath from typing import TYPE_CHECKING, Any from failure_lab import TEST_DEFINITION_VERSION @@ -1264,6 +1264,35 @@ def as_dict(self) -> dict[str, Any]: } +def _unsafe_bundle_path(root: Path, relative: str) -> str | None: + """Reject a manifest path that is not a regular file inside the bundle. + + Joining an absolute path, or a path with ``..``, drops the bundle + directory. A symlink can point at the same outside file. Either one + would let a matching hash pass for bytes that are not in the bundle. + """ + if ( + not isinstance(relative, str) + or not relative + or relative.startswith(("/", "\\")) + or "\\" in relative + or "\x00" in relative + ): + return "path escapes the bundle and was not read" + pure = PurePosixPath(relative) + if pure.is_absolute() or ".." in pure.parts: + return "path escapes the bundle and was not read" + root_real = root.resolve() + candidate = root_real.joinpath(*pure.parts) + if candidate.is_symlink(): + return "path is a symlink and was not read" + try: + candidate.resolve().relative_to(root_real) + except ValueError: + return "path escapes the bundle and was not read" + return None + + def verify_bundle_integrity(directory: Path | str) -> IntegrityReport: """Re-hash every file in a bundle against its manifest. @@ -1308,10 +1337,17 @@ def verify_bundle_integrity(directory: Path | str) -> IntegrityReport: listed: set[str] = set() for entry in entries: - relative = str(entry.get("path", "")) + relative = entry.get("path", "") + if not isinstance(relative, str): + problems.append("path escapes the bundle and was not read") + continue listed.add(relative) + unsafe = _unsafe_bundle_path(root, relative) + if unsafe is not None: + problems.append(f"{relative}: {unsafe}") + continue path = root / relative - if not path.is_file(): + if path.is_symlink() or not path.is_file(): missing.append(relative) continue checked += 1 diff --git a/gateway/failure_lab/refund_tool.py b/gateway/failure_lab/refund_tool.py index 19e1cae1..b44b2309 100644 --- a/gateway/failure_lab/refund_tool.py +++ b/gateway/failure_lab/refund_tool.py @@ -306,7 +306,16 @@ async def refund_create( def _control_ok(request: Request) -> bool: provided = request.headers.get(CONTROL_HEADER) - return provided is not None and hmac.compare_digest(provided, control_token) + if not provided: + return False + try: + # compare_digest on str raises TypeError for non-ASCII. Compare + # UTF-8 bytes so a latin-1 header is a denial, not a 500. + return hmac.compare_digest( + provided.encode("utf-8"), control_token.encode("utf-8") + ) + except (TypeError, ValueError, UnicodeError): + return False def _denied() -> JSONResponse: return JSONResponse({"detail": "failure_lab_control_denied"}, status_code=403) diff --git a/gateway/failure_lab/report.py b/gateway/failure_lab/report.py index 3618659d..54154761 100644 --- a/gateway/failure_lab/report.py +++ b/gateway/failure_lab/report.py @@ -81,7 +81,7 @@ class ConclusionKind(str, Enum): "and no comparison was made either way" ), ConclusionKind.INCONCLUSIVE.value: ( - "too few configurations ran for this test to say anything" + "the measurements are not enough to say a baseline handled this test" ), } @@ -293,6 +293,27 @@ def _differences( return added, lost +def _baseline_is_credible(column: ConfigurationView | None) -> bool: + """A baseline column the report may treat as having run the operation. + + Zero duplicate effects is not evidence on its own. A column that never + reached the one intended execution suppressed duplicates by doing + nothing, and a FAIL verdict means that column did not hold. + """ + return ( + column is not None + and column.ran + and column.verdict != Verdict.FAIL.value + and column.downstream_effects >= INTENDED_EXECUTIONS + ) + + +def _baseline_supports_sufficiency(column: ConfigurationView | None) -> bool: + if column is None or not _baseline_is_credible(column): + return False + return column.duplicate_effects == 0 + + def _conclude( result: ScenarioResult, existing: ConfigurationView | None, @@ -371,7 +392,7 @@ def _conclude( disadvantages, ) - if existing is not None and existing.ran and existing.duplicate_effects == 0: + if _baseline_supports_sufficiency(existing): return Conclusion( ConclusionKind.EXISTING_INTEGRATION_SUFFICIENT, "Your existing integration handled this test correctly. We did not " @@ -384,28 +405,43 @@ def _conclude( disadvantages, ) - if native is not None and native.ran and native.duplicate_effects == 0: - if prevented_vs_native == 0: - cost = ( - " On this test the baseline also did something the gateway did " - "not: see what it cost, below." - if disadvantages - else "" - ) + if _baseline_supports_sufficiency(native): + cost = ( + " On this test the baseline also did something the gateway did " + "not: see what it cost, below." + if disadvantages + else "" + ) + return Conclusion( + ConclusionKind.NATIVE_CONTROLS_SUFFICIENT, + "Correct native controls handled this test on their own. We did " + "not observe an additional duplicate effect prevented by Agent " + "Middleware in this scenario. If your downstream already honors " + "a durable operation id the way this baseline does, this " + "failure mode is already covered for you." + cost, + prevented_vs_native, + prevented_vs_existing, + differences, + disadvantages, + ) + + if prevented_vs_native == 0 and prevented_vs_existing == 0: + if (existing_ran or native_ran) and not ( + _baseline_is_credible(existing) or _baseline_is_credible(native) + ): return Conclusion( - ConclusionKind.NATIVE_CONTROLS_SUFFICIENT, - "Correct native controls handled this test on their own. We did " - "not observe an additional duplicate effect prevented by Agent " - "Middleware in this scenario. If your downstream already honors " - "a durable operation id the way this baseline does, this " - "failure mode is already covered for you." + cost, - prevented_vs_native, - prevented_vs_existing, + ConclusionKind.INCONCLUSIVE, + "A baseline ran, but it executed the business action fewer " + "times than this test requires, or its own verdict is FAIL. " + "A count of zero duplicate effects in that case does not show " + "that the baseline did the work this test requires: doing " + "nothing produces no duplicate. This report does not call " + "that baseline sufficient.", + 0, + 0, differences, disadvantages, ) - - if prevented_vs_native == 0 and prevented_vs_existing == 0: if differences: return Conclusion( ConclusionKind.GATEWAY_ADDED_EVIDENCE_ONLY, diff --git a/gateway/failure_lab/verifier.py b/gateway/failure_lab/verifier.py index f3690310..f8b19d7d 100644 --- a/gateway/failure_lab/verifier.py +++ b/gateway/failure_lab/verifier.py @@ -377,7 +377,12 @@ def _downstream_claim( ) executions = observation.get("executions") source = observation.get("source", "unspecified independent observer") - if not isinstance(executions, int): + # bool is a subclass of int. True would otherwise count as one execution. + if ( + isinstance(executions, bool) + or not isinstance(executions, int) + or executions < 0 + ): return Claim( "DOWNSTREAM_EXECUTION_ESTABLISHED", ClaimStatus.NOT_ESTABLISHED, diff --git a/gateway/tests/test_failure_lab_claims.py b/gateway/tests/test_failure_lab_claims.py index 64b35f9c..b1273d56 100644 --- a/gateway/tests/test_failure_lab_claims.py +++ b/gateway/tests/test_failure_lab_claims.py @@ -182,6 +182,25 @@ def test_loaded_manifest_cannot_promote_negative_rows_to_pass(tmp_path, row_verd assert supported_claims(loaded) == [] +@pytest.mark.parametrize("reported", ["false", "true", "yes", 1]) +def test_a_non_boolean_match_flag_does_not_grant_support(tmp_path, reported): + """The gate accepts JSON true, not every truthy value. + + The string "false" is truthy in Python. A hand-written manifest must not + become supportable because of that. + """ + manifest = build_claims_manifest([_scenario("T01")], environment="local") + document = manifest.as_dict() + document["claims"][0]["matches_documented_expectation"] = reported + path = tmp_path / "claims.json" + path.write_text(json.dumps(document)) + loaded = load_claims_manifest(path) + assert loaded.records[0].matches_documented_expectation is False + with pytest.raises(UnsupportedClaimError): + assert_claim_is_supported(loaded, CLAIM) + assert supported_claims(loaded) == [] + + @pytest.mark.parametrize("documented", [Verdict.PASS, Verdict.FAIL]) def test_loaded_manifest_keeps_passing_rows_and_expectation_gate(tmp_path, documented): manifest = build_claims_manifest( diff --git a/gateway/tests/test_failure_lab_evidence.py b/gateway/tests/test_failure_lab_evidence.py index 28956602..8b3ddd1d 100644 --- a/gateway/tests/test_failure_lab_evidence.py +++ b/gateway/tests/test_failure_lab_evidence.py @@ -8,6 +8,7 @@ from __future__ import annotations +import hashlib import json import pytest @@ -20,6 +21,7 @@ assert_no_secret_leak, find_leaked_secrets, redact, + verify_bundle_integrity, ) from failure_lab.telemetry import ( CONVERSION_SOURCES, @@ -238,3 +240,65 @@ def test_a_bundle_that_leaked_is_never_left_on_disk(self, tmp_path): if path.is_file() and secret in path.read_text(errors="ignore") ] assert not survivors, survivors + + +def _manifest(bundle, files): + (bundle / "manifest.json").write_text(json.dumps({"files": files})) + + +def test_integrity_check_accepts_a_regular_file_inside_the_bundle(tmp_path): + bundle = tmp_path / "bundle" + bundle.mkdir() + body = b"inside" + (bundle / "report.txt").write_bytes(body) + _manifest( + bundle, + [ + { + "path": "report.txt", + "sha256": hashlib.sha256(body).hexdigest(), + "bytes": len(body), + } + ], + ) + report = verify_bundle_integrity(bundle) + assert report.ok, report.problems + assert report.checked == 1 + + +def test_integrity_check_does_not_hash_a_path_that_leaves_the_bundle(tmp_path): + """A manifest path of ../ or an absolute path is not a bundle file. + + pathlib joins an absolute path by discarding the bundle directory, and + a matching hash would then make the check pass for bytes that are not + in the bundle. + """ + outside = tmp_path / "outside.txt" + payload = b"not-in-the-bundle" + outside.write_bytes(payload) + digest = hashlib.sha256(payload).hexdigest() + entry = {"sha256": digest, "bytes": len(payload)} + + parent_ref = tmp_path / "parent-ref" + parent_ref.mkdir() + _manifest(parent_ref, [{**entry, "path": "../outside.txt"}]) + parent_report = verify_bundle_integrity(parent_ref) + assert not parent_report.ok + assert parent_report.checked == 0 + assert any("not read" in problem for problem in parent_report.problems) + + absolute = tmp_path / "absolute" + absolute.mkdir() + _manifest(absolute, [{**entry, "path": str(outside)}]) + absolute_report = verify_bundle_integrity(absolute) + assert not absolute_report.ok + assert absolute_report.checked == 0 + + linked = tmp_path / "linked" + linked.mkdir() + (linked / "report.txt").symlink_to(outside) + _manifest(linked, [{**entry, "path": "report.txt"}]) + linked_report = verify_bundle_integrity(linked) + assert not linked_report.ok + assert linked_report.checked == 0 + assert outside.read_bytes() == payload diff --git a/gateway/tests/test_failure_lab_harness.py b/gateway/tests/test_failure_lab_harness.py index d7b4506b..688bc49e 100644 --- a/gateway/tests/test_failure_lab_harness.py +++ b/gateway/tests/test_failure_lab_harness.py @@ -22,6 +22,57 @@ from failure_lab.refund_tool import RefundRequest +def test_snapshot_does_not_mix_two_reads(tmp_path, monkeypatch): + """snapshot() must describe one read of the ledger. + + A second read can include an operation the first read never counted. + Indexing the second read with the first read's counts raises KeyError + and, short of that, reports two different moments as one snapshot. + """ + from failure_lab.effect_ledger import EffectRecord + + ledger = EffectLedger(tmp_path / "effects.db") + common = dict( + request_id="req_1", + idempotency_key="k", + amount=5000, + currency="USD", + customer_id="cus", + payment_id="pay", + configuration="test", + ) + ledger.execute(operation_id="refund:pay", **common) + original = ledger.effects + calls = {"n": 0} + + def two_moments(operation_id=None): + calls["n"] += 1 + rows = original(operation_id) + if calls["n"] == 1 or operation_id is not None: + return rows + late = EffectRecord( + effect_id="effect_late", + operation_id="refund:other", + request_id="req_2", + idempotency_key="k2", + amount=100, + currency="USD", + customer_id="cus", + payment_id="other", + executed_at=rows[0].executed_at, + worker_pid=rows[0].worker_pid, + configuration="test", + execution_ordinal=1, + ) + return [*rows, late] + + monkeypatch.setattr(ledger, "effects", two_moments) + rows = ledger.snapshot() + assert calls["n"] == 1 + assert [row["operation_id"] for row in rows] == ["refund:pay"] + assert rows[0]["execution_count"] == 1 + + def test_effect_ledger_is_duplicate_visible(tmp_path): ledger = EffectLedger(tmp_path / "effects.db") common = dict( @@ -84,6 +135,77 @@ def test_identity_distinguishes_restart_policies(): assert restarted.request_id != attempt.request_id +@pytest.mark.anyio +async def test_non_ascii_control_header_is_denied(tmp_path): + """A control header compare must deny, not raise. + + hmac.compare_digest rejects non-ASCII strings with TypeError. HTTP header + values are latin-1, so a client can send that byte. httpx refuses it + before the app runs. This calls the ASGI app with the raw header bytes. + """ + import json + + from failure_lab.faults import FaultInjector + from failure_lab.refund_tool import SimulatedRefundService, build_downstream_app + + service = SimulatedRefundService( + EffectLedger(tmp_path / "effects.db"), + native_idempotency=False, + configuration="test", + ) + downstream = build_downstream_app( + service, + FaultInjector(), + bearer_token="bearer-token-value", + control_token="control-token-value", + ) + + async def call(header: bytes) -> tuple[int, bytes]: + messages: list[dict] = [] + + async def receive(): + return {"type": "http.request", "body": b"", "more_body": False} + + async def send(message): + messages.append(message) + + scope = { + "type": "http", + "asgi": {"version": "3.0"}, + "http_version": "1.1", + "method": "GET", + "scheme": "http", + "path": "/__lab/health", + "raw_path": b"/__lab/health", + "query_string": b"", + "headers": [(b"x-failure-lab-control", header)], + "client": ("127.0.0.1", 1234), + "server": ("127.0.0.1", 80), + } + await downstream.asgi(scope, receive, send) + status = next( + message["status"] + for message in messages + if message["type"] == "http.response.start" + ) + body = b"".join( + message.get("body", b"") + for message in messages + if message["type"] == "http.response.body" + ) + return status, body + + non_ascii = "caf\u00e9-not-the-token".encode("latin-1") + refused_status, refused_body = await call(non_ascii) + wrong_status, wrong_body = await call(b"nope") + ok_status, _ok_body = await call(b"control-token-value") + assert refused_status == 403 + assert wrong_status == 403 + assert json.loads(refused_body)["detail"] == "failure_lab_control_denied" + assert json.loads(wrong_body)["detail"] == "failure_lab_control_denied" + assert ok_status == 200 + + def test_refund_request_is_strict_about_representation(): RefundRequest.from_mapping( {"operation_id": "o", "customer_id": "c", "payment_id": "p", "amount": 5000} diff --git a/gateway/tests/test_failure_lab_report.py b/gateway/tests/test_failure_lab_report.py index 3cbc91f5..5e3c3f5c 100644 --- a/gateway/tests/test_failure_lab_report.py +++ b/gateway/tests/test_failure_lab_report.py @@ -140,6 +140,89 @@ def test_the_gateway_adding_nothing_is_a_reachable_conclusion(): ) +def test_a_baseline_that_executed_nothing_is_not_called_sufficient(): + """Zero duplicates from a tool that never ran is not a handled test. + + A baseline that records no downstream effect suppresses duplicates by + doing nothing. The telemetry path already refuses to call that a pass. + The visitor-facing conclusion has to refuse it too, including when the + other baseline really did duplicate and the gateway prevented that. + """ + never_ran = _result( + [ + _entry(Configuration.DIRECT_NAIVE, verdict=Verdict.OBSERVED, executions=0), + _entry(Configuration.DIRECT_NATIVE, verdict=Verdict.FAIL, executions=0), + _entry( + Configuration.GATEWAY_NATIVE, + verdict=Verdict.PASS, + executions=1, + dispatches=1, + ), + ] + ) + conclusion = build_comparison(never_ran).conclusion + assert conclusion.kind is ConclusionKind.INCONCLUSIVE + assert "handled" not in conclusion.text + assert conclusion.duplicates_prevented_vs_existing == 0 + assert conclusion.duplicates_prevented_vs_native == 0 + + hidden_duplicate = _result( + [ + _entry(Configuration.DIRECT_NAIVE, verdict=Verdict.OBSERVED, executions=2), + _entry(Configuration.DIRECT_NATIVE, verdict=Verdict.FAIL, executions=0), + _entry( + Configuration.GATEWAY_NATIVE, + verdict=Verdict.PASS, + executions=1, + dispatches=1, + ), + ] + ) + prevented = build_comparison(hidden_duplicate).conclusion + assert prevented.kind is ConclusionKind.GATEWAY_PREVENTED_DUPLICATES + assert prevented.duplicates_prevented_vs_existing == 1 + + +def test_a_failing_baseline_is_not_reported_as_sufficient(): + """Verdict FAIL means the baseline did not hold. Do not invert that.""" + result = _result( + [ + _entry(Configuration.DIRECT_NAIVE, verdict=Verdict.FAIL, executions=1), + _entry(Configuration.DIRECT_NATIVE, verdict=Verdict.FAIL, executions=1), + _entry( + Configuration.GATEWAY_NATIVE, + verdict=Verdict.PASS, + executions=1, + dispatches=1, + ), + ] + ) + conclusion = build_comparison(result).conclusion + assert conclusion.kind is ConclusionKind.INCONCLUSIVE + assert conclusion.kind is not ConclusionKind.EXISTING_INTEGRATION_SUFFICIENT + assert conclusion.kind is not ConclusionKind.NATIVE_CONTROLS_SUFFICIENT + + +def test_a_short_existing_column_does_not_hide_a_native_baseline_that_ran(): + result = _result( + [ + _entry(Configuration.DIRECT_NAIVE, verdict=Verdict.OBSERVED, executions=0), + _entry( + Configuration.DIRECT_NATIVE, verdict=Verdict.PASS, executions=1, known=1 + ), + _entry( + Configuration.GATEWAY_NATIVE, + verdict=Verdict.PASS, + executions=1, + dispatches=1, + known=1, + ), + ] + ) + conclusion = build_comparison(result).conclusion + assert conclusion.kind is ConclusionKind.NATIVE_CONTROLS_SUFFICIENT + + def test_an_already_correct_existing_integration_is_told_so(): result = _result( [ diff --git a/gateway/tests/test_failure_lab_verifier.py b/gateway/tests/test_failure_lab_verifier.py index 00caebeb..6dd688f5 100644 --- a/gateway/tests/test_failure_lab_verifier.py +++ b/gateway/tests/test_failure_lab_verifier.py @@ -116,6 +116,24 @@ def test_downstream_execution_needs_an_independent_observation(signed_bundle): assert "effect ledger" in observed.downstream_execution.reason +@pytest.mark.parametrize("executions", [True, False, -1]) +def test_only_a_non_negative_integer_counts_as_executions(signed_bundle, executions): + """A JSON boolean is a Python bool, and bool is a subclass of int. + + Treating true as one execution would establish downstream execution from + a flag, not from a count. A negative number is not a count either. + """ + bundle, keys = signed_bundle + report = verify( + bundle, + keys, + downstream_observation={"executions": executions, "source": "effect ledger"}, + ) + assert report.signature.status is ClaimStatus.ESTABLISHED + assert report.downstream_execution.status is ClaimStatus.NOT_ESTABLISHED + assert "did not report an execution count" in report.downstream_execution.reason + + def test_a_success_receipt_contradicted_by_the_world_fails_that_claim(signed_bundle): bundle, keys = signed_bundle private = Ed25519PrivateKey.from_private_bytes(bytes(range(32))) From 23a6ecd42fbbda5b52b5cfa2e01a7e5ad75995b2 Mon Sep 17 00:00:00 2001 From: Christopher <131207452+PetrefiedThunder@users.noreply.github.com> Date: Mon, 19 Oct 2026 02:50:37 -0700 Subject: [PATCH 2/4] Close failure lab review gaps --- gateway/failure_lab/evidence.py | 2 +- gateway/failure_lab/report.py | 8 +++++--- gateway/tests/test_failure_lab_evidence.py | 19 +++++++++++++++++++ gateway/tests/test_failure_lab_report.py | 19 +++++++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/gateway/failure_lab/evidence.py b/gateway/failure_lab/evidence.py index 761e8373..f1c3ca09 100644 --- a/gateway/failure_lab/evidence.py +++ b/gateway/failure_lab/evidence.py @@ -1401,7 +1401,7 @@ def verify_bundle_integrity(directory: Path | str) -> IntegrityReport: on_disk = { str(path.relative_to(root)).replace(os.sep, "/") for path in root.rglob("*") - if path.is_file() and not path.is_symlink() + if path.is_symlink() or path.is_file() } unexpected = sorted(on_disk - listed - {MANIFEST_NAME}) problems.extend( diff --git a/gateway/failure_lab/report.py b/gateway/failure_lab/report.py index 42b40b55..2b3f381b 100644 --- a/gateway/failure_lab/report.py +++ b/gateway/failure_lab/report.py @@ -298,12 +298,13 @@ def _baseline_is_credible(column: ConfigurationView | None) -> bool: Zero duplicate effects is not evidence on its own. A column that never reached the one intended execution suppressed duplicates by doing - nothing, and a FAIL verdict means that column did not hold. + nothing. OBSERVED records measurements without establishing that the + baseline handled the test correctly; only PASS supports that claim. """ return ( column is not None and column.ran - and column.verdict in (Verdict.PASS.value, Verdict.OBSERVED.value) + and column.verdict == Verdict.PASS.value and column.downstream_effects >= INTENDED_EXECUTIONS ) @@ -432,7 +433,8 @@ def _conclude( return Conclusion( ConclusionKind.INCONCLUSIVE, "A baseline ran, but it executed the business action fewer " - "times than this test requires, or its own verdict is FAIL. " + "times than this test requires, or its verdict does not " + "establish success. " "A count of zero duplicate effects in that case does not show " "that the baseline did the work this test requires: doing " "nothing produces no duplicate. This report does not call " diff --git a/gateway/tests/test_failure_lab_evidence.py b/gateway/tests/test_failure_lab_evidence.py index b7dffdc7..490d52e6 100644 --- a/gateway/tests/test_failure_lab_evidence.py +++ b/gateway/tests/test_failure_lab_evidence.py @@ -373,6 +373,25 @@ def test_integrity_check_rejects_escaping_parent_link_and_linked_manifest( assert linked_report.checked == 0 +def test_integrity_check_reports_unlisted_symlink_without_reading_target( + tmp_path, monkeypatch +): + outside = tmp_path / "outside.txt" + outside.write_bytes(b"outside") + bundle = tmp_path / "bundle" + bundle.mkdir() + (bundle / "unlisted.txt").symlink_to(outside) + _manifest(bundle, []) + monkeypatch.setattr( + evidence, "_sha256_file", lambda _path: pytest.fail("unlisted link was hashed") + ) + report = verify_bundle_integrity(bundle) + assert not report.ok + assert report.checked == 0 + assert report.unexpected == ["unlisted.txt"] + assert outside.read_bytes() == b"outside" + + @pytest.mark.parametrize( "files", [None, {}, [1], [None], [{"path": "file", "bytes": "invalid"}]] ) diff --git a/gateway/tests/test_failure_lab_report.py b/gateway/tests/test_failure_lab_report.py index 7c98b438..b7c4541f 100644 --- a/gateway/tests/test_failure_lab_report.py +++ b/gateway/tests/test_failure_lab_report.py @@ -261,6 +261,25 @@ def test_equal_duplicate_counts_do_not_claim_native_sufficiency(): assert all(column.duplicate_effects == 1 for column in comparison.columns) +def test_observed_single_effect_does_not_prove_existing_integration_sufficient(): + comparison = build_comparison( + _result( + [ + _entry( + Configuration.DIRECT_NAIVE, verdict=Verdict.OBSERVED, executions=1 + ), + _entry(Configuration.DIRECT_NATIVE, verdict=Verdict.FAIL, executions=1), + _entry( + Configuration.GATEWAY_NATIVE, verdict=Verdict.PASS, executions=1 + ), + ] + ) + ) + assert comparison.conclusion.kind is ConclusionKind.INCONCLUSIVE + assert comparison.conclusion.duplicates_prevented_vs_existing == 0 + assert comparison.conclusion.duplicates_prevented_vs_native == 0 + + def test_a_short_existing_column_does_not_hide_a_native_baseline_that_ran(): result = _result( [ From e82e8e32cc68a37a4c8ecd1b206396380a2f51f5 Mon Sep 17 00:00:00 2001 From: Christopher <131207452+PetrefiedThunder@users.noreply.github.com> Date: Mon, 19 Oct 2026 03:02:06 -0700 Subject: [PATCH 3/4] Fail bundle verification on scan errors --- gateway/failure_lab/evidence.py | 21 +++- gateway/tests/test_failure_lab_evidence.py | 111 +++++++++++++++++++++ 2 files changed, 127 insertions(+), 5 deletions(-) diff --git a/gateway/failure_lab/evidence.py b/gateway/failure_lab/evidence.py index f1c3ca09..0e5944ca 100644 --- a/gateway/failure_lab/evidence.py +++ b/gateway/failure_lab/evidence.py @@ -1398,11 +1398,22 @@ def verify_bundle_integrity(directory: Path | str) -> IntegrityReport: f"{relative}: {size} bytes on disk, manifest says {entry['bytes']}" ) - on_disk = { - str(path.relative_to(root)).replace(os.sep, "/") - for path in root.rglob("*") - if path.is_symlink() or path.is_file() - } + on_disk: set[str] = set() + pending = [root] + while pending: + directory_to_scan = pending.pop() + try: + with os.scandir(directory_to_scan) as entries: + for entry in entries: + path = Path(entry.path) + if entry.is_dir(follow_symlinks=False): + pending.append(path) + else: + on_disk.add(str(path.relative_to(root)).replace(os.sep, "/")) + except (OSError, RuntimeError) as exc: + problems.append( + f"{directory_to_scan}: could not scan bundle directory: {exc}" + ) unexpected = sorted(on_disk - listed - {MANIFEST_NAME}) problems.extend( f"{name}: present on disk but not listed in the manifest" for name in unexpected diff --git a/gateway/tests/test_failure_lab_evidence.py b/gateway/tests/test_failure_lab_evidence.py index 490d52e6..682d076d 100644 --- a/gateway/tests/test_failure_lab_evidence.py +++ b/gateway/tests/test_failure_lab_evidence.py @@ -9,6 +9,7 @@ from __future__ import annotations import hashlib +import errno import json from pathlib import Path @@ -392,6 +393,116 @@ def test_integrity_check_reports_unlisted_symlink_without_reading_target( assert outside.read_bytes() == b"outside" +def test_integrity_check_accepts_empty_and_nested_regular_bundles(tmp_path): + empty = tmp_path / "empty" + empty.mkdir() + _manifest(empty, []) + assert verify_bundle_integrity(empty).ok + + nested = tmp_path / "nested" + (nested / "subdir").mkdir(parents=True) + payload = b"regular" + (nested / "subdir" / "payload.txt").write_bytes(payload) + _manifest( + nested, + [ + { + "path": "subdir/payload.txt", + "sha256": hashlib.sha256(payload).hexdigest(), + "bytes": len(payload), + } + ], + ) + report = verify_bundle_integrity(nested) + assert report.ok, report.problems + assert report.checked == 1 + + +@pytest.mark.parametrize("nested", [False, True], ids=["root", "nested"]) +@pytest.mark.parametrize("error", [errno.EIO, errno.EACCES], ids=["eio", "permission"]) +def test_integrity_check_reports_directory_scan_failure( + tmp_path, monkeypatch, nested, error +): + bundle = tmp_path / "bundle" + bundle.mkdir() + blocked = bundle / "subdir" if nested else bundle + if nested: + blocked.mkdir() + (blocked / "unlisted.txt").write_bytes(b"not indexed") + _manifest(bundle, []) + original_scandir = evidence.os.scandir + + def failing_scandir(path): + if Path(path) == blocked: + raise OSError(error, "synthetic directory scan failure") + return original_scandir(path) + + with monkeypatch.context() as patch: + patch.setattr(evidence.os, "scandir", failing_scandir) + report = verify_bundle_integrity(bundle) + assert not report.ok + assert report.checked == 0 + assert any("could not scan" in problem for problem in report.problems) + + +def test_integrity_check_does_not_descend_into_unlisted_directory_link( + tmp_path, monkeypatch +): + outside = tmp_path / "outside" + outside.mkdir() + (outside / "payload.txt").write_bytes(b"outside") + bundle = tmp_path / "bundle" + bundle.mkdir() + (bundle / "linked").symlink_to(outside, target_is_directory=True) + _manifest(bundle, []) + original_scandir = evidence.os.scandir + + def guarded_scandir(path): + assert Path(path) != outside, "outside directory was scanned" + return original_scandir(path) + + with monkeypatch.context() as patch: + patch.setattr(evidence.os, "scandir", guarded_scandir) + patch.setattr( + evidence, + "_sha256_file", + lambda _path: pytest.fail("outside target was hashed"), + ) + report = verify_bundle_integrity(bundle) + assert not report.ok + assert report.unexpected == ["linked"] + + +@pytest.mark.parametrize("failure", [OSError, RuntimeError]) +def test_integrity_check_reports_entry_classification_failure( + tmp_path, monkeypatch, failure +): + bundle = tmp_path / "bundle" + bundle.mkdir() + (bundle / "unlisted.txt").write_bytes(b"not indexed") + _manifest(bundle, []) + + class BrokenEntry: + path = str(bundle / "unlisted.txt") + + def is_dir(self, *, follow_symlinks): + assert follow_symlinks is False + raise failure("synthetic entry classification failure") + + class BrokenListing: + def __enter__(self): + return iter([BrokenEntry()]) + + def __exit__(self, *_args): + return False + + with monkeypatch.context() as patch: + patch.setattr(evidence.os, "scandir", lambda _path: BrokenListing()) + report = verify_bundle_integrity(bundle) + assert not report.ok + assert any("could not scan" in problem for problem in report.problems) + + @pytest.mark.parametrize( "files", [None, {}, [1], [None], [{"path": "file", "bytes": "invalid"}]] ) From 2ea9fa50359e123d5144486ab39ddbecf0b5a0c3 Mon Sep 17 00:00:00 2001 From: Christopher <131207452+PetrefiedThunder@users.noreply.github.com> Date: Mon, 26 Oct 2026 07:28:03 -0700 Subject: [PATCH 4/4] fix(failure-lab): reject Windows bundle paths and clarify inconclusive results Signed-off-by: Christopher <131207452+PetrefiedThunder@users.noreply.github.com> --- gateway/failure_lab/evidence.py | 1 + gateway/failure_lab/report.py | 12 ++++------ gateway/tests/test_failure_lab_evidence.py | 16 ++++++++++++- gateway/tests/test_failure_lab_report.py | 27 ++++++++++++++++++++++ 4 files changed, 48 insertions(+), 8 deletions(-) diff --git a/gateway/failure_lab/evidence.py b/gateway/failure_lab/evidence.py index 8faf1537..bc8af683 100644 --- a/gateway/failure_lab/evidence.py +++ b/gateway/failure_lab/evidence.py @@ -1277,6 +1277,7 @@ def _unsafe_bundle_path(root: Path, relative: str) -> str | None: not isinstance(relative, str) or not relative or relative.startswith(("/", "\\")) + or re.match(r"^[A-Za-z]:", relative) or "\\" in relative or "\x00" in relative ): diff --git a/gateway/failure_lab/report.py b/gateway/failure_lab/report.py index 2b3f381b..4d9d7056 100644 --- a/gateway/failure_lab/report.py +++ b/gateway/failure_lab/report.py @@ -432,13 +432,11 @@ def _conclude( ): return Conclusion( ConclusionKind.INCONCLUSIVE, - "A baseline ran, but it executed the business action fewer " - "times than this test requires, or its verdict does not " - "establish success. " - "A count of zero duplicate effects in that case does not show " - "that the baseline did the work this test requires: doing " - "nothing produces no duplicate. This report does not call " - "that baseline sufficient.", + "The available baseline measurements do not establish success " + "in this test: none has both a PASS verdict and " + "the required downstream execution. A zero duplicate count " + "alone cannot establish that the intended business action " + "succeeded.", 0, 0, differences, diff --git a/gateway/tests/test_failure_lab_evidence.py b/gateway/tests/test_failure_lab_evidence.py index 682d076d..e0e27259 100644 --- a/gateway/tests/test_failure_lab_evidence.py +++ b/gateway/tests/test_failure_lab_evidence.py @@ -329,7 +329,20 @@ def test_integrity_check_accepts_nested_files_and_reports_missing_files(tmp_path assert not report.ok -@pytest.mark.parametrize("unsafe", ["../outside.txt", "/outside.txt", "bad\x00name", 4]) +@pytest.mark.parametrize( + "unsafe", + [ + "../outside.txt", + "/outside.txt", + "bad\x00name", + 4, + "C:/x", + "C:\\x", + "c:x", + "//server/share", + "\\\\server\\share", + ], +) def test_integrity_check_never_hashes_unsafe_manifest_paths( tmp_path, monkeypatch, unsafe ): @@ -344,6 +357,7 @@ def forbidden_hash(_path): report = verify_bundle_integrity(bundle) assert not report.ok assert report.checked == 0 + assert any("not read" in problem for problem in report.problems) def test_integrity_check_rejects_escaping_parent_link_and_linked_manifest( diff --git a/gateway/tests/test_failure_lab_report.py b/gateway/tests/test_failure_lab_report.py index b7c4541f..c78c9949 100644 --- a/gateway/tests/test_failure_lab_report.py +++ b/gateway/tests/test_failure_lab_report.py @@ -201,6 +201,33 @@ def test_a_failing_baseline_is_not_reported_as_sufficient(): assert conclusion.kind is ConclusionKind.INCONCLUSIVE assert conclusion.kind is not ConclusionKind.EXISTING_INTEGRATION_SUFFICIENT assert conclusion.kind is not ConclusionKind.NATIVE_CONTROLS_SUFFICIENT + assert ( + "none has both a PASS verdict and the required downstream execution" + in conclusion.text + ) + assert ( + "cannot establish that the intended business action succeeded" + in conclusion.text + ) + + +@pytest.mark.parametrize( + "baseline", [Configuration.DIRECT_NAIVE, Configuration.DIRECT_NATIVE] +) +def test_pass_verdict_without_downstream_execution_is_inconclusive(baseline): + result = _result( + [ + _entry(baseline, verdict=Verdict.PASS, executions=0), + _entry(Configuration.GATEWAY_NATIVE, verdict=Verdict.PASS, executions=1), + ] + ) + + conclusion = build_comparison(result).conclusion + assert conclusion.kind is ConclusionKind.INCONCLUSIVE + assert ( + "none has both a PASS verdict and the required downstream execution" + in conclusion.text + ) @pytest.mark.parametrize(