From 47e90767b73736a6afdf43922afab2e6b7c4c053 Mon Sep 17 00:00:00 2001 From: fred Date: Sat, 15 Aug 2026 21:38:11 -0500 Subject: [PATCH] fix(installer): re-link runtime assets after the CLI stage, so greenfield keeps its enforcement hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- tools/install.sh | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tools/install.sh b/tools/install.sh index f6a29833..0d797d5c 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -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 # ═══════════════════════════════════════════════════════════════════════════════