feat(mosaic): install-ordering guard for lease-enforcement hook wiring (#869 Point-1 C2)
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful

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)
This commit is contained in:
mosaic-coder
2026-07-23 13:31:31 -05:00
parent 4422231bdb
commit b6f3656422
8 changed files with 1004 additions and 17 deletions

View File

@@ -18,12 +18,33 @@ set -Eeuo pipefail
# MOSAIC_INSTALL_MODE — prompt|keep|overwrite (default: prompt)
# MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING — 1 to bypass MCP check
# MOSAIC_SKIP_SKILLS_SYNC — 1 to skip skill sync
#
# Flags (CLI args, NOT environment variables — see #869 Point-1 C2):
# --allow-inactive-enforcement Explicit, per-invocation opt-out that lets the
# lease-enforcement hooks (mutator-gate.py,
# receipt-observer-client.py) be wired into
# ~/.claude/settings.json even when this host
# cannot confirm it can ACTIVATE them. Loud on
# use (see mosaic-link-runtime-assets). Default
# (flag absent) is fail-loud: the enforcement
# hooks are NOT wired and the framework's
# runtime-asset-link step reports a failure.
# ──────────────────────────────────────────────────────────────────────────────
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TARGET_DIR="${MOSAIC_HOME:-$HOME/.config/mosaic}"
INSTALL_MODE="${MOSAIC_INSTALL_MODE:-prompt}"
# Deliberately parsed from "$@" (a real, explicit, per-invocation argument) —
# never an environment variable — so this opt-out can never sit silently
# inherited in a shell profile. See #869 Point-1 C2.
ALLOW_INACTIVE_ENFORCEMENT=0
for _arg in "$@"; do
case "$_arg" in
--allow-inactive-enforcement) ALLOW_INACTIVE_ENFORCEMENT=1 ;;
esac
done
# Shared framework path-ownership manifest reader (#791). Parity with
# packages/mosaic/src/framework/manifest.ts — both consume framework-manifest.txt.
# Sourcing does not run its CLI dispatch (guarded by BASH_SOURCE==$0).
@@ -670,10 +691,15 @@ step "Post-install tasks"
SCRIPTS="$TARGET_DIR/tools/_scripts"
if [[ -x "$SCRIPTS/mosaic-link-runtime-assets" ]]; then
if "$SCRIPTS/mosaic-link-runtime-assets" >/dev/null 2>&1; then
link_args=()
[[ "$ALLOW_INACTIVE_ENFORCEMENT" == "1" ]] && link_args+=(--allow-inactive-enforcement)
# stdout is suppressed as before, but stderr is left connected: the
# install-ordering guard's FAIL LOUD message (#869 Point-1 C2) must reach
# the operator, not be swallowed silently.
if "$SCRIPTS/mosaic-link-runtime-assets" "${link_args[@]}" >/dev/null; then
ok "Runtime assets linked"
else
warn "Runtime asset linking failed (non-fatal)"
warn "Runtime asset linking failed (non-fatal) — see message above for details."
fi
fi