fix(contracts): refuse launch-only open options before the lifecycle touches the device
Summary
open <app> --relaunch --launch-console against a provider-owned iOS simulator (Limrun/TestMu shape) closed the running app and only then failed. The lifecycle binding now carries a supportsLocalSimulatorLaunch owner capability, and assertOpenAdmitted / assertOpenRequestShape (packages/contracts/src/application-lifecycle-interaction.ts) run first in openDirectApplication, openAndroidApplication and openAppleApplication: an open option the bound owner cannot honor, or a launch-only option on a request that cannot carry one (no app target, URL pair, deep link), is refused with the existing codes and typed reasons before any relaunch close, runner stop, or runtime-hint write. Extracted from #3928 (the --capture-network parts stay there; only options that exist on main are covered). Scope expansion: none.
Review follow-ups
- Provider scenario seam: the test-only provider module resolved an interactor and opened, skipping the admission every production provider runtime runs first. It now calls
assertOpenAdmitted, and a new scenario proves the refusal on a provider-owned iOS simulator ā it names the typed reason and the device, and the fake device records no open and no close. A new test removes that admission and fails, so the assertion is not vacuous. launch-console-owner-unadmittedis documented: named in theopenhelp the command owns, in the command reference, and in the debugging guide (the session must own the simulator locally).- Deliberately not applied: running the request-shape check before the owner check would change the error code for a request that is both malformed and aimed at an owner that cannot honor the option (
INVALID_ARGSwhere it is nowUNSUPPORTED_OPERATION). That is a caller-visible behavior change beyond this fix, which refuses before the device is touched, so it stays out of scope.
Validation
- Tested SHA:
e8b9aa617e(rebased ontomainated798f9cfe). pnpm check:affected --run --head e8b9aa617e: all runnable checks passed (exactCommit=true; format, lint, typecheck, layering, fallow, build, vitest-related, command-docs, docs-links, docs-prose).unit-coredaemon session-lifecycle and the lifecycle/contract suites pass together (237 tests), so this fix and the alert-recovery fix that landed ined798f9cfeare checked on the same open path.- CI lanes on this head are green, including the unit, integration, repo-guard and coverage lanes.
- New contract tests cover the provider-shaped relaunch refusal asserting close is never called, the local non-simulator refusal, and the URL-pair refusal before any close; Android and Apple entry-level tests prove the refusal precedes close/runner/hint steps.
- Not device-facing beyond refusal ordering; provider integration lanes run in CI.
š¤ Generated with Claude Code