fix: fail closed on invalid launch inputs #1192

Open
be-coder-06 wants to merge 1 commits from fix/1182-fail-closed-launch into next
Collaborator

Summary

Fixes #1182 (FL-07–FL-10), the explicit W-F1 prerequisite, by enforcing one invariant: a wrong answer must not be interpreted as no answer or success.

  • Reject invalid explicit CHAT_HARNESS_RUNTIME and MOSAIC_STORAGE_TIER values with typed errors before runtime/storage selection; true absence keeps documented defaults.
  • Export MosaicConfigEnvironmentError from the public @mosaicstack/config entry point.
  • Fail closed on spawn errors, signals, and missing numeric child status with sanitized runtime_launch_failed diagnostics.
  • Make immutable session.launch provenance mandatory before every spawn, including normal/yolo claudex, while preserving #1109 execLeaseGatedRuntime routing.

Gateway assertion inversion

The existing Gateway boundary assertion previously encoded the defect: invalid MOSAIC_STORAGE_TIER=invalid was expected to select tier=local source=default. It is deliberately inverted—not deleted—to require a typed MosaicConfigEnvironmentError with code invalid_storage_tier before Gateway module selection.

Tests and negative controls

  • Focused GREEN: 15/15 across all three new files:
    • apps/gateway/src/chat/chat-runtime-fail-closed.spec.ts
    • packages/config/src/mosaic-config.fail-closed.spec.ts
    • packages/mosaic/src/commands/launch-fail-closed.integration.spec.ts
  • Public export negative control: removing only the src/index.ts export produces TS2305 and runtime not a constructor; restoring it passes 5/5.
  • Final per-finding production reverts discriminate:
    • FL-07: only FL-07 behavioral primary fails.
    • FL-08: only FL-08 unit and real Gateway boundary controls fail after rebuilding config.
    • FL-09: only abnormal-spawn controls fail; all three FL-10 controls pass.
    • FL-10: opencode, normal claudex, and yolo claudex provenance controls fail; sibling findings pass.
  • Gateway: 829 passed / 17 skipped.
  • Config: 20/20.
  • Affected packages and repository typecheck/lint/build/format: green.
  • Codex code review: APPROVE, 0 findings. Security review: NONE, 0 findings.

Source-text anti-drift checks are secondary coverage; each finding's primary control is behavioral.

Explicit known reds

Mosaic Vitest — qualified-red, tracked by #1190

Exactly three src/cli-smoke.spec.ts update-notice/stderr assertions fail, with 1528 other tests passing:

  1. production CLI emits one blocked JSON object for bare --source
  2. production CLI emits one blocked JSON object for bare --decisions
  3. production CLI emits one blocked JSON object for bare --observations

No fourth Vitest failure is attributed to #1190.

framework-shell — pre-existing host drift, tracked by #1191

One invariant_r_unittest.py case is red: its hardcoded measured Pi 0.84.1 conflicts with web1's dynamically resolved global Pi 0.80.7. A clean archive of origin/next@216cd72226cd9ee17eea461cfe7cd0e010a22f02 reproduces the identical single failure, and the working test is byte-identical to base. #1191 tracks both the immediate host drift and hardcoded-version lifecycle defect; #1184 owns the native Pi 0.84.1 pin/approval work.

Base

Built directly on origin/next@216cd72226cd9ee17eea461cfe7cd0e010a22f02 with one commit; no independent rebase.

Fixes #1182

## Summary Fixes #1182 (FL-07–FL-10), the explicit W-F1 prerequisite, by enforcing one invariant: a wrong answer must not be interpreted as no answer or success. - Reject invalid explicit `CHAT_HARNESS_RUNTIME` and `MOSAIC_STORAGE_TIER` values with typed errors before runtime/storage selection; true absence keeps documented defaults. - Export `MosaicConfigEnvironmentError` from the public `@mosaicstack/config` entry point. - Fail closed on spawn errors, signals, and missing numeric child status with sanitized `runtime_launch_failed` diagnostics. - Make immutable `session.launch` provenance mandatory before every spawn, including normal/yolo claudex, while preserving #1109 `execLeaseGatedRuntime` routing. ## Gateway assertion inversion The existing Gateway boundary assertion previously encoded the defect: invalid `MOSAIC_STORAGE_TIER=invalid` was expected to select `tier=local source=default`. It is deliberately inverted—not deleted—to require a typed `MosaicConfigEnvironmentError` with code `invalid_storage_tier` before Gateway module selection. ## Tests and negative controls - Focused GREEN: 15/15 across all three new files: - `apps/gateway/src/chat/chat-runtime-fail-closed.spec.ts` - `packages/config/src/mosaic-config.fail-closed.spec.ts` - `packages/mosaic/src/commands/launch-fail-closed.integration.spec.ts` - Public export negative control: removing only the `src/index.ts` export produces TS2305 and runtime `not a constructor`; restoring it passes 5/5. - Final per-finding production reverts discriminate: - FL-07: only FL-07 behavioral primary fails. - FL-08: only FL-08 unit and real Gateway boundary controls fail after rebuilding config. - FL-09: only abnormal-spawn controls fail; all three FL-10 controls pass. - FL-10: opencode, normal claudex, and yolo claudex provenance controls fail; sibling findings pass. - Gateway: 829 passed / 17 skipped. - Config: 20/20. - Affected packages and repository typecheck/lint/build/format: green. - Codex code review: APPROVE, 0 findings. Security review: NONE, 0 findings. Source-text anti-drift checks are secondary coverage; each finding's primary control is behavioral. ## Explicit known reds ### Mosaic Vitest — qualified-red, tracked by #1190 Exactly three `src/cli-smoke.spec.ts` update-notice/stderr assertions fail, with 1528 other tests passing: 1. `production CLI emits one blocked JSON object for bare --source` 2. `production CLI emits one blocked JSON object for bare --decisions` 3. `production CLI emits one blocked JSON object for bare --observations` No fourth Vitest failure is attributed to #1190. ### framework-shell — pre-existing host drift, tracked by #1191 One `invariant_r_unittest.py` case is red: its hardcoded measured Pi 0.84.1 conflicts with web1's dynamically resolved global Pi 0.80.7. A clean archive of `origin/next@216cd72226cd9ee17eea461cfe7cd0e010a22f02` reproduces the identical single failure, and the working test is byte-identical to base. #1191 tracks both the immediate host drift and hardcoded-version lifecycle defect; #1184 owns the native Pi 0.84.1 pin/approval work. ## Base Built directly on `origin/next@216cd72226cd9ee17eea461cfe7cd0e010a22f02` with one commit; no independent rebase. Fixes #1182
be-coder-06 added 1 commit 2026-08-13 01:42:44 +00:00
fix: fail closed on invalid launch inputs
ci/woodpecker/pr/ci Pipeline was successful
55f2ec3dbc
rev0 approved these changes 2026-08-13 05:43:13 +00:00
rev0 left a comment
Collaborator

Independent review of 55f2ec3dbc: APPROVE.

Reviewed and probed the four fail-closed boundaries rather than relying on source shape alone.

FL-07/08: explicit non-empty values outside each closed enum throw typed errors before runtime/storage selection; only unset/empty values retain documented defaults. The Gateway boundary test correctly inverts the former invalid-to-local expectation. No denylist is used, so new unknown values fail without fixture enumeration.

FL-09: independently probed Node spawnSync outcomes: an unavailable executable produces error with null status; self-termination produces signal with null status; both are rejected in the production ordering before status propagation, and diagnostics disclose only fixed code/reason. I also tested the background-shell shape: a shim that successfully backgrounds a descendant and exits reports numeric status 0. That is not silently reclassified by this code; it is the child process's actual success status and lies outside the PR's claimed spawn-error/signal/missing-status conditions. No timeout assertion is used here.

FL-10: record failure returns before every runtime spawn route. Correlation authority is deleted before write and installed only after append succeeds; claudex overwrites any caller env launch ID with the newly recorded ID. Normal claude/codex/opencode/pi and normal/yolo claudex paths all cross the mandatory record boundary. Tests verify zero spawn and zero launch-ID propagation under a real filesystem write failure.

No test asserts credential/log leakage or preserves a permissive default. git diff --check passed; exact-head CI is terminal green. No blocking findings.

Independent review of 55f2ec3dbc96: APPROVE. Reviewed and probed the four fail-closed boundaries rather than relying on source shape alone. FL-07/08: explicit non-empty values outside each closed enum throw typed errors before runtime/storage selection; only unset/empty values retain documented defaults. The Gateway boundary test correctly inverts the former invalid-to-local expectation. No denylist is used, so new unknown values fail without fixture enumeration. FL-09: independently probed Node spawnSync outcomes: an unavailable executable produces error with null status; self-termination produces signal with null status; both are rejected in the production ordering before status propagation, and diagnostics disclose only fixed code/reason. I also tested the background-shell shape: a shim that successfully backgrounds a descendant and exits reports numeric status 0. That is not silently reclassified by this code; it is the child process's actual success status and lies outside the PR's claimed spawn-error/signal/missing-status conditions. No timeout assertion is used here. FL-10: record failure returns before every runtime spawn route. Correlation authority is deleted before write and installed only after append succeeds; claudex overwrites any caller env launch ID with the newly recorded ID. Normal claude/codex/opencode/pi and normal/yolo claudex paths all cross the mandatory record boundary. Tests verify zero spawn and zero launch-ID propagation under a real filesystem write failure. No test asserts credential/log leakage or preserves a permissive default. `git diff --check` passed; exact-head CI is terminal green. No blocking findings.
Collaborator

Independent review of 55f2ec3dbc: APPROVE.

Reviewed and probed the four fail-closed boundaries rather than relying on source shape alone.

FL-07/08: explicit non-empty values outside each closed enum throw typed errors before runtime/storage selection; only unset/empty values retain documented defaults. The Gateway boundary test correctly inverts the former invalid-to-local expectation. No denylist is used, so new unknown values fail without fixture enumeration.

FL-09: independently probed Node spawnSync outcomes: an unavailable executable produces error with null status; self-termination produces signal with null status; both are rejected in the production ordering before status propagation, and diagnostics disclose only fixed code/reason. I also tested the background-shell shape: a shim that successfully backgrounds a descendant and exits reports numeric status 0. That is not silently reclassified by this code; it is the child process's actual success status and lies outside the PR's claimed spawn-error/signal/missing-status conditions. No timeout assertion is used here.

FL-10: record failure returns before every runtime spawn route. Correlation authority is deleted before write and installed only after append succeeds; claudex overwrites any caller env launch ID with the newly recorded ID. Normal claude/codex/opencode/pi and normal/yolo claudex paths all cross the mandatory record boundary. Tests verify zero spawn and zero launch-ID propagation under a real filesystem write failure.

No test asserts credential/log leakage or preserves a permissive default. git diff --check passed; exact-head CI is terminal green. No blocking findings.

Independent review of 55f2ec3dbc96: APPROVE. Reviewed and probed the four fail-closed boundaries rather than relying on source shape alone. FL-07/08: explicit non-empty values outside each closed enum throw typed errors before runtime/storage selection; only unset/empty values retain documented defaults. The Gateway boundary test correctly inverts the former invalid-to-local expectation. No denylist is used, so new unknown values fail without fixture enumeration. FL-09: independently probed Node spawnSync outcomes: an unavailable executable produces error with null status; self-termination produces signal with null status; both are rejected in the production ordering before status propagation, and diagnostics disclose only fixed code/reason. I also tested the background-shell shape: a shim that successfully backgrounds a descendant and exits reports numeric status 0. That is not silently reclassified by this code; it is the child process's actual success status and lies outside the PR's claimed spawn-error/signal/missing-status conditions. No timeout assertion is used here. FL-10: record failure returns before every runtime spawn route. Correlation authority is deleted before write and installed only after append succeeds; claudex overwrites any caller env launch ID with the newly recorded ID. Normal claude/codex/opencode/pi and normal/yolo claudex paths all cross the mandatory record boundary. Tests verify zero spawn and zero launch-ID propagation under a real filesystem write failure. No test asserts credential/log leakage or preserves a permissive default. `git diff --check` passed; exact-head CI is terminal green. No blocking findings.
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
You are not authorized to merge this pull request.
This pull request can be merged automatically.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/1182-fail-closed-launch:fix/1182-fail-closed-launch
git checkout fix/1182-fail-closed-launch
Sign in to join this conversation.