import { describe, expect, it, vi } from "vitest"; import { createMemoryToolcraftBinaryAssetRepository } from "../source-assets/repository/memory-binary-asset-repository"; import { encodeToolcraftModelDocument } from "./canonical/model-document-codec"; import { digestToolcraftModelDocument } from "./canonical/model-document-digest"; import { createValidModelDocument } from "./canonical/model-document-test-support"; import { TOOLCRAFT_PRODUCTION_MODEL_FORMAT_ADAPTER_REGISTRY } from "./formats/production-model-format-registry"; import { createToolcraftModelDocumentResourceRef, createToolcraftModelRepairPlanResourceRef, } from "./model-source-asset-handler-resources"; import { createToolcraftModelSourceBundleDescriptorResource } from "./model-source-bundle-codec"; import { createToolcraftModelSourceBundleSnapshot, createToolcraftModelSourceBundleSnapshotFromExtractedPackage, } from "./model-source-bundle"; import { createModelSourceResourceRef, digestModelSourceBytes, } from "./model-source-digest"; import { hydrateToolcraftPersistedModels } from "./model-persistence-hydration"; import { createHydrationAnalysis as analysis, createHydrationModelAsset as modelAsset, createHydrationStateHarness as stateHarness, createHydrationWorkerAnalysis as workerAnalysis, createHydrationWorkerClient as createWorkerClient, } from "./model-persistence-hydration-test-support"; describe("persisted model hydration", () => { it("publishes a repository-backed model only after its document digest verifies", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const document = createValidModelDocument(); const bytes = encodeToolcraftModelDocument(document); const documentRef = createToolcraftModelDocumentResourceRef( digestToolcraftModelDocument(document), ); const lease = await repository.beginLease("existing-model"); await lease.put(documentRef, bytes, { contentType: "application/vnd.toolcraft.model-document", durable: false, }); await lease.commit(); const harness = stateHarness(modelAsset(documentRef)); const workerClient = createWorkerClient(); await hydrateToolcraftPersistedModels({ ...harness, repository, workerClient, }).promise; expect(harness.getState().mediaAssets[0]).toMatchObject({ activeDocumentRef: documentRef, lifecycle: "clean", }); expect(workerClient.importModel).not.toHaveBeenCalled(); }); it("keeps a missing persisted model as typed unavailable state", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const harness = stateHarness( modelAsset(createToolcraftModelDocumentResourceRef( `sha256:${"1".repeat(64)}`, )), ); await hydrateToolcraftPersistedModels({ ...harness, repository, workerClient: createWorkerClient(), }).promise; expect(harness.getState().mediaAssets[0]).toMatchObject({ lastRepairError: { category: "resource-unavailable" }, lifecycle: "unavailable", }); }); it("does not restore a repairable model from a forged repair-plan resource", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const document = createValidModelDocument(); const documentRef = createToolcraftModelDocumentResourceRef( digestToolcraftModelDocument(document), ); const repairPlanRef = createToolcraftModelRepairPlanResourceRef({ envelopeDigest: `sha256:${"3".repeat(64)}`, planDigest: `sha256:${"4".repeat(64)}`, }); const lease = await repository.beginLease("forged-repair-plan"); await lease.put(documentRef, encodeToolcraftModelDocument(document), { contentType: "application/vnd.toolcraft.model-document", durable: false, }); await lease.put(repairPlanRef, new Uint8Array([1, 2, 3]), { contentType: "application/vnd.toolcraft.model-repair-plan.v1", durable: false, }); await lease.commit(); const repairableAnalysis = { ...analysis(), outcome: "repairable" as const, repairPlanRef, }; const harness = stateHarness(modelAsset(documentRef, { analysis: repairableAnalysis, originalAnalysis: repairableAnalysis, })); await hydrateToolcraftPersistedModels({ ...harness, repository, workerClient: createWorkerClient(), }).promise; expect(harness.getState().mediaAssets[0]).toMatchObject({ lastRepairError: { category: "resource-unavailable" }, lifecycle: "unavailable", }); }); it("regenerates a missing derived document from the durable source bundle", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const source = new File([new Uint8Array([1, 2, 3, 4])], "cube.glb", { type: "model/gltf-binary", }); const snapshot = await createToolcraftModelSourceBundleSnapshot([source], { registry: TOOLCRAFT_PRODUCTION_MODEL_FORMAT_ADAPTER_REGISTRY, }); const descriptor = createToolcraftModelSourceBundleDescriptorResource( snapshot.bundle, ); const sourceLease = await repository.beginLease("persist-source"); for (const metadata of snapshot.bundle.sourceFiles) { const transfer = snapshot.transfer.sourceFiles.find( ({ path }) => path === metadata.path, )!; await sourceLease.put( metadata.resourceRef, new Uint8Array(transfer.bytes), { contentType: metadata.mimeType, durable: true }, ); } await sourceLease.put( descriptor.sourceBundleRef, descriptor.bytes, descriptor.stageOptions, ); await sourceLease.commit(); const document = createValidModelDocument(); const documentBytes = encodeToolcraftModelDocument(document); const documentDigest = digestToolcraftModelDocument(document); const importModel = vi.fn(async () => ({ generation: 1, jobId: "model-hydration-decode-model-1", kind: "result" as const, result: { analysis: workerAnalysis(), appearanceResources: [], canonicalDocument: documentBytes.slice().buffer, canonicalDocumentDigest: documentDigest, operation: "decode-and-analyze" as const, receiptDigest: `sha256:${"3".repeat(64)}`, }, })); const missingRef = createToolcraftModelDocumentResourceRef( `sha256:${"4".repeat(64)}`, ); const harness = stateHarness(modelAsset(missingRef, { sourceBundleDigest: snapshot.bundle.aggregateDigest, sourceBundleRef: descriptor.sourceBundleRef, })); await hydrateToolcraftPersistedModels({ ...harness, repository, workerClient: createWorkerClient(importModel), }).promise; const regeneratedRef = createToolcraftModelDocumentResourceRef(documentDigest); expect(harness.getState().mediaAssets[0]).toMatchObject({ activeDocumentRef: regeneratedRef, lifecycle: "clean", originalDocumentRef: regeneratedRef, }); await expect(repository.get(regeneratedRef)).resolves.toMatchObject({ contentType: "application/vnd.toolcraft.model-document", }); }); it("re-extracts a durable ZIP and verifies its manifest before regeneration", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const archiveBytes = new Uint8Array([9, 8, 7, 6]); const archiveDigest = digestModelSourceBytes(archiveBytes); const archiveRef = createModelSourceResourceRef( archiveDigest, "application/zip", ); const rootBytes = new Uint8Array([0x67, 0x6c, 0x54, 0x46]); const rootDigest = digestModelSourceBytes(rootBytes); const extractedEntry = Object.freeze({ bytes: rootBytes.slice().buffer, compressedBytes: rootBytes.byteLength, contentDigest: rootDigest, mimeType: "model/gltf-binary", path: "models/cube.glb", uncompressedBytes: rootBytes.byteLength, }); const snapshot = await createToolcraftModelSourceBundleSnapshotFromExtractedPackage( [extractedEntry], { archive: { byteLength: archiveBytes.byteLength, contentDigest: archiveDigest, displayName: "model.zip", mimeType: "application/zip", resourceRef: archiveRef, }, kind: "zip", }, { registry: TOOLCRAFT_PRODUCTION_MODEL_FORMAT_ADAPTER_REGISTRY }, ); const descriptor = createToolcraftModelSourceBundleDescriptorResource( snapshot.bundle, ); const sourceLease = await repository.beginLease("persist-zip-source"); await sourceLease.put(archiveRef, archiveBytes, { contentType: "application/zip", durable: true, }); await sourceLease.put( descriptor.sourceBundleRef, descriptor.bytes, descriptor.stageOptions, ); await sourceLease.commit(); const document = createValidModelDocument(); const documentBytes = encodeToolcraftModelDocument(document); const documentDigest = digestToolcraftModelDocument(document); const importModel = vi.fn(async (request) => { expect(new Uint8Array(request.bundle.sourceFiles[0]!.bytes)).toEqual( rootBytes, ); return { generation: 1, jobId: request.jobId, kind: "result" as const, result: { analysis: workerAnalysis(), appearanceResources: [], canonicalDocument: documentBytes.slice().buffer, canonicalDocumentDigest: documentDigest, operation: "decode-and-analyze" as const, receiptDigest: `sha256:${"3".repeat(64)}`, }, }; }); const extractModelPackage = vi.fn(async (request) => ({ generation: 1, jobId: request.jobId, kind: "package-result" as const, result: { archiveDigest: request.archiveDigest, entries: [{ ...extractedEntry, bytes: rootBytes.slice().buffer }], operation: "extract-model-package" as const, receiptDigest: `sha256:${"5".repeat(64)}`, }, })); const missingRef = createToolcraftModelDocumentResourceRef( `sha256:${"4".repeat(64)}`, ); const harness = stateHarness(modelAsset(missingRef, { sourceBundleDigest: snapshot.bundle.aggregateDigest, sourceBundleRef: descriptor.sourceBundleRef, })); await hydrateToolcraftPersistedModels({ ...harness, repository, workerClient: createWorkerClient(importModel, extractModelPackage), }).promise; expect(extractModelPackage).toHaveBeenCalledTimes(1); expect(importModel).toHaveBeenCalledTimes(1); expect(harness.getState().mediaAssets[0]).toMatchObject({ activeDocumentRef: createToolcraftModelDocumentResourceRef(documentDigest), lifecycle: "clean", }); }); it("rejects a fresh ZIP extraction whose path or digest differs from the descriptor", async () => { const repository = createMemoryToolcraftBinaryAssetRepository(); const archiveBytes = new Uint8Array([1, 3, 3, 7]); const archiveDigest = digestModelSourceBytes(archiveBytes); const archiveRef = createModelSourceResourceRef( archiveDigest, "application/zip", ); const rootBytes = new Uint8Array([0x67, 0x6c, 0x54, 0x46]); const rootDigest = digestModelSourceBytes(rootBytes); const entry = { bytes: rootBytes.slice().buffer, compressedBytes: 4, contentDigest: rootDigest, mimeType: "model/gltf-binary", path: "cube.glb", uncompressedBytes: 4, }; const snapshot = await createToolcraftModelSourceBundleSnapshotFromExtractedPackage( [entry], { archive: { byteLength: archiveBytes.byteLength, contentDigest: archiveDigest, displayName: "model.zip", mimeType: "application/zip", resourceRef: archiveRef, }, kind: "zip", }, { registry: TOOLCRAFT_PRODUCTION_MODEL_FORMAT_ADAPTER_REGISTRY }, ); const descriptor = createToolcraftModelSourceBundleDescriptorResource( snapshot.bundle, ); const lease = await repository.beginLease("forged-zip-extraction"); await lease.put(archiveRef, archiveBytes, { contentType: "application/zip", durable: true, }); await lease.put( descriptor.sourceBundleRef, descriptor.bytes, descriptor.stageOptions, ); await lease.commit(); const extractModelPackage = vi.fn(async (request) => ({ generation: 1, jobId: request.jobId, kind: "package-result" as const, result: { archiveDigest: request.archiveDigest, entries: [{ ...entry, path: "forged.glb" }], operation: "extract-model-package" as const, receiptDigest: `sha256:${"5".repeat(64)}`, }, })); const importModel = vi.fn(); const harness = stateHarness(modelAsset( createToolcraftModelDocumentResourceRef(`sha256:${"4".repeat(64)}`), { sourceBundleDigest: snapshot.bundle.aggregateDigest, sourceBundleRef: descriptor.sourceBundleRef, }, )); await hydrateToolcraftPersistedModels({ ...harness, repository, workerClient: createWorkerClient(importModel, extractModelPackage), }).promise; expect(importModel).not.toHaveBeenCalled(); expect(harness.getState().mediaAssets[0]).toMatchObject({ lastRepairError: { category: "resource-unavailable" }, lifecycle: "unavailable", }); }); });