From 7a0cc028ccca149485bf1bfdc916df3ae9d87acf Mon Sep 17 00:00:00 2001 From: Janic Duplessis Date: Tue, 6 Oct 2026 08:38:53 -0400 Subject: [PATCH 1/2] docs(remote): say only the host ends a macos-app lease --- docs/adr/0007-remote-device-leases.md | 8 +++++--- src/daemon/macos-app-lease.ts | 9 ++------- website/docs/docs/remote-proxy.md | 9 +++++---- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/docs/adr/0007-remote-device-leases.md b/docs/adr/0007-remote-device-leases.md index 9585d2f299..a889db9bd7 100644 --- a/docs/adr/0007-remote-device-leases.md +++ b/docs/adr/0007-remote-device-leases.md @@ -173,8 +173,10 @@ A `macos-app` lease's device key is a bundle id, optionally pinned to one proces only a host administrator allocates one, over the loopback `/admin/leases` route that uses the daemon token like host holds; tenant `lease_allocate` refuses the backend. The host picks the lease id, a repeated PUT renews it, and a PUT naming another scope for an existing id is refused rather than -rewritten. Heartbeat, expiry, release, and the loss on daemon restart are those of any lease, except -that a tenant heartbeat or request cannot renew it for longer than the window of the host's last PUT. +rewritten. Heartbeat, expiry, and the loss on daemon restart are those of any lease, except that a +tenant heartbeat or request cannot renew it for longer than the window of the host's last PUT, and +only the host ends it: `DELETE /admin/leases`, expiry, or `close` when `retainOnClose` is false. A +tenant `lease_release` is refused with `MACOS_APP_LEASE_HOST_OWNED`. Request admission confines every request admitted under the lease, so `batch` steps and `replay` actions are confined when they re-enter it: an allow list of commands, the ones whose command @@ -182,7 +184,7 @@ registry descriptor declares `appLease: 'allowed'` (later commands are refused, lease admission otherwise exempts only `lease_heartbeat` and `lease_release` declare it), `open` and `close` of the leased bundle only, the `app` surface only, window-only screenshots, no input that names a host path or launches beside the app, and an existing session that is the leased -app for every request but `open`, the `batch` envelope, and the lease's heartbeat and release, so a +app for every request but `open`, the `batch` envelope, and the lease's heartbeat, so a request naming no session cannot fall back to the host Mac. `open` requires the native app backend (ADR 0031), because XCTest posts screen events that can land outside the app's window. A pid-pinned lease is checked against the running process before each admitted request. A session opened under the lease holds its app, not diff --git a/src/daemon/macos-app-lease.ts b/src/daemon/macos-app-lease.ts index 8d54eab595..a739aed375 100644 --- a/src/daemon/macos-app-lease.ts +++ b/src/daemon/macos-app-lease.ts @@ -38,14 +38,9 @@ export function parseMacOsAppLeaseKey(deviceKey: string | undefined): MacOsAppLe /** * The requests that run without the leased app's session: `open` creates it, each `batch` step is - * admitted again when it runs, and a heartbeat acts on the lease alone, and a release is refused (the host ends the lease). + * admitted again when it runs. */ -const SESSIONLESS_COMMANDS: ReadonlySet = new Set([ - 'open', - 'batch', - 'lease_heartbeat', - 'lease_release', -]); +const SESSIONLESS_COMMANDS: ReadonlySet = new Set(['open', 'batch']); type MacOsAppLeaseRule = | 'command' diff --git a/website/docs/docs/remote-proxy.md b/website/docs/docs/remote-proxy.md index c90ca7cbbd..6efb8dfa2a 100644 --- a/website/docs/docs/remote-proxy.md +++ b/website/docs/docs/remote-proxy.md @@ -141,9 +141,10 @@ The lease id is 16 to 128 hex characters the host chooses. Repeating the PUT ren that names another scope for an existing id is refused. The lease stays allocated across the client's `close` unless the body sets `retainOnClose: false`, and DELETE revokes it at once. It expires after `ttlMs` without a renewal or a client request, like any lease. A client heartbeat can -shorten that window but never extend it past the `ttlMs` of the last PUT. A client cannot release -it: `disconnect` drops only its local connection state, and a tenant `lease_release` is refused -with `MACOS_APP_LEASE_HOST_OWNED`. +shorten that window but never extend it past the `ttlMs` of the last PUT. Only the host ends +the lease: `DELETE /admin/leases`, expiry, or `close` when `retainOnClose` is false. A client cannot +release it: `disconnect` drops only its local connection state, and a tenant `lease_release` is +refused with `MACOS_APP_LEASE_HOST_OWNED`. The client connects with a remote config that names the lease, and runs `open `: @@ -164,7 +165,7 @@ The client connects with a remote config that names the lease, and runs `open Date: Tue, 6 Oct 2026 23:47:54 -0400 Subject: [PATCH 2/2] docs(remote): name the lease id on host DELETE and the ways a macos-app lease ends --- docs/adr/0007-remote-device-leases.md | 7 ++++--- website/docs/docs/remote-proxy.md | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/adr/0007-remote-device-leases.md b/docs/adr/0007-remote-device-leases.md index a889db9bd7..dee137a90c 100644 --- a/docs/adr/0007-remote-device-leases.md +++ b/docs/adr/0007-remote-device-leases.md @@ -175,8 +175,9 @@ token like host holds; tenant `lease_allocate` refuses the backend. The host pic repeated PUT renews it, and a PUT naming another scope for an existing id is refused rather than rewritten. Heartbeat, expiry, and the loss on daemon restart are those of any lease, except that a tenant heartbeat or request cannot renew it for longer than the window of the host's last PUT, and -only the host ends it: `DELETE /admin/leases`, expiry, or `close` when `retainOnClose` is false. A -tenant `lease_release` is refused with `MACOS_APP_LEASE_HOST_OWNED`. +a tenant cannot release it: `lease_release` is refused with `MACOS_APP_LEASE_HOST_OWNED`. The lease +ends by the host's `DELETE /admin/leases/`, by expiry, by the loss on daemon restart, or +when a session it holds closes and the host set `retainOnClose` to false. Request admission confines every request admitted under the lease, so `batch` steps and `replay` actions are confined when they re-enter it: an allow list of commands, the ones whose command @@ -184,7 +185,7 @@ registry descriptor declares `appLease: 'allowed'` (later commands are refused, lease admission otherwise exempts only `lease_heartbeat` and `lease_release` declare it), `open` and `close` of the leased bundle only, the `app` surface only, window-only screenshots, no input that names a host path or launches beside the app, and an existing session that is the leased -app for every request but `open`, the `batch` envelope, and the lease's heartbeat, so a +app for every request but `open` and the `batch` envelope, so a request naming no session cannot fall back to the host Mac. `open` requires the native app backend (ADR 0031), because XCTest posts screen events that can land outside the app's window. A pid-pinned lease is checked against the running process before each admitted request. A session opened under the lease holds its app, not diff --git a/website/docs/docs/remote-proxy.md b/website/docs/docs/remote-proxy.md index 6efb8dfa2a..d75dbbbe1b 100644 --- a/website/docs/docs/remote-proxy.md +++ b/website/docs/docs/remote-proxy.md @@ -141,10 +141,11 @@ The lease id is 16 to 128 hex characters the host chooses. Repeating the PUT ren that names another scope for an existing id is refused. The lease stays allocated across the client's `close` unless the body sets `retainOnClose: false`, and DELETE revokes it at once. It expires after `ttlMs` without a renewal or a client request, like any lease. A client heartbeat can -shorten that window but never extend it past the `ttlMs` of the last PUT. Only the host ends -the lease: `DELETE /admin/leases`, expiry, or `close` when `retainOnClose` is false. A client cannot -release it: `disconnect` drops only its local connection state, and a tenant `lease_release` is -refused with `MACOS_APP_LEASE_HOST_OWNED`. +shorten that window but never extend it past the `ttlMs` of the last PUT. A client cannot release +it: `disconnect` drops only its local connection state, and a tenant `lease_release` is refused with +`MACOS_APP_LEASE_HOST_OWNED`. The lease ends by the host's `DELETE /admin/leases/`, by +expiry, by daemon restart, or when a session it holds closes and the body set `retainOnClose` to +false. The client connects with a remote config that names the lease, and runs `open `: