From ed48ea0399b57103aa506d5d011f5c4da44461eb Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Fri, 17 Jul 2026 16:46:11 -0500 Subject: [PATCH] test(#804): reproduce unknown installer argument --- docs/scratchpads/804-install-unknown-flags.md | 54 +++++++++++++++++++ .../src/commands/install-arguments.spec.ts | 41 ++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 docs/scratchpads/804-install-unknown-flags.md create mode 100644 packages/mosaic/src/commands/install-arguments.spec.ts diff --git a/docs/scratchpads/804-install-unknown-flags.md b/docs/scratchpads/804-install-unknown-flags.md new file mode 100644 index 0000000..1e10a59 --- /dev/null +++ b/docs/scratchpads/804-install-unknown-flags.md @@ -0,0 +1,54 @@ +# Issue #804 — fail closed on unknown installer arguments + +## Objective + +Implement Part 1 of Gitea issue #804 only: `tools/install.sh` must reject every unrecognized flag or argument with an actionable STDERR error and nonzero exit before installation starts. + +## Scope and constraints + +- Preserve all currently recognized options and behavior, including `-y` and `--ref `. +- No positional arguments are currently accepted by the parser. +- Do not add `--next`, `MOSAIC_NEXT`, prerelease routing, or any Part 2 behavior. +- TDD is mandatory: add and observe a failing process-level regression test before changing `tools/install.sh`. +- Worker lifecycle ends after branch push, PR creation, and coordinator notification; do not merge or close #804. +- Existing launcher-owned changes in `.mosaic/orchestrator/mission.json` and `.mosaic/orchestrator/session.lock` are out of scope and must not be committed. + +## Requirements and acceptance criteria + +- Unknown input names the offending argument on STDERR. +- STDERR includes a short installer usage hint. +- Exit status is nonzero. +- The installer does not invoke npm or otherwise proceed into installation. +- Existing recognized flags remain unchanged. + +## Plan + +1. Add a process-level Vitest regression using the installer test location under `packages/mosaic/src/commands/`. +2. Run the focused test and record the expected RED failure. +3. Commit the RED test as `test(#804): ...`. +4. Replace the parser catch-all with a fail-closed STDERR error and usage hint. +5. Update concise installer-facing documentation without introducing prerelease behavior. +6. Run focused tests, shell syntax validation, package tests, lint, typecheck, and format checks. +7. Run independent review tooling and remediate findings. +8. Commit as `fix(#804): ...`, queue-guard, push, open a PR containing `Closes #804.`, notify the coordinator, and exit. + +## Budget + +- No explicit token cap supplied. +- Working estimate: 8K tokens; narrow two-file behavior/test change plus concise docs and delivery gates. + +## Progress + +- 2026-07-17: Loaded mission state, issue #804, delivery/QA/documentation rails, and relevant TDD/Vitest/pnpm/Gitea skills. +- 2026-07-17: Confirmed the parser has no legitimate positional arguments and currently drops all unmatched input via `*) shift ;;`. +- 2026-07-17: Installed locked workspace dependencies with a worktree-local pnpm store; no lockfile changes. +- 2026-07-17: Added the process-level unknown-argument regression with an isolated `$HOME` and npm shim. + +## Verification + +- RED: `pnpm --filter @mosaicstack/mosaic exec vitest run src/commands/install-arguments.spec.ts` — expected failure: installer exited `0` instead of nonzero at the exit-status assertion; confirms the test reproduces the silent-drop defect before production changes. + +## Risks and blockers + +- Part 2 remains owner-gated under #805 and is intentionally excluded. +- Test harness must isolate `$HOME` and shim npm so the pre-fix installer cannot mutate operator state or contact the registry. diff --git a/packages/mosaic/src/commands/install-arguments.spec.ts b/packages/mosaic/src/commands/install-arguments.spec.ts new file mode 100644 index 0000000..44f01db --- /dev/null +++ b/packages/mosaic/src/commands/install-arguments.spec.ts @@ -0,0 +1,41 @@ +import { spawnSync } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import { chmod, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { describe, expect, it } from 'vitest'; + +const INSTALLER_PATH = fileURLToPath(new URL('../../../../tools/install.sh', import.meta.url)); + +describe('installer arguments', () => { + it('rejects an unknown argument before installation starts', async () => { + const home = await mkdtemp(join(tmpdir(), 'mosaic-installer-args-')); + const bin = join(home, 'bin'); + const npmMarker = join(home, 'npm-called'); + + try { + await mkdir(bin); + const npmShim = join(bin, 'npm'); + await writeFile(npmShim, `#!/bin/sh\n: > "${npmMarker}"\n`); + await chmod(npmShim, 0o755); + + const result = spawnSync('bash', [INSTALLER_PATH, '--cli', '--bogus'], { + encoding: 'utf8', + env: { + ...process.env, + HOME: home, + MOSAIC_NO_COLOR: '1', + PATH: `${bin}:${process.env.PATH ?? ''}`, + }, + }); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain('Unknown argument: --bogus'); + expect(result.stderr).toMatch(/Usage: .*install\.sh/); + expect(existsSync(npmMarker)).toBe(false); + } finally { + await rm(home, { recursive: true, force: true }); + } + }); +});