feat(869-c4): enforcement/activation version-coupling assertion
Some checks failed
ci/woodpecker/pr/ci Pipeline was canceled

Part of #869 (Point-1 C4). Locks the lease broker's ENFORCEMENT half
(launch-runtime.py) and ACTIVATION half (execLeaseGatedRuntime, C1's
LEASE_ACTIVATION_CAPABILITY) as one versioned unit, closing the #828
version-skew gap C1 only made observable.

- New packages/mosaic/framework/tools/lease-broker/activation_version_gate.py:
  EXPECTED_ACTIVATION_CAPABILITY (enforcement-owned, mirrors but is
  independent from C1's LEASE_ACTIVATION_CAPABILITY), a fail-closed
  subprocess probe of the CLI's hidden `mosaic __lease-capability`
  command, and an assertion that FAILS LOUD (VersionCouplingError with
  an actionable #869 remediation message) on mismatch OR absence —
  never a silent pass, never a dead gate.
- launch-runtime.py now runs this assertion first, before any broker
  registration, and denies with a dedicated exit code (65) distinct
  from its existing usage (64) and registration-failure (1) codes.
- Red-first tests: src/mutator-gate/version_coupling_unittest.py
  (module-level match/mismatch/name-mismatch/absent-capability cases,
  and seam-level LAUNCHER.main() cases proving the gate runs before
  broker registration and never silently passes).
- Existing fail-closed-on-absent-identity lock
  (runtime_tools_unittest.py) stays green: matching-capability fakes
  were injected into its pre-existing LAUNCHER.main() calls so the new
  gate runs alongside, not in place of, identity enforcement.
- mutator-gate.acceptance.spec.ts updated to supply a fake CLI-capability
  probe (matching the existing fake-broker/fake-runtime-binary test
  doubles already in that suite) everywhere it drives launch-runtime.py
  as a real subprocess.

launch.ts and lease-activation-probe.ts are untouched (C2/C5 avoidance,
C1 read-only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
mosaic-coder
2026-07-23 13:44:20 -05:00
parent 4422231bdb
commit eaeb633d24
6 changed files with 543 additions and 1 deletions

View File

@@ -12,11 +12,24 @@ from collections.abc import Callable, Mapping, Sequence
from pathlib import Path
from typing import Final
from activation_version_gate import (
EXPECTED_ACTIVATION_CAPABILITY,
ActivationCapability,
VersionCouplingError,
assert_activation_capability_matches,
default_probe_activation_capability,
)
from lease_generation import initialize_runtime_generation
MAX_FRAME: Final = 64 * 1024
BROKER_TIMEOUT_SECONDS: Final = 1.5
CLAUDE_DANGEROUS_FLAG: Final = "--dangerously-skip-permissions"
# Distinct, non-overlapping exit code for the C4 version-coupling gate (see
# `activation_version_gate.py`) — deliberately different from the `1`
# (broker registration failed closed) and `64` (usage error) codes already
# owned by this script, so a version-skew denial is unambiguous in caller
# logs/tests and is never confused with a broker-availability failure.
EXIT_VERSION_SKEW: Final = 65
def broker_request(socket_path: Path, request: dict[str, object]) -> dict[str, object]:
@@ -47,6 +60,10 @@ def main(
request: Callable[[Path, dict[str, object]], dict[str, object]] = broker_request,
execute: Callable[[str, list[str], dict[str, str]], object] = os.execvpe,
initialize_generation: Callable[[Path, int], None] = initialize_runtime_generation,
probe_activation_capability: Callable[
[Mapping[str, str]], ActivationCapability | None
] = default_probe_activation_capability,
expected_activation_capability: ActivationCapability = EXPECTED_ACTIVATION_CAPABILITY,
) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--runtime", required=True, choices=("claude", "pi"))
@@ -66,6 +83,25 @@ def main(
command = [command[0], CLAUDE_DANGEROUS_FLAG, *command[1:]]
source_environment = os.environ if environ is None else environ
# C4 version-coupling gate (#869 Point-1): before this ENFORCEMENT half
# chains into anything, assert that the ACTIVATION contract it is about
# to rely on (MOSAIC_LEASE_* injection, broker chaining) matches what
# this enforcement build expects. This is a build/deploy-defect check,
# not a broker-availability question, so it runs before — and
# independently of — broker registration below, and it FAILS LOUD: a
# clear stderr message plus a dedicated non-zero exit code, never a
# silent pass and never folded into the generic registration-failure
# branch.
try:
assert_activation_capability_matches(
probe_activation_capability(source_environment),
expected_activation_capability,
)
except VersionCouplingError as version_error:
print(str(version_error), file=sys.stderr)
return EXIT_VERSION_SKEW
try:
socket_path = Path(source_environment["MOSAIC_LEASE_BROKER_SOCKET"])
generation = int(source_environment.get("MOSAIC_RUNTIME_GENERATION", "1"))