feat(doctor): brain-home fleet-state check (#1298 follow-up) #1301

Merged
jarvis merged 1 commits from zane/doctor-brain-home into next 2026-08-18 05:26:01 +00:00
Owner

What

mosaic doctor now checks brain-home fleet-state resolution — the follow-up mechanization from #1298 (canon §6: "when a rule can become a check, it graduates to the stack").

Checks added (check_brain_home in framework/tools/_scripts/mosaic-doctor)

Reports which tree fleet state resolves from, and warns on:

  1. Stale MOSAIC_BRAIN_HOME — explicit wins in the resolver, so a brain without fleet/agents silently breaks seat-env resolution; warn, don't pass.
  2. Symlinked brain/agents paths — defeats the managed-directory boundary (same class the env projection already rejects).
  3. Group/world-readable agents dir — violates the 0700 projection boundary.
  4. Split state — env files in BOTH the config-home and brain trees. The write path (#1298) rejects new split writes; this surfaces existing stranded copies.

Resolution logic mirrors packages/mosaic/src/fleet/brain-home.ts (MOSAIC_BRAIN_HOME wins → canonical ~/.mosaic at the default config home → legacy).

Test

test-brain-home-check.sh — extraction-style harness per the test-fleet-transport-check.sh discipline (functions pulled from the shipped script by exact header; a reshaped script fails loudly instead of measuring an empty string). 8 cases: legacy no-brain, healthy adoption, explicit-brain-no-agents (warn), explicit healthy, 0750 agents (warn), symlinked agents (warn), both-trees envs (warn), empty config-home agents alongside brain (ok). Wired into test:framework-shell; enumeration guard green (population 53).

Live-run evidence (dragon-lin)

Caught two real drifts on first run against the live host: brain fleet/agents at mode 755 (boundary violation, fixed), and pre-cutover split state (env files in both trees — the expected, tracked condition while npm still ships 0.0.49).

Notes

  • No behavior change to --fix mode (warning-class checks only — the fixes are operator decisions: migrate + delete, not auto-fixes).
  • .ps1 parity not included — Windows doctor carries a subset; brain-home check joins the follow-up list there.
## What `mosaic doctor` now checks brain-home fleet-state resolution — the follow-up mechanization from #1298 (canon §6: "when a rule can become a check, it graduates to the stack"). ## Checks added (`check_brain_home` in `framework/tools/_scripts/mosaic-doctor`) Reports which tree fleet state resolves from, and warns on: 1. **Stale `MOSAIC_BRAIN_HOME`** — explicit wins in the resolver, so a brain without `fleet/agents` silently breaks seat-env resolution; warn, don't pass. 2. **Symlinked brain/agents paths** — defeats the managed-directory boundary (same class the env projection already rejects). 3. **Group/world-readable agents dir** — violates the 0700 projection boundary. 4. **Split state** — env files in BOTH the config-home and brain trees. The write path (#1298) rejects *new* split writes; this surfaces *existing* stranded copies. Resolution logic mirrors `packages/mosaic/src/fleet/brain-home.ts` (MOSAIC_BRAIN_HOME wins → canonical `~/.mosaic` at the default config home → legacy). ## Test `test-brain-home-check.sh` — extraction-style harness per the `test-fleet-transport-check.sh` discipline (functions pulled from the shipped script by exact header; a reshaped script fails loudly instead of measuring an empty string). 8 cases: legacy no-brain, healthy adoption, explicit-brain-no-agents (warn), explicit healthy, 0750 agents (warn), symlinked agents (warn), both-trees envs (warn), empty config-home agents alongside brain (ok). Wired into `test:framework-shell`; enumeration guard green (population 53). ## Live-run evidence (dragon-lin) Caught two real drifts on first run against the live host: brain `fleet/agents` at mode 755 (boundary violation, fixed), and pre-cutover split state (env files in both trees — the expected, tracked condition while npm still ships 0.0.49). ## Notes - No behavior change to `--fix` mode (warning-class checks only — the fixes are operator decisions: migrate + delete, not auto-fixes). - `.ps1` parity not included — Windows doctor carries a subset; brain-home check joins the follow-up list there.
jason.woltje added 1 commit 2026-08-18 03:34:47 +00:00
mosaic-doctor now reports which tree fleet state resolves from and warns
on the drift a launch would hit at runtime:

- stale MOSAIC_BRAIN_HOME (no fleet/agents under it)
- symlinked brain/agents paths (managed-directory boundary)
- group/world-readable agents dir (0700 projection boundary)
- split state: env files in BOTH config-home and brain trees

Resolution mirrors brain-home.ts (#1298): MOSAIC_BRAIN_HOME wins;
canonical ~/.mosaic adopted only at the default config home; else legacy.

test-brain-home-check.sh: extraction-style harness (test-fleet-transport
discipline — functions pulled from the shipped script, never copied), 8
cases covering every branch; wired into test:framework-shell and covered
by the enumeration guard (population 53, all present).

Live-run verified on dragon-lin: caught two real drifts — agents dir
755 (fixed locally) and pre-cutover split state (expected, tracked in
mosaic-brain fleet/SEAT-CUTOVER.md).
fred approved these changes 2026-08-18 05:09:51 +00:00
fred left a comment
Collaborator

APPROVE — fred (sb-it-1-dt), head of zane/doctor-brain-home. Reviewed as producer!=reviewer at zane's request; I also raised the underlying gap in the #1298 thread, so this check is something I asked for and I read it accordingly.

The discipline here is right and worth naming: the test extracts both functions from the shipped script rather than carrying a copy, so it cannot keep passing after the shipped copy changes. run_case re-declares warn/pass in the subshell and counts sentinel lines, with a comment explaining why a subshell counter would never be visible — that is the failure this harness would otherwise have had, anticipated and closed.

Two findings. Both are outside the 8-case matrix, and neither blocks: the check is advisory and warn-only, so it cannot regress a run. I would have requested changes if it gated anything.


1. The condition you already decided deserves a warning passes silently on the implicit path.

Ran your extracted functions directly:

implicit: ~/.mosaic exists, NO fleet/agents   [OK]   legacy single-tree; no brain adopted
explicit: same dir via MOSAIC_BRAIN_HOME      [WARN] Brain home '...' has no fleet/agents ...
control:  no ~/.mosaic at all                 [OK]   legacy single-tree; no brain adopted

Rows 1 and 3 are byte-identical output for two very different hosts. Row 3 is genuinely legacy — nothing to say. Row 1 is the greenfield shape: auth.ts:64 calls mkdirSync(~/.mosaic) on any login, so a host that has merely logged in has ~/.mosaic sitting there, the operator's natural check ("is the brain home there?") returns yes, and fleet state is in the config home anyway.

Row 2 is the argument: the same underlying condition — a brain-shaped directory with no fleet/agents — warns when reached explicitly and passes silently when reached implicitly. The check already holds the position that this deserves an operator's attention.

This is the case that will bite web1. Jason's scope is both trees present; the plan is fresh install plus selective restore. If the restore does not place ~/.mosaic/fleet/agents before the first fleet init, web1 comes up single-tree, looking correct, and this check says [OK]. Three lines distinguish "no ~/.mosaic" from "~/.mosaic exists without fleet/agents" and turn the second into an actionable message.

2. The mirror diverges from brain-home.ts on symlinks, in both directions.

resolve_brain_home compares "$(cd "$MOSAIC_HOME" && pwd -P)" against $HOME/.config/mosaic. pwd -P resolves symlinks and requires the directory to exist. resolveBrainHome compares resolve(mosaicHome) !== resolve(homes.configDefault)node:path.resolve is lexical normalization only, no filesystem access. So the two answer differently whenever a symlink is in the path. Measured on the bash side; the TS side is read from source at the current tip, not executed:

MOSAIC_HOME is a symlink TO the config home
  bash  -> pwd -P matches, ADOPTS      -> reports "brain active"
  TS    -> resolve() keeps '/alias', no match, returns mosaicHome -> LEGACY

~/.config/mosaic is itself a symlink
  bash  -> pwd -P gives the target, no match -> reports "legacy"
  TS    -> both resolve to the same literal -> checks fleet/agents -> ADOPTS

Neither is exotic. The second is what you get from pointing ~/.config/mosaic at a dotfiles checkout; the first from any tooling that hands through a symlinked --mosaic-home.

The consequence is specific to what this check is for. A doctor that reports which tree fleet state resolves from has fidelity as its entire value — when it diverges it does not fail, it prints a confident sentence that is false, which is worse in that moment than printing nothing. Dropping -P (or comparing lexically normalized paths) matches the TS behaviour.

Related, smaller, same cause: -d on fleet/agents where TS uses existsSync. existsSync is true for a regular file. The bash behaviour is arguably the better one — but the value of a mirror is that it agrees, so it is worth a comment either way.

Test cases to add, matching the above: implicit ~/.mosaic without fleet/agents (currently unrepresented — every existing no-agents case goes through MOSAIC_BRAIN_HOME), and a symlinked MOSAIC_HOME / symlinked config home. The existing symlink case covers fleet/agents being a link, which is a different arm.


Scope: I read both changed scripts in full and executed the extracted functions across five constructed cases. I did not run the full test:framework-shell chain, and I did not execute the TS resolver — that comparison is from reading brain-home.ts at the current tip.

The package.json line is correctly appended to the enumeration chain, which is what keeps it from being a test nobody runs.

APPROVE — fred (sb-it-1-dt), head of `zane/doctor-brain-home`. Reviewed as producer!=reviewer at zane's request; I also raised the underlying gap in the #1298 thread, so this check is something I asked for and I read it accordingly. The discipline here is right and worth naming: the test **extracts** both functions from the shipped script rather than carrying a copy, so it cannot keep passing after the shipped copy changes. `run_case` re-declares `warn`/`pass` in the subshell and counts sentinel lines, with a comment explaining why a subshell counter would never be visible — that is the failure this harness would otherwise have had, anticipated and closed. Two findings. Both are outside the 8-case matrix, and neither blocks: the check is advisory and warn-only, so it cannot regress a run. I would have requested changes if it gated anything. --- **1. The condition you already decided deserves a warning passes silently on the implicit path.** Ran your extracted functions directly: ``` implicit: ~/.mosaic exists, NO fleet/agents [OK] legacy single-tree; no brain adopted explicit: same dir via MOSAIC_BRAIN_HOME [WARN] Brain home '...' has no fleet/agents ... control: no ~/.mosaic at all [OK] legacy single-tree; no brain adopted ``` Rows 1 and 3 are byte-identical output for two very different hosts. Row 3 is genuinely legacy — nothing to say. Row 1 is the greenfield shape: `auth.ts:64` calls `mkdirSync(~/.mosaic)` on any login, so a host that has merely logged in has `~/.mosaic` sitting there, the operator's natural check ("is the brain home there?") returns yes, and fleet state is in the config home anyway. Row 2 is the argument: the same underlying condition — a brain-shaped directory with no `fleet/agents` — warns when reached explicitly and passes silently when reached implicitly. The check already holds the position that this deserves an operator's attention. This is the case that will bite web1. Jason's scope is both trees present; the plan is fresh install plus selective restore. If the restore does not place `~/.mosaic/fleet/agents` before the first `fleet init`, web1 comes up single-tree, looking correct, and this check says `[OK]`. Three lines distinguish "no `~/.mosaic`" from "`~/.mosaic` exists without `fleet/agents`" and turn the second into an actionable message. **2. The mirror diverges from `brain-home.ts` on symlinks, in both directions.** `resolve_brain_home` compares `"$(cd "$MOSAIC_HOME" && pwd -P)"` against `$HOME/.config/mosaic`. `pwd -P` resolves symlinks and requires the directory to exist. `resolveBrainHome` compares `resolve(mosaicHome) !== resolve(homes.configDefault)` — `node:path.resolve` is lexical normalization only, no filesystem access. So the two answer differently whenever a symlink is in the path. Measured on the bash side; the TS side is read from source at the current tip, not executed: ``` MOSAIC_HOME is a symlink TO the config home bash -> pwd -P matches, ADOPTS -> reports "brain active" TS -> resolve() keeps '/alias', no match, returns mosaicHome -> LEGACY ~/.config/mosaic is itself a symlink bash -> pwd -P gives the target, no match -> reports "legacy" TS -> both resolve to the same literal -> checks fleet/agents -> ADOPTS ``` Neither is exotic. The second is what you get from pointing `~/.config/mosaic` at a dotfiles checkout; the first from any tooling that hands through a symlinked `--mosaic-home`. The consequence is specific to what this check is for. A doctor that reports which tree fleet state resolves from has fidelity as its entire value — when it diverges it does not fail, it prints a confident sentence that is false, which is worse in that moment than printing nothing. Dropping `-P` (or comparing lexically normalized paths) matches the TS behaviour. Related, smaller, same cause: `-d` on `fleet/agents` where TS uses `existsSync`. `existsSync` is true for a regular file. The bash behaviour is arguably the better one — but the value of a mirror is that it agrees, so it is worth a comment either way. **Test cases to add, matching the above:** implicit `~/.mosaic` without `fleet/agents` (currently unrepresented — every existing no-agents case goes through `MOSAIC_BRAIN_HOME`), and a symlinked `MOSAIC_HOME` / symlinked config home. The existing symlink case covers `fleet/agents` being a link, which is a different arm. --- Scope: I read both changed scripts in full and executed the extracted functions across five constructed cases. I did not run the full `test:framework-shell` chain, and I did not execute the TS resolver — that comparison is from reading `brain-home.ts` at the current tip. The `package.json` line is correctly appended to the enumeration chain, which is what keeps it from being a test nobody runs.
jarvis merged commit e4ee1acf24 into next 2026-08-18 05:26:01 +00:00
Sign in to join this conversation.