M0 e M1 revisados e fixes implementados

This commit is contained in:
Marcos Lohmann
2026-09-14 08:18:53 -03:00
parent 439f60598e
commit f3efcc3414
72 changed files with 4831 additions and 128 deletions

View File

@@ -136,13 +136,13 @@ async def test_transaction_policy_denies_over_limit_even_though_entitled(
assert "TRANSACTION_POLICY_DENIED" in _text(result)
async def test_require_approval_is_denied_pending_ep14(
async def test_require_approval_is_denied_without_a_grant(
governed_stack: None, sign_token: Callable[..., str]
) -> None:
"""README.md §26: Finance.Payments allows payment.execute below the approval
"""README.md §26: Finance.Payments allows payment.execute above the approval
threshold (README.md §7.2/§17 profile) but must never silently upgrade a
REQUIRE_APPROVAL to ALLOW -- and EP-14 (approval workflow) does not exist
yet in this milestone, so the only safe outcome is DENY."""
REQUIRE_APPROVAL to ALLOW -- the first attempt of any such call is always
denied, since no approval has been granted for it yet (EP-14-T02)."""
token = sign_token(FINANCE_PAYMENTS_CLIENT_ID)
result = await run_against_gateway(
@@ -154,6 +154,152 @@ async def test_require_approval_is_denied_pending_ep14(
assert "REQUIRE_APPROVAL" in _text(result)
async def test_full_approval_cycle_grant_then_execute_then_replay_denied(
governed_stack: None, sign_token: Callable[..., str]
) -> None:
"""EP-14 end to end: deny (no grant) -> out-of-band grant -> the *same*
call now reaches the real backend -> a second attempt of the identical
call is denied again (README.md §46, single-use "approval replay").
Uses Sales.Write + sales.quote.create (approval_threshold=100000,
config/clients/profiles/sales-write.yaml); see
`test_finance_full_approval_cycle_routes_through_fabric_to_finance_domain`
below for the same cycle proven against the Finance domain (EP-06-T03).
"""
import emcp_bus.gateway.server as gateway_module
token = sign_token(SALES_WRITE_CLIENT_ID)
arguments = {"customer_id": "cust-001", "amount": 150_000, "region": "BR-SP"}
first_attempt = await run_against_gateway(
lambda session: session.call_tool("sales.quote.create", arguments), token=token
)
assert first_attempt.is_error is True
assert "REQUIRE_APPROVAL" in _text(first_attempt)
approval_id = _text(first_attempt).rsplit(":", 1)[-1]
# Out-of-band: an approver grants the specific request the Gateway itself
# created (see `on_call_tool`'s REQUIRE_APPROVAL branch) -- not a new one
# constructed by the test, proving the Gateway and the approver are
# looking at the same `ApprovalService` instance (`get_current_state()`).
gateway_module.get_current_state().approval_service.grant(approval_id)
granted_attempt = await run_against_gateway(
lambda session: session.call_tool("sales.quote.create", arguments), token=token
)
assert granted_attempt.is_error is not True
assert granted_attempt.structured_content is not None
assert granted_attempt.structured_content["status"] == "draft"
replay_attempt = await run_against_gateway(
lambda session: session.call_tool("sales.quote.create", arguments), token=token
)
assert replay_attempt.is_error is True
assert "REQUIRE_APPROVAL" in _text(replay_attempt)
async def test_sales_client_tools_list_never_includes_finance_tools(
governed_stack: None, sign_token: Callable[..., str]
) -> None:
"""EP-06-T01: the Fabric router queries only the backends a client's
entitlement actually touches -- a Sales.Read client's tools/list never
even connects to the Finance backend."""
token = sign_token(SALES_READ_CLIENT_ID)
result = await run_against_gateway(lambda session: session.list_tools(), token=token)
tool_names = {tool.name for tool in result.tools}
assert tool_names == {
"sales.customer.search",
"sales.customer.get",
"sales.order.get",
"sales.inventory.check",
}
async def test_finance_client_tools_list_never_includes_sales_tools(
governed_stack_with_finance: None, sign_token: Callable[..., str]
) -> None:
"""EP-06-T01, the mirror image: a Finance.Payments client's tools/list
routes to the Finance backend only, never Sales."""
token = sign_token(FINANCE_PAYMENTS_CLIENT_ID)
result = await run_against_gateway(lambda session: session.list_tools(), token=token)
tool_names = {tool.name for tool in result.tools}
assert tool_names == {
"finance.invoice.get",
"finance.payment.create",
"finance.payment.execute",
}
# EP-05-T03's SEP-2549 `ttl_ms`/`cache_scope` fields are per-version wire
# vocabulary (`mcp_types._v2026_07_28`) that the SDK's own serialization
# sieve strips for every earlier protocol revision (verified: neither field
# exists in any pre-2026-07-28 per-version result model at all). This RI's
# entire transport uses the classic session `initialize()` handshake, which
# never negotiates 2026-07-28 (that revision is reached only via the
# stateless per-request `server/discover` mode this RI does not use) -- so
# there is no e2e test here asserting on `result.cache_scope`/`result.ttl_ms`:
# whatever the Gateway sets internally, a client on this transport only ever
# sees the field's own class default, making such an assertion pass
# regardless of what the Gateway actually computed. `tools_list_cache_hint()`
# itself (always `scope="private"`, configurable `ttl_ms`) is the real,
# meaningful test -- see tests/unit/test_gateway_cache.py -- and
# `emcp_bus.gateway.cache`'s module docstring has the full, verified
# explanation.
async def test_finance_full_approval_cycle_routes_through_fabric_to_finance_domain(
governed_stack_with_finance: None, sign_token: Callable[..., str]
) -> None:
"""EP-06-T01/T03 + EP-14 end to end against the *Finance* domain server:
the Fabric router resolves finance.payment.create/execute to the Finance
backend, and the same deny -> grant -> execute -> replay-denied cycle as
`test_full_approval_cycle_grant_then_execute_then_replay_denied` (Sales)
holds identically here."""
import emcp_bus.gateway.server as gateway_module
token = sign_token(FINANCE_PAYMENTS_CLIENT_ID)
create_result = await run_against_gateway(
lambda session: session.call_tool(
"finance.payment.create", {"invoice_id": "inv-002", "amount": 5_000}
),
token=token,
)
assert create_result.is_error is not True
assert create_result.structured_content is not None
payment_id = create_result.structured_content["payment_id"]
execute_arguments = {"payment_id": payment_id, "amount": 50_000}
first_attempt = await run_against_gateway(
lambda session: session.call_tool("finance.payment.execute", execute_arguments),
token=token,
)
assert first_attempt.is_error is True
assert "REQUIRE_APPROVAL" in _text(first_attempt)
approval_id = _text(first_attempt).rsplit(":", 1)[-1]
gateway_module.get_current_state().approval_service.grant(approval_id)
granted_attempt = await run_against_gateway(
lambda session: session.call_tool("finance.payment.execute", execute_arguments),
token=token,
)
assert granted_attempt.is_error is not True
assert granted_attempt.structured_content is not None
assert granted_attempt.structured_content["status"] == "executed"
replay_attempt = await run_against_gateway(
lambda session: session.call_tool("finance.payment.execute", execute_arguments),
token=token,
)
assert replay_attempt.is_error is True
assert "REQUIRE_APPROVAL" in _text(replay_attempt)
async def test_gateway_lifespan_fails_closed_when_pdp_unreachable(
monkeypatch: pytest.MonkeyPatch,
) -> None:
@@ -188,10 +334,19 @@ async def test_deny_by_default_when_no_oidc_provider_is_configured(
"""
import uvicorn
from tests.e2e.conftest import GATEWAY_PORT, SALES_PORT, RunningServer, run_against_gateway
from tests.e2e.conftest import (
RunningServer,
free_port,
run_against_gateway,
set_current_gateway_port,
)
monkeypatch.setenv("SALES_DOMAIN_URL", f"http://127.0.0.1:{SALES_PORT}/mcp")
sales_port = free_port()
gateway_port = free_port()
monkeypatch.setenv("SALES_DOMAIN_URL", f"http://127.0.0.1:{sales_port}/mcp")
monkeypatch.setenv("OPA_URL", opa_url)
monkeypatch.setenv("SALES_DOMAIN_SERVICE_TOKEN", "test-sales-domain-backend-credential")
monkeypatch.delenv("OIDC_JWKS_URL", raising=False)
monkeypatch.delenv("OIDC_ISSUER", raising=False)
@@ -201,7 +356,7 @@ async def test_deny_by_default_when_no_oidc_provider_is_configured(
uvicorn.Config(
sales_module.server.streamable_http_app(host="127.0.0.1"),
host="127.0.0.1",
port=SALES_PORT,
port=sales_port,
log_level="warning",
)
)
@@ -214,15 +369,14 @@ async def test_deny_by_default_when_no_oidc_provider_is_configured(
gateway_server = RunningServer(
uvicorn.Config(
gateway_module.server.streamable_http_app(
host="127.0.0.1", auth=auth_settings, token_verifier=token_verifier
),
gateway_module.build_app(auth_settings, token_verifier, host="127.0.0.1"),
host="127.0.0.1",
port=GATEWAY_PORT,
port=gateway_port,
log_level="warning",
)
)
await gateway_server.start()
set_current_gateway_port(gateway_port)
try:
list_result = await run_against_gateway(lambda session: session.list_tools())
assert list_result.tools == []
@@ -233,6 +387,7 @@ async def test_deny_by_default_when_no_oidc_provider_is_configured(
assert call_result.is_error is True
assert "UNAUTHENTICATED" in _text(call_result)
finally:
set_current_gateway_port(None)
await gateway_server.stop()
await sales_server.stop()