feat(869-c4): enforcement/activation version-coupling assertion
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
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:
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user