You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d17f710
Browse filesBrowse the repository at this point in the historyBrowse files
fix(daemon): scope a transient device claim to the session that holds it (#3714)
* fix(daemon): scope a transient device claim to the session that holds it
#1809 made install/reinstall and their transient-exclusive siblings covered by
any claim this daemon's process held, so a second session in the same daemon
could install over a device another session had open. Sessions of one daemon
are separate device owners: admission now compares the request's routed session
against the held claim, and a foreign session's claim refuses the command with
DEVICE_IN_USE the way open already does. Session addresses are copyable, so the
covered claim must additionally record the caller's own workspace, canonicalized
on both sides through the same scope root session routing hashes; a request
without a cwd carries no workspace claim. A release whose unlink fails now
abandons the claim instead of leaving it live, so the owner is never locked out
of its own device. The versioned help and user docs name the second
DEVICE_IN_USE flavor's real owner; main's device-verification notes already
attribute that claim to another session, so no guidance budget moves there.
* test(integration): run the raw file push from inside the claiming workspace
#3345's coverage rule pairs the routed session with the workspace that took the
claim. The raw push request carried a temp cwd, so it routed under another
workspace, and the open session's claim no longer covered it. Running it from a
directory of the checkout reproduces the real CLI owner shape the client calls
above already form, and keeps the relative payload resolution the test is for.
Probe evidence at the pre-fix commit: the push refused with
DEVICE_CLAIM_LIVE_OWNER naming the checkout workspace.
* fix(daemon): record which kind of owner holds a device claim
An in-flight command's transient claim and an open session's claim differed
only by the shape of the claim's `session` label, so a request whose routed
session string happened to equal `transient:<command>` — reachable today with
`--session transient:install`, which the store sanitizes only in directory
segments — matched the coverage branch and ran while the first request still
held the device (review r4295092657). The record now stores the kind:
`transientCommand` names the scoped command (schema v3), and coverage admits
only open-session claims; a command-scoped claim is a live owner to every
request but its holder, proven by the release token at the admission layer.
Legacy v1/v2 records translate the label that encoded the kind, once, in the
record decode (tag history: v2 is released in v0.21.28, so live records use
it). A session literally named this command's label is refused rather than
renew-adopted, so its claim keeps only what its principal grants.
Fold in the review's documentation findings: the DEVICE_IN_USE help names
in-flight commands as owners a caller cannot close (r4295092708 sibling),
commands.md and sessions.md state the workspace condition next to session
ownership without implying every caller sends a workspace (r4295092708,
r4295092739), and sessions.md names the owner only when the claim is
readable, pointing at `device status` otherwise (r4295092723).
Shrink the android-lifecycle raw-push fixture under the merge-base length:
the file is over the 1,000-line ratchet tripwire and may not grow, so the
push payloads are written directly into the claiming workspace by a loop
(Coverage job test-file-size ratchet, run 38905771494).
* fix(daemon): decode legacy device claims kindless instead of guessing a kind
r4295744998: released v1/v2 recorded a command-scoped claim and an open
session literally named `transient:<command>` as the same record, so the
label is not a kind proof, and stamping a guessed kind made the schema
version alone decide whether a colon-named session's claim covered its
commands. Legacy records now decode kindless, which is safe by
construction rather than by guess: every consumer of a claim's kind first
requires `isClaimOwnedByThisDaemon` (same stateDir, pid, and process start
time), a live process writes only v3 records whose kind is stored, and the
forged-identity window that reaches a kind decision anyway is pinned by a
closest-negative test: a kindless legacy record under this process's
identity behaves exactly as a colon-named open session's claim — refused
to foreign commands by the label guard, covering for the named session —
never as a command claim any in-flight request can adopt or leak.
The record test pins the kind assertions directly (ordinary legacy and
current claims decode `transientCommand` undefined; the v3 kind comes only
from its stored field; malformed kinds fail closed) and the
android-lifecycle provenance comment states what the no-cwd client calls
actually record: the daemon-side fallback workspace at the routed `default`
address, joined explicitly by the pushes whose checkout-child cwd exists
for relative payload resolution, not as a workspace-authority test
(r4295745008, r4295745014).
* test(daemon): tighten the device-claim admission tests
Drop the duplicated transient-claim refusal case (its label-guard sibling already
proves it) and rewrite comments that narrated mutations and review rounds into
statements of the constraint each test pins.
* fix(daemon): state a claim kind only from a record that states one
A migrated v1/v2 record decoded with no kind at all, and coverage read an
absent transientCommand as proof of an open session. The ownership check in
front of that decision matches pid plus start time, and two processes agree
on both whenever a start time is unreadable, so a leftover v2 command claim
can outlive PID reuse and present as this daemon's own session.
Decoded claims now carry a persisted claimOwnerKind. A current writer states
open-session or transient-command; a record that predates the field states
unstated and can hold and fence a device but can never cover a command. The
kind is stored rather than derived from schemaVersion so renewal, abandon,
and clear keep writing the current shape — a renewed record that kept its
legacy version would be stamped with the migrated device shape and read back
as inconsistent.
* fix(daemon): hold an existing claim only for its own claim kind
An acquirer now holds an existing claim only when the record states the very
kind of claim the acquirer would write itself — the same transientCommand for
transient admission — under the same session, workspace, and daemon identity.
An unstated record matches no acquirer, so acquisition never adopts a migrated
v1/v2 record, and an open session named after a command label never inherits an
in-flight command's owner token. claimHeldDevice passes the acquirer's typed
kind and command to resolveExistingClaim, the boundary every acquire route
goes through, which retires the label guard in acquireTransientDeviceClaim.
Ref: #3714 (comment)
0 commit comments