Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc6b593c1e | ||
|
|
9fed383884 |
@@ -54,6 +54,22 @@ def main(
|
|||||||
arguments = parser.parse_args(argv)
|
arguments = parser.parse_args(argv)
|
||||||
source_environment = os.environ if environ is None else environ
|
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:
|
try:
|
||||||
if not arguments.reason or len(arguments.reason) > 128:
|
if not arguments.reason or len(arguments.reason) > 128:
|
||||||
raise ValueError("invalid revoke reason")
|
raise ValueError("invalid revoke reason")
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"test": "vitest run --passWithNoTests && pnpm run test:framework-shell",
|
"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": {
|
"dependencies": {
|
||||||
"@mosaicstack/brain": "workspace:*",
|
"@mosaicstack/brain": "workspace:*",
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -337,7 +337,13 @@ class ExecutableEntrypointTest(unittest.TestCase):
|
|||||||
runpy.run_path(str(TOOLS_DIR / "launch-runtime.py"), run_name="__main__")
|
runpy.run_path(str(TOOLS_DIR / "launch-runtime.py"), run_name="__main__")
|
||||||
self.assertEqual(raised.exception.code, 64)
|
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(
|
with patch.object(
|
||||||
sys,
|
sys,
|
||||||
"argv",
|
"argv",
|
||||||
@@ -352,9 +358,42 @@ class ExecutableEntrypointTest(unittest.TestCase):
|
|||||||
io.StringIO()
|
io.StringIO()
|
||||||
), self.assertRaises(SystemExit) as raised:
|
), self.assertRaises(SystemExit) as raised:
|
||||||
runpy.run_path(str(TOOLS_DIR / "revoke-lease.py"), run_name="__main__")
|
runpy.run_path(str(TOOLS_DIR / "revoke-lease.py"), run_name="__main__")
|
||||||
|
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)
|
self.assertEqual(raised.exception.code, 2)
|
||||||
|
|
||||||
def test_gate_entrypoint_denies_when_identity_environment_is_absent(self) -> None:
|
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:
|
class Stdin:
|
||||||
buffer = io.BytesIO(b'{"tool_name":"Bash"}')
|
buffer = io.BytesIO(b'{"tool_name":"Bash"}')
|
||||||
|
|
||||||
@@ -703,8 +742,11 @@ class LeaseRevocationTest(unittest.TestCase):
|
|||||||
"MOSAIC_RUNTIME_GENERATION": "1",
|
"MOSAIC_RUNTIME_GENERATION": "1",
|
||||||
}
|
}
|
||||||
malformed_session = {**good, "MOSAIC_LEASE_SESSION_ID": "not-a-session"}
|
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 = [
|
cases = [
|
||||||
({}, lambda *_args: {"ok": True, "state": "UNVERIFIED"}),
|
|
||||||
(malformed_session, 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": False, "state": "UNVERIFIED"}),
|
||||||
(good, lambda *_args: {"ok": True, "state": "VERIFIED"}),
|
(good, lambda *_args: {"ok": True, "state": "VERIFIED"}),
|
||||||
|
|||||||
Reference in New Issue
Block a user