Compare commits

...
Author SHA1 Message Date
fred dc6b593c1e test(#1338): amend runtime_tools pins that encoded the D29 defect semantics
ci/woodpecker/pr/ci Pipeline was successful
Two assertions in packages/mosaic/src/mutator-gate/runtime_tools_unittest.py
pinned the behaviour D29 identifies as the defect: an envless revoke-lease.py
returning rc=2. They are changed here under the test-is-the-defect exemption,
stated on the record rather than made quietly. One new assertion is added to
cover what the relaxation must NOT reach.

Why the reclassification is justified. Not because the old pin was careless: it
was deliberate entrypoint doctrine, born in e4d7d45 (WI-3, #842) alongside the
tool itself, and its twin pins the identical property for mutator-gate. Two
measurements beat the doctrine.

  1. Unsatisfiable precondition. The doctrine's only reachable firing point
     cannot be reached by the live fleet. Seats launch with zero MOSAIC_LEASE_*
     (measured, pane pid 8596; 19 of 19 live panes, see #1340), the framework
     ships no provisioning path for fleet launches, and the deployed extension
     does not enforce the total gate, since live seats use tools daily.

  2. Gate-enforced redundancy. For the one case the doctrine protects, a leased
     session whose identity env is stripped: the strip must occur in the
     extension host's own env to reach the observer, at which point the gate
     also loses identity and denies GATE_UNAVAILABLE rc=2 on every tool call
     (measured). The stripped session may compact but cannot mutate. Pre-fix it
     merely died at compaction instead. No mutation path opens either way.

The no-ungated-mutation property lives in the gate, not the revoker. That is
why the revoker moves and the gate does not.

Changes:

1. test_revoker_entrypoint_denies_when_identity_environment_is_absent becomes
   test_revoker_entrypoint_noops_when_identity_environment_is_absent and asserts
   rc=0. The stem is kept so history greps still find it.

2. NEW: test_revoker_entrypoint_denies_when_identity_environment_is_half_provisioned
   asserts rc=2 for socket-only and session-only environments. The no-op is
   reachable only on TOTAL absence; half-provisioned is a machinery-present
   failure and must still fail closed. main() already pinned this, but the
   entrypoint did not, and the entrypoint is what the runtime extension spawns.

3. In test_revoker_fails_closed_on_identity_reply_and_transport_errors, ONLY the
   ({}, ...) element leaves the cases list. The five machinery-present cases
   (malformed session id, ok:false, state:VERIFIED, OSError, JSONDecodeError)
   stay fail-closed and are untouched.

4. test_gate_entrypoint_denies_when_identity_environment_is_absent is left
   UNCHANGED, with a comment recording why. Relaxing it the same way would be a
   real downgrade. The revoker/gate asymmetry is intentional and must not be
   "fixed" later.

Controls run, all three directions, none assumed:

  - Fix plus amendments: 26/26 OK, rc=0.
  - revoke-lease.py alone reverted to origin/next, amendments kept:
    FAILED (failures=1), rc=1. The amended entrypoint assertion still bites.
  - Guard weakened to `len(present) < 2` so half-provisioned falls through to
    the no-op: FAILED (failures=1), rc=1. The new assertion bites.

Required by two independent re-verdicts, both REQUEST_CHANGES on head 9fed3838,
both of which superseded their own earlier APPROVE after CI surfaced the pinned
contract: rev-security-01 review id 242, rev-security-02 review id 243. Each was
asked to refute the reclassification before accepting it and each attempted it
in writing. Item 2 above is rev-security-02's requirement and was not in the
first re-verdict. Both re-affirm the guard's own semantics as correct; what was
missing was the second half of a deliberate contract change.

Refs #1338, #1340
2026-08-20 18:11:51 -05:00
fred 9fed383884 fix(lease-broker): no lease held is a no-op success, not a denied transition
ci/woodpecker/pr/ci Pipeline failed
revoke-lease.py read MOSAIC_LEASE_BROKER_SOCKET and MOSAIC_LEASE_SESSION_ID
with direct dict access inside the try block. A session that never held a
lease raised KeyError, fell into the fail-closed handler, and returned rc=2
on every lifecycle transition -- including compaction. Denying compaction to
a session that holds no lease protects nothing; it converts a recoverable
context limit into a lost session. That is D29.

Absence must be TOTAL to qualify for the no-op. If exactly one variable is
present the session is half-provisioned, which is real misconfiguration, and
it still takes the fail-closed path. An exported-but-empty variable counts as
absent.

Five unit tests cover the contract: no-op success, vacuous no-op (the broker
must not be contacted), both half-provisioned mirrors still rc=2, and empty
string as absent. Red control: 2 of the 5 fail without the guard.

The test is enumerated in test:framework-shell. Every other
src/lease-broker/*_unittest.py is enumerated there, and the #1017 membership
guard's population is *test*.sh under framework/tools/, so it does not see
python suites -- an unenumerated python test is silently never run.

Still open, not fixed here: why seat panes carry no MOSAIC_LEASE_* variables
at all.
2026-08-20 17:37:39 -05:00
4 changed files with 141 additions and 4 deletions
@@ -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")
+1 -1
View File
@@ -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"}),