@@ -4,6 +4,7 @@ import crypto from 'node:crypto';
44import { expect , test } from 'vitest' ;
55import { canonicalLocalDeviceKey } from '@agent-device/daemon-contracts/device-claims' ;
66import { deviceIdentity } from '@agent-device/kernel/device' ;
7+ import { publishDaemonRegistration } from '@agent-device/test-kit' ;
78import { resourceAdmissionWorld , assertRpcOk } from './composite-resource-admission.fixtures.ts' ;
89import { seedExistingCapture } from './composite-resource-existing.fixtures.ts' ;
910
@@ -106,44 +107,59 @@ test('bounded close may release a local lease when the daemon has no provider re
106107 }
107108} ) ;
108109
109- test ( 'active provider device skips local foreign-claim preview' , async ( ) => {
110- const world = await resourceAdmissionWorld ( 'capture' , { providerOwned : true } ) ;
111- try {
112- const device = world . sessionStore . requireCurrent ( world . ref ) . device ;
113- const deviceKey = canonicalLocalDeviceKey ( deviceIdentity ( device ) ) ;
114- const claimsDir = path . join ( world . daemon . stateDir , 'claims' ) ;
115- fs . mkdirSync ( claimsDir , { recursive : true } ) ;
116- const claimPath = path . join (
117- claimsDir ,
118- `${ crypto . createHash ( 'sha256' ) . update ( deviceKey ) . digest ( 'hex' ) } .json` ,
119- ) ;
120- fs . writeFileSync (
121- claimPath ,
122- JSON . stringify ( {
123- schemaVersion : 3 ,
124- deviceKey,
125- device : { ...deviceIdentity ( device ) , name : device . name } ,
126- session : 'foreign' ,
127- workspace : '/foreign' ,
128- stateDir : '/foreign/state' ,
129- ownerPid : process . ppid ,
130- ownerStartTime : null ,
131- ownerToken : 'foreign-token' ,
132- createdAtMs : 1 ,
133- updatedAtMs : 1 ,
134- claimOwnerKind : 'open-session' ,
135- } ) ,
136- ) ;
137- const before = fs . readFileSync ( claimPath ) ;
138- assertRpcOk (
139- await world . call ( 'batch' , [ ] , {
140- batchSteps : [
141- { command : 'open' , positionals : [ 'com.android.settings' ] , flags : { relaunch : true } } ,
142- ] ,
143- } ) ,
144- ) ;
145- expect ( fs . readFileSync ( claimPath ) ) . toEqual ( before ) ;
146- } finally {
147- await world . daemon . close ( ) ;
148- }
149- } ) ;
110+ test . each ( [ true , false ] ) (
111+ 'fresh bounded open with providerOwned=%s handles a foreign local claim' ,
112+ async ( providerOwned ) => {
113+ const world = await resourceAdmissionWorld ( 'capture' , { providerOwned } ) ;
114+ try {
115+ const device = world . sessionStore . requireCurrent ( world . ref ) . device ;
116+ world . sessionStore . retire ( world . ref ) ;
117+ const deviceKey = canonicalLocalDeviceKey ( deviceIdentity ( device ) ) ;
118+ const claimsDir = path . join ( world . daemon . stateDir , 'claims' ) ;
119+ const foreignStateDir = path . join ( world . daemon . stateDir , 'foreign-owner' ) ;
120+ fs . mkdirSync ( foreignStateDir , { recursive : true } ) ;
121+ publishDaemonRegistration ( foreignStateDir , { pid : process . ppid , startTime : null } ) ;
122+ fs . mkdirSync ( claimsDir , { recursive : true } ) ;
123+ const claimPath = path . join (
124+ claimsDir ,
125+ `${ crypto . createHash ( 'sha256' ) . update ( deviceKey ) . digest ( 'hex' ) } .json` ,
126+ ) ;
127+ fs . writeFileSync (
128+ claimPath ,
129+ JSON . stringify ( {
130+ schemaVersion : 3 ,
131+ deviceKey,
132+ device : { ...deviceIdentity ( device ) , name : device . name } ,
133+ session : 'foreign' ,
134+ workspace : '/foreign' ,
135+ stateDir : foreignStateDir ,
136+ ownerPid : process . ppid ,
137+ ownerStartTime : null ,
138+ ownerToken : 'foreign-token' ,
139+ createdAtMs : 1 ,
140+ updatedAtMs : 1 ,
141+ claimOwnerKind : 'open-session' ,
142+ } ) ,
143+ ) ;
144+ const before = fs . readFileSync ( claimPath ) ;
145+ const response = await world . call ( 'batch' , [ ] , {
146+ batchSteps : [ { command : 'open' , positionals : [ 'com.android.settings' ] } ] ,
147+ } ) ;
148+ if ( providerOwned ) {
149+ assertRpcOk ( response ) ;
150+ expect ( world . sessionStore . lookup ( 'resource-session' ) ) . toBeDefined ( ) ;
151+ } else {
152+ expect ( response . json . error ?. data ) . toMatchObject ( {
153+ code : 'DEVICE_IN_USE' ,
154+ details : { reason : 'DEVICE_CLAIM_LIVE_OWNER' } ,
155+ } ) ;
156+ expect ( world . sessionStore . lookup ( 'resource-session' ) ) . toBeUndefined ( ) ;
157+ expect ( world . inspectFacts ) . not . toHaveBeenCalled ( ) ;
158+ expect ( world . bind ) . not . toHaveBeenCalled ( ) ;
159+ }
160+ expect ( fs . readFileSync ( claimPath ) ) . toEqual ( before ) ;
161+ } finally {
162+ await world . daemon . close ( ) ;
163+ }
164+ } ,
165+ ) ;
0 commit comments