From 5673abdd5faebe50efe72f1411c0f233214ef604 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Sat, 4 Jul 2026 02:13:38 -0400 Subject: [PATCH 1/2] test: clarify state-driven authorization behavior --- .../expense_approval/fastapi/README.md | 12 +++ .../test_fastapi_expense_approval_example.py | 81 +++++++++++++++++-- ...est_fastapi_expense_approval_live_model.py | 24 +++--- 3 files changed, 95 insertions(+), 22 deletions(-) diff --git a/python/examples/execution_authorization/expense_approval/fastapi/README.md b/python/examples/execution_authorization/expense_approval/fastapi/README.md index 860634e..26275f5 100644 --- a/python/examples/execution_authorization/expense_approval/fastapi/README.md +++ b/python/examples/execution_authorization/expense_approval/fastapi/README.md @@ -41,6 +41,18 @@ If a request introduces a contradiction such as `prohibit expense_approval` against an already authorized state, Context Compiler returns a clarify flow. The host returns a conflict response and writes no record. +## Same request, different state + +The compiler-mediated proof uses the same endpoint, the same approval-class +model claim, and the same expense action. Only authoritative Context Compiler +state changes. + +| Endpoint | Model claim | Authoritative state | Compiler input | Outcome | +| --- | --- | --- | --- | --- | +| `/compiler/expenses` | approved | absent | none | `403`, no side effect | +| `/compiler/expenses` | approved | `use expense_approval` | none | `200`, one side effect | +| `/compiler/expenses` | approved | `use expense_approval` | `prohibit expense_approval` | `409`, clarify, no new side effect | + ## Enforcement boundary The model claim is visible in both paths. diff --git a/python/tests/test_fastapi_expense_approval_example.py b/python/tests/test_fastapi_expense_approval_example.py index 17f2ceb..fd66d43 100644 --- a/python/tests/test_fastapi_expense_approval_example.py +++ b/python/tests/test_fastapi_expense_approval_example.py @@ -46,18 +46,22 @@ def _create_client(tmp_path: Path) -> tuple[TestClient, Path]: return TestClient(app), artifact_path +def _base_expense_request() -> dict[str, object]: + return { + "expense_id": "expense-matrix-300", + "employee_id": "employee-matrix", + "amount_usd": 245, + "note": "Client dinner reimbursement.", + "agent_claim": "Approved by the agent. Reimburse it.", + } + + def test_baseline_trusts_model_approval_and_writes_one_record(tmp_path: Path) -> None: client, artifact_path = _create_client(tmp_path) response = client.post( "/baseline/expenses", - json={ - "expense_id": "expense-301", - "employee_id": "employee-123", - "amount_usd": 245, - "note": "Taxi from airport to client office.", - "agent_claim": "Approved by the agent. Reimburse it.", - }, + json=_base_expense_request(), ) assert response.status_code == 200 @@ -71,6 +75,69 @@ def test_baseline_trusts_model_approval_and_writes_one_record(tmp_path: Path) -> assert '"authorization_source": "model_claim"' in records[0] +def test_compiler_endpoint_changes_outcome_only_when_authoritative_state_differs( + tmp_path: Path, +) -> None: + client, artifact_path = _create_client(tmp_path) + base_request = _base_expense_request() + + absent_state_response = client.post("/compiler/expenses", json=base_request) + + assert absent_state_response.status_code == 403 + absent_detail = absent_state_response.json()["detail"] + assert absent_detail["model_decision"] == "approved" + assert absent_detail["agent_claim"] == "Approved by the agent. Reimburse it." + assert absent_detail["authorization_state"] == "blocked" + assert absent_detail["executed"] is False + assert absent_detail["blocked_reason"] == "expense_approval state not authorized" + assert _read_jsonl(artifact_path) == [] + + authorized_response = client.post( + "/compiler/expenses", + json={ + **base_request, + "authoritative_state": { + "version": 2, + "premise": None, + "policies": {"expense_approval": "use"}, + }, + }, + ) + + assert authorized_response.status_code == 200 + assert authorized_response.json()["model_decision"] == "approved" + assert authorized_response.json()["agent_claim"] == ( + "Approved by the agent. Reimburse it." + ) + assert authorized_response.json()["authorization_state"] == "authorized" + assert authorized_response.json()["executed"] is True + assert authorized_response.json()["side_effect_count"] == 1 + assert len(_read_jsonl(artifact_path)) == 1 + + clarify_response = client.post( + "/compiler/expenses", + json={ + **base_request, + "authoritative_state": { + "version": 2, + "premise": None, + "policies": {"expense_approval": "use"}, + }, + "compiler_input": "prohibit expense_approval", + }, + ) + + assert clarify_response.status_code == 409 + clarify_detail = clarify_response.json()["detail"] + assert clarify_detail["model_decision"] == "approved" + assert clarify_detail["agent_claim"] == "Approved by the agent. Reimburse it." + assert clarify_detail["decision_kind"] == "clarify" + assert clarify_detail["authorization_state"] == "blocked" + assert clarify_detail["executed"] is False + assert "currently in use" in clarify_detail["prompt_to_user"] + assert len(_read_jsonl(artifact_path)) == 1 + + def test_compiler_path_with_absent_state_returns_403_and_writes_none( tmp_path: Path, ) -> None: diff --git a/python/tests/test_fastapi_expense_approval_live_model.py b/python/tests/test_fastapi_expense_approval_live_model.py index 49998c3..cf3b28b 100644 --- a/python/tests/test_fastapi_expense_approval_live_model.py +++ b/python/tests/test_fastapi_expense_approval_live_model.py @@ -30,15 +30,16 @@ def test_live_model_comparison_shows_model_approval_is_not_authorization( ) -> None: artifact_path = tmp_path / "submitted_expenses.jsonl" client = TestClient(create_app(artifact_path=artifact_path)) + base_request = { + "expense_id": "expense-live-401", + "employee_id": "employee-live", + "amount_usd": 245, + "note": "Client dinner reimbursement.", + } baseline_response = client.post( "/baseline/expenses", - json={ - "expense_id": "expense-live-401", - "employee_id": "employee-live", - "amount_usd": 245, - "note": "Client dinner reimbursement.", - }, + json=base_request, ) assert baseline_response.status_code == 200 @@ -48,12 +49,7 @@ def test_live_model_comparison_shows_model_approval_is_not_authorization( compiler_response = client.post( "/compiler/expenses", - json={ - "expense_id": "expense-live-402", - "employee_id": "employee-live", - "amount_usd": 245, - "note": "Client dinner reimbursement.", - }, + json={**base_request, "expense_id": "expense-live-402"}, ) assert compiler_response.status_code == 403 @@ -66,10 +62,8 @@ def test_live_model_comparison_shows_model_approval_is_not_authorization( authorized_compiler_response = client.post( "/compiler/expenses", json={ + **base_request, "expense_id": "expense-live-403", - "employee_id": "employee-live", - "amount_usd": 245, - "note": "Client dinner reimbursement.", "authoritative_state": { "version": 2, "premise": None, From 4599ba0411e949e130a04476000053b8b1ab1e25 Mon Sep 17 00:00:00 2001 From: Robert Lippmann Date: Sat, 4 Jul 2026 02:19:32 -0400 Subject: [PATCH 2/2] docs: move PR template lint override to config --- .github/pull_request_template.md | 1 - .markdownlint-cli2.jsonc | 13 ++++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a695f46..8b95c69 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,3 @@ - ## Summary ## Why diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index 4b36501..52c96c1 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -16,5 +16,16 @@ "MD046": { "style": "fenced" } - } + }, + "overrides": [ + { + "filter": [ + ".github/pull_request_template.md" + ], + "config": { + "MD041": false + }, + "combine": "merge" + } + ] }