From de13931c75678d34225a4452bb49f1e8df882f8f Mon Sep 17 00:00:00 2001 From: codex Date: Tue, 3 Nov 2026 05:36:45 +0100 Subject: [PATCH] Send organization_id when the MAS gate lists the application's API keys. The attended mint run on 2026-11-03 got HTTP 400 from the key listing before any key was minted. A Console session needs organization_id there. Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 4541@bnt-lap001 Assistant-Session: 2099b3aa-d7d9-49a9-ab4e-fa8b7d0e39e8 --- scripts/mas_evidence_publisher_gate.py | 22 ++++++++++++------- tests/test_mas_evidence_publisher_gate.py | 3 ++- .../RPF-WP-0070-mas-evidence-recovery.md | 8 +++++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/scripts/mas_evidence_publisher_gate.py b/scripts/mas_evidence_publisher_gate.py index 97c9945..62e9a37 100755 --- a/scripts/mas_evidence_publisher_gate.py +++ b/scripts/mas_evidence_publisher_gate.py @@ -671,11 +671,16 @@ def write_pair(pair): return summary -def _app_keys(api): - value = api( - "GET", - "/iam/v1alpha1/api-keys?application_id=" + APPLICATION_ID + "&page_size=100", - ) +def _app_keys(api, organization): + """List the application's keys. A Console session needs organization_id or gets HTTP 400.""" + from urllib.parse import urlencode + + query = urlencode({ + "organization_id": organization, + "application_id": APPLICATION_ID, + "page_size": 100, + }) + value = api("GET", "/iam/v1alpha1/api-keys?" + query) keys = value.get("api_keys") if isinstance(value, dict) else None if not isinstance(keys, list) or value.get("total_count") != len(keys): raise GateError("key_inventory_rejected") @@ -699,9 +704,10 @@ def mint_workload_pair(api, prove, write, sleep=time.sleep): """ receipt = {"application_id": APPLICATION_ID, "values_recorded": False} app = api("GET", "/iam/v1alpha1/applications/" + APPLICATION_ID) - if app.get("name") != POLICY_NAME: + organization = app.get("organization_id") + if app.get("name") != POLICY_NAME or not isinstance(organization, str) or not organization: raise GateError("application_binding_refused") - before = _app_keys(api) + before = _app_keys(api, organization) receipt["keys_before"] = _key_view(before) if len(before) != 1 or before[0].get("default_project_id") != PROJECT_ID: raise GateError("supplier_key_state_requires_review", receipt["keys_before"]) @@ -745,7 +751,7 @@ def mint_workload_pair(api, prove, write, sleep=time.sleep): detail = dict(exc.detail or {}) if isinstance(exc, GateError) and isinstance(exc.detail, dict) else {} detail["minted_key_deleted"] = deleted raise GateError(code, detail) from None - receipt["keys_after"] = _key_view(_app_keys(api)) + receipt["keys_after"] = _key_view(_app_keys(api, organization)) receipt["status"] = "passed" return receipt diff --git a/tests/test_mas_evidence_publisher_gate.py b/tests/test_mas_evidence_publisher_gate.py index 6ee23d1..60a7565 100644 --- a/tests/test_mas_evidence_publisher_gate.py +++ b/tests/test_mas_evidence_publisher_gate.py @@ -269,8 +269,9 @@ class _FakeConsole: def __call__(self, method, path, data=None): self.calls.append((method, path)) if method == "GET" and path.startswith("/iam/v1alpha1/applications/"): - return {"name": self.name} + return {"name": self.name, "organization_id": "org-1"} if method == "GET" and path.startswith("/iam/v1alpha1/api-keys?"): + assert "organization_id=org-1" in path and "application_id=" + gate.APPLICATION_ID in path return {"api_keys": list(self.keys), "total_count": len(self.keys)} if method == "POST": assert data["application_id"] == gate.APPLICATION_ID diff --git a/workplans/RPF-WP-0070-mas-evidence-recovery.md b/workplans/RPF-WP-0070-mas-evidence-recovery.md index 25cf380..9b7a208 100644 --- a/workplans/RPF-WP-0070-mas-evidence-recovery.md +++ b/workplans/RPF-WP-0070-mas-evidence-recovery.md @@ -104,6 +104,14 @@ The supplier key stays founder custody and can be revoked separately. Tests cover the mint, retry, deletion and refusal paths. The attended run is still pending. +2026-11-03 05:36 CET: the first attended `--mint-workload` run exited with +`provider_http_error`, a GET returning HTTP 400, before any key was minted. +The denials receipt was not rewritten and the pair stayed empty. Cause: the +API-key listing omitted `organization_id`, which a Console session requires. +`mas_evidence_iam_grant.py` already sends it. Fix: the gate takes +`organization_id` from the application read and sends it on both key +listings. The next attended run is the same command. + ## Admit delivery and place the node file ```task