fix(#792): fleet roster ENOENT actionable exit + installer heading printf #818
Reference in New Issue
Block a user
Delete Branch "fix/792-fleet-enoent-installer"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
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).