feat(mosaic): install-ordering guard for lease-enforcement hook wiring (#869 Point-1 C2) #879
Reference in New Issue
Block a user
Delete Branch "feat/869-c2-install-ordering-guard"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
mosaic-link-runtime-assetscopiesruntime/claude/settings.json— whichembeds the
PreToolUsemutator-gate.pyhook and theStopreceipt-observer-client.pyhook — straight into~/.claude/settings.json,unconditionally. If the ACTIVATION half of the lease broker (C1's
leaseEnforcementActivatable(): a CLI build advertising launch-runtimeactivation + a running broker supervisor) can't be confirmed on the host, the
fail-closed gate then denies every tool call with
GATE_UNAVAILABLE— abricked host.
This adds the install-ordering guard: before the enforcement hooks are
wired, call
leaseEnforcementActivatable()(imported directly from the C1module,
lease-activation-probe.ts) and decide whether they may be written.The guard (
packages/mosaic/src/commands/install-ordering-guard.ts)guardClaudeSettingsWiring()/runInstallOrderingGuard()— pure,injectable-deps functions; a hidden CLI subcommand
(
mosaic __link-claude-settings <src> <dest>) bridges the bash wiringpoint to this TS logic.
hook entries (every other hook —
PreCompact/SessionStartrevoke-lease,prevent-memory-write.sh, thePostToolUseqa/typecheck hooks, and thesibling
reflect-stop-hook.shinside the sameStoptrigger — is leftbyte-identical), exit 1, print an actionable message:
The opt-out:
--allow-inactive-enforcementA real CLI flag, threaded through
install.sh->mosaic-link-runtime-assets-> the hidden
mosaic __link-claude-settingssubcommand's own--allow-inactive-enforcementoption. Deliberately never an environmentvariable — it can't sit as a silently-inherited default in a shell profile.
Absent (the default) => hard fail-loud path. When set on a non-activatable
host, wiring proceeds but a loud, explicit warning is always logged.
Wiring point located
packages/mosaic/framework/tools/_scripts/mosaic-link-runtime-assetsiswhere
settings.json(and thus the enforcement hooks baked into it) actuallygets copied to
~/.claude/, called from bothframework/install.sh'spost-install step and the wizard's
finalize.ts. Both callers previouslyswallowed stderr (
2>&1 >/dev/null/stdio: 'pipe'with no surfacing) —fixed so the guard's message actually reaches the operator instead of being
silent.
What's unchanged
mutator-gate.py's own fail-closed-on-absent-identity runtime behavior isuntouched — this guard only decides whether the hook gets wired, never
touches the gate's own denial logic.
runtime_tools_unittest.pyandfail-closed-regression.spec.tsremain green, unmodified.Red-first tests
install-ordering-guard.spec.ts(19 tests): probe=false => hooks stripped,exit 1, actionable message; probe=false + opt-out => hooks wired, loud warn,
exit 0; probe=true => hooks wired normally, no logs; file-level
runInstallOrderingGuardexercised against temp dirs only (backup-on-changeparity with
copy_file_managed, idempotent no-op writes) — never touchesthe real
~/.claude/settings.json.test-install-ordering-guard.sh(5 shell scenarios, added totest:framework-shell): exercises the actual bash wiring inmosaic-link-runtime-assets(fakemosaicstub + the no-mosaic-on-PATHpython3 fallback), including that every other runtime file still gets
copied when the settings.json guard degrades.
Test plan
pnpm typecheck(via turbo,--filter=@mosaicstack/mosaic) — passpnpm lint(via turbo,--filter=@mosaicstack/mosaic) — passpnpm format:check— passpnpm --filter @mosaicstack/mosaic test(vitest +test:framework-shell,including the new shell regression) — pass, 1484+ tests
fail-closed-regression.spec.ts/runtime_tools_unittest.py— unchanged, greenPart of #869 (Point-1 C2)
mosaic-link-runtime-assets wired the PreToolUse mutator-gate.py and Stop receipt-observer-client.py hooks into ~/.claude/settings.json unconditionally. If the activation half (C1's leaseEnforcementActivatable()) can't be confirmed on the host, the fail-closed gate then denies every tool call — bricking it. Add a TS install-ordering guard (guardClaudeSettingsWiring / runInstallOrderingGuard, hidden CLI bridge `mosaic __link-claude-settings`) that imports leaseEnforcementActivatable() directly and decides whether the enforcement hooks get wired: - activatable -> wire as-is. - NOT activatable, no opt-out -> strip the enforcement hooks, exit non-zero with an actionable message (default, fail-loud). - NOT activatable + explicit --allow-inactive-enforcement -> wire anyway, loud warning logged. --allow-inactive-enforcement is a real CLI flag threaded through install.sh -> mosaic-link-runtime-assets -> the hidden subcommand — deliberately never an environment variable, so it can't sit as a silently-inherited default. Wire mosaic-link-runtime-assets's guarded settings.json copy to call out to the CLI (with a python3 fallback if `mosaic` isn't resolvable at all), and stop swallowing stderr in install.sh/finalize.ts so the guard's message actually reaches the operator. mutator-gate.py's own fail-closed-on-absent-identity runtime behavior is untouched (this only gates the WIRING); runtime_tools_unittest.py and fail-closed-regression.spec.ts remain green. Part of #869 (Point-1 C2)Gate-16 APPROVE -- stamped by Mos (id-11)
PR author jason.woltje(id2) != approver Mos(id11); commit-author mosaic-coder != reviewer -> no Finding-0. Registers the independent reviewer's APPROVE (#869 Point-1 C2, install-ordering guard); MS-LEAD relayed, Mos stamps+merges.
VERDICT: APPROVE @
b6f3656422(Part of #869 -- Point-1 card, umbrella stays open).Independent review, all 8 PASS -- verified against the REAL production runtime/claude/settings.json template (not just fixtures): (1) guard strips ONLY the 2 enforcement hooks per-entry, siblings survive, activatable round-trip byte-identical, C1 probe read-only; (2) opt-out --allow-inactive-enforcement CLI-flag-ONLY (no env escape, strict ===true), absent->hard-fail(exit1,strip,actionable), present-on-non-activatable->wired+loud-warn, no silent path; (3) fail-loud reaches operator (install.sh stderr no longer swallowed); (4) bash wiring routes settings.json through guard + python3 fallback; (5) fail-closed locks untouched+green; (6) red-first 19 vitest + 5 shell, mktemp isolation; (7) firewall/scope clean (8 files, operator-agnostic).
The guard: wires the 2 enforcement hooks ONLY if leaseEnforcementActivatable(), else strips + fails LOUD -- the install-ordering half of the #828/#869 brick-prevention.
6-CHECK re-verified by Mos @b6f365642249390b7dbe9ff983fbc514220b7aab: open/not-merged/mergeable onto current main/base main; CI success wp1988 (8/8) @exact head; reviewed==CI==head; independent APPROVE author!=reviewer no REQUEST-CHANGES; body 'Part of #869'; queue clear.