Compare commits
3 Commits
e0b0bde5ad
...
50276943cd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50276943cd | ||
|
|
1d4a94d66f | ||
|
|
22bf43178d |
@@ -22,10 +22,10 @@
|
|||||||
FROM node:24-alpine
|
FROM node:24-alpine
|
||||||
|
|
||||||
# Native toolchain required to compile node-gyp deps on musl, plus the
|
# Native toolchain required to compile node-gyp deps on musl, plus the
|
||||||
# postgresql-client used by the test step's pg_isready readiness probe. `bash`,
|
# postgresql-client used by the test step's pg_isready readiness probe. `bash`
|
||||||
# `git`, and `jq` are baked here too — framework shell tests and the shipped
|
# is baked here too — the sanitization step in ci.yml otherwise does a per-run
|
||||||
# Codex review wrappers require them without per-run installation in ci.yml.
|
# `apk add bash`.
|
||||||
RUN apk add --no-cache python3 make g++ postgresql-client bash git jq
|
RUN apk add --no-cache python3 make g++ postgresql-client bash
|
||||||
|
|
||||||
# Pin pnpm to the repo's packageManager version via corepack.
|
# Pin pnpm to the repo's packageManager version via corepack.
|
||||||
RUN corepack enable && corepack prepare pnpm@10.6.2 --activate
|
RUN corepack enable && corepack prepare pnpm@10.6.2 --activate
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ Make expected missing or malformed fleet roster configuration fail with an actio
|
|||||||
- 2026-07-16: Replaced installer heading `echo` with format-safe `printf`; added a regression check for the scoped package heading.
|
- 2026-07-16: Replaced installer heading `echo` with format-safe `printf`; added a regression check for the scoped package heading.
|
||||||
- 2026-07-16: Rebuilt CLI and manually verified `fleet ps` with no roster prints the initialization hint, exits 1, and has no stack trace.
|
- 2026-07-16: Rebuilt CLI and manually verified `fleet ps` with no roster prints the initialization hint, exits 1, and has no stack trace.
|
||||||
- 2026-07-17: Rebased #818 onto `origin/main` at `9ddc6fbd` (#791 PR3). The added `fleet regen` command had a canonical roster read in its sibling module; it now uses the same missing-roster guard and Commander exit path. Internal NORTH_STAR, preset, and post-write invariant reads remain intentionally unguarded.
|
- 2026-07-17: Rebased #818 onto `origin/main` at `9ddc6fbd` (#791 PR3). The added `fleet regen` command had a canonical roster read in its sibling module; it now uses the same missing-roster guard and Commander exit path. Internal NORTH_STAR, preset, and post-write invariant reads remain intentionally unguarded.
|
||||||
- 2026-07-17: RoR found that semantically invalid v1 documents still escaped as plain `Error` values. `normalizeFleetRosterV1` now preserves each validation message while converting it to `FleetRosterConfigurationError`, so its command callers use the actionable nonzero Commander path.
|
|
||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
@@ -30,7 +29,6 @@ Make expected missing or malformed fleet roster configuration fail with an actio
|
|||||||
- `pnpm format:check` — PASS
|
- `pnpm format:check` — PASS
|
||||||
- `pnpm --filter @mosaicstack/mosaic exec vitest run src/commands/fleet.spec.ts src/commands/install-heading.spec.ts` — PASS (209 tests)
|
- `pnpm --filter @mosaicstack/mosaic exec vitest run src/commands/fleet.spec.ts src/commands/install-heading.spec.ts` — PASS (209 tests)
|
||||||
- `pnpm --filter @mosaicstack/mosaic exec vitest run src/commands/fleet-regen-command.spec.ts` — PASS (27 tests, including missing canonical roster)
|
- `pnpm --filter @mosaicstack/mosaic exec vitest run src/commands/fleet-regen-command.spec.ts` — PASS (27 tests, including missing canonical roster)
|
||||||
- `pnpm --filter @mosaicstack/mosaic exec vitest run src/commands/fleet.spec.ts -t "semantically invalid v1 roster"` — RED then PASS; verifies duplicate agent names are reported as `fleet.roster` exit 1 without a stack trace.
|
|
||||||
- Instrumented Vitest coverage is unavailable because `@vitest/coverage-v8` is not declared in this repository. Each branch added in the roster guard has direct unit coverage.
|
- Instrumented Vitest coverage is unavailable because `@vitest/coverage-v8` is not declared in this repository. Each branch added in the roster guard has direct unit coverage.
|
||||||
|
|
||||||
## Risks / blockers
|
## Risks / blockers
|
||||||
|
|||||||
@@ -214,42 +214,6 @@ describe('fleet roster configuration failures', () => {
|
|||||||
'Fleet roster at /srv/mosaic/fleet/roster.yaml is invalid. Fix the file or run `mosaic fleet init --force`.',
|
'Fleet roster at /srv/mosaic/fleet/roster.yaml is invalid. Fix the file or run `mosaic fleet init --force`.',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reports a semantically invalid v1 roster as an actionable nonzero command error', async () => {
|
|
||||||
const home = await tempDir();
|
|
||||||
const rosterPath = join(home, 'fleet', 'roster.yaml');
|
|
||||||
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
|
|
||||||
try {
|
|
||||||
await mkdir(dirname(rosterPath), { recursive: true });
|
|
||||||
await writeFile(
|
|
||||||
rosterPath,
|
|
||||||
[
|
|
||||||
'version: 1',
|
|
||||||
'transport: tmux',
|
|
||||||
'agents:',
|
|
||||||
' - name: canary-pi',
|
|
||||||
' runtime: pi',
|
|
||||||
' - name: canary-pi',
|
|
||||||
' runtime: codex',
|
|
||||||
].join('\n'),
|
|
||||||
);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
buildProgram().parseAsync(['node', 'mosaic', 'fleet', '--mosaic-home', home, 'ps']),
|
|
||||||
).rejects.toMatchObject({
|
|
||||||
code: 'fleet.roster',
|
|
||||||
exitCode: 1,
|
|
||||||
message: 'Fleet roster has duplicate agent name: canary-pi.',
|
|
||||||
});
|
|
||||||
expect(stderrSpy.mock.calls.flat().join('')).toContain(
|
|
||||||
'Fleet roster has duplicate agent name: canary-pi.',
|
|
||||||
);
|
|
||||||
expect(stderrSpy.mock.calls.flat().join('')).not.toMatch(/\n\s+at\s/);
|
|
||||||
} finally {
|
|
||||||
stderrSpy.mockRestore();
|
|
||||||
await rm(home, { recursive: true, force: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('fleet roster parsing', () => {
|
describe('fleet roster parsing', () => {
|
||||||
|
|||||||
@@ -197,16 +197,6 @@ export function getRosterAgent(roster: FleetRoster, name: string): FleetAgent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeFleetRosterV1(raw: RawFleetRoster): FleetRoster {
|
export function normalizeFleetRosterV1(raw: RawFleetRoster): FleetRoster {
|
||||||
try {
|
|
||||||
return normalizeFleetRosterV1Unchecked(raw);
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof FleetRosterConfigurationError) throw error;
|
|
||||||
if (error instanceof Error) throw new FleetRosterConfigurationError(error.message);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeFleetRosterV1Unchecked(raw: RawFleetRoster): FleetRoster {
|
|
||||||
assertObject(raw, 'Fleet roster');
|
assertObject(raw, 'Fleet roster');
|
||||||
assertKnownKeys(raw, 'Fleet roster', [
|
assertKnownKeys(raw, 'Fleet roster', [
|
||||||
'version',
|
'version',
|
||||||
|
|||||||
Reference in New Issue
Block a user