From c71ab1fa0013290f767704bb25c5585e8875bdac Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 18 Sep 2026 13:24:02 +0000 Subject: [PATCH 1/3] fix(test): assert idempotency_key in fail-closed enrichment result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `request_enrichment` mints the idempotency key before the GATE_URL check and returns it on every path, including the fail-closed `gate_not_configured` branch. The test for that branch asserted an exact dict without the key, so it has failed since the seam-audit commit that introduced both (#259) — CI's Test Suite is red on main HEAD for this one assertion and nothing else. Classified as a test defect, not a product defect: the success-path test in the same file already asserts `result["idempotency_key"]`, so the contract is that the key is always returned. The assertion now names the key via `enrichment_idempotency_key(...)`, keeping the exact-dict form so no extra field can leak in unnoticed. Verified: tests/unit 1421 passed, 7 skipped; full suite (no Docker) 2006 passed, 56 xfailed; ruff check + ruff format + mypy engine/ clean. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX --- tests/unit/test_gate_egress.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_gate_egress.py b/tests/unit/test_gate_egress.py index 922080c7..4426cb18 100644 --- a/tests/unit/test_gate_egress.py +++ b/tests/unit/test_gate_egress.py @@ -82,7 +82,14 @@ async def test_request_enrichment_fails_closed_without_gate_url(monkeypatch: pyt fake = _FakeClient(response=_response_packet()) monkeypatch.setattr(gate_egress, "get_gate_client", lambda: fake) result = await request_enrichment(tenant="acme", entity_id="ent-1", domain="plasticos", target_fields=["polymer"]) - assert result == {"status": "failed", "error": "gate_not_configured", "action": "enrich"} + assert result == { + "status": "failed", + "error": "gate_not_configured", + "action": "enrich", + # The key is minted before the GATE_URL check, so a fail-closed result stays + # replayable: the caller can retry the same request under the same key. + "idempotency_key": enrichment_idempotency_key("acme", "ent-1", ["polymer"]), + } assert fake.calls == [], "no direct fallback: nothing may be sent when Gate is not configured" From b776c8bc60b925745a2188bda60dc06da20c7c3b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 18 Sep 2026 13:25:47 +0000 Subject: [PATCH 2/3] test(gate-egress): assert idempotency_key on the SDK-error return Validate & Repair surfaced that the "always replayable" contract was asserted on the success and fail-closed returns but not on the SDK-error one, though request_enrichment returns the key there too. Adds the missing assertion so every failing return of request_enrichment is covered. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX --- tests/unit/test_gate_egress.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/test_gate_egress.py b/tests/unit/test_gate_egress.py index 4426cb18..f0589618 100644 --- a/tests/unit/test_gate_egress.py +++ b/tests/unit/test_gate_egress.py @@ -121,6 +121,7 @@ async def test_request_enrichment_reports_sdk_errors_as_failure(monkeypatch: pyt result = await request_enrichment(tenant="acme", entity_id="ent-1", domain="plasticos", target_fields=["polymer"]) assert result["status"] == "failed" assert result["error"] == "GateConnectionError" + assert result["idempotency_key"] == enrichment_idempotency_key("acme", "ent-1", ["polymer"]) assert len(fake.calls) == 1 From 9c690388507c74b998ba31b7b5e885742d4f20ad Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 18 Sep 2026 13:27:08 +0000 Subject: [PATCH 3/3] test(gate-egress): match PR #262's fail-closed hunk byte-for-byte Open PR #262 (feat/idea-portfolio-domain) already carries the identical fail-closed assertion fix. The only difference was an explanatory comment on this branch, which made the same region conflict textually and blocked the overlap gate. Dropping the comment makes both sides of the hunk identical, so whichever PR lands first the other merges clean. The rationale lives in the PR body and in this branch's first commit message. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01BXqHneK2x7xKy1WkbBMumX --- tests/unit/test_gate_egress.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/unit/test_gate_egress.py b/tests/unit/test_gate_egress.py index f0589618..784a3e97 100644 --- a/tests/unit/test_gate_egress.py +++ b/tests/unit/test_gate_egress.py @@ -86,8 +86,6 @@ async def test_request_enrichment_fails_closed_without_gate_url(monkeypatch: pyt "status": "failed", "error": "gate_not_configured", "action": "enrich", - # The key is minted before the GATE_URL check, so a fail-closed result stays - # replayable: the caller can retry the same request under the same key. "idempotency_key": enrichment_idempotency_key("acme", "ent-1", ["polymer"]), } assert fake.calls == [], "no direct fallback: nothing may be sent when Gate is not configured"