fix(lease): raise capability-probe timeout to 10s on both halves (#869)
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
The activation/enforcement capability probes (#869 C1/C4) budget 2.0s for
an out-of-process launch of the mosaic CLI, but the CLI's Node cold start
alone measures 2.2-2.3s on a mid-range workstation (sb-it-1-dt,
2026-08-13). Result: every probe timed out, was treated as NO capability
(fail-closed), and every `mosaic <runtime>` launch on such hosts died
with the misleading version-skew message even though the capability
matched exactly. 10s costs nothing on healthy hosts — the happy path
returns as soon as the probe exits; the timeout only bounds hangs.
Also fixes a latent test-hermeticity bug the new budget exposed:
_resolve_probe_command() ignored the provided environ and resolved
`mosaic` against the ambient os.environ PATH, so the "not resolvable on
PATH" unittest actually spawned the host's real CLI — and only passed on
hosts where that real probe happened to exceed the old 2s timeout.
Resolution now honors the provided environment's PATH (fail-closed when
absent); the unittest suite drops from ~2.1s to ~0.004s, confirming no
real process is spawned.
Verified: version_coupling_unittest.py 15/15; lease-activation-probe
spec 15/15; lease-doctor + mutator-gate specs unchanged vs clean next
(4 acceptance failures pre-exist on 216cd722, unrelated seam); eslint +
prettier clean; tsc --noEmit emits the identical pre-existing error set
as clean next. End-to-end on the affected host: patched gate passes the
real probe in 2.19s and `mosaic yolo claude -p` launches successfully.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Dtdjx4Gxude9fwyLezCrhh
This commit is contained in:
co-authored by
Claude Fable 5
parent
216cd72226
commit
2373a5ad34
@@ -62,7 +62,14 @@ EXPECTED_ACTIVATION_CAPABILITY: Final[ActivationCapability] = {
|
|||||||
# capability as compact JSON.
|
# capability as compact JSON.
|
||||||
LEASE_CAPABILITY_PROBE_COMMAND: Final = "__lease-capability"
|
LEASE_CAPABILITY_PROBE_COMMAND: Final = "__lease-capability"
|
||||||
|
|
||||||
PROBE_TIMEOUT_SECONDS: Final = 2.0
|
# Budget for the out-of-process `mosaic __lease-capability` probe. The CLI
|
||||||
|
# is a Node program whose cold start alone measures 2.2-2.3s on a mid-range
|
||||||
|
# workstation (sb-it-1-dt, 2026-08-13), so a 2s budget made every launch on
|
||||||
|
# such hosts fail closed with the #869 skew message even though the
|
||||||
|
# capability matched. The timeout only bounds the pathological hang case —
|
||||||
|
# the happy path returns as soon as the probe exits — so a generous budget
|
||||||
|
# costs nothing on healthy hosts.
|
||||||
|
PROBE_TIMEOUT_SECONDS: Final = 10.0
|
||||||
|
|
||||||
# Override hook: a full shell-style command line (parsed with `shlex.split`)
|
# Override hook: a full shell-style command line (parsed with `shlex.split`)
|
||||||
# to run INSTEAD of resolving `mosaic` on PATH and appending the probe
|
# to run INSTEAD of resolving `mosaic` on PATH and appending the probe
|
||||||
@@ -88,7 +95,13 @@ def _resolve_probe_command(environ: Mapping[str, str]) -> list[str] | None:
|
|||||||
if override:
|
if override:
|
||||||
parsed = shlex.split(override)
|
parsed = shlex.split(override)
|
||||||
return parsed or None
|
return parsed or None
|
||||||
resolved = shutil.which("mosaic")
|
# Resolve against the PROVIDED environment's PATH, not the ambient
|
||||||
|
# os.environ. Before this, a test passing a hermetic environ still
|
||||||
|
# resolved (and spawned) the host's real `mosaic` — masked only on hosts
|
||||||
|
# where the real probe happened to exceed the old 2s timeout. No PATH in
|
||||||
|
# the provided environment means nothing is resolvable (fail-closed),
|
||||||
|
# matching the probe's overall contract.
|
||||||
|
resolved = shutil.which("mosaic", path=environ.get("PATH", ""))
|
||||||
if resolved is None:
|
if resolved is None:
|
||||||
return None
|
return None
|
||||||
return [resolved, LEASE_CAPABILITY_PROBE_COMMAND]
|
return [resolved, LEASE_CAPABILITY_PROBE_COMMAND]
|
||||||
|
|||||||
@@ -55,6 +55,19 @@ export const LEASE_ACTIVATION_CAPABILITY: LeaseActivationCapability = {
|
|||||||
/** Hidden CLI probe subcommand name — wired via {@link registerLeaseCapabilityProbe}. */
|
/** Hidden CLI probe subcommand name — wired via {@link registerLeaseCapabilityProbe}. */
|
||||||
export const LEASE_CAPABILITY_PROBE_COMMAND = '__lease-capability';
|
export const LEASE_CAPABILITY_PROBE_COMMAND = '__lease-capability';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Budget for the out-of-process capability probe. The probe launches a fresh
|
||||||
|
* Node process on the built CLI entrypoint, whose cold start alone measures
|
||||||
|
* 2.2-2.3s on a mid-range workstation (sb-it-1-dt, 2026-08-13) — so the
|
||||||
|
* previous 2s budget made the probe time out and report NO capability on
|
||||||
|
* such hosts, failing every launch with the #869 skew message even though
|
||||||
|
* the capability matched. The timeout only bounds the pathological hang
|
||||||
|
* case; the happy path returns as soon as the probe exits. Mirrors
|
||||||
|
* PROBE_TIMEOUT_SECONDS in the enforcement half
|
||||||
|
* (framework/tools/lease-broker/activation_version_gate.py).
|
||||||
|
*/
|
||||||
|
export const LEASE_CAPABILITY_PROBE_TIMEOUT_MS = 10_000;
|
||||||
|
|
||||||
function capabilityMatches(candidate: LeaseActivationCapability | null): boolean {
|
function capabilityMatches(candidate: LeaseActivationCapability | null): boolean {
|
||||||
return (
|
return (
|
||||||
candidate !== null &&
|
candidate !== null &&
|
||||||
@@ -141,7 +154,7 @@ export function defaultCapabilityProbe(
|
|||||||
|
|
||||||
const output = execFileSync(process.execPath, [cliEntry, LEASE_CAPABILITY_PROBE_COMMAND], {
|
const output = execFileSync(process.execPath, [cliEntry, LEASE_CAPABILITY_PROBE_COMMAND], {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
timeout: 2000,
|
timeout: LEASE_CAPABILITY_PROBE_TIMEOUT_MS,
|
||||||
stdio: ['ignore', 'pipe', 'ignore'],
|
stdio: ['ignore', 'pipe', 'ignore'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user