Phase 5 UAT discovered the helper scripts surfaced missing-vibium as
`actual: "eval error: vibium binary not found..."` inside a `failed`
envelope. The Fallback Policy in SKILL.md said scripts MUST return
status:"skipped" with reason "browser-engine-unavailable" — that
contract was aspirational. F1 makes it real.
Contract changes:
- lib/vibium.js: ENOENT now throws typed VibiumUnavailableError (with
stable code:"BROWSER_ENGINE_UNAVAILABLE" for cross-module duck typing)
- New helper unavailableEnvelope() returns canonical skipped envelope
with top-level vibiumUnavailable:true and output.reason field
- New runOrSkip() wraps each helper's main() and converts the typed
error into the documented envelope on the way out
- New rethrowIfUnavailable() helper used inside per-script catch blocks
so missing-vibium bubbles past local "convert exception to fail()"
handlers — assert.runConsoleCheck/runNetworkCheck/runBrowserSideCheck,
batch's per-step catch, check-injection main, intent-score main,
visual-diff main all updated
- emit() now returns exit code 2 for status:"skipped" (was 1)
- Exit code contract documented: 0=success, 1=failed, 2=skipped
Downstream skills can now branch on result.vibiumUnavailable === true
or exit code === 2 instead of grepping the actual string for
"vibium binary not found".
Tests: 103 (was 86). New file qe-browser-unavailable-e2e.test.ts spawns
each of the 5 helpers under PATH=/tmp/fake-bin (node only, no vibium)
and asserts exit 2 + skipped envelope shape. vibium-lib test grew from
10 to 22 cases including VibiumUnavailableError, runOrSkip, exit codes.
Smoke test: 10/10 PASS (was 9/9). New tc011 builds a fake bin dir
inside the smoke test work dir and verifies the contract end-to-end
against the actual installed scripts.
SKILL.md updated:
- Output Contract section now documents all 3 statuses with shapes
and exit codes
- Fallback Policy replaced generic prose with concrete bash + Node
snippets that branch on the structured field
ADR-091 Phase 6 documents the change and verification.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>