1import { describe, expect, it, vi } from "vitest";2import { computeVaultId } from "./sync-identity";3import {4 grantAdoption,5 isAdoptGrant,6 requestAdoption,7 sendFrame,8 type AdoptRequest,9 type AdoptionReceiverDeps,10 type AdoptionSourceDeps,11} from "./vault-adopt";12import type { PairingTransport } from "./pairing-handshake";1314function memoryPair(): [PairingTransport, PairingTransport] {15 const a: Uint8Array[] = [];16 const b: Uint8Array[] = [];17 const ar: Array<(v: Uint8Array) => void> = [];18 const br: Array<(v: Uint8Array) => void> = [];19 function make(20 inbox: Uint8Array[],21 outbox: Uint8Array[],22 readers: Array<(v: Uint8Array) => void>,23 outReaders: Array<(v: Uint8Array) => void>,24 ): PairingTransport {25 return {26 async send(msg) {27 const reader = outReaders.shift();28 if (reader) reader(msg);29 else outbox.push(msg);30 },31 receive() {32 const msg = inbox.shift();33 if (msg) return Promise.resolve(msg);34 return new Promise((resolve) => readers.push(resolve));35 },36 };37 }38 return [make(a, b, ar, br), make(b, a, br, ar)];39}4041function identity(name: string) {42 return {43 deviceId: `${name}-id`,44 deviceName: name,45 deviceType: "extension" as const,46 longTermPublicKeyHex: "ab".repeat(32),47 signingPublicKeyHex: "cd".repeat(32),48 };49}5051describe("vault adoption sub-protocol", () => {52 it("T-A10: completes ADOPT_REQUEST through mutual completion acknowledgements", async () => {53 const [receiverChannel, sourceChannel] = memoryPair();54 const rootKeyHex = "11".repeat(32);55 const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));56 const sourceUpsert = vi.fn();57 const receiverUpsert = vi.fn();58 const pairState = {59 rendezvousSecretHex: "42".repeat(32),60 relayUrl: "wss://relay.example.test",61 };62 const receiverDeps: AdoptionReceiverDeps = {63 deviceName: "Receiver",64 generateDeviceId: () => "receiver-ephemeral",65 confirmSas: vi.fn(async () => true),66 createVaultFromRootKey: vi.fn(async () => ({ vaultId })),67 deriveLocalIdentity: vi.fn(async () => identity("receiver")),68 upsertPairedDevice: receiverUpsert,69 applyLicense: vi.fn(),70 pairState,71 onError: vi.fn(),72 };73 const sourceDeps: AdoptionSourceDeps = {74 sourceIdentity: identity("source"),75 rootKeyHex: vi.fn(async () => rootKeyHex),76 vaultId: vi.fn(async () => vaultId),77 license: vi.fn(async () => "PPK-test"),78 onConsent: vi.fn(async () => true),79 upsertPairedDevice: sourceUpsert,80 pairState,81 };8283 await Promise.all([84 requestAdoption(receiverChannel, receiverDeps),85 (async () => {86 const request = JSON.parse(new TextDecoder().decode(await sourceChannel.receive())) as AdoptRequest;87 await grantAdoption(sourceChannel, request, sourceDeps);88 })(),89 ]);9091 expect(receiverDeps.createVaultFromRootKey).toHaveBeenCalledWith(rootKeyHex);92 expect(receiverUpsert).toHaveBeenCalledWith(expect.objectContaining({93 deviceId: "source-id",94 ...pairState,95 }));96 expect(sourceUpsert).toHaveBeenCalledWith(expect.objectContaining({97 deviceId: "receiver-id",98 ...pairState,99 }));100 expect(receiverDeps.applyLicense).toHaveBeenCalledWith("PPK-test");101 });102103 it("T-A10b: rejects without creating or pairing when neither peer has Pro", async () => {104 const [receiverChannel, sourceChannel] = memoryPair();105 const rootKeyHex = "12".repeat(32);106 const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));107 const createVault = vi.fn();108 const receiverUpsert = vi.fn();109 const sourceUpsert = vi.fn();110 const onError = vi.fn();111112 const receiver = requestAdoption(receiverChannel, {113 deviceName: "Free receiver",114 generateDeviceId: () => "free-receiver",115 confirmSas: vi.fn(async () => true),116 license: vi.fn(async () => undefined),117 createVaultFromRootKey: createVault,118 deriveLocalIdentity: vi.fn(),119 upsertPairedDevice: receiverUpsert,120 onError,121 });122 const request = JSON.parse(123 new TextDecoder().decode(await sourceChannel.receive()),124 ) as AdoptRequest;125 const source = grantAdoption(sourceChannel, request, {126 sourceIdentity: identity("free-source"),127 rootKeyHex: vi.fn(async () => rootKeyHex),128 vaultId: vi.fn(async () => vaultId),129 license: vi.fn(async () => undefined),130 onConsent: vi.fn(async () => true),131 upsertPairedDevice: sourceUpsert,132 });133134 await expect(receiver).resolves.toBeNull();135 await expect(source).rejects.toThrow("ADOPT_PRO_REQUIRED");136 expect(onError).toHaveBeenCalledWith("ADOPT_PRO_REQUIRED");137 expect(createVault).not.toHaveBeenCalled();138 expect(receiverUpsert).not.toHaveBeenCalled();139 expect(sourceUpsert).not.toHaveBeenCalled();140 });141142 it("T-A10c: accepts and transfers a Pro license offered by the receiver", async () => {143 const [receiverChannel, sourceChannel] = memoryPair();144 const rootKeyHex = "13".repeat(32);145 const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));146 let sourceLicense: string | undefined;147 const sourceApply = vi.fn(async (license: string) => {148 sourceLicense = license;149 });150151 const receiver = requestAdoption(receiverChannel, {152 deviceName: "Pro receiver",153 generateDeviceId: () => "pro-receiver",154 confirmSas: vi.fn(async () => true),155 license: vi.fn(async () => "PPK-receiver"),156 createVaultFromRootKey: vi.fn(async () => ({ vaultId })),157 deriveLocalIdentity: vi.fn(async () => identity("receiver")),158 upsertPairedDevice: vi.fn(),159 applyLicense: vi.fn(),160 onError: vi.fn(),161 });162 const request = JSON.parse(163 new TextDecoder().decode(await sourceChannel.receive()),164 ) as AdoptRequest;165 const source = grantAdoption(sourceChannel, request, {166 sourceIdentity: identity("free-source"),167 rootKeyHex: vi.fn(async () => rootKeyHex),168 vaultId: vi.fn(async () => vaultId),169 license: vi.fn(async () => sourceLicense),170 applyLicense: sourceApply,171 onConsent: vi.fn(async () => true),172 upsertPairedDevice: vi.fn(),173 });174175 await expect(Promise.all([receiver, source])).resolves.toEqual([176 { device: expect.objectContaining({ deviceId: "free-source-id" }), snapshotCapable: true },177 expect.objectContaining({ deviceId: "receiver-id" }),178 ]);179 expect(sourceApply).toHaveBeenCalledWith("PPK-receiver");180 });181182 it("T-A11: SAS reject aborts before ADOPT_GRANT", async () => {183 const [receiverChannel, sourceChannel] = memoryPair();184 const rootKeyHex = vi.fn(async () => "11".repeat(32));185 const createVaultFromRootKey = vi.fn();186 const receiver = requestAdoption(receiverChannel, {187 deviceName: "Receiver",188 generateDeviceId: () => "receiver-ephemeral",189 confirmSas: vi.fn(async () => false),190 createVaultFromRootKey,191 deriveLocalIdentity: vi.fn(),192 upsertPairedDevice: vi.fn(),193 onError: vi.fn(),194 });195 const request = JSON.parse(196 new TextDecoder().decode(await sourceChannel.receive()),197 ) as AdoptRequest;198 const source = grantAdoption(sourceChannel, request, {199 sourceIdentity: identity("source"),200 rootKeyHex,201 vaultId: vi.fn(),202 onConsent: vi.fn(async () => true),203 upsertPairedDevice: vi.fn(),204 });205206 await expect(receiver).resolves.toBeNull();207 await expect(source).resolves.toBeNull();208 expect(rootKeyHex).not.toHaveBeenCalled();209 expect(createVaultFromRootKey).not.toHaveBeenCalled();210 });211212 it("T-A12: consent reject aborts before ADOPT_GRANT and receiver remains vault-less", async () => {213 const [receiverChannel, sourceChannel] = memoryPair();214 const createVaultFromRootKey = vi.fn();215 const errors: string[] = [];216 const receiver = requestAdoption(receiverChannel, {217 deviceName: "Receiver",218 generateDeviceId: () => "receiver-ephemeral",219 confirmSas: vi.fn(async () => true),220 createVaultFromRootKey,221 deriveLocalIdentity: vi.fn(),222 upsertPairedDevice: vi.fn(),223 onError: (err) => errors.push(err),224 });225 const request = JSON.parse(new TextDecoder().decode(await sourceChannel.receive())) as AdoptRequest;226 await grantAdoption(sourceChannel, request, {227 sourceIdentity: identity("source"),228 rootKeyHex: vi.fn(),229 vaultId: vi.fn(),230 onConsent: vi.fn(async () => false),231 upsertPairedDevice: vi.fn(),232 });233 await Promise.race([receiver, Promise.resolve()]);234235 expect(createVaultFromRootKey).not.toHaveBeenCalled();236 expect(errors).toEqual([]);237 });238239 it("T-A13: invalid root_key_hex aborts gracefully", async () => {240 const [receiverChannel, sourceChannel] = memoryPair();241 const onError = vi.fn();242 const createVaultFromRootKey = vi.fn();243 const receiver = requestAdoption(receiverChannel, {244 deviceName: "Receiver",245 generateDeviceId: () => "receiver-ephemeral",246 confirmSas: vi.fn(async () => true),247 createVaultFromRootKey,248 deriveLocalIdentity: vi.fn(),249 upsertPairedDevice: vi.fn(),250 onError,251 });252 await sourceChannel.receive();253 await sendFrame(sourceChannel, {254 type: "ADOPT_GRANT",255 root_key_hex: "bad",256 vault_id: "00".repeat(32),257 source: { device_id: "source", device_name: "Source", device_type: "extension", long_term_public_key_hex: "ab".repeat(32) },258 } as never);259 await receiver;260261 expect(onError).toHaveBeenCalledWith("ADOPT_UNEXPECTED_FRAME");262 expect(createVaultFromRootKey).not.toHaveBeenCalled();263 });264265 it("T-A14: vault_id mismatch aborts before vault creation", async () => {266 const [receiverChannel, sourceChannel] = memoryPair();267 const onError = vi.fn();268 const createVaultFromRootKey = vi.fn();269 const receiver = requestAdoption(receiverChannel, {270 deviceName: "Receiver",271 generateDeviceId: () => "receiver-ephemeral",272 confirmSas: vi.fn(async () => true),273 createVaultFromRootKey,274 deriveLocalIdentity: vi.fn(),275 upsertPairedDevice: vi.fn(),276 onError,277 });278 await sourceChannel.receive();279 await sendFrame(sourceChannel, {280 type: "ADOPT_GRANT",281 root_key_hex: "22".repeat(32),282 vault_id: "00".repeat(32),283 source: { device_id: "source", device_name: "Source", device_type: "extension", long_term_public_key_hex: "ab".repeat(32) },284 });285 await receiver;286287 expect(onError).toHaveBeenCalledWith("ADOPT_VAULT_ID_MISMATCH");288 expect(createVaultFromRootKey).not.toHaveBeenCalled();289 });290291 it("T-A15: source does not persist receiver until ADOPT_IDENTITY arrives", async () => {292 const [receiverChannel, sourceChannel] = memoryPair();293 const upsert = vi.fn();294 const promise = grantAdoption(sourceChannel, {295 type: "ADOPT_REQUEST",296 device_id: "receiver-ephemeral",297 device_name: "Receiver",298 device_type: "extension",299 }, {300 sourceIdentity: identity("source"),301 rootKeyHex: vi.fn(async () => "33".repeat(32)),302 vaultId: vi.fn(async () => await computeVaultId(Uint8Array.from(Buffer.from("33".repeat(32), "hex")))),303 license: vi.fn(async () => "PPK-test"),304 onConsent: vi.fn(async () => true),305 upsertPairedDevice: upsert,306 });307 await sendFrame(receiverChannel, {308 type: "ADOPT_CONFIRM",309 confirmed: true,310 });311312 await Promise.race([promise, Promise.resolve()]);313 expect(upsert).not.toHaveBeenCalled();314 });315316 it("T-A16: receiver does not persist source without ADOPT_COMPLETE", async () => {317 const [receiverChannel, sourceChannel] = memoryPair();318 const rootKeyHex = "66".repeat(32);319 const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));320 const receiverUpsert = vi.fn();321 const onError = vi.fn();322 const receiver = requestAdoption(receiverChannel, {323 deviceName: "Receiver",324 generateDeviceId: () => "receiver-ephemeral",325 confirmSas: vi.fn(async () => true),326 license: vi.fn(async () => "PPK-local"),327 createVaultFromRootKey: vi.fn(async () => ({ vaultId })),328 deriveLocalIdentity: vi.fn(async () => identity("receiver")),329 upsertPairedDevice: receiverUpsert,330 onError,331 });332333 await sourceChannel.receive(); 334 await sourceChannel.receive(); 335 await sendFrame(sourceChannel, {336 type: "ADOPT_GRANT",337 root_key_hex: rootKeyHex,338 vault_id: vaultId,339 source: {340 device_id: "source-id",341 device_name: "Source",342 device_type: "extension",343 long_term_public_key_hex: "ab".repeat(32),344 },345 });346 await sourceChannel.receive(); 347 await sendFrame(sourceChannel, { type: "ADOPT_CONFIRM", confirmed: true });348349 await expect(receiver).resolves.toBeNull();350 expect(onError).toHaveBeenCalledWith("ADOPT_COMPLETION_MISSING");351 expect(receiverUpsert).not.toHaveBeenCalled();352 });353354 it("T-A17: receiver safety policy runs before vault creation", async () => {355 const [receiverChannel, sourceChannel] = memoryPair();356 const rootKeyHex = "77".repeat(32);357 const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));358 const createVaultFromRootKey = vi.fn();359 const receiver = requestAdoption(receiverChannel, {360 deviceName: "Receiver",361 generateDeviceId: () => "receiver-ephemeral",362 confirmSas: vi.fn(async () => true),363 license: vi.fn(async () => "PPK-local"),364 validateSource: vi.fn(async () => {365 throw new Error("JOIN_POPULATED_VAULT_BLOCKED");366 }),367 createVaultFromRootKey,368 deriveLocalIdentity: vi.fn(),369 upsertPairedDevice: vi.fn(),370 onError: vi.fn(),371 });372373 await sourceChannel.receive(); 374 await sourceChannel.receive(); 375 await sendFrame(sourceChannel, {376 type: "ADOPT_GRANT",377 root_key_hex: rootKeyHex,378 vault_id: vaultId,379 source: {380 device_id: "new-source",381 device_name: "Source",382 device_type: "extension",383 long_term_public_key_hex: "ab".repeat(32),384 },385 });386387 await expect(receiver).rejects.toThrow("JOIN_POPULATED_VAULT_BLOCKED");388 expect(createVaultFromRootKey).not.toHaveBeenCalled();389 });390391 it("T-A31: does not apply a license if vault creation fails", async () => {392 const [receiverChannel, sourceChannel] = memoryPair();393 const rootKeyHex = "44".repeat(32);394 const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));395 const applyLicense = vi.fn();396 const receiver = requestAdoption(receiverChannel, {397 deviceName: "Receiver",398 generateDeviceId: () => "receiver-ephemeral",399 confirmSas: vi.fn(async () => true),400 createVaultFromRootKey: vi.fn(async () => {401 throw new Error("commit_failed");402 }),403 deriveLocalIdentity: vi.fn(),404 upsertPairedDevice: vi.fn(),405 applyLicense,406 onError: vi.fn(),407 });408 await sourceChannel.receive();409 await sendFrame(sourceChannel, {410 type: "ADOPT_GRANT",411 root_key_hex: rootKeyHex,412 vault_id: vaultId,413 license: "PPK-test",414 source: { device_id: "source", device_name: "Source", device_type: "extension", long_term_public_key_hex: "ab".repeat(32) },415 });416417 await expect(receiver).rejects.toThrow("commit_failed");418 expect(applyLicense).not.toHaveBeenCalled();419 });420421 it("T-A32: never creates a vault when the grant vault_id mismatches the root key", async () => {422 const [receiverChannel, sourceChannel] = memoryPair();423 const createVaultFromRootKey = vi.fn();424 const receiver = requestAdoption(receiverChannel, {425 deviceName: "Receiver",426 generateDeviceId: () => "receiver-ephemeral",427 confirmSas: vi.fn(async () => true),428 createVaultFromRootKey,429 deriveLocalIdentity: vi.fn(),430 upsertPairedDevice: vi.fn(),431 applyLicense: vi.fn(),432 onError: vi.fn(),433 });434 await sourceChannel.receive();435 await sendFrame(sourceChannel, {436 type: "ADOPT_GRANT",437 root_key_hex: "55".repeat(32),438 vault_id: "99".repeat(32),439 license: "PPK-test",440 source: { device_id: "source", device_name: "Source", device_type: "extension", long_term_public_key_hex: "ab".repeat(32) },441 });442443 await receiver;444 expect(createVaultFromRootKey).not.toHaveBeenCalled();445 });446});447448describe("snapshot_capable echo on ADOPT_GRANT", () => {449 function validGrant() {450 return {451 type: "ADOPT_GRANT" as const,452 root_key_hex: "11".repeat(32),453 vault_id: "22".repeat(32),454 license: "PPK-test",455 source: {456 device_id: "src-id",457 device_name: "Source",458 device_type: "extension" as const,459 long_term_public_key_hex: "ab".repeat(32),460 },461 };462 }463464 it("isAdoptGrant accepts a grant without snapshot_capable (pre-C source)", () => {465 expect(isAdoptGrant(validGrant())).toBe(true);466 });467468 it("isAdoptGrant accepts snapshot_capable: true", () => {469 expect(isAdoptGrant({ ...validGrant(), snapshot_capable: true })).toBe(true);470 });471472 it("isAdoptGrant accepts snapshot_capable: false", () => {473 expect(isAdoptGrant({ ...validGrant(), snapshot_capable: false })).toBe(true);474 });475476 it("isAdoptGrant rejects snapshot_capable with wrong type", () => {477 expect(isAdoptGrant({ ...validGrant(), snapshot_capable: "yes" })).toBe(false);478 });479480 it("grantAdoption echoes snapshot_capable: true when request had it", async () => {481 const [receiverChannel, sourceChannel] = memoryPair();482 const rootKeyHex = "33".repeat(32);483 const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));484 const sourceDeps: AdoptionSourceDeps = {485 sourceIdentity: identity("source"),486 rootKeyHex: vi.fn(async () => rootKeyHex),487 vaultId: vi.fn(async () => vaultId),488 license: vi.fn(async () => "PPK-test"),489 onConsent: vi.fn(async () => true),490 upsertPairedDevice: vi.fn(),491 };492493 494 const grantPromise = grantAdoption(sourceChannel, {495 type: "ADOPT_REQUEST",496 device_id: "recv-id",497 device_name: "Receiver",498 device_type: "extension",499 snapshot_capable: true,500 }, sourceDeps);501502 503 await sendFrame(receiverChannel, { type: "ADOPT_CONFIRM", confirmed: true });504 const grantBytes = await receiverChannel.receive();505 const grantFrame = JSON.parse(new TextDecoder().decode(grantBytes));506 expect(grantFrame.type).toBe("ADOPT_GRANT");507 expect(grantFrame.snapshot_capable).toBe(true);508509 510 await sendFrame(receiverChannel, {511 type: "ADOPT_IDENTITY",512 device_id: "recv-id",513 device_name: "Receiver",514 device_type: "extension",515 long_term_public_key_hex: "ab".repeat(32),516 });517 await receiverChannel.receive(); 518 await sendFrame(receiverChannel, { type: "ADOPT_APPLIED" });519 await grantPromise;520 });521522 it("grantAdoption sets snapshot_capable: false when request lacked it", async () => {523 const [receiverChannel, sourceChannel] = memoryPair();524 const rootKeyHex = "44".repeat(32);525 const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));526 const sourceDeps: AdoptionSourceDeps = {527 sourceIdentity: identity("source"),528 rootKeyHex: vi.fn(async () => rootKeyHex),529 vaultId: vi.fn(async () => vaultId),530 license: vi.fn(async () => "PPK-test"),531 onConsent: vi.fn(async () => true),532 upsertPairedDevice: vi.fn(),533 };534535 const grantPromise = grantAdoption(sourceChannel, {536 type: "ADOPT_REQUEST",537 device_id: "recv-id",538 device_name: "Receiver",539 device_type: "extension",540 541 }, sourceDeps);542543 await sendFrame(receiverChannel, { type: "ADOPT_CONFIRM", confirmed: true });544 const grantBytes = await receiverChannel.receive();545 const grantFrame = JSON.parse(new TextDecoder().decode(grantBytes));546 expect(grantFrame.type).toBe("ADOPT_GRANT");547 expect(grantFrame.snapshot_capable).toBe(false);548549 await sendFrame(receiverChannel, {550 type: "ADOPT_IDENTITY",551 device_id: "recv-id",552 device_name: "Receiver",553 device_type: "extension",554 long_term_public_key_hex: "ab".repeat(32),555 });556 await receiverChannel.receive(); 557 await sendFrame(receiverChannel, { type: "ADOPT_APPLIED" });558 await grantPromise;559 });560561 it("requestAdoption returns snapshotCapable: true when grant echoes it", async () => {562 const [receiverChannel, sourceChannel] = memoryPair();563 const rootKeyHex = "55".repeat(32);564 const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));565 const deps: AdoptionReceiverDeps = {566 deviceName: "recv",567 generateDeviceId: () => "recv-id",568 async confirmSas() { return true; },569 async createVaultFromRootKey() { return { vaultId }; },570 async deriveLocalIdentity() {571 return { deviceId: "recv-id", deviceName: "recv", deviceType: "extension" as const, longTermPublicKeyHex: "ab".repeat(32) };572 },573 async upsertPairedDevice() {},574 onError() {},575 };576577 const receiverPromise = requestAdoption(receiverChannel, deps);578 579 580 await sourceChannel.receive(); 581 await sourceChannel.receive(); 582 await sendFrame(sourceChannel, {583 type: "ADOPT_GRANT",584 root_key_hex: rootKeyHex,585 vault_id: vaultId,586 license: "PPK-test",587 source: { device_id: "src-id", device_name: "Source", device_type: "extension", long_term_public_key_hex: "ab".repeat(32) },588 snapshot_capable: true,589 });590 await sourceChannel.receive(); 591 await sendFrame(sourceChannel, { type: "ADOPT_COMPLETE" });592 await sourceChannel.receive(); 593594 const result = await receiverPromise;595 expect(result).not.toBeNull();596 expect(result!.snapshotCapable).toBe(true);597 });598599 it("requestAdoption returns snapshotCapable: false when grant lacks the field", async () => {600 const [receiverChannel, sourceChannel] = memoryPair();601 const rootKeyHex = "66".repeat(32);602 const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));603 const deps: AdoptionReceiverDeps = {604 deviceName: "recv",605 generateDeviceId: () => "recv-id",606 async confirmSas() { return true; },607 async createVaultFromRootKey() { return { vaultId }; },608 async deriveLocalIdentity() {609 return { deviceId: "recv-id", deviceName: "recv", deviceType: "extension" as const, longTermPublicKeyHex: "ab".repeat(32) };610 },611 async upsertPairedDevice() {},612 onError() {},613 };614615 const receiverPromise = requestAdoption(receiverChannel, deps);616 await sourceChannel.receive(); 617 await sourceChannel.receive(); 618 await sendFrame(sourceChannel, {619 type: "ADOPT_GRANT",620 root_key_hex: rootKeyHex,621 vault_id: vaultId,622 license: "PPK-test",623 source: { device_id: "src-id", device_name: "Source", device_type: "extension", long_term_public_key_hex: "ab".repeat(32) },624 625 });626 await sourceChannel.receive(); 627 await sendFrame(sourceChannel, { type: "ADOPT_COMPLETE" });628 await sourceChannel.receive(); 629630 const result = await receiverPromise;631 expect(result).not.toBeNull();632 expect(result!.snapshotCapable).toBe(false);633 });634});