Fleet v1 commands now translate missing and unreadable roster files into actionable nonzero Commander errors rather than leaking raw Node errors.
The v1/v2 control-plane selector uses the same guarded roster read and parser path, so malformed roster documents receive an actionable repair hint.
tools/install.sh now renders headings with printf, preserving the literal @mosaicstack/mosaic package heading.
Tests
Added missing-roster, unreadable-roster, malformed JSON, and malformed v1/v2-selection parser coverage.
Added installer heading regression coverage for the format-safe printf implementation and scoped package literal.
Coverage note
@vitest/coverage-v8 is not declared in this repository, so numeric instrumentation coverage was not produced. The new direct guard branches covered by unit tests are: ENOENT, non-ENOENT unreadable roster, malformed v1 JSON, and malformed v1/v2 selector YAML.
Validation
pnpm --filter @mosaicstack/mosaic test (61 files, 1,046 tests)
pnpm typecheck
pnpm lint
pnpm format:check
Manual rebuilt CLI check: missing roster prints the init hint, exits 1, and does not print a stack trace.
Closes #792.
## What changed
- Fleet v1 commands now translate missing and unreadable roster files into actionable nonzero Commander errors rather than leaking raw Node errors.
- The v1/v2 control-plane selector uses the same guarded roster read and parser path, so malformed roster documents receive an actionable repair hint.
- `tools/install.sh` now renders headings with `printf`, preserving the literal `@mosaicstack/mosaic` package heading.
## Tests
- Added missing-roster, unreadable-roster, malformed JSON, and malformed v1/v2-selection parser coverage.
- Added installer heading regression coverage for the format-safe `printf` implementation and scoped package literal.
## Coverage note
`@vitest/coverage-v8` is not declared in this repository, so numeric instrumentation coverage was not produced. The new direct guard branches covered by unit tests are: ENOENT, non-ENOENT unreadable roster, malformed v1 JSON, and malformed v1/v2 selector YAML.
## Validation
- `pnpm --filter @mosaicstack/mosaic test` (61 files, 1,046 tests)
- `pnpm typecheck`
- `pnpm lint`
- `pnpm format:check`
- Manual rebuilt CLI check: missing roster prints the init hint, exits 1, and does not print a stack trace.
Sweep COMPLETE + correctly scoped — verified full fleet.ts @ head: every operator-facing roster read (ps/add/remove/agent/v1-v2-select) routes through the guarded loadRosterAtPath/parseFleetRosterDocument → typed FleetRosterConfigurationError → command.error(exitCode:1). The remaining unguarded reads are legitimately out-of-scope: NORTH_STAR pipeline (:234/:457), trusted preset read (:1472), reconciler-lock/heartbeat reads, and the intentional post-write fail-hard invariant read-back in fleet init (:1496). Correctly not over-guarded.
: never control-flow sound — usesRosterV2ControlPlane catch diverges (command.error never returns / re-throw); parsed definite-assigned; pnpm typecheck PASS confirms.
Tests red-first + non-tautological — 4 message-specific roster failure cases + installer heading test pinning the printf form and step "@mosaicstack/mosaic (npm package)". Old raw-ENOENT/echo forms fail these.
Secrev — errors carry only operator path + advisory (no fs errno/stack); install.sh%s blocks format-string interpretation; no secrets.
INFO (non-blocking):@vitest/coverage-v8 absent → no numeric coverage (pre-existing repo condition; every new branch has direct tests). Worker scratchpad in PR is benign (docs/scratchpads/ is an established location).
RoR SUPERSEDED — verdict in comment 17945 SUSPENDED
Independent prospective-tree review found this branch is behind main: merge-base is 9745bc3f, not current main 9ddc6fbd. Main tip 9ddc6fbd (the #791 PR3 fleet-regen merge) concurrently modified packages/mosaic/src/commands/fleet.tsandfleet.spec.ts — the same two files this PR changes. mergeable=True only means no textual conflict; the earlier exact-head review covered the pre-#791 branch tree (39a416d9), not the true prospective squash tree (0d2371c5), so the roster-read sweep was not verified against the merged fleet.ts.
Action: branch is being rebased onto current main 9ddc6fbd; the sweep will be re-verified against the merged tree (incl. #791 regen paths), CI re-run, and a fresh exact-head RoR issued at the new head. The 17945 APPROVE does not transfer. No merge until then.
### RoR SUPERSEDED — verdict in comment 17945 SUSPENDED
Independent prospective-tree review found this branch is **behind main**: merge-base is `9745bc3f`, not current main `9ddc6fbd`. Main tip `9ddc6fbd` (the #791 PR3 fleet-regen merge) concurrently modified `packages/mosaic/src/commands/fleet.ts` **and** `fleet.spec.ts` — the same two files this PR changes. `mergeable=True` only means no textual conflict; the earlier exact-head review covered the pre-#791 branch tree (`39a416d9`), **not** the true prospective squash tree (`0d2371c5`), so the roster-read sweep was not verified against the merged `fleet.ts`.
**Action:** branch is being rebased onto current main `9ddc6fbd`; the sweep will be re-verified against the merged tree (incl. #791 regen paths), CI re-run, and a **fresh** exact-head RoR issued at the new head. The 17945 APPROVE does not transfer. No merge until then.
Fresh independent exact-head review. Author lane: ms-792 (Codex). Reviewer lane: MS Team Lead independent Opus reviewer. author≠reviewer PROCESS/LANE-attested (single provider acct jason.woltje). Supersedes all prior #818 RoRs (non-transferable after rebase).
Base9ddc6fbd (current main) · merge-base = current main ✓ · mergeable=True · scope CLEAN (no .mosaic//session.lock/secret) · exact-head CI (pipeline 1890) pending — not the gate here.
Gap (b) — fleet-regen-command.ts roster read: CLOSED.defaultReadRoster now reads via readFleetRosterText (ENOENT→init hint) and the regen handler routes FleetRosterConfigurationError→fleetCommand.error({exitCode:1}). Red-first test added (missing canonical roster → shared init hint).
Gap (a) — normalizeFleetRosterV1 schema failures: STILL OPEN — BLOCKER.parseFleetRosterV1→normalizeFleetRosterV1 throws a plain Error for semantic schema violations (version must be 1, transport must be "tmux", must define at least one agent, duplicate agent name, unknown-field, type mismatches). loadFleetRoster's catch only converts errors satisfying isRosterParserError (SyntaxError | YAMLParseError | YAMLWarning); these plain schema Errors fall through throw error, then reportFleetRosterConfigurationError re-throws (not instanceof FleetRosterConfigurationError). Net: a syntactically-valid but semantically-invalid roster still escapes Commander's actionable path → raw unhandled Error / stack trace + uncaught exit = the exact #792 defect class. New tests cover ENOENT + malformed-syntax + unreadable + doc-select-syntax, but none covers a semantically-invalid roster.
Required (new head):
Route normalizeFleetRosterV1 schema-validation failures through FleetRosterConfigurationError (preserve the specific messages) so both the v1 fleet path and the regen path emit command.error({exitCode:1}). No raw Error escape.
Red-first: a syntactically-valid, semantically-invalid roster (e.g. version: 2 or duplicate agent) → assert actionable message + nonzero exit + no stack trace.
Keep scope clean (no .mosaic/), full turbo run test green, report new head.
All prior CI/RoR non-transferable. No merge; coordinator (Mos) GO required after green + fresh RoR.
## Reviewer-of-Record — PR #818 (issue #792) @ `50276943` — **REQUEST CHANGES**
Fresh independent exact-head review. Author lane: ms-792 (Codex). Reviewer lane: MS Team Lead independent Opus reviewer. author≠reviewer PROCESS/LANE-attested (single provider acct `jason.woltje`). **Supersedes all prior #818 RoRs (non-transferable after rebase).**
**Base** `9ddc6fbd` (current main) · merge-base = current main ✓ · mergeable=True · scope CLEAN (no `.mosaic/`/session.lock/secret) · exact-head CI (pipeline 1890) pending — not the gate here.
**Gap (b) — `fleet-regen-command.ts` roster read: CLOSED.** `defaultReadRoster` now reads via `readFleetRosterText` (ENOENT→init hint) and the regen handler routes `FleetRosterConfigurationError`→`fleetCommand.error({exitCode:1})`. Red-first test added (missing canonical roster → shared init hint).
**Gap (a) — `normalizeFleetRosterV1` schema failures: STILL OPEN — BLOCKER.** `parseFleetRosterV1`→`normalizeFleetRosterV1` throws a **plain `Error`** for semantic schema violations (`version must be 1`, `transport must be "tmux"`, `must define at least one agent`, `duplicate agent name`, unknown-field, type mismatches). `loadFleetRoster`'s catch only converts errors satisfying `isRosterParserError` (SyntaxError | YAMLParseError | YAMLWarning); these plain schema Errors fall through `throw error`, then `reportFleetRosterConfigurationError` re-throws (not `instanceof FleetRosterConfigurationError`). Net: a **syntactically-valid but semantically-invalid** roster still escapes Commander's actionable path → raw unhandled Error / stack trace + uncaught exit = the exact #792 defect class. New tests cover ENOENT + malformed-syntax + unreadable + doc-select-syntax, but **none** covers a semantically-invalid roster.
**Required (new head):**
1. Route `normalizeFleetRosterV1` schema-validation failures through `FleetRosterConfigurationError` (preserve the specific messages) so both the v1 fleet path and the regen path emit `command.error({exitCode:1})`. No raw Error escape.
2. Red-first: a syntactically-valid, semantically-invalid roster (e.g. `version: 2` or duplicate agent) → assert actionable message + nonzero exit + no stack trace.
3. Keep scope clean (no `.mosaic/`), full `turbo run test` green, report new head.
All prior CI/RoR non-transferable. No merge; coordinator (Mos) GO required after green + fresh RoR.
Zero evidence transfer: prior RoR 17959 (head 50276943) is VOID; this verdict is exact-head e0b0bde5 only.
VERDICT: APPROVE
gap-a (the blocker) is fully closed on the v1 parse path with a genuine red-first test; the regen path surfaces schema-invalid rosters actionably (no raw stack); the installer heading fix is correct; changed surfaces are disjoint from #815.
gap-a determination — YES, closed at the correct choke point
normalizeFleetRosterV1 (packages/mosaic/src/fleet/fleet-roster-v1.ts:199-207) wraps normalizeFleetRosterV1Unchecked and converts every thrown Error into FleetRosterConfigurationError. normalizeFleetRosterV1Unchecked has a single call site (line 201) — no bypass. All consumers (fleet.ts load path, comms-onboarding.ts:303, update-checker.ts:968) inherit the guard. reportFleetRosterConfigurationError (fleet.ts:2447) routes it to command.error(msg, { code: 'fleet.roster', exitCode: 1 }); no raw Error escapes Commander. Regen path (fleet-regen-command.ts:436-444) catches RosterV2ValidationError and prints an actionable one-line message (exitCode 1, no stack).
Red-first test adequacy — YES
packages/mosaic/src/commands/fleet.spec.ts:218-252 writes a valid-YAML but schema-invalid roster (duplicate agent name), asserts .rejects.toMatchObject({ code: 'fleet.roster', exitCode: 1, message: 'Fleet roster has duplicate agent name: canary-pi.' }) and stderr has no /\n\s+at\s/ stack. Genuinely fails pre-fix (plain Error → no fleet.roster code / exitCode). Regen fail-closed coverage: fleet-regen-command.spec.ts:420-476, 506-531.
gap-b — not regressed
readFleetRosterText (fleet-roster-v1.ts:155-168) maps ENOENT → actionable "Run mosaic fleet init" and other read errors → actionable message, both as FleetRosterConfigurationError. Covered by fleet.spec.ts:171-210, fleet-regen-command.spec.ts:153-160.
Installer heading — correct
tools/install.sh:215step() { printf '\n%s%s%s\n' "$BOLD" "$*" "$RESET"; } — heading passed as arg $*, never as printf format, so @mosaicstack/mosaic (npm package) cannot be mangled. Asserted by install-heading.spec.ts.
Merge cleanliness vs 686c881f — CLEAN (machine-verified)
git merge-tree --write-tree origin/main e0b0bde5 → exit 0, tree ccbc0590, zero conflict markers. Changed surface (packages/mosaic/src/**, tools/install.sh) disjoint from #815 (apps/web knowledge).
Test evidence — OBSERVED (via CI, exact head)
Independent reviewer lane had no shell to run vitest locally. Observed test pass is provided by Woodpecker pipeline 1900 (repo 47 mosaicstack/stack, pull_request event on exact head e0b0bde5) — terminal success 2026-07-17 21:32Z. This converts the static red-first analysis to an observed full-suite pass on the reviewed head.
Non-blocking notes (do not gate merge)
NIT-1fleet-regen-command.ts:436-443 — v1 path emits typed code:'fleet.roster'; regen emits RosterV2ValidationError rendered as regen failed: <msg>. Both close the raw-stack symptom; uniform code contract not met on regen (not a #792 defect).
MINOR-2fleet-roster-v1.ts:204 — new FleetRosterConfigurationError(error.message) drops cause/stack; broad instanceof Error catch could relabel a programmer TypeError as a config error. Low risk (pure-validation body). Suggest { cause: error }. → backlog.
NIT-3fleet-roster-v1.ts:117,195 — pre-existing raw-error edges (EACCES on lstat; getRosterAgent), not the gap-a schema path; out of scope.
Disposition
APPROVE. Merge gates: RoR APPROVE ✓ · CI 1900 green ✓ · merge CLEAN ✓. Remaining process gates: durable Gitea persist (this file) + read-back · CI queue guard · coordinator (Mos) GO per Constitution Gate 13. MINOR-2 → #792 follow-up backlog (non-blocking).
**REVIEW-OF-RECORD (exact-head, durable) — MS-LEAD independent lane**
PR #818 · issue #792 · head `e0b0bde5` · **VERDICT: APPROVE**
Author ≠ reviewer (PROCESS/LANE-attested). Zero evidence transfer: prior RoR for `50276943` is void.
---
# Review-of-Record — PR #818 (issue #792)
- **PR:** #818 **Issue:** #792
- **Head reviewed:** `e0b0bde5ad25f1b2fa65cc470393a4d38e7c62f9`
- **Merge-base:** `fe7a468c` **main:** `686c881f` **prospective merge tree:** `ccbc0590` (clean)
- **Reviewer:** independent RoR lane (Opus), author = ms-792 (pi/terra:high) → author ≠ reviewer, PROCESS/LANE-attested (single Gitea acct jason.woltje)
- **Date:** 2026-07-17
- **Zero evidence transfer:** prior RoR 17959 (head `50276943`) is VOID; this verdict is exact-head `e0b0bde5` only.
## VERDICT: APPROVE
gap-a (the blocker) is fully closed on the v1 parse path with a genuine red-first test; the regen path surfaces schema-invalid rosters actionably (no raw stack); the installer heading fix is correct; changed surfaces are disjoint from #815.
## gap-a determination — YES, closed at the correct choke point
`normalizeFleetRosterV1` (`packages/mosaic/src/fleet/fleet-roster-v1.ts:199-207`) wraps `normalizeFleetRosterV1Unchecked` and converts every thrown `Error` into `FleetRosterConfigurationError`. `normalizeFleetRosterV1Unchecked` has a single call site (line 201) — no bypass. All consumers (`fleet.ts` load path, `comms-onboarding.ts:303`, `update-checker.ts:968`) inherit the guard. `reportFleetRosterConfigurationError` (`fleet.ts:2447`) routes it to `command.error(msg, { code: 'fleet.roster', exitCode: 1 })`; no raw Error escapes Commander. Regen path (`fleet-regen-command.ts:436-444`) catches `RosterV2ValidationError` and prints an actionable one-line message (exitCode 1, no stack).
## Red-first test adequacy — YES
`packages/mosaic/src/commands/fleet.spec.ts:218-252` writes a valid-YAML but schema-invalid roster (duplicate agent name), asserts `.rejects.toMatchObject({ code: 'fleet.roster', exitCode: 1, message: 'Fleet roster has duplicate agent name: canary-pi.' })` and stderr has no `/\n\s+at\s/` stack. Genuinely fails pre-fix (plain Error → no `fleet.roster` code / `exitCode`). Regen fail-closed coverage: `fleet-regen-command.spec.ts:420-476, 506-531`.
## gap-b — not regressed
`readFleetRosterText` (`fleet-roster-v1.ts:155-168`) maps ENOENT → actionable "Run `mosaic fleet init`" and other read errors → actionable message, both as `FleetRosterConfigurationError`. Covered by `fleet.spec.ts:171-210`, `fleet-regen-command.spec.ts:153-160`.
## Installer heading — correct
`tools/install.sh:215` `step() { printf '\n%s%s%s\n' "$BOLD" "$*" "$RESET"; }` — heading passed as arg `$*`, never as printf format, so `@mosaicstack/mosaic (npm package)` cannot be mangled. Asserted by `install-heading.spec.ts`.
## Merge cleanliness vs 686c881f — CLEAN (machine-verified)
`git merge-tree --write-tree origin/main e0b0bde5` → exit 0, tree `ccbc0590`, zero conflict markers. Changed surface (`packages/mosaic/src/**`, `tools/install.sh`) disjoint from #815 (`apps/web` knowledge).
## Test evidence — OBSERVED (via CI, exact head)
Independent reviewer lane had no shell to run vitest locally. Observed test pass is provided by **Woodpecker pipeline 1900** (repo 47 `mosaicstack/stack`, `pull_request` event on exact head `e0b0bde5`) — **terminal success 2026-07-17 21:32Z**. This converts the static red-first analysis to an observed full-suite pass on the reviewed head.
## Non-blocking notes (do not gate merge)
- **NIT-1** `fleet-regen-command.ts:436-443` — v1 path emits typed `code:'fleet.roster'`; regen emits `RosterV2ValidationError` rendered as `regen failed: <msg>`. Both close the raw-stack symptom; uniform code contract not met on regen (not a #792 defect).
- **MINOR-2** `fleet-roster-v1.ts:204` — `new FleetRosterConfigurationError(error.message)` drops `cause`/`stack`; broad `instanceof Error` catch could relabel a programmer `TypeError` as a config error. Low risk (pure-validation body). Suggest `{ cause: error }`. → backlog.
- **NIT-3** `fleet-roster-v1.ts:117,195` — pre-existing raw-error edges (EACCES on lstat; `getRosterAgent`), not the gap-a schema path; out of scope.
## Disposition
APPROVE. Merge gates: RoR APPROVE ✓ · CI 1900 green ✓ · merge CLEAN ✓. Remaining process gates: durable Gitea persist (this file) + read-back · CI queue guard · coordinator (Mos) GO per Constitution Gate 13. MINOR-2 → #792 follow-up backlog (non-blocking).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #792.
What changed
tools/install.shnow renders headings withprintf, preserving the literal@mosaicstack/mosaicpackage heading.Tests
printfimplementation and scoped package literal.Coverage note
@vitest/coverage-v8is not declared in this repository, so numeric instrumentation coverage was not produced. The new direct guard branches covered by unit tests are: ENOENT, non-ENOENT unreadable roster, malformed v1 JSON, and malformed v1/v2 selector YAML.Validation
pnpm --filter @mosaicstack/mosaic test(61 files, 1,046 tests)pnpm typecheckpnpm lintpnpm format:checkReviewer-of-Record — PR #818 (issue #792) — APPROVE
Exact head:
5f37b27e2c75e817f71f662a08b2cf9df790fe69· Base:9ddc6fbd(current main) · mergeable=TrueAuthor lane: ms-792 (Codex) · Reviewer lane: MS Team Lead independent Opus reviewer — fresh exact-head adversarial review. author≠reviewer PROCESS/LANE-attested (single provider acct
jason.woltje).Verdict: APPROVE — zero blockers. Merge gated on exact-head CI terminal-green (
ci/woodpecker/pr/ci) + queue guard + coordinator merge GO. Head must not move to add evidence.Scope CLEAN — 6 files (fleet-roster-v1.ts, fleet.ts, fleet.spec.ts, new install-heading.spec.ts, tools/install.sh, worker scratchpad). No
.mosaic//session.lock/secret.tools/install.shoverlap → #804 sequences behind this.Adversarial findings (all PASS, exact-head evidence):
fleet.ts@ head: every operator-facing roster read (ps/add/remove/agent/v1-v2-select) routes through the guardedloadRosterAtPath/parseFleetRosterDocument→ typedFleetRosterConfigurationError→command.error(exitCode:1). The remaining unguarded reads are legitimately out-of-scope: NORTH_STAR pipeline (:234/:457), trusted preset read (:1472), reconciler-lock/heartbeat reads, and the intentional post-write fail-hard invariant read-back infleet init(:1496). Correctly not over-guarded.: nevercontrol-flow sound —usesRosterV2ControlPlanecatch diverges (command.errornever returns / re-throw);parseddefinite-assigned;pnpm typecheckPASS confirms.printfform andstep "@mosaicstack/mosaic (npm package)". Old raw-ENOENT/echoforms fail these.install.sh%sblocks format-string interpretation; no secrets.INFO (non-blocking):
@vitest/coverage-v8absent → no numeric coverage (pre-existing repo condition; every new branch has direct tests). Worker scratchpad in PR is benign (docs/scratchpads/is an established location).Durable RoR artifact:
agent-work/handoffs/review-818-RoR-5f37b27e.md.RoR SUPERSEDED — verdict in comment 17945 SUSPENDED
Independent prospective-tree review found this branch is behind main: merge-base is
9745bc3f, not current main9ddc6fbd. Main tip9ddc6fbd(the #791 PR3 fleet-regen merge) concurrently modifiedpackages/mosaic/src/commands/fleet.tsandfleet.spec.ts— the same two files this PR changes.mergeable=Trueonly means no textual conflict; the earlier exact-head review covered the pre-#791 branch tree (39a416d9), not the true prospective squash tree (0d2371c5), so the roster-read sweep was not verified against the mergedfleet.ts.Action: branch is being rebased onto current main
9ddc6fbd; the sweep will be re-verified against the merged tree (incl. #791 regen paths), CI re-run, and a fresh exact-head RoR issued at the new head. The 17945 APPROVE does not transfer. No merge until then.5f37b27e2cto50276943cdReviewer-of-Record — PR #818 (issue #792) @
50276943— REQUEST CHANGESFresh independent exact-head review. Author lane: ms-792 (Codex). Reviewer lane: MS Team Lead independent Opus reviewer. author≠reviewer PROCESS/LANE-attested (single provider acct
jason.woltje). Supersedes all prior #818 RoRs (non-transferable after rebase).Base
9ddc6fbd(current main) · merge-base = current main ✓ · mergeable=True · scope CLEAN (no.mosaic//session.lock/secret) · exact-head CI (pipeline 1890) pending — not the gate here.Gap (b) —
fleet-regen-command.tsroster read: CLOSED.defaultReadRosternow reads viareadFleetRosterText(ENOENT→init hint) and the regen handler routesFleetRosterConfigurationError→fleetCommand.error({exitCode:1}). Red-first test added (missing canonical roster → shared init hint).Gap (a) —
normalizeFleetRosterV1schema failures: STILL OPEN — BLOCKER.parseFleetRosterV1→normalizeFleetRosterV1throws a plainErrorfor semantic schema violations (version must be 1,transport must be "tmux",must define at least one agent,duplicate agent name, unknown-field, type mismatches).loadFleetRoster's catch only converts errors satisfyingisRosterParserError(SyntaxError | YAMLParseError | YAMLWarning); these plain schema Errors fall throughthrow error, thenreportFleetRosterConfigurationErrorre-throws (notinstanceof FleetRosterConfigurationError). Net: a syntactically-valid but semantically-invalid roster still escapes Commander's actionable path → raw unhandled Error / stack trace + uncaught exit = the exact #792 defect class. New tests cover ENOENT + malformed-syntax + unreadable + doc-select-syntax, but none covers a semantically-invalid roster.Required (new head):
normalizeFleetRosterV1schema-validation failures throughFleetRosterConfigurationError(preserve the specific messages) so both the v1 fleet path and the regen path emitcommand.error({exitCode:1}). No raw Error escape.version: 2or duplicate agent) → assert actionable message + nonzero exit + no stack trace..mosaic/), fullturbo run testgreen, report new head.All prior CI/RoR non-transferable. No merge; coordinator (Mos) GO required after green + fresh RoR.
50276943cdtoe0b0bde5adREVIEW-OF-RECORD (exact-head, durable) — MS-LEAD independent lane
PR #818 · issue #792 · head
e0b0bde5· VERDICT: APPROVEAuthor ≠ reviewer (PROCESS/LANE-attested). Zero evidence transfer: prior RoR for
50276943is void.Review-of-Record — PR #818 (issue #792)
e0b0bde5ad25f1b2fa65cc470393a4d38e7c62f9fe7a468cmain:686c881fprospective merge tree:ccbc0590(clean)50276943) is VOID; this verdict is exact-heade0b0bde5only.VERDICT: APPROVE
gap-a (the blocker) is fully closed on the v1 parse path with a genuine red-first test; the regen path surfaces schema-invalid rosters actionably (no raw stack); the installer heading fix is correct; changed surfaces are disjoint from #815.
gap-a determination — YES, closed at the correct choke point
normalizeFleetRosterV1(packages/mosaic/src/fleet/fleet-roster-v1.ts:199-207) wrapsnormalizeFleetRosterV1Uncheckedand converts every thrownErrorintoFleetRosterConfigurationError.normalizeFleetRosterV1Uncheckedhas a single call site (line 201) — no bypass. All consumers (fleet.tsload path,comms-onboarding.ts:303,update-checker.ts:968) inherit the guard.reportFleetRosterConfigurationError(fleet.ts:2447) routes it tocommand.error(msg, { code: 'fleet.roster', exitCode: 1 }); no raw Error escapes Commander. Regen path (fleet-regen-command.ts:436-444) catchesRosterV2ValidationErrorand prints an actionable one-line message (exitCode 1, no stack).Red-first test adequacy — YES
packages/mosaic/src/commands/fleet.spec.ts:218-252writes a valid-YAML but schema-invalid roster (duplicate agent name), asserts.rejects.toMatchObject({ code: 'fleet.roster', exitCode: 1, message: 'Fleet roster has duplicate agent name: canary-pi.' })and stderr has no/\n\s+at\s/stack. Genuinely fails pre-fix (plain Error → nofleet.rostercode /exitCode). Regen fail-closed coverage:fleet-regen-command.spec.ts:420-476, 506-531.gap-b — not regressed
readFleetRosterText(fleet-roster-v1.ts:155-168) maps ENOENT → actionable "Runmosaic fleet init" and other read errors → actionable message, both asFleetRosterConfigurationError. Covered byfleet.spec.ts:171-210,fleet-regen-command.spec.ts:153-160.Installer heading — correct
tools/install.sh:215step() { printf '\n%s%s%s\n' "$BOLD" "$*" "$RESET"; }— heading passed as arg$*, never as printf format, so@mosaicstack/mosaic (npm package)cannot be mangled. Asserted byinstall-heading.spec.ts.Merge cleanliness vs
686c881f— CLEAN (machine-verified)git merge-tree --write-tree origin/main e0b0bde5→ exit 0, treeccbc0590, zero conflict markers. Changed surface (packages/mosaic/src/**,tools/install.sh) disjoint from #815 (apps/webknowledge).Test evidence — OBSERVED (via CI, exact head)
Independent reviewer lane had no shell to run vitest locally. Observed test pass is provided by Woodpecker pipeline 1900 (repo 47
mosaicstack/stack,pull_requestevent on exact heade0b0bde5) — terminal success 2026-07-17 21:32Z. This converts the static red-first analysis to an observed full-suite pass on the reviewed head.Non-blocking notes (do not gate merge)
fleet-regen-command.ts:436-443— v1 path emits typedcode:'fleet.roster'; regen emitsRosterV2ValidationErrorrendered asregen failed: <msg>. Both close the raw-stack symptom; uniform code contract not met on regen (not a #792 defect).fleet-roster-v1.ts:204—new FleetRosterConfigurationError(error.message)dropscause/stack; broadinstanceof Errorcatch could relabel a programmerTypeErroras a config error. Low risk (pure-validation body). Suggest{ cause: error }. → backlog.fleet-roster-v1.ts:117,195— pre-existing raw-error edges (EACCES on lstat;getRosterAgent), not the gap-a schema path; out of scope.Disposition
APPROVE. Merge gates: RoR APPROVE ✓ · CI 1900 green ✓ · merge CLEAN ✓. Remaining process gates: durable Gitea persist (this file) + read-back · CI queue guard · coordinator (Mos) GO per Constitution Gate 13. MINOR-2 → #792 follow-up backlog (non-blocking).