Repository navigation
Conversation
d1d5390 to
2a55127
Compare
|
Nothing in the code at 2a55127 blocks merge, but one design question below is open, and the lease lifecycle needs settling with #3269. At this head, CI is green (1 check, passing) and there are no conflicts. I did not run the tests or check:daemon-wire-compat, so the PR body's pass claims are unverified. I also reviewed only commits 6b542a9 and 2a55127, so the #3272 base is not re-reviewed. I did not check the eager-closure budget claim for the dynamic import at connection-runtime.ts:949. Not blocking, and you can take or leave these. First, a Host lease can leak its device: allocateHostShapeLease calls HostShapeAllocator.release only when publishing to the registry fails. A normal lease_release, TTL expiry, or a requester that cancels mid-allocate never releases a device Simlock provisioned. The rule should be that every Host lease that reaches the registry releases its allocator device on every path that ends the lease. That belongs on the owning lifecycle, as LeaseLifecycleProvider does for allocate, heartbeat and release, not on one catch block. Second, two separate options decide two linked facts in http-server.ts: the device-shape feature and the allocator. A daemon can advertise device-shape with no allocator, so the client pre-check passes and the daemon then refuses with host-shape-allocation-unavailable. Deriving the feature from allocator presence at the one construction site would make that mismatch impossible. Third, the tests are loose: the plain-proxy test in host-device-shape-connection.test.ts uses a bare rejects.toThrow(), and the "given back" test in lease.test.ts:533 has no predicate. Both should pin the error code production raises. The Host front-end feature lift at host-front-end.ts:78-81 has no test. Could the Host branch be simpler? It copies part of the allocate lifecycle (deadline, signal, release on failure) beside the LeaseLifecycleProvider path, and it drops that path's cancel and work-retention handling. Would one allocate path work, where the shape allocator resolves the deviceKey before leaseRegistry.allocateLease and the existing runDeviceMutation, cancel and release machinery plus a release hook own the rest? Before merge, please settle the HostShapeAllocator contract with #3269 so it covers release on lease end, expiry and cancel, and so advertising follows allocator presence. Do this before any Simlock implementation lands on the seam. After that, the tests and check:daemon-wire-compat need a run on the final commit. |
2a55127 to
5d30071
Compare
Add `agent-device host`, the Host front-end from ADR 0021 §3. It runs as its own process, starts or reuses the local HTTP daemon, and serves it to remote verification workers through the daemon proxy. Workers authenticate with one persistent service credential. Host creates it on first start at <state dir>/host/service-credential.json (directory 0700, file 0600) and reuses it after every restart. A malformed or group/other-readable file stops startup with a typed reason, and so does a TLS file Host cannot read. Both checks run before any daemon starts. --tls-cert and --tls-key serve HTTPS. A wildcard bind advertises the machine's hostname, since workers cannot dial 0.0.0.0. The proxy command behaves as before. Its daemon startup and listen helpers move into a module both commands use. Closes callstack#3265
- Host checks that the TLS certificate and key load together, and that the key is mode 0600, before any daemon starts. A bind other than loopback without TLS is refused (host-tls-required), so the service token never travels in cleartext. - A new credential reaches disk only once Host is serving, so a start that fails earlier never hides the token from the next one. A credential another start wrote first is refused (host-credential-raced). - A credential that is a link or unreadable gets a typed reason, and platforms without POSIX ownership skip the mode check. - The advertised URL keeps the host name the operator bound to, and the worker command in the startup output names the real URL and token. - The proxy command keeps its original code. Host's daemon and listen helpers live in src/cli/host/local-daemon.ts. - The host help topic moves into its own module.
Add `host` to the reviewed device-claim policy set, give the --tls-cert/--tls-key flags their own Host bucket in the integration progress model, list `hostCommand` with the dynamically loaded CLI handlers in the fallow production exemptions, and waive the operator-facing `host` help topic from the help benchmark.
Host now owns identity and the public route policy (ADR 0021 §6): - The front-end drops every identity a client claims (tenant headers and body fields) and sends the credential's principal to the daemon as x-agent-device-principal on the daemon-token loopback request. - The daemon reads that header only after the daemon token matched and treats it as an attested tenant, so sessions are isolated under the principal and req.internal.hostPrincipal carries it to admission. With an auth hook configured the header is refused with a typed reason. - Administration routes, macos-app allocation, inputs naming a path on the Host machine and allowDownload are refused with 403 and a typed details.reason. The host-path inputs move out of the macos-app lease into one declaration both policies read. - Anonymous /health shows only ok, service and rpcProtocolVersion. The principal handoff is the contract proposed to the lease side on callstack#3264. Closes callstack#3266
- The proxy gains an optional admitRpc hook that runs on an authorized /rpc request with its params already parsed. Host uses it instead of re-reading the body and repeating the token check, so oversized and unauthorized requests get the proxy's own answers. Plain proxy sets no hook and behaves as before. - Path positionals come from each command's schema (`path`, `appOrPath`, `payloadOrJson`) instead of a hand-kept table, so trace, push and session save-script are covered. URLs no longer exempt a positional, an upload id exempts only install and reinstall, and only the exact temp locations the remote client writes are accepted. - Batch steps are checked one by one, and replay and test are refused (host-script-refused) because Host cannot check their nested actions. - launchConsole counts as a Host path, macos-app is matched the way the daemon normalizes it, and /admin/* is simply not served. - The daemon reads the principal header only on a request that holds the daemon token, so an unauthenticated caller learns nothing about an auth hook. - Host answers with an error response instead of rejecting.
… changes DaemonHealthPayload widens `service` with 'agent-device-host', and the auxiliary HTTP authorizer reads x-agent-device-principal after the daemon token check. Both are additive under ADR 0006: released peers send and parse the same bytes as before.
On Host, `--device "<type>"` names a device type Host allocates a fresh
device for (ADR 0021 §5), instead of a name resolved against inventory.
- The client asks the endpoint's health first, through the cache the RPC
transport already fills. On `service: agent-device-host` it skips the
inventory lookup and allocates with the type, platform and --os-version in
the device-selection fields lease.allocate already carries.
- A Host that does not advertise the `device-shape` feature fails with
host-shape-unsupported before any lease request (§8). Plain proxy is
unchanged.
- The daemon builds a strict { platform, deviceType, osVersion? } shape for
a lease_allocate that carries a Host principal, calls the new
HostShapeAllocator seam, and only then publishes the Host lease bound to
the returned device; an allocation it cannot publish is given back.
A UDID or serial, or a daemon with no allocator, is refused with a typed
reason.
The seam is the lease side's to implement (callstack#3269). Nothing configures it in
production yet; the tests drive it through the scripted allocator fake.
Closes callstack#3267
…ot allocate - After a by-type allocation, the command addresses the device the lease bound (its UDID or serial) instead of a name another simulator may share. - On a Host, every lease-allocating command is checked before any lease request. A missing --device, a UDID or serial, a missing token (host-unauthenticated) and a different type on a session that already holds one (host-shape-mismatch) are typed refusals. The platform comes from the connection when --platform is absent. - The daemon validates the lease scope before it provisions anything, gives an allocation back when the requester has left, and keeps the original error if giving it back fails. --os-version must be a string. - The daemon advertises device-shape exactly when it has an allocator, and the Host service name is one shared constant. - The proxy lease device selection moves into its own module, loaded on demand, so connection-runtime.ts stays under 1,000 lines.
List DaemonHealthFeature, DAEMON_HOST_DEVICE_SHAPE_FEATURE and DAEMON_HOST_SERVICE in the wire manifest, and acknowledge the additive `features` field on the health payload, its builder and the client's health parser under ADR 0006.
5d30071 to
d3b9856
Compare
|
The PR is ready at d3b9856. The earlier gap is fixed: the requester-left path now cancels before the allocator is touched, and the new test covers it. Not blocking: the requester-left test at https://github.com/callstack/agent-device/blob/d3b9856/src/daemon/handlers/__tests__/lease.test.ts#L707 uses CI shows 1 check passing and none failing, and there are no conflicts. I did not run the tests or One thing is unchanged from the earlier review and out of scope here. The end of a published Host lease never reaches Before a Simlock implementation lands on this seam, please settle with #3269 which side releases the allocator device and renews its TTL when a published Host lease is released, expires or is heartbeated. Please also re-run the tests and |
Stacked on #3272. Please review only the top three commits.
Summary
On Host, a worker requests a device by type (ADR 0021 §5):
agent-device open com.example.app --platform ios --device "iPhone 16"If authenticated health reports
device-shape, the client allocates by type instead of reading inventory, then addresses the leased device by UDID or serial. Anything else on a Host is refused, typed, before any lease request:--device, or a UDID or serial;Plain proxy behaves as before.
The daemon validates a Host-principal
lease_allocate, calls the newHostShapeAllocatorseam, and publishes the lease bound to the returned device. It gives the allocation back if publishing fails or the requester left./healthadvertisesdevice-shapeexactly when an allocator is set. No protocol bump.The seam is proposed for #3269; nothing configures it yet, so tests use the scripted fake.
Closes #3267. 19 files, 924 gross lines vs #3272. Proxy lease device selection moves out of
connection-runtime.ts(now under 1,000 lines).Validation
Tested commit
d3b9856c2:pnpm check:affected --run: all pass except a flakyaffected-selectortest (ENOTEMPTY on temp cleanup; unchanged from main). The 17 checks after it pass when run separately.pnpm check:daemon-wire-compatpassed.