Repository navigation
fix(proxy): one decision on what a device spec names, for every surface - #4009
Conversation
The CLI dropped a blank --os or --image-tag and physical: false, while the daemon gate refused any spec key that was not undefined, so the SDK and MCP were refused for the same inputs the CLI accepted. readLeaseDeviceSpec now decides each key is named, not named, or invalid, and the daemon gate, the lease allocate params and the CLI all read it: - a non-blank osVersion or imageTag and physical: true are named; - an absent key, a blank string and physical: false name nothing; - any other type is refused with INVALID_ARGS, reason device-spec-invalid. namedLeaseDeviceSpecKeys is removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@janjankowski-cs is attempting to deploy a commit to the Callstack Team on Vercel. A member of the Team first needs to authorize it. |
The SDK and MCP send command flags to the daemon as given, so a blank osVersion or imageTag and physical: false reach the gate unchanged. The lease-allocate test did not cover that path: readLeaseAllocateProviderFlags drops those values on both sides before the gate runs. Under the pre-fix gate rule (any key not undefined is named), both cases fail: the not-named request is refused, and physical: false joins keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 10 files
Reply to a comment to ask cubic a question or push back. It learns from your replies.
View guided diff | Re-trigger cubic
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
New test:
|
| Test | Flags sent | Expected |
|---|---|---|
A command whose blank osVersion, blank imageTag and physical: false name no spec runs |
osVersion: "", imageTag: " ", physical: false |
ok: true, and the command ran once |
A command naming a spec is refused with device-spec-unsupported before it runs |
osVersion: "18", physical: false |
UNSUPPORTED_OPERATION, reason: "device-spec-unsupported", keys: ["osVersion"], and the command never ran |
The first test is the regression test for the review finding: values that name nothing must go through the gate, as they do on the CLI.
The second test is the closest negative case. It checks that:
- a named key is still refused when it's mixed with a value that names nothing;
physical: falsedoesn't appear inkeys;- the refusal happens before the command runs.
Proof that it catches the bug
This was run in a separate checkout of ff3b3dbee plus the new test file, rebuilt before each run. For the second run, the pre-fix gate rule ("any key that is not undefined is named") was put back into request-router.ts:
| Run | Gate test (new) | SDK test file (existing) | Total |
|---|---|---|---|
| Unmodified | 2 passed | 4 passed | 6 passed |
| Pre-fix gate rule | 2 failed | 4 passed | 2 failed, 4 passed |
Both failures are for the intended reason:
- First test: the request was refused (
ok: falseinstead ofok: true). This is the bug the reviewer found. - Second test:
keyswas["osVersion", "physical"]instead of["osVersion"], because the old rule countedphysical: falseas named.
The existing SDK test file passed under the old rule, which confirms it doesn't cover this path.
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
…refusal rule; assert.throws cleanup;
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
|
The code looks good at bc880e1, and the duplicated ADR block from the earlier review is fixed. Not blocking, and you can take or leave it: the inputDescription strings for osVersion, imageTag and physical in https://github.com/callstack/agent-device/blob/bc880e1/packages/command-registry/src/flag-definitions-target.ts#L87 still only mention device-spec-unsupported. They could add one clause each saying that a blank string or physical:false names nothing, and that another type fails with INVALID_ARGS device-spec-invalid. The comment at cli-flags.ts:64-65 could also point at readLeaseDeviceSpec instead of LEASE_DEVICE_SPEC_KEYS. The earlier Cubic thread on the ADR no longer applies, because the duplicated block is gone and the INVALID_ARGS sentence carries details.keys. Please resolve it: #4009 (comment) The only failing check is Vercel. Its link is a git-authorization page for a fork PR, so the preview deploy was not authorized. It did not fail a build, and this diff does not cause it. There are no conflicts. I did not re-run the live SDK runs or the manual steps; they come from the PR body, and the latest change is ADR-only. I did not run the unit or daemon tests locally. Before merge, please land this into feat/proxy-device-spec ahead of #3865 reaching main, so #3865 does not ship the CLI/daemon contract mismatch. |
| readLeaseAllocateProviderFlags({ | ||
| platform: 'android', | ||
| osVersion: '34', | ||
| osVersion: ' 34 ', |
There was a problem hiding this comment.
that way we test right away for trimming of the string itself, not necessary
There was a problem hiding this comment.
Good catch. The padding was there because we trimmed these values, and the test pinned that. Nothing actually sends padded values: the CLI, the config file and the SDK all pass strings as given, and we don't trim platform or device either. So the trim is gone in 1afb732: a named osVersion or imageTag is now sent exactly as given, and a blank one still names nothing. This test now uses a plain '34', and a separate test named for the rule covers a padded value being kept as is.
Checking this also showed that connect proxy --os "" wrote an empty value into the stored profile and then failed to read it back. The profile now goes through the same rule, so a blank value is not stored.
…ect proxy default for a blank one readLeaseDeviceSpec no longer trims a named osVersion or imageTag. No producer pads these values: the CLI parser, the remote config reader and the SDK/MCP input all pass strings as given, and the other lease allocate flags are not trimmed either. A blank string still names nothing. connect proxy now builds its profile spec through requireLeaseDeviceSpec, so a blank --os or --image-tag is not persisted. Before, the generated profile stored "" and failed its own re-read with "Expected non-empty string". The flag input descriptions, CLI help, ADR 0007 and the remote proxy docs state the rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 10 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
|
The code in 1afb732 looks good, and I found no new problems since my earlier review (#4009 (comment)). I read the head tree and the delta only. I did not run tests, gates or CI jobs. No live device run is needed, because the delta changes only CLI and contract spec reading plus docs. The only check that is not passing is Vercel. Its link goes to vercel.com/git/authorize, which is an authorization prompt for a fork author, not a build of this diff. The diff touches no website build input beyond one docs sentence, so I think this is unrelated. I could not see a failure excerpt, so that rests on the URL. Two Cubic threads on The thread at #4009 (comment) no longer applies. The padded Once those threads are settled, I see nothing else in the code that must change before merge. |
…, or rejects as invalid Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
…the same reason" Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* base-3865: (76 commits) fix(open): refuse --launch-console on a provider-owned device before the relaunch close (callstack#3938) fix(test-app): keep the inert surface control above the settings fold (callstack#3993) fix(fill): redact the read-back of a sensitive fill (callstack#4014) fix(apple): read the runner cache toolchain key from Xcode's own files (callstack#3995) docs: point stable test suites to e2e (callstack#4010) docs(mutation): state the file-name rule that marks test support code (callstack#4011) refactor(kernel): read the Action Button leaf from isHandheldAppleDevice (callstack#3988) fix(ios): match alert labels through the OS prompt strings of the preferred languages (callstack#4006) fix(mutation): measure each kernel against the tests that import it (callstack#3994) fix(webdriver): avoid unused pre-tap focus geometry reads (callstack#3997) refactor(apple): share coordinate drag execution (callstack#4003) fix(apple): type a text-entry-trait element as a text input in the simulator AX tree (callstack#4008) refactor(contracts): own the unsupported-interactor stub both platforms duplicated (callstack#4002) refactor: reuse canonical device identity projections (callstack#3996) refactor(apple): share simulator status-bar diagnostics (callstack#3998) refactor(runtime): share host command runner construction (callstack#3999) refactor(android): centralize session error encoding (callstack#4000) chore: mark 0.21.33 as released chore(gates): detect new native duplication with jscpd (callstack#3980) fix(android): publish the Compose role on its clickable parent instead of a fake child (callstack#3983) ...
… already loads flag-groups declared LEASE_DEVICE_SPEC_FLAGS by importing lease-scope, which pulled lease-scope and three kernel modules into the flag-groups closure. The list is now declared in flag-groups and pinned to LEASE_DEVICE_SPEC_KEYS by a type-only satisfies. lease-device-spec.ts was a new eager module in the CLI closure. Its field set moves to command-input.ts beside repeatedFields(), and its reader moves to cli-grammar/common.ts beside the other flag readers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te-proxy.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
All reported issues were addressed across 8 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
The open, install, reinstall and uninstall input fields read osVersion and imageTag as non-empty strings, so MCP refused a blank value with a generic INVALID_ARGS before readLeaseDeviceSpec ran, and a wrong type got no device-spec-invalid reason. The fields now read through requireLeaseDeviceSpec. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
94b061b
into
callstack:feat/proxy-device-spec
One decision on what a device spec names
Problem
Two parts of the code decided independently whether a request named a device spec, and they disagreed:
osVersion: ""readLeaseDeviceSpec)physical: falsedon't countopenproceedsnamedLeaseDeviceSpecKeys)undefinedcountsThe CLI cleaned the values before sending, so it never hit the difference. The SDK and MCP send values unchanged, so the daemon refused
""andphysical: falsewithdevice-spec-unsupported.The rule
readLeaseDeviceSpecinlease-scope.tsis now the only place that decides. Each key gets exactly one of three outcomes:"18"," >=26 "(sent as given),physical: true""," ",physical: falseosVersion: 26,physical: "yes",imageTag: nullChanges
1.
readLeaseDeviceSpec(lease-scope.ts)It returns
{ spec, keys, invalidKeys }. The old version returned onlyspecand silently ignored wrong types.namedLeaseDeviceSpecKeysis removed.2.
deviceSpecInvalidError(lease-scope.ts)A wrong type is a caller mistake, not a missing feature, so it is refused with
INVALID_ARGSand the typed reasondevice-spec-invalid, withdetails.keys, instead ofdevice-spec-unsupported.3.
requireLeaseDeviceSpec(lease-scope.ts)The same decision for client-side sources: it throws
device-spec-invalidon an invalid key, so the CLI fails before any request is sent. The CLI callers use it:proxy-lease-device.ts: deciding whether to allocate by spec, and the spec check on a live lease.connection-runtime.ts: the spec sent onlease.allocate.lease-device-spec.ts: the command readers foropen,install,reinstallanduninstall.4. Daemon gate (
request-router.ts)This fixes the SDK and MCP: the daemon now ignores
""andfalsethe same way the CLI does.5. Lease request params (
readLeaseAllocateProviderFlags)""orfalseAn invalid value is sent unchanged on purpose: dropping it here would let a bad request through without an error.
6. Tests and docs
lease-scope.test.ts: covers all three outcomes. The old assertion thatphysical: 'yes'names nothing is replaced, because that behaviour changed.agent-device-client-lease-device-spec.test.ts: the SDK against a real daemon. Blank values andphysical: falseallocate a lease;physical: 'yes'is refused withdevice-spec-invalidand leaves no lease.help remote,remote-proxy.mdand ADR 0007 each state the rule in one sentence.Update: CI and module loading
osVersionorimageTagis sent exactly as given. Step 4 of the validation below ran before that change.feat/proxy-device-spec(feat(proxy): lease by device spec — --os, --image-tag, --physical honoured or refused by the backend #3865) was behind main, sodaemon-wire-compatcompared this branch against v0.21.33 without fix(daemon): answer 404 for an expired or unknown resource id #3958. Main is merged into feat(proxy): lease by device spec — --os, --image-tag, --physical honoured or refused by the backend #3865, and feat(proxy): lease by device spec — --os, --image-tag, --physical honoured or refused by the backend #3865 is merged into this branch.eager-closure-budgets(inherited from feat(proxy): lease by device spec — --os, --image-tag, --physical honoured or refused by the backend #3865):flag-groups.tsimportedlease-scope.tsat runtime, so it loaded 5 modules instead of 1. It now declaresLEASE_DEVICE_SPEC_FLAGSitself, pinned toLEASE_DEVICE_SPEC_KEYSwith a type-onlysatisfies.lease-device-spec.tswas a new eager module in the CLI closure. Its field set moved tocommand-input.tsasleaseDeviceSpecFields(), next torepeatedFields(). Its reader moved tocli-grammar/common.ts, next to the other flag readers.help remoteandremote-proxy.mdnow state each device-spec rule once.Validation
Steps 2 and 2c of the manual test, re-run with the same inputs on the iPhone 17 Pro simulator against a local daemon:
osVersion: "18"device-spec-unsupported[osVersion]physical: truedevice-spec-unsupported[physical]osVersion: ""osVersion: " "physical: falsephysical: "yes"device-spec-unsupportedINVALID_ARGSdevice-spec-invalid✅osVersion: 26device-spec-unsupportedINVALID_ARGSdevice-spec-invalid✅¹ The CLI parser types the flags, so it can't produce these values.
--physical=falsefails earlier withFlag --physical does not take a value, and leaving out--physicalis how the CLI says false.pnpm install --frozen-lockfile && pnpm buildonpr-3865plus the change--os,--image-tagand--physicalappear inopen --help;help remoteincludes the newdevice-spec-invalidsentence--os 18and--physical→device-spec-unsupported;--os "",--os " "and--image-tag ""→ open proceedsosVersion: "18"andphysical: true→device-spec-unsupported;""," "andphysical: false→ open proceeds;physical: "yes"andosVersion: 26→INVALID_ARGS,device-spec-invalidagent-device proxyrefuses--os 18; a plainopenstraight afterwards succeeds, so no lease was left behindLeased device: Stub Device, 18.6, virtual;--os ' 18.6 'reaches the backend trimmed to"18.6";snapshotworks and doesn't carry the specopenwith the same--oson a live lease is servedopenwith a different--os→CONNECTION_DEVICE_CONFLICT,keys: ["osVersion"]openwith a different--udidand no specLEASE_SCOPE_MISMATCH(unchanged - unrelated to this change)openwith a different--udidplus--os→CONNECTION_DEVICE_CONFLICTopennaming no spec is served on the existing leaseopenwith--os ""on a live spec lease is served, because a blank value names nothingconnect proxy --os 18.6stores the spec, and a later plainopensends itlease.device: the lease is released (released: true) and the command fails withdevice-spec-unsupported; a plainopenafterwards succeeds