fix(installer): re-link runtime assets after the CLI stage, so greenfield keeps its enforcement hooks
ci/woodpecker/pr/ci Pipeline was canceled

The framework's install.sh ends by running mosaic-link-runtime-assets, which
asks the `mosaic` CLI whether lease enforcement can be activated before
deciding whether to wire the #828 hooks into settings.json. Part 1 (framework)
runs before Part 2 (npm CLI), so on a first install there is no CLI to ask. The
script takes its fail-safe branch, prints a four-line ERROR, and writes
settings.json with mutator-gate.py and receipt-observer-client.py stripped out.

Measured on canary 1125, rolled back to greenfield, `--next --yes`, no TTY:

  framework template ~/.config/mosaic/runtime/claude/settings.json
    mutator-gate.py            1 occurrence
    receipt-observer-client.py 1 occurrence
  installed ~/.claude/settings.json after a clean rc=0 install
    mutator-gate.py            wired: False
    receipt-observer-client.py wired: False

So enforcement ends up off because of the order the two halves install in, not
because of anything about the host. Falsified by running the same script by
hand once the CLI existed: rc=0, both hooks wired: True. The guard's real
verdict on that host was 'activatable' the whole time.

This adds one more pass after Part 2. The script is idempotent (unchanged files
are skipped), so on an upgrade — CLI already present, first pass already
correct — it is a no-op. It deliberately does not pass
--allow-inactive-enforcement: Part 1 does not either, and a repair pass must
not be more permissive than the pass it corrects.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-15 21:38:11 -05:00
co-authored by Claude Opus 5
parent 00bc602f93
commit 47e90767b7
+33
View File
@@ -953,6 +953,39 @@ if [[ "$FLAG_CLI" == "true" ]]; then
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# Post-install repair: link runtime assets again, now that the CLI exists
# ═══════════════════════════════════════════════════════════════════════════════
#
# The framework's own install.sh ends by running `mosaic-link-runtime-assets`,
# and that script asks the `mosaic` CLI whether lease enforcement can be
# activated on this host before deciding whether to wire the #828 enforcement
# hooks into settings.json. On a first install there is no CLI to ask — Part 1
# runs before Part 2 — so it takes its fail-safe branch, prints a four-line
# ERROR, and writes settings.json with the hooks stripped out.
#
# The result is that a greenfield host always ends up with enforcement
# disabled, decided by the order the two halves install in rather than by
# anything about the host. Measured on canary 1125, greenfield, both the
# `--ref` and `--next` lanes.
#
# Running it once more here lets the guard reach its real verdict. The script
# is idempotent — unchanged files are skipped — so on an upgrade, where the CLI
# was already present and the first pass already succeeded, this is a no-op.
# No `--allow-inactive-enforcement`: Part 1 does not pass it either, and this
# pass must not be more permissive than the one it is correcting.
if [[ "$FLAG_CHECK" == "false" && "$FLAG_FRAMEWORK" == "true" && "$FLAG_CLI" == "true" ]]; then
RELINK_SCRIPT="$MOSAIC_HOME/tools/_scripts/mosaic-link-runtime-assets"
if [[ -x "$RELINK_SCRIPT" && -x "$PREFIX/bin/mosaic" ]]; then
step "Runtime assets (re-check with CLI present)"
if PATH="$PREFIX/bin:$PATH" "$RELINK_SCRIPT" >/dev/null; then
ok "Runtime assets linked"
else
warn "Runtime asset linking is still degraded — see message above."
fi
fi
fi
# ═══════════════════════════════════════════════════════════════════════════════
# Summary
# ═══════════════════════════════════════════════════════════════════════════════