Skip to content

feat(lease): name a backend baseline in a proxy lease device spec - #4236

Open
V3RON wants to merge 1 commit into
mainfrom
feat/device-baseline
Open

V3RON wants to merge 1 commit into
mainfrom
feat/device-baseline

Conversation

@V3RON

@V3RON V3RON commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A proxy lease can now name a backend baseline (a saved, prepared device state) with --device-baseline <name> (spec key deviceBaseline), modelled on deviceMode (#4183). Simlock side: callstackincubator/simlock#1308.

  • deviceBaseline joins LEASE_DEVICE_SPEC_KEYS: blank names nothing, a non-string is device-spec-invalid, a daemon refuses it with device-spec-unsupported. Carried by CLI flags, remote config, connect proxy defaults, MCP and the SDK.
  • LeaseDevice.baseline?: { name; version } (version a positive integer), read by readLeaseDevice; a malformed echo is dropped.
  • A new lease allocated with deviceBaseline whose lease.device.baseline.name differs (or is missing) is released and fails with device-spec-unsupported. A reused lease serves it only when allocated with the same name.
  • diff --baseline is unchanged.
  • Help, remote-proxy.md and ADR 0007 updated. 21 files.
agent-device open MyApp --platform ios --device-baseline logged-in

Closes #4235

Validation

At b7331fa: pnpm check:affected --run --head passed format, lint, typecheck, layering, fallow, build, package, integration-node and the related tests on its second run. Each run had one unrelated timeout under load (macos-recording.test.ts, then platform-runtime-android-adb-host.test.ts). Neither file is touched here, and both pass when run alone (the adb test 3/3). CI owns the full unit suite.

Written by an agent.


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

View guided diff Turn on auto-fix

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.86 MB 5.86 MB +1.9 kB
Package (unpacked) 5.86 MB 5.86 MB +1.9 kB
Package (download) 1.78 MB 1.78 MB +499 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 23.4 ms 22.5 ms -0.8 ms
CLI --help 52.8 ms 47.2 ms -5.6 ms

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 21 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/commands/schema/cli-help.ts">

<violation number="1" location="src/commands/schema/cli-help.ts:573">
P3: This help text names the screenshot comparison option as `diff --baseline`, but the CLI syntax is `diff screenshot --baseline <path>`; use the full form.</violation>
</file>

<file name="packages/contracts/src/lease-scope.ts">

<violation number="1" location="packages/contracts/src/lease-scope.ts:396">
P2: `readLeaseDevice` trims `baseline.name`, but device-spec requests preserve names as given; a valid echoed name with surrounding whitespace is therefore rejected as a mismatch. Preserve the original string while only trimming to test whether it is blank.</violation>

<violation number="2" location="packages/contracts/src/lease-scope.ts:398">
P3: `Number.isInteger` accepts unsafe positive numbers, so malformed baseline echoes can expose an unreliable version as valid. Require `Number.isSafeInteger` for the parsed version.</violation>
</file>

Tip: Get a Slack DM when cubic finishes reviewing your PRs. Connect Slack

View guided diff | Turn on auto-fix | Re-trigger cubic

function readLeaseDeviceBaseline(value: unknown): LeaseDevice['baseline'] {
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
const record = value as Record<string, unknown>;
const name = nonEmptyString(record.name);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: readLeaseDevice trims baseline.name, but device-spec requests preserve names as given; a valid echoed name with surrounding whitespace is therefore rejected as a mismatch. Preserve the original string while only trimming to test whether it is blank.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/contracts/src/lease-scope.ts, line 396:

<comment>`readLeaseDevice` trims `baseline.name`, but device-spec requests preserve names as given; a valid echoed name with surrounding whitespace is therefore rejected as a mismatch. Preserve the original string while only trimming to test whether it is blank.</comment>

<file context>
@@ -371,34 +379,47 @@ export function readLeaseDevice(value: unknown): LeaseDevice | undefined {
+function readLeaseDeviceBaseline(value: unknown): LeaseDevice['baseline'] {
+  if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined;
+  const record = value as Record<string, unknown>;
+  const name = nonEmptyString(record.name);
+  const { version } = record;
+  if (!name || typeof version !== 'number' || !Number.isInteger(version) || version < 1) {
</file context>
Suggested change
const name = nonEmptyString(record.name);
const name =
typeof record.name === 'string' && record.name.trim().length > 0 ? record.name : undefined;


Proxy lease by device spec:
Without --udid or --serial, open, install, reinstall, and uninstall through connect proxy send --platform, --target, and --device on the lease allocate, and the backend in front of the daemon picks the device. --os <version|range>, --image-tag <tag>, --device-mode <slim|full>, and --physical add a device spec to that request. A non-blank osVersion, imageTag or deviceMode and physical: true name a spec key, sent as given; the backend validates its value (for example, Android --os is an API level, and --device-mode full asks for an unslimmed iOS simulator). A blank string and physical: false name nothing, from the CLI, the SDK, or MCP. A value of another type fails with INVALID_ARGS, reason device-spec-invalid. connect proxy stores the spec keys it names as defaults like --platform and --device.
Without --udid or --serial, open, install, reinstall, and uninstall through connect proxy send --platform, --target, and --device on the lease allocate, and the backend in front of the daemon picks the device. --os <version|range>, --image-tag <tag>, --device-mode <slim|full>, --device-baseline <name>, and --physical add a device spec to that request. A non-blank osVersion, imageTag, deviceMode or deviceBaseline and physical: true name a spec key, sent as given; the backend validates its value (for example, Android --os is an API level, --device-mode full asks for an unslimmed iOS simulator, and --device-baseline names a saved device state the backend restores; it is unrelated to diff --baseline). A blank string and physical: false name nothing, from the CLI, the SDK, or MCP. A value of another type fails with INVALID_ARGS, reason device-spec-invalid. connect proxy stores the spec keys it names as defaults like --platform and --device.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This help text names the screenshot comparison option as diff --baseline, but the CLI syntax is diff screenshot --baseline <path>; use the full form.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/commands/schema/cli-help.ts, line 573:

<comment>This help text names the screenshot comparison option as `diff --baseline`, but the CLI syntax is `diff screenshot --baseline <path>`; use the full form.</comment>

<file context>
@@ -570,10 +570,10 @@ Direct proxy flow for a remote Mac/simulator:
 
 Proxy lease by device spec:
-  Without --udid or --serial, open, install, reinstall, and uninstall through connect proxy send --platform, --target, and --device on the lease allocate, and the backend in front of the daemon picks the device. --os <version|range>, --image-tag <tag>, --device-mode <slim|full>, and --physical add a device spec to that request. A non-blank osVersion, imageTag or deviceMode and physical: true name a spec key, sent as given; the backend validates its value (for example, Android --os is an API level, and --device-mode full asks for an unslimmed iOS simulator). A blank string and physical: false name nothing, from the CLI, the SDK, or MCP. A value of another type fails with INVALID_ARGS, reason device-spec-invalid. connect proxy stores the spec keys it names as defaults like --platform and --device.
+  Without --udid or --serial, open, install, reinstall, and uninstall through connect proxy send --platform, --target, and --device on the lease allocate, and the backend in front of the daemon picks the device. --os <version|range>, --image-tag <tag>, --device-mode <slim|full>, --device-baseline <name>, and --physical add a device spec to that request. A non-blank osVersion, imageTag, deviceMode or deviceBaseline and physical: true name a spec key, sent as given; the backend validates its value (for example, Android --os is an API level, --device-mode full asks for an unslimmed iOS simulator, and --device-baseline names a saved device state the backend restores; it is unrelated to diff --baseline). A blank string and physical: false name nothing, from the CLI, the SDK, or MCP. A value of another type fails with INVALID_ARGS, reason device-spec-invalid. connect proxy stores the spec keys it names as defaults like --platform and --device.
     agent-device open MyApp --platform ios --device "iPhone 17 Pro" --os ">=26"
     agent-device open MyApp --platform android --os 34 --image-tag google_apis_playstore
</file context>
Suggested change
Without --udid or --serial, open, install, reinstall, and uninstall through connect proxy send --platform, --target, and --device on the lease allocate, and the backend in front of the daemon picks the device. --os <version|range>, --image-tag <tag>, --device-mode <slim|full>, --device-baseline <name>, and --physical add a device spec to that request. A non-blank osVersion, imageTag, deviceMode or deviceBaseline and physical: true name a spec key, sent as given; the backend validates its value (for example, Android --os is an API level, --device-mode full asks for an unslimmed iOS simulator, and --device-baseline names a saved device state the backend restores; it is unrelated to diff --baseline). A blank string and physical: false name nothing, from the CLI, the SDK, or MCP. A value of another type fails with INVALID_ARGS, reason device-spec-invalid. connect proxy stores the spec keys it names as defaults like --platform and --device.
Without --udid or --serial, open, install, reinstall, and uninstall through connect proxy send --platform, --target, and --device on the lease allocate, and the backend in front of the daemon picks the device. --os <version|range>, --image-tag <tag>, --device-mode <slim|full>, --device-baseline <name>, and --physical add a device spec to that request. A non-blank osVersion, imageTag, deviceMode or deviceBaseline and physical: true name a spec key, sent as given; the backend validates its value (for example, Android --os is an API level, --device-mode full asks for an unslimmed iOS simulator, and --device-baseline names a saved device state the backend restores; it is unrelated to diff screenshot --baseline). A blank string and physical: false name nothing, from the CLI, the SDK, or MCP. A value of another type fails with INVALID_ARGS, reason device-spec-invalid. connect proxy stores the spec keys it names as defaults like --platform and --device.

const record = value as Record<string, unknown>;
const name = nonEmptyString(record.name);
const { version } = record;
if (!name || typeof version !== 'number' || !Number.isInteger(version) || version < 1) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Number.isInteger accepts unsafe positive numbers, so malformed baseline echoes can expose an unreliable version as valid. Require Number.isSafeInteger for the parsed version.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/contracts/src/lease-scope.ts, line 398:

<comment>`Number.isInteger` accepts unsafe positive numbers, so malformed baseline echoes can expose an unreliable version as valid. Require `Number.isSafeInteger` for the parsed version.</comment>

<file context>
@@ -371,34 +379,47 @@ export function readLeaseDevice(value: unknown): LeaseDevice | undefined {
+  const record = value as Record<string, unknown>;
+  const name = nonEmptyString(record.name);
+  const { version } = record;
+  if (!name || typeof version !== 'number' || !Number.isInteger(version) || version < 1) {
+    return undefined;
+  }
</file context>
Suggested change
if (!name || typeof version !== 'number' || !Number.isInteger(version) || version < 1) {
if (!name || typeof version !== 'number' || !Number.isSafeInteger(version) || version < 1) {

@thymikee

thymikee commented Nov 5, 2026

Copy link
Copy Markdown
Member

At b7331fa, the baseline routing and the contract look right: deviceBaseline follows the deviceMode path, and existing callers are unaffected. All 18 checks pass and there are no conflicts.

One thing before merge: readLeaseDeviceBaseline trims the echoed baseline name, while the requested name is kept as given, so a padded name compares unequal and a lease the backend honoured is released and refused (thread). Both sides should use the same comparison, either both untrimmed or both trimmed.

The help wording thread also still applies: please write diff screenshot --baseline in full. The Number.isSafeInteger thread does not apply, since nothing branches on baseline.version and the integer check already matches the contract, so it can be resolved.

Not blocking, take or leave: reflow the doc comments on readLeaseDeviceSpec in lease-scope.ts and on bindLeaseNamedDevice in proxy-lease-device.ts, and add baseline to the lease.device field list in website/docs/docs/remote-proxy.md, as cli-help.ts already does. I read the diff and tests without running them; the backend echo shape {name, version} comes from #4235 and was not checked against a real backend.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

proxy lease: name a backend baseline with --device-baseline

2 participants