diff --git a/packages/mosaic/src/mutator-gate/runtime_tools_unittest.py b/packages/mosaic/src/mutator-gate/runtime_tools_unittest.py index 25947b6a..9707895d 100644 --- a/packages/mosaic/src/mutator-gate/runtime_tools_unittest.py +++ b/packages/mosaic/src/mutator-gate/runtime_tools_unittest.py @@ -337,7 +337,13 @@ class ExecutableEntrypointTest(unittest.TestCase): runpy.run_path(str(TOOLS_DIR / "launch-runtime.py"), run_name="__main__") self.assertEqual(raised.exception.code, 64) - def test_revoker_entrypoint_denies_when_identity_environment_is_absent(self) -> None: + def test_revoker_entrypoint_noops_when_identity_environment_is_absent(self) -> None: + # D29 supersession. This assertion previously pinned rc=2. Absent identity + # means no lease was ever held, so there is nothing to revoke and the correct + # result is no-op success. The old pin was written in e4d7d45 (WI-3), the same + # commit that shipped launch-runtime.py's lease-var provisioning, on the + # assumption that an envless revoker was unreachable. D29 falsified that in + # production. Behavioural pins live in src/lease-broker/revoke_noop_unittest.py. with patch.object( sys, "argv", @@ -352,9 +358,42 @@ class ExecutableEntrypointTest(unittest.TestCase): io.StringIO() ), self.assertRaises(SystemExit) as raised: runpy.run_path(str(TOOLS_DIR / "revoke-lease.py"), run_name="__main__") - self.assertEqual(raised.exception.code, 2) + self.assertEqual(raised.exception.code, 0) + + def test_revoker_entrypoint_denies_when_identity_environment_is_half_provisioned( + self, + ) -> None: + # The no-op above is reachable ONLY when identity is TOTALLY absent. A + # half-provisioned environment is a machinery-present failure and must still + # fail closed. main() already pins this; the entrypoint did not, and the + # entrypoint is what the runtime extension actually spawns. + half_provisioned = ( + {"MOSAIC_LEASE_BROKER_SOCKET": "/run/test/broker.sock"}, + {"MOSAIC_LEASE_SESSION_ID": "d" * 64}, + ) + for environment in half_provisioned: + with self.subTest(environment=environment), patch.object( + sys, + "argv", + [ + str(TOOLS_DIR / "revoke-lease.py"), + "--runtime", + "claude", + "--reason", + "pre-compact", + ], + ), patch.dict(os.environ, environment, clear=True), redirect_stderr( + io.StringIO() + ), self.assertRaises(SystemExit) as raised: + runpy.run_path(str(TOOLS_DIR / "revoke-lease.py"), run_name="__main__") + self.assertEqual(raised.exception.code, 2) def test_gate_entrypoint_denies_when_identity_environment_is_absent(self) -> None: + # Deliberately NOT changed alongside its revoker twin above. The asymmetry is + # intentional: the gate's deny-on-absent is the authorization path and is + # load-bearing, so absent identity must fail closed here. The revoker's rc=2 + # was inert in the same case (no session id means no broker call is possible), + # which is why only the revoker moved under D29. Do not "restore symmetry". class Stdin: buffer = io.BytesIO(b'{"tool_name":"Bash"}') @@ -703,8 +742,11 @@ class LeaseRevocationTest(unittest.TestCase): "MOSAIC_RUNTIME_GENERATION": "1", } malformed_session = {**good, "MOSAIC_LEASE_SESSION_ID": "not-a-session"} + # D29 exemption: the `({}, ...)` case was removed from this list. An empty + # environment is absence-of-lease, not an identity/reply/transport failure, and + # its correct result is no-op success (pinned in revoke_noop_unittest.py). The + # five cases below are all machinery-present failures and stay fail-closed. cases = [ - ({}, lambda *_args: {"ok": True, "state": "UNVERIFIED"}), (malformed_session, lambda *_args: {"ok": True, "state": "UNVERIFIED"}), (good, lambda *_args: {"ok": False, "state": "UNVERIFIED"}), (good, lambda *_args: {"ok": True, "state": "VERIFIED"}),