mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
5fddf1ce14
* feat(apple): reclaim retained runners under device-claim authority Implement the #1320 retained-runner rule: a daemon holding the host-global device claim may stop and replace a warm XCTest runner whose owner no longer holds that claim. Previously worktree B's open failed with an unstructured COMMAND_FAILED (IOS_RUNNER_OWNED_BY_OTHER_DAEMON) for up to five minutes after worktree A closed, until A's daemon idled out. - Runner leases now record deviceClaimProtocol: 1; takeover is gated on the lease declaring claim arbitration, so owners from pre-claims builds are never preempted. - The claim-authority probe is daemon-bound through the existing runner-owner seam and answers from the claim store by process identity; unbound embedders answer false and keep today's refusal. - Disposal now skips device-wide runner container-app termination when the on-disk lease is owned by someone else, so the losing daemon's idle stop or shutdown cannot kill the successor's runner on the shared simulator. - Help topics updated: the live-owner runner rejection now names claim arbitration instead of being unconditional. Live-validated with two daemons sharing one claim store against a throwaway simulator: live-owner open still rejects with structured DEVICE_IN_USE; after close, the contender opens in ~8s with the lease re-owned while the loser daemon is still alive; stopping the loser afterwards leaves the winner's runner healthy. * fix(apple): make disposal ownership check and device-wide teardown one lease-locked operation Review P1 on #2160: cleanupRunnerSessionResources read the on-disk lease token and then terminated the device-global runner container bundles without holding the runner-lease lock, so a loser could pass its check while a successor's reclaim was in flight and then kill the successor's runner. The ownership check, the container-app termination, and the lease release now run as one operation under the runner-lease lock — the same lock a successor holds for its entire reclaim-and-publish window — on every disposal path. Callers already inside the lock (startup abort, lease-publish failure, stopIosRunnerSession) declare leaseLockHeld instead of re-acquiring the non-reentrant lock; if the lock cannot be acquired, all device-wide teardown is skipped and an own unreleased lease turns stale on process exit. The two-owner regression starts loser disposal inside a held successor window, completes the takeover, and proves the loser neither terminates the successor's runner nor touches its lease. Observed red against the pre-fix code. Live-revalidated twice with overlapping daemon stop and takeover open on a real simulator. * fix(apple): match runner takeover authority by canonical device identity Review P1 on #2160: the authority probe matched any active claim with the same raw device id, while claims are canonically scoped by platform family + Apple OS + id — an Android claim whose serial equals an Apple runner id could authorize destructive takeover of a live Apple runner (and the prior probe regression used an Android fixture, blessing exactly that). The probe now receives the full DeviceInfo through the runner-owner seam and the daemon answers with an exact canonical-local-device-key lookup: family, Apple OS, and id must all match the held claim, which also replaces the store scan with a direct keyed read. prepareRunnerLeaseForStartup takes the device rather than a bare id so the lease seam can never degrade the match. Adversarial regressions: a same-id Android claim grants no authority for the Apple device (and the true Apple claim does) at the probe, and the lease-seam test pins that the probe receives full family/OS identity, never a bare id.
18 lines
738 B
TypeScript
18 lines
738 B
TypeScript
import type { RunnerDeviceClaimAuthorityProbe } from '@agent-device/platform-apple/runner-owner';
|
|
|
|
/** Root-composed daemon ownership inputs consumed by the Apple runner host. */
|
|
export async function configureAppleRunnerLeaseOwnerStateDir(
|
|
stateDir: string | undefined,
|
|
): Promise<void> {
|
|
const { setRunnerLeaseOwnerStateDir } = await import('@agent-device/platform-apple/runner-owner');
|
|
setRunnerLeaseOwnerStateDir(stateDir);
|
|
}
|
|
|
|
export async function configureAppleRunnerDeviceClaimAuthorityProbe(
|
|
probe: RunnerDeviceClaimAuthorityProbe | undefined,
|
|
): Promise<void> {
|
|
const { setRunnerDeviceClaimAuthorityProbe } =
|
|
await import('@agent-device/platform-apple/runner-owner');
|
|
setRunnerDeviceClaimAuthorityProbe(probe);
|
|
}
|