diff --git a/packages/mosaic/framework/tools/lease-broker/revoke-lease.py b/packages/mosaic/framework/tools/lease-broker/revoke-lease.py index 0e6a829f..e5484f97 100644 --- a/packages/mosaic/framework/tools/lease-broker/revoke-lease.py +++ b/packages/mosaic/framework/tools/lease-broker/revoke-lease.py @@ -54,6 +54,22 @@ def main( arguments = parser.parse_args(argv) source_environment = os.environ if environ is None else environ + # D29: a session that never held a lease has nothing to revoke, and that is a + # SUCCESS, not a failed revocation. The block below is deliberately fail-closed + # for a broker that is unreachable, which is right — but it cannot distinguish + # "the broker is down" from "there was never a lease", so a bare-launched + # session was denied every lifecycle transition, including compaction. Denying + # compaction protects nothing there; it converts a recoverable context limit + # into a lost session. + # + # Absence must be TOTAL to qualify. If exactly one variable is present the + # session is half-provisioned, which is real misconfiguration, and it still + # takes the fail-closed path below. + lease_variables = ("MOSAIC_LEASE_BROKER_SOCKET", "MOSAIC_LEASE_SESSION_ID") + present = [name for name in lease_variables if source_environment.get(name)] + if not present: + return 0 + try: if not arguments.reason or len(arguments.reason) > 128: raise ValueError("invalid revoke reason") diff --git a/packages/mosaic/package.json b/packages/mosaic/package.json index 9548f844..67eae276 100644 --- a/packages/mosaic/package.json +++ b/packages/mosaic/package.json @@ -25,7 +25,7 @@ "lint": "eslint src", "typecheck": "tsc --noEmit", "test": "vitest run --passWithNoTests && pnpm run test:framework-shell", - "test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 framework/tools/quality/scripts/test-framework-drift-check.py && bash framework/tools/quality/scripts/test-framework-drift-doctor.sh && bash framework/systemd/user/test-fleet-units.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/promotion_binding_unittest.py && python3 src/lease-broker/promotion_trigger_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/receipt_observer_client_unittest.py && python3 src/lease-broker/invariant_r_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-edit.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-no-status.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-fork-ci-status.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-pr-merge-message-field.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/git/test-explain-diagnostic-status-neutral.sh && bash framework/tools/git/test-detect-platform-outside-repo.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/_scripts/test-mosaic-init-rce.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh && bash framework/tools/glpi/test-list-http-status.sh && bash framework/tools/orchestrator/test-board-roll.sh && bash framework/tools/woodpecker/test-ci-wait-exit-matrix.sh && bash framework/tools/_scripts/test-fleet-transport-check.sh && bash framework/tools/_scripts/test-brain-home-check.sh" + "test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 framework/tools/quality/scripts/test-framework-drift-check.py && bash framework/tools/quality/scripts/test-framework-drift-doctor.sh && bash framework/systemd/user/test-fleet-units.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/promotion_binding_unittest.py && python3 src/lease-broker/promotion_trigger_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/receipt_observer_client_unittest.py && python3 src/lease-broker/invariant_r_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/lease-broker/revoke_noop_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-edit.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-no-status.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-fork-ci-status.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-pr-merge-message-field.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/git/test-explain-diagnostic-status-neutral.sh && bash framework/tools/git/test-detect-platform-outside-repo.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/_scripts/test-mosaic-init-rce.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh && bash framework/tools/glpi/test-list-http-status.sh && bash framework/tools/orchestrator/test-board-roll.sh && bash framework/tools/woodpecker/test-ci-wait-exit-matrix.sh && bash framework/tools/_scripts/test-fleet-transport-check.sh && bash framework/tools/_scripts/test-brain-home-check.sh" }, "dependencies": { "@mosaicstack/brain": "workspace:*", diff --git a/packages/mosaic/src/lease-broker/revoke_noop_unittest.py b/packages/mosaic/src/lease-broker/revoke_noop_unittest.py new file mode 100644 index 00000000..453102e4 --- /dev/null +++ b/packages/mosaic/src/lease-broker/revoke_noop_unittest.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""D29 contracts: no lease is a no-op success; half-provisioned still fails closed.""" + +from __future__ import annotations + +import importlib.util +import unittest +from pathlib import Path + +TOOLS = Path(__file__).parents[2] / "framework/tools/lease-broker" +REVOKE_PATH = TOOLS / "revoke-lease.py" + +_spec = importlib.util.spec_from_file_location("revoke_lease", REVOKE_PATH) +assert _spec and _spec.loader +revoke_lease = importlib.util.module_from_spec(_spec) +import sys as _sys + +_sys.path.insert(0, str(TOOLS)) +_spec.loader.exec_module(revoke_lease) + +ARGV = ["--runtime", "claude", "--reason", "pre-compact"] +VALID_SESSION = "a" * 64 + + +def _explode(*_args, **_kwargs): + raise AssertionError("broker must not be contacted when no lease is held") + + +class RevokeWithoutLease(unittest.TestCase): + def test_no_lease_variables_is_a_noop_success(self) -> None: + """The D29 case: bare-launched session, nothing to revoke, must not deny.""" + self.assertEqual( + revoke_lease.main(ARGV, environ={}, request=_explode), + 0, + ) + + def test_no_lease_does_not_contact_the_broker(self) -> None: + """A no-op must be vacuous: no socket, no generation bump, no transport.""" + revoke_lease.main(ARGV, environ={"HOME": "/nonexistent"}, request=_explode) + + def test_socket_without_session_still_fails_closed(self) -> None: + """Half-provisioned is misconfiguration, not absence. Fail-closed stands.""" + self.assertEqual( + revoke_lease.main( + ARGV, + environ={"MOSAIC_LEASE_BROKER_SOCKET": "/tmp/nonexistent.sock"}, + request=_explode, + ), + 2, + ) + + def test_session_without_socket_still_fails_closed(self) -> None: + """The mirror case, so the guard cannot be satisfied by either half alone.""" + self.assertEqual( + revoke_lease.main( + ARGV, + environ={"MOSAIC_LEASE_SESSION_ID": VALID_SESSION}, + request=_explode, + ), + 2, + ) + + def test_empty_string_counts_as_absent(self) -> None: + """An exported-but-empty variable is not a lease.""" + self.assertEqual( + revoke_lease.main( + ARGV, + environ={ + "MOSAIC_LEASE_BROKER_SOCKET": "", + "MOSAIC_LEASE_SESSION_ID": "", + }, + request=_explode, + ), + 0, + ) + + +if __name__ == "__main__": + unittest.main()