Files
Endoleap/tests/headless/browser-storage-cases.mjs
T

888 lines
172 KiB
JavaScript

import {depotFilesCase,depotFilesUpgradeCase,depotAdoptionRaceCase} from './depot-files-browser-cases.mjs';
import {assetAdoptionStorageCase} from './asset-adoption-browser-cases.mjs';
import {assetDepotStorageCase} from './asset-depot-browser-cases.mjs';
import {createBrowserImageDecoder} from '../../src/lib/canvas-runtime/browser-images.ts';
import {decodeRecovery} from '../../src/lib/canvas-runtime/recovery.ts';
import {createBrowserDigests} from '../../src/lib/canvas-runtime/browser-digests.ts';
import {sealRecoveryImage} from '../../src/lib/canvas-runtime/recovery-encoding.ts';
import {planDuplicateAsync} from '../../src/lib/canvas-runtime/duplicate.ts';
import {replayHistoryEvidence,sameHistory} from '../../src/lib/canvas-runtime/history-replay.ts';
import { createBrowserStorage } from '../../src/lib/canvas-runtime/storage.ts';
import { openProjectEngine } from '../../src/lib/canvas-runtime/engine.ts';
import { createDocument, createRegistry, builtinComponents, applyChanges, canvasTransform, applyHistoryTransition } from '../../src/lib/canvas-core/index.ts';
import { createFoundryRuns } from '../../src/lib/canvas-service/runs.ts';
import { createFoundryAssets } from '../../src/lib/canvas-service/assets.ts';
import { createFoundryPersistence } from '../../src/lib/canvas-service/foundry.ts';
import { createResourceView } from '../../src/lib/canvas-bindings/resource-view.ts';
import { createScopedLoader,loadQueryKey } from '../../src/lib/canvas-runtime/loader.ts';
async function scopedNetworkLoaderCase(assert) {
const scope={accountId:'loader-account',workspaceId:'loader-workspace',projectId:'loader-project',canvasId:'loader-canvas',viewKey:'canvas',format:'fixture-ndjson-v1',queryFingerprint:'all',source:'network-fixture',authEpoch:1,navigationGeneration:1};
async function run(fail) {
const key=crypto.randomUUID();let signal,first;const initial=new Promise(resolve=>{first=resolve;});
const loader=createScopedLoader({current:()=>true,async *load(query,abort){
signal=abort;const response=await fetch(`/loader-stream?key=${key}`,{signal:abort}),reader=response.body.getReader(),decoder=new TextDecoder();let pending='',sequence=0;
try{while(true){const next=await reader.read();pending+=decoder.decode(next.value??new Uint8Array(),{stream:!next.done});let end;while((end=pending.indexOf('\n'))>=0){const line=pending.slice(0,end);pending=pending.slice(end+1);if(!line)continue;const value=JSON.parse(line);yield{queryKey:loadQueryKey(query),projectId:'loader-project',snapshotToken:key,serverVersions:{'loader-canvas':1},permissionEpoch:query.authEpoch,sequence:sequence++,coverage:[`part-${value.part}`],complete:value.complete,byteLength:new TextEncoder().encode(line).byteLength,value:Object.freeze(value)};}if(next.done)break;}}finally{await reader.cancel().catch(()=>{});}
}});
const a=loader.acquire(scope,{onUpdate:state=>{if(state.status==='initial-ready')first();}}),b=loader.acquire({...scope,navigationGeneration:2});
const rejected=fail?b.ready.then(()=>{throw Error('invalid tail became complete');},()=>{}):undefined;
try{
await initial;assert(a.getSnapshot().chunks.length===1&&b.getSnapshot().status==='initial-ready','first network chunk waited for the tail');
const cancelled=a.ready.then(()=>{throw Error('cancelled subscriber completed');},error=>assert(error.code==='load_aborted','subscriber cancellation changed'));a.release();await cancelled;assert(!signal.aborted,'one subscriber cancelled the shared fetch');
const response=await(await fetch(`/loader-release?key=${key}${fail?'&fail=1':''}`)).json();assert(response.reads===1,'subscribers fetched the same query twice');
if(fail){await rejected;assert(b.getSnapshot().status==='failed-with-partial'&&b.getSnapshot().chunks.length===1,'invalid network tail discarded or completed the prefix');}
else{const result=await b.ready;assert(result.status==='complete'&&result.chunks.length===2&&result.scope.navigationGeneration===2,'stream completion lost subscriber scope or chunks');assert(a.getSnapshot().status==='cancelled'&&a.getSnapshot().chunks.length===1,'cancelled subscriber observed its peer tail');}
}finally{a.release();b.release();loader.close();}
}
await run(false);await run(true);
}
async function sharedDisplayReadersCase(engine, storage, assert) {
const resource = engine.getSnapshot().document.elements.get('picture').data.assetId, read = storage.readFile, createURL = URL.createObjectURL, revokeURL = URL.revokeObjectURL;
const revision = engine.getSnapshot().rootRevision, errors = []; let reads = 0, created = 0, revoked = 0, first = new Map(), second = new Map();
const a = createResourceView({ resolve: (id, signal) => engine.displayResource(id, { signal }), onChange: sources => { first = sources; }, onError: error => errors.push(error) });
const b = createResourceView({ resolve: (id, signal) => engine.displayResource(id, { signal }), onChange: sources => { second = sources; }, onError: error => errors.push(error) });
const wait = async predicate => { for (let i = 0; i < 100; i++) { if (predicate()) return; await new Promise(resolve => setTimeout(resolve, 5)); } throw Error('shared display did not settle'); };
try {
storage.readFile = (...args) => { reads++; return read(...args); }; URL.createObjectURL = bytes => { created++; return createURL.call(URL, bytes); }; URL.revokeObjectURL = url => { revoked++; return revokeURL.call(URL, url); };
a.sync([['picture', resource]]); await wait(() => first.has('picture')); const url = first.get('picture').url;
b.sync([['other-view', resource]]); await wait(() => second.has('other-view')); assert(second.get('other-view').url === url && reads === 1 && created === 1, 'two views did not share one IndexedDB read and Blob URL');
a.sync([['picture', resource], ['copy', resource]]); await wait(() => first.has('copy')); assert(first.get('copy').url === url && reads === 1 && created === 1 && revoked === 0, 'adding an image reference replaced existing display handles');
a.dispose(); assert(revoked === 0 && (await (await fetch(url)).arrayBuffer()).byteLength > 0, 'closing one view revoked another view image');
assert(engine.getResourcePins().pins.get(resource).has('display'), 'remaining view lost its display pin'); b.dispose(); assert(revoked === 1 && !engine.getResourcePins().pins.get(resource).has('display'), 'last view did not release its URL and display pin');
const fresh = await engine.displayResource(resource); assert(fresh.url !== url && reads === 2 && created === 2, 'released URL was retained as an idle cache'); fresh.release(); assert(revoked === 2, 'fresh handle did not revoke');
assert(errors.length === 0 && engine.getSnapshot().rootRevision === revision, 'display caching changed the business Root or reported a reader failure');
} finally { a.dispose(); b.dispose(); storage.readFile = read; URL.createObjectURL = createURL; URL.revokeObjectURL = revokeURL; }
}
async function defaultImageSizeCase(name, assert) {
const storage = {...createBrowserStorage({ name: `${name}-default-size`, checkpointEvery: 3 })}, images = createBrowserImageDecoder();
const scope = { accountId: 'sizing-account', workspaceId: 'sizing-workspace', projectId: 'sizing-project' }, registry = createRegistry(builtinComponents());
const original = createDocument(scope.projectId, [{ id: 'root', parent: null }], []), receipts = new Map(), writes = [], queries = [], uploads = [];
let server = original, version = 1, next = 0, engine;
const persistence = {
compile(intent, id, snapshot) { return { payload: { intent, id }, canvasIds: intent.canvasIds, expectedVersions: { root: snapshot.runtime.versions.get('root') } }; },
async submit(request) { writes.push(request); const { intent } = JSON.parse(request.body); server = applyChanges(server, intent.changes); receipts.set(request.id, { document: server, versions: new Map([['root', ++version]]) }); return intent.historyEffect ? { outcome: 'unresolved', blocked: true, error: 'lost_size_ack' } : { outcome: 'applied' }; },
async query(request) { queries.push(request); return receipts.has(request.id) ? { outcome: 'applied' } : { outcome: 'unresolved', blocked: true }; },
canonical(request) { return receipts.get(request.id); },
};
const assets = { sourceURL: id => `/asset/${id}`, displayURL: id => `/asset/${id}`, async upload(request, bytes) { uploads.push(request);return { outcome: 'applied', canonical: { id: JSON.parse(request.body).resource_id, bytes: bytes.size, mime_type: bytes.type, width: 4, height: 2, url: '/stored' } }; }, async query() { throw Error('unexpected asset query'); }, accept: (_request, status) => status.canonical };
const options = { scope, registry, document: original, versions: new Map([['root', 1]]), storage, images, assets, persistence, createId: () => `size-${++next}` };
try {
engine = await openProjectEngine(options); const canvas = new OffscreenCanvas(4, 2); canvas.getContext('2d').fillRect(0, 0, 4, 2); const bytes = await canvas.convertToBlob({ type: 'image/png' });
const result = engine.stageFiles([{ name: 'pixel.png', bytes, defaultSize: true }], ([ref], tx) => tx.dispatch({ type: 'element.create', element: { id: 'picture', incarnation: 1, componentId: 'image', dataVersion: 1, structure: 'node', ownerCanvasId: 'root', data: { title: 'Picture', text: '', assetId: ref.id, src: ref.src }, geometry: { x: 0, y: 0, width: 260, height: 200, rotation: 0 } } }));
assert(result.status === 'applied', 'default image admission failed'); await engine.settle();
assert(engine.getSnapshot().document.elements.get('picture').geometry.height === 130 && engine.getSnapshot().history.undo.length === 1, 'decode did not enrich the original creation');
const request = writes.find(request => JSON.parse(request.body).intent.historyEffect); assert(!!request, 'size change was never submitted'); const body = request.body;
await engine.close(); const owner = await storage.claim(scope), cut = await storage.recover(owner); assert(cut.formatVersion === 6, 'size effect did not require recovery v6'); decodeRecovery(cut, registry); await owner.release();
engine = await openProjectEngine(options); await engine.settle();
assert(queries.some(query => query.id === request.id && query.body === body), 'lost size receipt did not query the frozen original request');
assert(writes.filter(write => write.id === request.id).length === 1, 'lost size receipt repeated the write');
await sharedDisplayReadersCase(engine, storage, assert);
assert(engine.editor.history.undo().status === 'applied', 'sized creation could not undo after reopening'); await engine.settle(); assert(!server.elements.has('picture'), 'undo left the server image behind');
assert(engine.editor.history.redo().status === 'applied', 'sized creation could not redo after reopening'); await engine.settle();
assert(server.elements.get('picture').incarnation === 2 && server.elements.get('picture').geometry.height === 130, 'redo lost size or reused a deleted incarnation'); assert(uploads.length === 1, 'size recovery repeated the upload');
} finally { await engine?.close(); images.close(); storage.close(); indexedDB.deleteDatabase(`${name}-default-size`); }
}
async function imageDecodeWorkerCase(assert){
const NativeWorker=globalThis.Worker;let active=0,peak=0,created=0;globalThis.Worker=class extends NativeWorker{constructor(...args){super(...args);active++;created++;peak=Math.max(peak,active);this.live=true;}terminate(){if(this.live){this.live=false;active--;}super.terminate();}};
const pool=createBrowserImageDecoder();try{const canvas=new OffscreenCanvas(4,2);canvas.getContext('2d').fillRect(0,0,4,2);const bytes=await canvas.convertToBlob({type:'image/png'}),results=await Promise.all([pool.decode(bytes),pool.decode(bytes),pool.decode(bytes)]);assert(results.every(size=>size.width===4&&size.height===2),'real bitmap decoder returned incorrect dimensions');assert(peak===2&&active===0&&created===3,'image worker concurrency/cleanup failed');let code;try{await pool.decode(new Blob(['not an image'],{type:'image/png'}));}catch(error){code=error.code;}assert(code==='image_decode_failed'&&active===0,'invalid image did not fail or leaked a worker');const cancel=new AbortController(),pending=pool.decode(bytes,cancel.signal),cancelled=pending.then(()=>{throw Error('cancelled image returned dimensions');},error=>assert(error.code==='image_decode_aborted','image cancellation code changed'));cancel.abort();await cancelled;assert(active===0,'cancelled image worker leaked');}finally{pool.close();globalThis.Worker=NativeWorker;}
}
async function digestWorkerCase(assert){
const NativeWorker=globalThis.Worker;let active=0,peak=0,created=0;const replies=[];
globalThis.Worker=class extends NativeWorker{constructor(...args){super(...args);created++;active++;peak=Math.max(peak,active);this.live=true;this.addEventListener('message',event=>replies.push(event.data));}terminate(){if(this.live){this.live=false;active--;}super.terminate();}};
const pool=createBrowserDigests();try{
const bytes=new Blob([new Uint8Array(4*1024*1024).fill(97)]),expected=[...new Uint8Array(await crypto.subtle.digest('SHA-256',await bytes.arrayBuffer()))].map(value=>value.toString(16).padStart(2,'0')).join(''),cancel=new AbortController(),a=pool.digest(bytes),b=pool.digest(bytes),queued=pool.digest(bytes,cancel.signal),cancelled=queued.then(()=>{throw Error('queued digest ran');},error=>assert(error.code==='import_aborted','queued digest cancellation changed'));
cancel.abort();await cancelled;const results=await Promise.all([a,b]);assert(results.every(value=>value===expected),'worker digest differs from native SHA256');assert(peak===2&&created===2&&active===0,'worker pool exceeded bounds or leaked a worker');assert(replies.length===2&&replies.every(reply=>reply.version===1&&reply.byteLength===bytes.size),'worker protocol did not actually run');
const late=pool.digest(bytes),closed=late.then(()=>{throw Error('closed worker published');},error=>assert(error.code==='import_aborted','pool close did not cancel work'));await Promise.resolve();pool.close();await closed;assert(active===0,'pool close retained a worker');
}finally{pool.close();globalThis.Worker=NativeWorker;}
}
async function ownedImageStorageCase(name,assert){
const storage=createBrowserStorage({name:`${name}-owned-image`}),scope={accountId:'owner',workspaceId:'workspace',projectId:'project'};let leader;
try{
leader=await storage.claim(scope);const payload={nested:{title:'captured'}},descriptor={id:'captured-file',bytes:new Blob(['captured bytes'])},raw={formatVersion:1,rootRevision:0,localRevision:0,payload};
const first=storage.commit(leader,{image:raw,files:[descriptor]});payload.nested.title='changed';descriptor.id='changed-file';descriptor.bytes=new Blob(['changed bytes']);await first;
assert((await storage.recover(leader)).payload.nested.title==='captured','unowned image changed while checksum was pending');assert(await(await storage.readFile(leader,'captured-file')).text()==='captured bytes','file descriptor changed while checksum was pending');assert(await storage.readFile(leader,'changed-file')===undefined,'late descriptor identity leaked into storage');
const sealed=sealRecoveryImage(1,1,1,{nested:{title:'sealed'}});await storage.commit(leader,{image:sealed});assert((await storage.recover(leader)).payload.nested.title==='sealed','sealed image did not survive IndexedDB/checksum');
const nested={title:'before'},impostor=Object.freeze({formatVersion:1,rootRevision:2,localRevision:1,payload:{nested}}),pending=storage.commit(leader,{image:impostor});nested.title='after';await pending;assert((await storage.recover(leader)).payload.nested.title==='before','a shallow-frozen impostor bypassed the defensive copy');
}finally{await leader?.release();storage.close();indexedDB.deleteDatabase(`${name}-owned-image`);}
}
async function cooperativeCopyCase(assert) {
const registry=createRegistry(builtinComponents()),source=createDocument('copy-project',[{id:'copy-root',parent:null}],Array.from({length:1500},(_,index)=>({id:`source-${index}`,incarnation:1,structure:'node',ownerCanvasId:'copy-root',componentId:'text',dataVersion:1,data:{title:`item ${index}`,text:''},geometry:{x:index,y:0,width:100,height:100,rotation:0}})));
let next=0,timerRan=false;
const timer=setTimeout(()=>{timerRan=true;},0);
try {
const plan=await planDuplicateAsync(source,registry,[{id:'source-0',incarnation:1}],{createId:()=>`copy-${++next}`},{maxStepsPerSlice:512});
assert(timerRan,'copy planning starved a real browser task');assert(plan.changes.length===1,'copy planning changed selection scope');assert(source.elements.size===1500,'copy planning mutated its source');
const controller=new AbortController(),cancel=setTimeout(()=>controller.abort(),0);
try { let code;try { await planDuplicateAsync(source,registry,[{id:'source-0',incarnation:1}],{createId:()=>`unused-${++next}`},{signal:controller.signal,maxStepsPerSlice:128}); } catch(error){code=error.code;}assert(code==='copy_aborted','browser cancellation did not stop planning'); } finally {clearTimeout(cancel);}
} finally {clearTimeout(timer);}
}
async function adoptionConsumerCase(name,assert){
for(const structural of [false,true]){
const root=crypto.randomUUID(),child=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),node=(id,owner=root)=>({id,incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:owner,geometry:{x:0,y:0,width:200,height:100,rotation:0},data:{title:id,text:''}}),original=createDocument(root,[{id:root,parent:null}],[node('a')]),database=`${name}-adoption-consumer-${structural}`,storage=createBrowserStorage({name:database}),calls=[],saved=new Map();let document=original,versions=new Map([[root,1]]),reject=true,engine;
const model=(complete,participants=[...versions.keys()])=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:participants.filter(id=>!document.canvases.has(id)),canvases:[...document.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))});
const transport=async request=>{calls.push(request);if(request.method==='GET')return{status:200,body:{model:model(true)}};const body=JSON.parse(request.body);if(reject)return{status:409,body:{protocol_version:2,request_id:request.requestId,status:'not-applied',terminal:true,error:'version_conflict'}};for(const[id,version]of Object.entries(body.expected_versions))assert(version===(versions.get(id)??0),'adoption consumer lost a version fence');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,(versions.get(id)??0)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false,Object.keys(body.expected_versions))}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map(versions),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})},selected=()=>new Map([...engine.getSnapshot().runtime.ledger.operations].filter(([,record])=>record.phase!=='resolved').map(([id])=>[id,'keep-local']));
const reopen=async()=>{for(const[id,request]of engine.getSnapshot().runtime.ledger.requests){if(saved.has(id))assert(saved.get(id)===request.body,'an earlier recovery request was rewritten');else saved.set(id,request.body);}await engine.close();engine=await openProjectEngine(options);};
try{
engine=await openProjectEngine(options);
if(structural){engine.editor.transaction(tx=>{tx.dispatch({type:'element.create',element:{...node('frame'),componentId:'frame'}});tx.dispatch({type:'canvas.create',canvas:{id:child,parent:{canvasId:root,entryElementId:'frame'}}});tx.dispatch({type:'element.create',element:node('member',child)});});await engine.settle();engine.editor.dispatch({type:'element.data',target:{id:'member',incarnation:1},patch:{title:'discard member edit'}});}
else{engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{title:'one'}},{groupId:'chain'});await engine.settle();engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{title:'two'}},{groupId:'chain'});}
await engine.settle();let session=await engine.prepareRecovery(),first=session.plan(selected());await session.resolve(first);await engine.settle();await reopen();document=applyChanges(document,[{kind:'field',target:{id:'a',incarnation:1},field:'data.title',before:'a',after:'remote 1',beforePresent:true,afterPresent:true}]);versions.set(root,versions.get(root)+1);
session=await engine.prepareRecovery();const decisions=selected();decisions.set(first.operationIds[structural?1:0],'discard-local');if(structural)reject=false;const second=session.plan(decisions);await session.resolve(second);await engine.settle();await reopen();
if(!structural){engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{text:'after adoption'}},{groupId:'chain'});await engine.settle();document=applyChanges(document,[{kind:'field',target:{id:'a',incarnation:1},field:'data.title',before:'remote 1',after:'remote 2',beforePresent:true,afterPresent:true}]);versions.set(root,versions.get(root)+1);session=await engine.prepareRecovery();const third=session.plan(selected());reject=false;await session.resolve(third);await engine.settle();await reopen();assert(engine.getSnapshot().runtime.recoveries.size===3,'nested adoption audit lineage was lost');assert(document.elements.get('a').data.title==='two'&&document.elements.get('a').data.text==='after adoption','post-adoption native edit was lost');assert(engine.editor.history.undo().status==='applied','nested field recovery undo failed');await engine.settle();assert(document.elements.get('a').data.title==='remote 2'&&document.elements.get('a').data.text==='','undo did not restore newest reviewed server baseline');assert(engine.editor.history.redo().status==='applied','nested field recovery redo failed');await engine.settle();assert(calls.filter(call=>call.method==='POST').length===6,'nested field recovery did not batch its replacements');}
else{assert(engine.getSnapshot().runtime.recoveries.size===2&&document.elements.get('member').data.title==='member','structural choice did not survive adoption');assert(engine.editor.history.undo().status==='applied','adopted structure undo failed');await engine.settle();assert(!document.canvases.has(child),'adopted structure undo left a child canvas');assert(engine.editor.history.redo().status==='applied','adopted structure redo failed');await engine.settle();assert(document.elements.get('member').incarnation===2&&document.elements.get('a').data.title==='remote 1','structural redo reused a life or erased remote data');assert(calls.filter(call=>call.method==='POST').length===5,'structural adoption repeated a request');}
await reopen();for(const[id,body]of saved)assert(engine.getSnapshot().runtime.ledger.requests.get(id).body===body,'original frozen request bytes changed after final reopen');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
}
async function adoptionEvidenceCase(name,assert){
const root=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),node={id:'a',incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:root,geometry:{x:0,y:0,width:200,height:100,rotation:0},data:{title:'a',text:''}},original=createDocument(root,[{id:root,parent:null}],[node]),database=`${name}-adoption-evidence`,storage=createBrowserStorage({name:database}),calls=[];let document=original,version=1,reject=true,engine;
const model=complete=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:[],canvases:[...document.canvases.values()],elements:[...document.elements.values()],tombstones:Object.fromEntries(document.tombstones),versions:{[root]:version}});
const transport=async request=>{calls.push(request);if(request.method==='GET')return{status:200,body:{model:model(true)}};const body=JSON.parse(request.body);assert(!request.body.includes('historyReplay'),'local history evidence leaked into wire payload');if(reject)return{status:409,body:{protocol_version:2,request_id:request.requestId,status:'not-applied',terminal:true,error:'version_conflict'}};assert(body.expected_versions[root]===version,'adoption evidence changed the version fence');for(const operation of body.operations)document=applyChanges(document,operation.changes);version++;return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false)}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map([[root,1]]),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);engine.editor.dispatch({type:'element.geometry',target:{id:'a',incarnation:1},patch:{width:250}},{groupId:'adoption'});await engine.settle();const old=[...engine.getSnapshot().runtime.ledger.requests.values()][0],discard=engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{title:'discard me'}},{groupId:'adoption'});engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{title:'kept'}},{groupId:'adoption'});await engine.settle();const session=await engine.prepareRecovery(),selected=new Map(session.pendingOperationIds.map(id=>[id,id===discard.operationIds[0]?'discard-local':'keep-local'])),plan=session.plan(selected);reject=false;await session.resolve(plan);await engine.settle();await engine.close();engine=await openProjectEngine(options);
const snapshot=engine.getSnapshot(),audit=snapshot.runtime.recoveries.get(plan.id),backup=audit.backup.history,before={undo:backup.undo,redo:backup.redo,entries:new Map(backup.entries)},reconstructed=replayHistoryEvidence(before,audit.historyReplay,id=>snapshot.operations.has(id));assert(sameHistory(reconstructed,snapshot.history),'persisted adoption witness did not compose to its history');assert(audit.historyReplay.events.filter(event=>event.kind==='operation').length===2,'discarded contribution became a replay operation');assert(snapshot.runtime.ledger.requests.get(old.id).body===old.body,'adoption evidence changed old request bytes');assert(document.elements.get('a').geometry.width===250&&document.elements.get('a').data.title==='kept','adoption content or geometry changed');const replacementRequests=calls.filter(call=>call.method==='POST'&&plan.operationIds.every(id=>JSON.parse(call.body).operations.some(operation=>operation.id===id)));assert(replacementRequests.length===1,'adopted logical IDs did not share one frozen request');assert(engine.editor.history.undo().status==='applied','geometry evidence broke undo');await engine.settle();assert(document.elements.get('a').geometry.width===200&&document.elements.get('a').data.title==='a','adopted group inverse is incorrect');assert(engine.editor.history.redo().status==='applied','geometry evidence broke redo');await engine.settle();assert(calls.filter(call=>call.method==='POST').length===4,'adoption evidence did not use one replacement batch');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
async function confirmedTraversalCase(name,assert){
for(const direction of ['undo','redo']){
const root=crypto.randomUUID(),child=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),node=(id,owner=root)=>({id,incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:owner,geometry:{x:0,y:0,width:200,height:100,rotation:0},data:{title:id,text:''}}),original=createDocument(root,[{id:root,parent:null},{id:child,parent:{canvasId:root,entryElementId:'entry'}}],[node('a'),{...node('entry'),componentId:'frame'},node('c',child)]),database=`${name}-confirmed-${direction}`,storage=createBrowserStorage({name:database}),calls=[];let document=original,versions=new Map([[root,1],[child,1]]),reject=false,engine;
const model=(complete,participants=[...versions.keys()])=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:[],canvases:[...document.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))});
const transport=async request=>{calls.push(request);if(request.method==='GET')return{status:200,body:{model:model(true)}};const body=JSON.parse(request.body);if(reject)return{status:409,body:{protocol_version:2,request_id:request.requestId,status:'not-applied',terminal:true,error:'version_conflict'}};for(const[id,version]of Object.entries(body.expected_versions))assert(version===versions.get(id),'confirmed traversal lost its version condition');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,versions.get(id)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false,Object.keys(body.expected_versions))}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map(versions),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
const edit=async(id,text)=>{const result=engine.editor.dispatch({type:'element.data',target:{id,incarnation:document.elements.get(id).incarnation},patch:{text}});assert(result.status==='applied','fixture edit failed');await engine.settle();};
try{
engine=await openProjectEngine(options);await edit('a','edited a');
if(direction==='undo'){engine.editor.dispatch({type:'element.remove',target:{id:'a',incarnation:1}});await engine.settle();await edit('c','edited c');}
else{engine.editor.dispatch({type:'element.create',element:node('new',child)});await engine.settle();await edit('new','edited new');for(let i=0;i<3;i++){engine.editor.history.undo();await engine.settle();}}
reject=true;const first=engine.editor.history[direction]();await engine.settle();const old=[...engine.getSnapshot().runtime.ledger.requests.values()].find(request=>request.operationIds.includes(first.operationIds[0]));reject=false;const confirmed=engine.editor.history[direction]();await engine.settle();const applied=[...engine.getSnapshot().runtime.ledger.requests.values()].find(request=>request.operationIds.includes(confirmed.operationIds[0]));await engine.close();engine=await openProjectEngine(options);
const session=await engine.prepareRecovery();let denied=false;try{session.plan(new Map([[first.operationIds[0],'discard-local']]));}catch(error){denied=error.code==='recovery_history_traversal_order';}assert(denied,'confirmed traversal silently skipped a discarded predecessor');const plan=session.plan(new Map([[first.operationIds[0],'keep-local']]));await session.resolve(plan);await engine.settle();await engine.close();engine=await openProjectEngine(options);assert(engine.getSnapshot().runtime.ledger.requests.get(old.id).body===old.body&&engine.getSnapshot().runtime.ledger.requests.get(applied.id).body===applied.body,'history reconstruction rewrote original request facts');assert(calls.filter(call=>call.method==='POST'&&call.requestId===applied.id).length===1,'confirmed traversal was submitted again');
if(direction==='undo'){assert(document.elements.get('a').incarnation===2&&document.elements.get('c').data.text==='','confirmed undo restoration was lost');assert(engine.editor.history.undo().status==='applied','older remapped history was stale');await engine.settle();assert(document.elements.get('a').data.text==='','older history inverse used the wrong lifecycle');assert(engine.editor.history.redo().status==='applied','older history redo failed');await engine.settle();assert(calls.filter(call=>call.method==='POST').length===8,'confirmed undo recovery duplicated a write');}
else{assert(document.elements.get('new').incarnation===2,'confirmed redo incarnation was lost');assert(engine.editor.history.redo().status==='applied','remaining redo entry was not remapped');await engine.settle();for(let i=0;i<3;i++){assert(engine.editor.history.undo().status==='applied','post-recovery undo failed');await engine.settle();}for(let i=0;i<3;i++){assert(engine.editor.history.redo().status==='applied','post-recovery redo failed');await engine.settle();}assert(document.elements.get('new').incarnation===3&&document.elements.get('new').data.text==='edited new','later traversal reused an old lifecycle');assert(calls.filter(call=>call.method==='POST').length===16,'confirmed redo recovery duplicated a write');}
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
}
async function userJournalRecoveryCase(name,assert){
for(const keep of [true,false]){
const root=crypto.randomUUID(),child=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),node=(id,owner=root)=>({id,incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:owner,geometry:{x:0,y:0,width:200,height:100,rotation:0},data:{title:id,text:''}}),original=createDocument(root,[{id:root,parent:null},{id:child,parent:{canvasId:root,entryElementId:'entry'}}],[node('a'),{...node('entry'),componentId:'frame'},node('c',child)]),database=`${name}-user-journal-${keep}`,storage=createBrowserStorage({name:database}),calls=[];let document=original,versions=new Map([[root,1],[child,1]]),reject=true,engine;
const model=(complete,participants=[...versions.keys()])=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:[],canvases:[...document.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))});
const transport=async request=>{calls.push(request);if(request.method==='GET')return{status:200,body:{model:model(true)}};const body=JSON.parse(request.body);if(reject)return{status:409,body:{protocol_version:2,request_id:request.requestId,status:'not-applied',terminal:true,error:'version_conflict'}};for(const[id,version]of Object.entries(body.expected_versions))assert(version===versions.get(id),'group recovery lost its version condition');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,versions.get(id)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false,Object.keys(body.expected_versions))}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map(versions),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);const first=engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{text:'pending first'}},{groupId:'shared'});await engine.settle();const old=[...engine.getSnapshot().runtime.ledger.requests.values()][0];reject=false;const confirmed=engine.editor.dispatch({type:'element.data',target:{id:'c',incarnation:1},patch:{text:'confirmed later'}},{groupId:'shared'});await engine.settle();await engine.close();engine=await openProjectEngine(options);
const session=await engine.prepareRecovery(),plan=session.plan(new Map([[first.operationIds[0],keep?'keep-local':'discard-local']]));await session.resolve(plan);await engine.settle();assert(calls.filter(call=>call.method==='POST'&&JSON.parse(call.body).operations.some(operation=>operation.changes.some(change=>change.kind==='field'&&change.target.id==='c'))).length===1,'confirmed group contribution was sent again');await engine.close();engine=await openProjectEngine(options);
const history=engine.getSnapshot().history;assert(history.undo.length===1&&history.entries.values().next().value.operationIds.includes(confirmed.operationIds[0]),'group lost its confirmed logical fact');assert(engine.getSnapshot().runtime.ledger.requests.get(old.id).body===old.body,'group replan changed rejected bytes');assert(document.elements.get('a').data.text===(keep?'pending first':''),'group prefix choice was ignored');assert(engine.editor.history.undo().status==='applied','group undo failed');await engine.settle();assert(document.elements.get('a').data.text===''&&document.elements.get('c').data.text==='','group undo did not restore both selected contributions');assert(engine.editor.history.redo().status==='applied','group redo failed');await engine.settle();assert(document.elements.get('a').data.text===(keep?'pending first':'')&&document.elements.get('c').data.text==='confirmed later','group redo lost selected content');assert(calls.filter(call=>call.method==='POST').length===(keep?5:4),'group recovery repeated a write');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
}
async function journalRecoveryCase(name,assert){
const root=crypto.randomUUID(),child=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),node=(id,owner=root)=>({id,incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:owner,geometry:{x:0,y:0,width:200,height:100,rotation:0},data:{title:id,text:''}}),original=createDocument(root,[{id:root,parent:null},{id:child,parent:{canvasId:root,entryElementId:'entry'}}],[node('a'),{...node('entry'),componentId:'frame'},node('c',child)]),storage=createBrowserStorage({name:`${name}-journal-recovery`}),calls=[];let document=original,versions=new Map([[root,1],[child,1]]),reject=false,engine;
const model=(complete,participants=[...versions.keys()])=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:[],canvases:[...document.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))});
const transport=async request=>{calls.push(request);if(request.method==='GET')return{status:200,body:{model:model(true)}};const body=JSON.parse(request.body);if(reject)return{status:409,body:{protocol_version:2,request_id:request.requestId,status:'not-applied',terminal:true,error:'version_conflict'}};for(const[id,version]of Object.entries(body.expected_versions))assert(version===versions.get(id),'journal recovery lost its version condition');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,versions.get(id)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false,Object.keys(body.expected_versions))}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map(versions),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{text:'edited'}});await engine.settle();reject=true;const undo=engine.editor.history.undo();await engine.settle();const old=[...engine.getSnapshot().runtime.ledger.requests.values()].find(request=>request.operationIds.includes(undo.operationIds[0]));reject=false;const branch=engine.editor.dispatch({type:'element.data',target:{id:'c',incarnation:1},patch:{text:'confirmed branch'}});await engine.settle();assert(engine.getSnapshot().history.redo.length===0,'branch did not invalidate redo');await engine.close();engine=await openProjectEngine(options);
const session=await engine.prepareRecovery(),plan=session.plan(new Map([[undo.operationIds[0],'keep-local']]));await session.resolve(plan);await engine.settle();await engine.close();engine=await openProjectEngine(options);assert(document.elements.get('a').data.text===''&&document.elements.get('c').data.text==='confirmed branch','confirmed branch was lost or pending undo was not restored');assert(engine.getSnapshot().history.undo.length===1&&engine.getSnapshot().history.redo.length===0,'journal recovery retained an invalidated redo');assert(engine.getSnapshot().history.entries.values().next().value.operationIds.includes(branch.operationIds[0]),'confirmed branch logical identity changed');assert(engine.getSnapshot().runtime.ledger.requests.get(old.id).body===old.body,'journal adoption changed the rejected request');assert(engine.editor.history.undo().status==='applied','confirmed branch undo failed');await engine.settle();assert(document.elements.get('c').data.text==='','branch inverse used an incorrect baseline');assert(engine.editor.history.redo().status==='applied','confirmed branch redo failed');await engine.settle();assert(calls.filter(call=>call.method==='POST').length===6,'confirmed content was sent twice during event replay');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-journal-recovery`);}
}
async function historyEvidenceCase(name,assert){
const root=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),node={id:'a',incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:root,geometry:{x:0,y:0,width:200,height:100,rotation:0},data:{title:'a',text:''}},document=createDocument(root,[{id:root,parent:null}],[node]),storage=createBrowserStorage({name:`${name}-history-evidence`}),calls=[];let engine;
const transport=async request=>{calls.push(request);return{status:409,body:{protocol_version:2,request_id:request.requestId,status:'not-applied',terminal:true,error:'version_conflict'}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document,versions:new Map([[root,1]]),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})},value=history=>JSON.stringify({undo:history.undo,redo:history.redo,entries:[...history.entries].sort(([a],[b])=>a.localeCompare(b))});
try{
engine=await openProjectEngine(options);engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{text:'first'}});await engine.settle();const first=engine.getSnapshot().history,undo=engine.editor.history.undo();await engine.settle();const beforeBranch=engine.getSnapshot().history,branch=engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{text:'branch'}});await engine.settle();const old=[...engine.getSnapshot().runtime.ledger.requests.values()][0];assert(engine.getSnapshot().history.redo.length===0,'new branch did not clear redo');await engine.close();
engine=await openProjectEngine(options);const snapshot=engine.getSnapshot(),transition=snapshot.operations.get(branch.operationIds[0]).historyTransition,restored=applyHistoryTransition(snapshot.history,transition,'reverse');assert(value(restored)===value(beforeBranch),'cleared redo evidence did not survive IndexedDB');const baseline=applyHistoryTransition(restored,snapshot.operations.get(undo.operationIds[0]).historyTransition,'reverse');assert(value(baseline)===value(first),'undo stack transition could not be reconstructed after reopening');assert(snapshot.document.elements.get('a').data.text==='branch','metadata reconstruction mutated the document');assert(snapshot.runtime.ledger.requests.get(old.id).body===old.body&&calls.length===1,'evidence capture changed or repeated the old rejected request');assert(calls.every(call=>!String(call.body).includes('historyTransition')),'local evidence leaked into protocol 2');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-history-evidence`);}
}
async function traversalRecoveryCase(name,assert){
const root=crypto.randomUUID(),child=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents());
const node=(id,owner=root)=>({id,incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:owner,geometry:{x:20,y:30,width:300,height:200,rotation:0},data:{title:id,text:''}});
const original=createDocument(root,[{id:root,parent:null},{id:child,parent:{canvasId:root,entryElementId:'entry'}}],[node('anchor'),{...node('entry'),componentId:'frame'},node('member',child)]),storage=createBrowserStorage({name:`${name}-traversal`}),calls=[];let document=original,versions=new Map([[root,1],[child,1]]),reject=false,engine;
const model=(complete,participants=[...versions.keys()])=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:participants.filter(id=>!document.canvases.has(id)),canvases:[...document.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))});
const transport=async request=>{calls.push(request);if(request.method==='GET')return{status:200,body:{model:model(true)}};const body=JSON.parse(request.body);if(reject)return{status:409,body:{protocol_version:2,request_id:request.requestId,status:'not-applied',terminal:true,error:'version_conflict'}};for(const[id,version]of Object.entries(body.expected_versions))assert(version===versions.get(id),'traversal restoration lost an archive version fence');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,versions.get(id)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false,Object.keys(body.expected_versions))}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map(versions),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);engine.editor.dispatch({type:'element.remove',target:{id:'entry',incarnation:1}});await engine.settle();reject=true;const undo=engine.editor.history.undo();await engine.settle();const old=[...engine.getSnapshot().runtime.ledger.requests.values()].find(request=>request.operationIds.includes(undo.operationIds[0]));
document=applyChanges(document,[{kind:'field',target:{id:'anchor',incarnation:1},field:'data.title',before:'anchor',after:'remote',beforePresent:true,afterPresent:true}]);versions.set(root,versions.get(root)+1);
const session=await engine.prepareRecovery(),plan=session.plan(new Map([[undo.operationIds[0],'keep-local']]));reject=false;await session.resolve(plan);await engine.settle();await engine.close();engine=await openProjectEngine(options);
assert(engine.getSnapshot().history.redo.length===1&&engine.getSnapshot().history.undo.length===0,'recovered undo did not retain its stack transition');assert(document.elements.get('member').incarnation===2,'restoration used the deleted incarnation');assert(engine.getSnapshot().runtime.ledger.requests.get(old.id).body===old.body,'rejected undo bytes changed');
assert(engine.editor.history.redo().status==='applied','redo after recovered undo failed');await engine.settle();assert(!document.canvases.has(child),'redo did not archive the restored child');assert(engine.editor.history.undo().status==='applied','next lifecycle undo failed');await engine.settle();assert(document.elements.get('member').incarnation===3,'next undo did not advance incarnation');assert(document.elements.get('anchor').data.title==='remote','traversal recovery erased remote content');await engine.close();engine=await openProjectEngine(options);assert(engine.getSnapshot().document.elements.get('member').incarnation===3&&engine.getSnapshot().history.redo.length===1,'restored document and history were not in one durable cut');assert(calls.filter(call=>call.method==='POST').length===5,'traversal recovery duplicated a write');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-traversal`);}
}
async function mixedHistoryCase(name,assert){
const root=crypto.randomUUID(),child=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents());
const node=(id,owner=root)=>({id,incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:owner,geometry:{x:20,y:30,width:300,height:200,rotation:0},data:{title:id,text:''}});
const original=createDocument(root,[{id:root,parent:null}],[node('anchor')]),storage=createBrowserStorage({name:`${name}-mixed-history`}),calls=[];let document=original,versions=new Map([[root,1]]),reject=false,engine;
const model=(complete,participants=[...versions.keys()])=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:participants.filter(id=>!document.canvases.has(id)),canvases:[...document.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))});
const transport=async request=>{calls.push(request);if(request.method==='GET')return{status:200,body:{model:model(true)}};const body=JSON.parse(request.body);if(reject)return{status:409,body:{protocol_version:2,request_id:request.requestId,status:'not-applied',terminal:true,error:'version_conflict'}};for(const[id,version]of Object.entries(body.expected_versions))assert(version===(versions.get(id)??0),'mixed history lost its version fence');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,(versions.get(id)??0)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false,Object.keys(body.expected_versions))}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map([[root,1]]),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);engine.editor.dispatch({type:'element.create',element:{...node('entry'),componentId:'frame'}},{groupId:'partition'});await engine.settle();const acknowledged=[...engine.getSnapshot().runtime.ledger.operations.keys()][0];
reject=true;const edit=engine.editor.transaction(tx=>{tx.dispatch({type:'canvas.create',canvas:{id:child,parent:{canvasId:root,entryElementId:'entry'}}});tx.dispatch({type:'element.create',element:node('member',child)});},{groupId:'partition'});await engine.settle();const old=[...engine.getSnapshot().runtime.ledger.requests.values()].find(request=>request.operationIds.includes(edit.operationIds[0]));
document=applyChanges(document,[{kind:'field',target:{id:'anchor',incarnation:1},field:'data.title',before:'anchor',after:'remote',beforePresent:true,afterPresent:true}]);versions.set(root,versions.get(root)+1);
const session=await engine.prepareRecovery(),plan=session.plan(new Map([[edit.operationIds[0],'keep-local']]));reject=false;await session.resolve(plan);await engine.settle();await engine.close();engine=await openProjectEngine(options);
assert(engine.getSnapshot().history.undo.length===1,'mixed group split or lost its acknowledged prefix');assert(engine.getSnapshot().history.entries.values().next().value.operationIds.includes(acknowledged),'applied logical fact was replaced');assert(engine.getSnapshot().runtime.ledger.requests.get(old.id).body===old.body,'old rejected bytes changed');
assert(engine.editor.history.undo().status==='applied','mixed structural undo failed after reopening');await engine.settle();assert(!document.elements.has('entry')&&!document.canvases.has(child),'mixed undo left its acknowledged entry');assert(document.elements.get('anchor').data.title==='remote','mixed undo erased remote data');
assert(engine.editor.history.redo().status==='applied','mixed structural redo failed');await engine.settle();assert(document.elements.get('entry').incarnation===2&&document.elements.get('member').incarnation===2,'mixed redo reused deleted identities');await engine.close();engine=await openProjectEngine(options);assert(engine.getSnapshot().document.elements.get('member').incarnation===2,'mixed history restoration did not survive another reopen');assert(calls.filter(call=>call.method==='POST').length===5,'mixed recovery duplicated a write');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-mixed-history`);}
}
async function scaledContainerCase(name,assert){
const root=crypto.randomUUID(),child=crypto.randomUUID(),target=crypto.randomUUID(),deep=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents());
const node=(id,owner,kind='text',scale=1)=>({id,incarnation:1,structure:'node',componentId:kind,dataVersion:1,ownerCanvasId:owner,geometry:{x:id==='g'?700:100,y:80,width:300,height:200,rotation:id==='g'?70:30},data:{title:id,text:'body',fontSize:17,...(kind==='frame'?{presentation:{contentScale:scale}}:{})}});
const original=createDocument(root,[{id:root,parent:null},{id:child,parent:{canvasId:root,entryElementId:'f'}},{id:target,parent:{canvasId:root,entryElementId:'g'}},{id:deep,parent:{canvasId:child,entryElementId:'h'}}],[node('f',root,'frame',.75),node('g',root,'frame',2.5),node('h',child,'frame',.8),node('a',deep)]),storage=createBrowserStorage({name:`${name}-units`}),calls=[];let document=original,versions=new Map([...original.canvases.keys()].map(id=>[id,1])),engine;
const transport=async request=>{calls.push(request);const body=JSON.parse(request.body);for(const[id,version]of Object.entries(body.expected_versions))assert(version===versions.get(id),'scaled move lost version ordering');for(const operation of body.operations)document=applyChanges(document,operation.changes);const scopes=Object.keys(body.expected_versions);for(const id of scopes)versions.set(id,versions.get(id)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:{documentFormatVersion:1,projectId:root,complete:false,removedCanvasIds:[],canvases:[...document.canvases.values()].filter(canvas=>scopes.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>scopes.includes(element.ownerCanvasId)),tombstones:{},versions:Object.fromEntries(scopes.map(id=>[id,versions.get(id)]))}}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map(versions),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
const check=()=>{for(const id of [child,deep]){const a=canvasTransform(engine.getSnapshot().document,id),b=canvasTransform(original,id);for(const key of ['x','y','rotation','scale'])assert(Math.abs(a[key]-b[key])<1e-7,'descendant transform changed: '+key);}assert(engine.getSnapshot().document.elements.get('a').data.fontSize===17,'subtree typography data was rewritten');};
try{engine=await openProjectEngine(options);assert(engine.editor.dispatch({type:'element.move',target:{id:'f',incarnation:1},canvasId:target}).status==='applied','scaled container move failed');await engine.settle();check();assert(Math.abs(engine.getSnapshot().document.elements.get('f').data.presentation.unitScale-.4)<1e-9,'entry unit ratio was not stored');await engine.close();engine=await openProjectEngine(options);check();assert(engine.editor.history.undo().status==='applied','unit-normalized undo failed');await engine.settle();check();assert(engine.getSnapshot().document.elements.get('f').data.presentation.unitScale===undefined,'undo did not restore absent units');assert(engine.editor.history.redo().status==='applied','unit-normalized redo failed');await engine.settle();check();assert(calls.filter(call=>call.method==='POST').length===3,'unit conversion scheduled duplicate writes');await engine.close();engine=await openProjectEngine(options);check();
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-units`);}
}
async function structureRecoveryCase(name,assert){
const root=crypto.randomUUID(),child=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents());
const node=(id,owner=root)=>({id,incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:owner,geometry:{x:0,y:0,width:300,height:200,rotation:0},data:{title:id,text:''}}),original=createDocument(root,[{id:root,parent:null}],[node('a')]),storage=createBrowserStorage({name:`${name}-structure`}),calls=[];let document=original,versions=new Map([[root,1]]),reject=true,engine;
const model=(complete,participants=[...versions.keys()])=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:participants.filter(id=>!document.canvases.has(id)),canvases:[...document.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))});
const transport=async request=>{calls.push(request);if(request.method==='GET')return{status:200,body:{model:model(true)}};const body=JSON.parse(request.body);if(reject){reject=false;versions.set(root,2);return{status:409,body:{protocol_version:2,request_id:request.requestId,status:'not-applied',terminal:true,error:'version_conflict'}};}for(const[id,version]of Object.entries(body.expected_versions))assert(version===(versions.get(id)??0),'structural request version mismatch');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,(versions.get(id)??0)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false,Object.keys(body.expected_versions))}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map([[root,1]]),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{engine=await openProjectEngine(options);const edit=engine.editor.transaction(tx=>{tx.dispatch({type:'element.create',element:{...node('frame'),componentId:'frame'}});tx.dispatch({type:'canvas.create',canvas:{id:child,parent:{canvasId:root,entryElementId:'frame'}}});tx.dispatch({type:'element.create',element:node('member',child)});});await engine.settle();const old=[...engine.getSnapshot().runtime.ledger.requests.values()][0],session=await engine.prepareRecovery(),plan=session.plan(new Map([[edit.operationIds[0],'keep-local']]));await session.resolve(plan);await engine.settle();await engine.close();
engine=await openProjectEngine(options);assert(engine.getSnapshot().document.elements.get('member').ownerCanvasId===child,'structural replan did not recover its owner tree');assert(engine.getSnapshot().runtime.ledger.requests.get(old.id).body===old.body,'old structural request was changed');assert(engine.editor.history.undo().status==='applied','structural undo failed');await engine.settle();assert(!document.canvases.has(child),'undo left an orphan canvas');assert(engine.editor.history.redo().status==='applied','structural redo failed');await engine.settle();assert(document.elements.get('member').incarnation===2,'redo reused a deleted lifecycle');await engine.close();engine=await openProjectEngine(options);assert(engine.getSnapshot().document.elements.get('member').incarnation===2,'restored tree/history lifecycle did not survive reopening');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-structure`);}
}
async function appliedReceiptRecoveryCase(name,assert){
const root=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),target={id:'a',incarnation:1};
const element={...target,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:root,geometry:{x:0,y:0,width:300,height:200,rotation:0},data:{title:'a',text:'original'}};
const original=createDocument(root,[{id:root,parent:null}],[element]),storage=createBrowserStorage({name:`${name}-applied-receipt`}),calls=[],receipts=new Map();let server=original,version=1,engine,broken=true;
const model=complete=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:[],canvases:[...server.canvases.values()],elements:[...server.elements.values()],tombstones:{},versions:{[root]:version}});
const transport=async request=>{
calls.push(request);if(request.method==='GET')return{status:200,body:request.path.endsWith('/model')?{model:model(true)}:receipts.get(request.path.split('/').at(-1))};
const body=JSON.parse(request.body);assert(body.expected_versions[root]===version,'write did not use reviewed version');for(const operation of body.operations)server=applyChanges(server,operation.changes);version++;
const receipt={protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false)};receipts.set(request.requestId,receipt);return{status:200,body:receipt};
};
const base=createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport}),persistence={...base,canonical(...args){if(broken)throw Error('codec_temporarily_unavailable');return base.canonical(...args);}};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map([[root,1]]),storage,persistence};
try{
engine=await openProjectEngine(options);engine.editor.dispatch({type:'element.data',target,patch:{title:'first'}});await engine.settle();
const request=[...engine.getSnapshot().runtime.ledger.requests.values()][0];assert(request.outcome==='applied'&&request.phase==='waiting','failed coordination lost the successful receipt');
const edit=engine.editor.dispatch({type:'element.geometry',target,patch:{x:200}});await engine.settle();await engine.close();
server=applyChanges(server,[{kind:'field',target,field:'geometry',before:element.geometry,after:{...element.geometry,x:500,width:400}}]);version++;
engine=await openProjectEngine(options);const session=await engine.prepareRecovery();assert(session.acknowledgedRequestIds.includes(request.id),'saved applied receipt was not recovered');assert(session.pendingOperationIds.length===1&&session.pendingOperationIds[0]===edit.operationIds[0],'already executed operation was offered for local re-execution');
const plan=session.plan(new Map([[edit.operationIds[0],'keep-local']]));broken=false;await session.resolve(plan);await engine.settle();assert(calls.filter(call=>call.method==='POST').length===2,'successful original operation was sent again');
assert(engine.getSnapshot().runtime.ledger.requests.get(request.id).body===request.body,'original frozen receipt identity changed');await engine.close();
engine=await openProjectEngine(options);assert(engine.getSnapshot().runtime.ledger.requests.get(request.id).phase==='resolved','acknowledgement and rebased document did not recover together');assert(engine.getSnapshot().document.elements.get('a').geometry.width===400,'old receipt rewound newer geometry');
assert(engine.editor.history.undo().status==='applied','rebased geometry history did not recover');await engine.settle();assert(engine.getSnapshot().document.elements.get('a').geometry.x===500,'undo did not return to the reviewed geometry');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-applied-receipt`);}
}
async function conflictReplanCase(name,assert){
const root=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),target={id:'a',incarnation:1};
const element={...target,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:root,geometry:{x:0,y:0,width:300,height:200,rotation:0},data:{title:'a',text:'original'}};
const original=createDocument(root,[{id:root,parent:null}],[element]),storage=createBrowserStorage({name:`${name}-conflict-replan`}),calls=[],receipts=new Map();let server=original,version=1,engine,reject=true,lose=true;
const model=complete=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:[],canvases:[...server.canvases.values()],elements:[...server.elements.values()],tombstones:{},versions:{[root]:version}});
const transport=async request=>{
calls.push(request);if(request.method==='GET')return{status:200,body:request.path.endsWith('/model')?{model:model(true)}:receipts.get(request.path.split('/').at(-1))};
const body=JSON.parse(request.body);if(reject){reject=false;server=applyChanges(server,[{kind:'field',target,field:'data.title',before:'a',after:'remote',beforePresent:true,afterPresent:true}]);version++;return{status:409,body:{protocol_version:2,request_id:request.requestId,status:'not-applied',terminal:true,error:'version_conflict'}};}
assert(body.expected_versions[root]===version,'replanned write did not use reviewed version');for(const operation of body.operations)server=applyChanges(server,operation.changes);version++;
const receipt={protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false)};receipts.set(request.requestId,receipt);return lose?{status:502,body:{error:'lost_response'}}:{status:200,body:receipt};
};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map([[root,1]]),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);const edit=engine.editor.dispatch({type:'element.data',target,patch:{title:'local'}});await engine.settle();
const old=[...engine.getSnapshot().runtime.ledger.requests.values()][0],session=await engine.prepareRecovery(),plan=session.plan(new Map([[edit.operationIds[0],'keep-local']]));await session.resolve(plan);await engine.settle();
const replacement=[...engine.getSnapshot().runtime.ledger.requests.values()].find(request=>request.id!==old.id);assert(replacement&&replacement.outcome==='unresolved','lost replacement ACK was guessed successful');await engine.close();lose=false;
engine=await openProjectEngine({...options,clock:{now:()=>Date.now()+100000,setTimer:(fn,ms)=>setTimeout(fn,ms),clearTimer:id=>clearTimeout(id)}});
for(let i=0;i<100&&engine.getSnapshot().runtime.ledger.requests.get(replacement.id)?.phase!=='resolved';i++)await new Promise(resolve=>setTimeout(resolve,10));await engine.settle();
const restored=engine.getSnapshot();assert(restored.runtime.recoveries.has(plan.id),'recovery audit disappeared');assert(restored.runtime.ledger.requests.get(old.id).body===old.body&&restored.runtime.ledger.requests.get(old.id).outcome==='not-applied','old rejected request was rewritten');
assert(restored.runtime.ledger.requests.get(replacement.id).body===replacement.body,'replacement identity changed after reopening');assert(calls.filter(call=>call.method==='POST').length===2,'recovery repeated a server write');
assert(engine.editor.history.undo().status==='applied','replanned history did not restore');await engine.settle();assert(engine.getSnapshot().document.elements.get('a').data.title==='remote','undo did not restore the reviewed server value');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-conflict-replan`);}
}
async function canonicalRefreshCase(name,assert){
const root=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents());
const target={id:'a',incarnation:1},element={...target,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:root,geometry:{x:0,y:0,width:300,height:200,rotation:0},data:{title:'a',text:'original'}};
const original=createDocument(root,[{id:root,parent:null}],[element]),storage=createBrowserStorage({name:`${name}-canonical-refresh`}),calls=[];let server=original,version=1,engine;
const model=complete=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:[],canvases:[...server.canvases.values()],elements:[...server.elements.values()],tombstones:{},versions:{[root]:version}});
const transport=async request=>{calls.push(request);if(request.method==='GET')return{status:200,body:{model:model(true)}};const body=JSON.parse(request.body);for(const operation of body.operations)server=applyChanges(server,operation.changes);version++;return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false)}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map([[root,1]]),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);engine.editor.dispatch({type:'element.data',target,patch:{title:'local'}});await engine.settle();
const historyId=engine.getSnapshot().history.undo[0],request=[...engine.getSnapshot().runtime.ledger.requests.values()][0];
server=applyChanges(server,[{kind:'field',target,field:'geometry',before:element.geometry,after:{...element.geometry,x:400}}]);version++;
const session=await engine.prepareRecovery();assert(session.conflict===undefined,'clean canonical refresh reported a conflict');
assert(engine.getSnapshot().document.elements.get('a').geometry.x===0,'preview mutated the working document');
assert(engine.editor.history.undo().code==='recovery_in_progress','history bypassed the recovery fence');
assert((await session.apply()).status==='applied','fresh canonical state did not publish');await engine.close();
engine=await openProjectEngine(options);const restored=engine.getSnapshot();
assert(restored.document.elements.get('a').geometry.x===400&&restored.document.elements.get('a').data.title==='local','canonical refresh did not survive IndexedDB recovery');
assert(restored.runtime.versions.get(root)===3&&restored.history.undo[0]===historyId,'refresh lost its version/history cut');
assert(restored.runtime.ledger.requests.size===1&&restored.runtime.ledger.requests.get(request.id).body===request.body,'refresh changed an original request');
assert(calls.filter(call=>call.method==='POST').length===1,'refresh or reopen resent an applied write');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-canonical-refresh`);}
}
async function runRecoveryCase(name,assert){
const root=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),runId=crypto.randomUUID();
const registry=createRegistry(builtinComponents()),element={id:'a',incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:root,geometry:{x:0,y:0,width:300,height:200,rotation:0},data:{title:'a',text:'original'}},original=createDocument(root,[{id:root,parent:null}],[element]);
const storage=createBrowserStorage({name:`${name}-runs`}),calls=[];let accepted,after,plan,engine;
const chunk=(document,version)=>({documentFormatVersion:1,projectId:root,complete:false,removedCanvasIds:[],canvases:[...document.canvases.values()],elements:[...document.elements.values()],tombstones:{},versions:{[root]:version}});
const transport=async request=>{
calls.push(request);
if(request.method==='POST'){
const input=JSON.parse(request.body),configured=applyChanges(original,[{kind:'field',target:{id:'a',incarnation:1},field:'data.ai',before:null,beforePresent:false,after:{enabled:true,modelId:input.model_id,prompt:input.prompt,parameters:{},propagate:false},afterPresent:true}]);
plan={steps:[{step_id:'step-a',board_id:root,node_id:'a',target_incarnation:1,canonical_root:root}],edges:[],frozen_versions:{[root]:2}};
accepted={protocol_version:2,request_id:request.requestId,operation_id:input.operation_id,admission:'accepted',status:'queued',run_id:runId,plan,canvas_version:2,document_format_version:1,target_incarnation:1,model:chunk(configured,2)};
after=applyChanges(configured,[{kind:'field',target:{id:'a',incarnation:1},field:'data.text',before:'original',after:'durable generated',beforePresent:true,afterPresent:true}]);
return{status:502,body:{error:'response_lost'}};
}
if(request.path.includes('/ai/operations/'))return{status:200,body:accepted};
return{status:200,body:{run_id:runId,status:'succeeded',plan,events:[{seq:1,run_id:runId,type:'run.queued',status:'queued'},{seq:2,run_id:runId,type:'step.applied',step_id:'step-a',board_id:root,node_id:'a',target_incarnation:1,document_format_version:1,canvas_version:3,model:chunk(after,3)}],next_seq:2,has_more:false,outputs:[],steps:[]}};
};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map([[root,1]]),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport}),runs:createFoundryRuns({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);const detach=engine.sessions.attach('view');engine.startRun({target:{id:'a',incarnation:1},boardId:root,modelId:'text',prompt:'Generate',parameters:{},propagate:false});detach();await engine.settle();
const request=[...engine.getSnapshot().runtime.ledger.requests.values()][0];assert(engine.getSnapshot().runtime.runs.size===0,'lost admission was guessed as resolved');await engine.close();
engine=await openProjectEngine({...options,clock:{now:()=>Date.now()+100000,setTimer:(fn,ms)=>setTimeout(fn,ms),clearTimer:id=>clearTimeout(id)}});
for(let i=0;i<100&&engine.getSnapshot().runtime.runs.get(runId)?.cursor!==2;i++)await new Promise(resolve=>setTimeout(resolve,10));await engine.settle();
assert(calls.filter(call=>call.method==='POST').length===1,'recovery invoked a second generation');assert(calls.some(call=>call.path.endsWith('/ai/operations/'+request.id)),'recovery lost the original admission identity');
assert(engine.getSnapshot().runtime.runs.get(runId)?.cursor===2&&engine.getSnapshot().document.elements.get('a').data.text==='durable generated','run and canonical result did not publish together');assert(engine.getSnapshot().history.undo.length===0,'ACK replay created user history');await engine.close();
const leader=await storage.claim(options.scope);try{const image=await storage.recover(leader);assert(image.payload.runtime.runs[0][1].cursor===2,'run cursor was not durable');assert(image.payload.document.elements[0].data.text==='durable generated','cursor advanced without durable document');}finally{await leader.release();}
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-runs`);}
}
async function atomicResourceDeletionCase(name,assert){
const database=`${name}-file-collection`,storage=createBrowserStorage({name:database,checkpointEvery:2}),scope={accountId:'account',workspaceId:'workspace',projectId:'project'};let leader,other;
const marker={version:1,unreferencedAt:100,purgedAt:200},image={formatVersion:3,rootRevision:1,localRevision:0,payload:{runtime:{resources:[['asset',{retirement:marker}]]}}};
try{
other=await storage.claim({...scope,projectId:'other-project'});await storage.commit(other,{image:{formatVersion:1,rootRevision:0,localRevision:0,payload:{}},files:[{id:'asset',bytes:new Blob(['other project'])}]});
leader=await storage.claim(scope);await storage.commit(leader,{image:{formatVersion:1,rootRevision:0,localRevision:0,payload:{}},files:[{id:'asset',bytes:new Blob(['delete me'])},{id:'keep',bytes:new Blob(['keep me'])}]});
const nativeDelete=IDBObjectStore.prototype.delete;let queued=false;
IDBObjectStore.prototype.delete=function(key){const request=nativeDelete.call(this,key);if(this.name==='files'&&Array.isArray(key)&&key[1]==='asset'){queued=true;throw Error('fault_after_delete');}return request;};
let failure;try{await storage.commit(leader,{image,deleteFiles:['asset']});}catch(error){failure=error.message;}finally{IDBObjectStore.prototype.delete=nativeDelete;}
assert(queued&&failure==='fault_after_delete','fault did not occur after a real IDB delete request');assert(await(await storage.readFile(leader,'asset')).text()==='delete me','aborted GC transaction lost bytes');assert((await storage.recover(leader)).rootRevision===0,'aborted GC transaction advanced recovery');
const ids=['asset'],pending=storage.commit(leader,{image,deleteFiles:ids});ids[0]='keep';await pending;
assert(await storage.readFile(leader,'asset')===undefined&&await(await storage.readFile(leader,'keep')).text()==='keep me','mutable deletion descriptor escaped capture');assert((await storage.recover(leader)).formatVersion===3,'GC cut did not persist');await storage.commit(leader,{image,deleteFiles:['asset']});
let code;try{await storage.commit(leader,{image:{...image,rootRevision:2},deleteFiles:['keep']});}catch(error){code=error.code;}assert(code==='invalid_file_deletion','file deletion without a matching purge marker was accepted');
await leader.release();leader=await storage.claim(scope);assert(await storage.readFile(leader,'asset')===undefined&&(await storage.recover(leader)).rootRevision===1,'GC did not survive owner reopening');assert(await(await storage.readFile(other,'asset')).text()==='other project','local GC crossed its project scope');
}finally{await other?.release();await leader?.release();storage.close();indexedDB.deleteDatabase(database);}
}
async function importSourceCollectionCase(name,assert){
const database=`${name}-import-source-gc`,storage=createBrowserStorage({name:database,checkpointEvery:3}),scope={accountId:'account',workspaceId:'workspace',projectId:'project'},registry=createRegistry(builtinComponents()),original=createDocument('project',[{id:'P',parent:null}],[]),receipts=new Map();let engine,owner,server=original,version=1,now=1000;
const port={compile:(intent,id)=>({payload:{intent,id},canvasIds:intent.canvasIds,expectedVersions:{P:version}}),async submit(request){const {intent}=JSON.parse(request.body);server=applyChanges(server,intent.changes);receipts.set(request.id,{document:server,versions:new Map([['P',++version]])});return{outcome:'applied'};},async query(request){return{outcome:receipts.has(request.id)?'applied':'unresolved'};},canonical:request=>receipts.get(request.id)};
const options={scope,registry,document:original,versions:new Map([['P',1]]),storage:{...storage,commit:(leader,batch)=>{owner=leader;return storage.commit(leader,batch);}},persistence:port,clock:{now:()=>now,setTimer:(fn,ms)=>setTimeout(fn,ms),clearTimer:id=>clearTimeout(id)}};
try{
engine=await openProjectEngine(options);const line={kind:'element',key:'text',record:{id:'text',incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:'$target',data:{title:'text',text:''},geometry:{x:0,y:0,width:100,height:100,rotation:0}}},id=engine.imports.startNdjson(new Blob([JSON.stringify(line)+'\nnot-json']),'P');await engine.settle();const job=engine.imports.getJobs().get(id),fileId=job.source.fileId;assert(job.status==='partial','partial source fixture did not finish');const document=engine.getSnapshot().document,history=engine.getSnapshot().history;
await engine.collectImportSources({retentionMs:100});now+=100;const nativeDelete=IDBObjectStore.prototype.delete;let injected=false;
IDBObjectStore.prototype.delete=function(key){const request=nativeDelete.call(this,key);if(!injected&&this.name==='files'&&Array.isArray(key)&&key[1]===fileId){injected=true;throw Error('source_gc_abort');}return request;};
let failure;try{await engine.collectImportSources({retentionMs:100});}catch(error){failure=error.message;}finally{IDBObjectStore.prototype.delete=nativeDelete;}
assert(injected&&failure==='source_gc_abort','source GC did not reach an IDB deletion fault');assert((await storage.readFile(owner,fileId)).size===job.source.byteLength,'source bytes vanished after aborted GC');const soft=await storage.recover(owner);assert(soft.formatVersion===4&&soft.payload.runtime.imports[0][1].source.retirement.purgedAt===undefined,'aborted source GC advanced the purge cut');
await engine.persist();assert(await storage.readFile(owner,fileId)===undefined,'source deletion retry did not remove bytes');const cut=await storage.recover(owner);assert(cut.formatVersion===4&&decodeRecovery(cut,registry).runtime.imports.get(id).source.retirement.purgedAt===1100,'source purge and recovery marker disagree');assert(engine.getSnapshot().document===document&&engine.getSnapshot().history===history,'source GC changed content/history');
let oldReader;try{decodeRecovery({...cut,formatVersion:3},registry);}catch(error){oldReader=error.code;}assert(oldReader==='unsupported_recovery_version','source retirement was accepted as v3');await engine.close();engine=await openProjectEngine(options);await engine.imports.resume(id);await engine.settle();assert(engine.imports.getJobs().get(id).status==='partial'&&engine.imports.getJobs().get(id).failures.length===1,'reopening a retired source lost its failure report');let retired;try{await engine.imports.openSource(id);}catch(error){retired=error.code;}assert(retired==='import_source_retired','retired source was silently reinterpreted or fetched as an asset');assert(engine.editor.history.undo().status==='applied','undo depended on purged source');await engine.settle();assert(engine.getSnapshot().document.elements.size===0,'undo retained imported data');assert(engine.editor.history.redo().status==='applied','redo depended on purged source');await engine.settle();assert(engine.getSnapshot().document.elements.size===1,'redo did not restore imported content');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
async function orphanInventoryCase(name,assert){
const database=`${name}-orphan-inventory`,storage=createBrowserStorage({name:database,checkpointEvery:3}),scope={accountId:'account',workspaceId:'workspace',projectId:'project'},registry=createRegistry(builtinComponents()),document=createDocument('project',[{id:'P',parent:null}],[{id:'image',incarnation:1,ownerCanvasId:'P',structure:'node',componentId:'image',dataVersion:1,data:{title:'image',text:'',assetId:'z-pinned'},geometry:{x:0,y:0,width:100,height:100,rotation:0}}]);let engine,owner,other,now=1000;
const options={scope,registry,document,versions:new Map([['P',1]]),storage:{...storage,claim:async scope=>{owner=await storage.claim(scope);return owner;}},persistence:{compile(){throw Error('unexpected write');},async submit(){throw Error('unexpected write');},async query(){throw Error('unexpected write');},canonical(){throw Error('unexpected write');}},clock:{now:()=>now,setTimer:(fn,ms)=>setTimeout(fn,ms),clearTimer:id=>clearTimeout(id)}};
try{
engine=await openProjectEngine(options);await engine.persist();await engine.close();let legacy=await storage.claim(scope),cut=await storage.recover(legacy);await storage.commit(legacy,{image:{...cut,rootRevision:cut.rootRevision+1},files:[{id:'a-orphan',bytes:new Blob(['orphan bytes'])},{id:'z-pinned',bytes:new Blob(['pinned bytes'])}]});await legacy.release();
other=await storage.claim({...scope,projectId:'other'});await storage.commit(other,{image:{formatVersion:1,rootRevision:0,localRevision:0,payload:{}},files:[{id:'a-foreign',bytes:new Blob(['other project'])}]});engine=await openProjectEngine(options);
const before=engine.getSnapshot(),first=await engine.inventoryLocalFiles({limit:1});assert(first.files.length===1&&first.files[0].id==='a-orphan'&&first.nextAfter==='a-orphan','inventory paging/scope prefix is wrong');assert(engine.getSnapshot()===before,'read-only inventory mutated the Root');const last=await engine.inventoryLocalFiles({after:first.nextAfter,limit:1});assert(last.files[0].id==='z-pinned'&&last.files[0].pins.length>0&&last.nextAfter===undefined,'referenced unregistered bytes lost their pin');
await engine.collectOrphanFiles({retentionMs:100});now+=100;const nativeDelete=IDBObjectStore.prototype.delete;let injected=false;
IDBObjectStore.prototype.delete=function(key){const request=nativeDelete.call(this,key);if(!injected&&this.name==='files'&&Array.isArray(key)&&key[1]==='a-orphan'){injected=true;throw Error('orphan_gc_abort');}return request;};
let failure;try{await engine.collectOrphanFiles({retentionMs:100});}catch(error){failure=error.message;}finally{IDBObjectStore.prototype.delete=nativeDelete;}
assert(injected&&failure==='orphan_gc_abort','orphan GC did not reach post-delete rollback');assert(await(await storage.readFile(owner,'a-orphan')).text()==='orphan bytes','orphan byte rollback failed');cut=await storage.recover(owner);assert(cut.formatVersion===5&&cut.payload.runtime.orphanFiles[0][1].retirement.purgedAt===undefined,'aborted orphan GC advanced its cut');await engine.persist();assert(await storage.readFile(owner,'a-orphan')===undefined,'orphan deletion retry failed');assert(await(await storage.readFile(owner,'z-pinned')).text()==='pinned bytes','orphan GC deleted referenced bytes');assert(await(await storage.readFile(other,'a-foreign')).text()==='other project','orphan GC crossed its project scope');
cut=await storage.recover(owner);assert(decodeRecovery(cut,registry).runtime.orphanFiles.get('a-orphan').retirement.purgedAt===1100,'orphan v5 record failed recovery');let stale;try{await storage.scanFiles(owner,{expectedRootRevision:before.rootRevision,limit:1});}catch(error){stale=error.code;}assert(stale==='stale_file_inventory','old inventory revision was accepted');
await engine.close();legacy=await storage.claim(scope);let reuse;try{await storage.commit(legacy,{image:{...cut,rootRevision:cut.rootRevision+1},files:[{id:'a-orphan',bytes:new Blob(['reused identity'])}]});}catch(error){reuse=error.code;}assert(reuse==='resource_identity_conflict','retired file identity was reused');await legacy.release();engine=await openProjectEngine(options);let retired;try{await engine.displayResource('a-orphan');}catch(error){retired=error.code;}assert(retired==='local_file_retired','retired orphan became a guessed remote asset');assert(engine.getSnapshot().document.elements.has('image'),'orphan GC mutated the document');
}finally{await engine?.close();await other?.release();storage.close();indexedDB.deleteDatabase(database);}
}
async function resourceRecoveryCase(name, assert) {
const scope = { accountId: crypto.randomUUID(), workspaceId: crypto.randomUUID(), projectId: crypto.randomUUID() };
const registry = createRegistry(builtinComponents()), document = createDocument(scope.projectId, [{ id: scope.projectId, parent: null }], []);
const storage = createBrowserStorage({ name: `${name}-resources` }), receipts = new Map(), uploads = [], queries = [], writes = [];
const assets = createFoundryAssets({ workspaceId: scope.workspaceId, transport: async input => {
if (input.method === 'POST') {
uploads.push(input); const file = input.body.get('image');
assert(await file.text() === 'durable-image-bytes', 'upload reconstructed different file bytes');
receipts.set(input.requestId, { protocol_version: 2, request_id: input.requestId, status: 'applied', asset: { id: input.resourceId, bytes: file.size, mime_type: 'image/png', width: 2, height: 1, url: assets.sourceURL(input.resourceId) } });
return { status: 502, body: { error: 'lost_upload_ack' } };
}
queries.push(input); return { status: 200, body: receipts.get(input.path.split('/').at(-1)) };
} });
let server = document;
const persistence = {
compile: (intent, id, snapshot) => ({ payload: { intent, id }, canvasIds: intent.canvasIds, expectedVersions: { [scope.projectId]: snapshot.runtime.versions.get(scope.projectId) } }),
async submit(request) { writes.push(request); server = applyChanges(server, JSON.parse(request.body).intent.changes); return { outcome: 'applied' }; },
async query() { return { outcome: 'unresolved' }; },
canonical: () => ({ document: server, versions: new Map([[scope.projectId, writes.length + 1]]) }),
};
const options = { scope, registry, document, versions: new Map([[scope.projectId, 1]]), storage, persistence, assets,historyLimit:1 };
let engine, resource;
try {
engine = await openProjectEngine(options);
const result = engine.stageFiles([{ name: 'picture.png', bytes: new Blob(['durable-image-bytes'], { type: 'image/png' }) }], ([ref], tx) => {
resource = ref;
tx.dispatch({ type: 'element.create', element: { id: 'picture', incarnation: 1, componentId: 'image', dataVersion: 1, structure: 'node', ownerCanvasId: scope.projectId, data: { title: 'Picture', text: '', src: ref.src, assetId: ref.id }, geometry: { x: 10, y: 20, width: 100, height: 80, rotation: 0 } } });
});
assert(result.status === 'applied' && engine.getSnapshot().document.elements.has('picture'), 'pending image was hidden until prepare/upload');
const handle = await engine.displayResource(resource.id); assert(handle.url.startsWith('blob:'), 'pending image did not expose a local preview'); handle.release();
await engine.settle(); assert(uploads.length === 1 && writes.length === 0, 'document was sent before its resource was confirmed');
await engine.close();
const leader = await storage.claim(scope), image = await storage.recover(leader);
assert(await (await storage.readFile(leader, resource.id)).text() === 'durable-image-bytes', 'file bytes did not survive actual IndexedDB recovery');
assert(image.payload.runtime.resources[0][1].id === resource.id && image.payload.document.elements[0].data.assetId === resource.id, 'resource metadata and element belong to different durable cuts');
await leader.release();
engine = await openProjectEngine({ ...options, clock: { now: () => Date.now() + 100000, setTimer: (fn, ms) => setTimeout(fn, ms), clearTimer: id => clearTimeout(id) } });
await new Promise(resolve => setTimeout(resolve, 10)); await engine.settle();
assert(uploads.length === 1 && queries.length === 1 && writes.length === 1, 'reopen uploaded again or lost the pending element operation');
assert(queries[0].path.endsWith(uploads[0].requestId), 'recovery queried a replacement request identity');
assert(engine.getSnapshot().runtime.resources.get(resource.id).stored.id === resource.id, 'confirmed asset changed resource identity');
assert(engine.getSnapshot().history.undo.length === 1 && engine.getSnapshot().document.elements.size === 1, 'resource ACK recreated the node/history');
assert((await engine.collectResources({retentionMs:0})).purgedIds.length===0,'live image was collected');
engine.editor.dispatch({type:'element.remove',target:{id:'picture',incarnation:1}});await engine.settle();assert((await engine.collectResources({retentionMs:0})).purgedIds.length===0,'undo-owned image was collected');
engine.editor.dispatch({type:'element.create',element:{id:'keep-text',incarnation:1,componentId:'text',dataVersion:1,structure:'node',ownerCanvasId:scope.projectId,data:{title:'keep',text:''},geometry:{x:0,y:0,width:100,height:100,rotation:0}}});await engine.settle();
const collected=await engine.collectResources({retentionMs:0});assert(collected.purgedIds.length===1&&collected.purgedIds[0]===resource.id,'unreferenced local image was not collected');await engine.close();
const inspected=await storage.claim(scope),cut=await storage.recover(inspected);assert(cut.formatVersion===3&&await storage.readFile(inspected,resource.id)===undefined,'recovered GC marker and byte deletion disagree');assert(decodeRecovery(cut,registry).runtime.resources.get(resource.id).retirement.purgedAt!==undefined,'GC marker did not decode');await inspected.release();
engine=await openProjectEngine(options);const remote=await engine.displayResource(resource.id);assert(remote.url===assets.displayURL(resource.id),'purged confirmed image did not fall back to its existing remote asset');remote.release();assert(uploads.length===1,'reopening collected bytes repeated upload');
} finally { await engine?.close(); storage.close(); indexedDB.deleteDatabase(`${name}-resources`); }
}
async function sourceIntegrityCase(name,assert){
const root=crypto.randomUUID(),workspace=crypto.randomUUID(),registry=createRegistry(builtinComponents()),databaseName=`${name}-source-integrity`,storage=createBrowserStorage({name:databaseName}),scope={accountId:crypto.randomUUID(),workspaceId:workspace,projectId:root},original=createDocument(root,[{id:root,parent:null}],[]);let document=original,versions=new Map([[root,1]]),engine,calls=0,raw;
const transport=async request=>{calls++;const body=JSON.parse(request.body);for(const[id,version]of Object.entries(body.expected_versions))assert(version===(versions.get(id)??0),'integrity lost a version fence');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,(versions.get(id)??0)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(row=>row.id),model:{documentFormatVersion:1,projectId:root,complete:false,removedCanvasIds:[],canvases:[...document.canvases.values()],elements:[...document.elements.values()],tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(versions)}}};};
const node=id=>({kind:'element',key:id,record:{id,incarnation:1,structure:'node',componentId:'text',dataVersion:1,ownerCanvasId:'$target',data:{title:id,text:''},geometry:{x:0,y:0,width:200,height:100,rotation:0}}}),bytes=new Blob([[node('first'),node('last')].map(row=>JSON.stringify(row)).join('\n')]),options={scope,registry,document:original,versions:new Map(versions),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);const id=engine.imports.startNdjson(bytes,root);await new Promise(resolve=>{const stop=engine.editor.subscribe((_,snapshot)=>{if(snapshot.runtime.imports.get(id).committedSourceKeys.length===1){stop();resolve();}});});await engine.imports.pause(id);await engine.settle();const before=engine.imports.getJobs().get(id),first=before.stableIds['element:first'];assert(/^[a-f0-9]{64}$/.test(before.source.sha256),'source hash was not bound before publication');await engine.close();const sent=calls;
raw=await new Promise((resolve,reject)=>{const request=indexedDB.open(databaseName);request.onsuccess=()=>resolve(request.result);request.onerror=()=>reject(request.error);});const key=[JSON.stringify([scope.accountId,scope.workspaceId,scope.projectId]),before.source.fileId];
const write=async value=>{const tx=raw.transaction('files','readwrite'),done=new Promise((resolve,reject)=>{tx.oncomplete=resolve;tx.onabort=()=>reject(tx.error);});tx.objectStore('files').put(value,key);await done;};const text=await bytes.text(),changed=text.replaceAll('last','evil');assert(new Blob([changed]).size===bytes.size,'corruption fixture changed its byte length');await write(new Blob([changed]));engine=await openProjectEngine(options);await engine.imports.resume(id);await engine.settle();assert(engine.imports.getJobs().get(id).error==='import_source_changed','same-length source tampering was accepted');assert(engine.getSnapshot().document.elements.size===1&&engine.getSnapshot().document.elements.has(first)&&calls===sent,'source tampering changed the preserved prefix or sent new work');
await write(bytes);await engine.imports.resume(id);await engine.settle();assert(engine.imports.getJobs().get(id).status==='completed'&&document.elements.size===2&&engine.imports.getJobs().get(id).stableIds['element:first']===first,'restoring exact bytes did not resume the original task');
}finally{raw?.close();await engine?.close();storage.close();indexedDB.deleteDatabase(databaseName);}
}
async function deltaJournalCase(name,assert){
const databaseName=`${name}-delta-journal`,scope={accountId:'delta-account',workspaceId:'workspace',projectId:'project'},key=JSON.stringify(Object.values(scope)),rows=Array.from({length:100},(_,i)=>({id:String(i),text:'x'.repeat(1000)})),image=revision=>({formatVersion:1,rootRevision:revision,localRevision:revision,payload:{rows:rows.map((row,i)=>i===0?{...row,text:`version-${revision}`}:row),ledger:{frozenBody:'exact original request bytes'}}});
const done=tx=>new Promise((resolve,reject)=>{tx.oncomplete=resolve;tx.onabort=()=>reject(tx.error);}),read=request=>new Promise((resolve,reject)=>{request.onsuccess=()=>resolve(request.result);request.onerror=()=>reject(request.error);});
let raw=await new Promise((resolve,reject)=>{const request=indexedDB.open(databaseName,1);request.onupgradeneeded=()=>{for(const store of ['metadata','checkpoints','journal','files'])request.result.createObjectStore(store);};request.onsuccess=()=>resolve(request.result);request.onerror=()=>reject(request.error);});
const initial=image(0),checksum=[...new Uint8Array(await crypto.subtle.digest('SHA-256',new TextEncoder().encode(JSON.stringify(initial))))].map(byte=>byte.toString(16).padStart(2,'0')).join('');let tx=raw.transaction(['metadata','checkpoints'],'readwrite'),terminal=done(tx);tx.objectStore('metadata').put({fence:0,throughRevision:0,checkpointRevision:0},key);tx.objectStore('checkpoints').put({image:initial,checksum},[key,0]);await terminal;raw.close();
let storage=createBrowserStorage({name:databaseName,checkpointEvery:3}),leader;
try{
leader=await storage.claim(scope);assert(JSON.stringify(await storage.recover(leader))===JSON.stringify(initial),'legacy checkpoint did not migrate');await storage.commit(leader,{image:image(1),files:[{id:'asset',bytes:new Blob(['immutable'])}]});
raw=await new Promise((resolve,reject)=>{const request=indexedDB.open(databaseName);request.onsuccess=()=>resolve(request.result);request.onerror=()=>reject(request.error);});
const row=await read(raw.transaction('journal').objectStore('journal').get([key,1]));assert(row.journalVersion===1&&!row.image&&JSON.stringify(row).length<JSON.stringify(image(1)).length/10,'small edit repeated the full image');
let code;try{await storage.commit(leader,{image:image(2),files:[{id:'orphan',bytes:new Blob(['orphan'])},{id:'asset',bytes:new Blob(['replacement'])}]});}catch(error){code=error.code;}assert(code==='resource_identity_conflict'&&await storage.readFile(leader,'orphan')===undefined,'delta transaction leaked a file or advanced its cut');assert((await storage.recover(leader)).rootRevision===1,'aborted delta advanced the head');await storage.commit(leader,{image:image(2)});await leader.release();storage.close();storage=createBrowserStorage({name:databaseName,checkpointEvery:3});leader=await storage.claim(scope);assert(JSON.stringify(await storage.recover(leader))===JSON.stringify(image(2)),'fresh adapter did not replay the delta chain');
const intact=await read(raw.transaction('journal').objectStore('journal').get([key,2]));
const write=async value=>{const tx=raw.transaction('journal','readwrite'),wait=done(tx);tx.objectStore('journal').put(value,[key,2]);await wait;};
for(const[kind,expected]of [['payload','recovery_checksum_mismatch'],['base','recovery_delta_base_mismatch'],['future','recovery_delta_interpreter_unavailable']]){const damaged=structuredClone(intact);if(kind==='payload')damaged.patches[0].value='tampered';else if(kind==='base')damaged.baseChecksum='0'.repeat(64);else damaged.journalVersion=99;await write(damaged);code=undefined;try{await storage.recover(leader);}catch(error){code=error.code;}assert(code===expected,`delta ${kind} corruption was not detected: ${code}`);await write(intact);}
await storage.commit(leader,{image:image(3)});assert((await storage.recover(leader)).rootRevision===3,'checkpoint after deltas failed');assert(await read(raw.transaction('journal').objectStore('journal').count())===0,'checkpoint did not compact prior deltas');
const detached=await storage.recover(leader);detached.payload.rows[0].text='caller mutation';const fourth=image(4),fifth=image(5),one=storage.commit(leader,{image:fourth}),two=storage.commit(leader,{image:fifth});fifth.payload.rows[0].text='late mutation';await Promise.all([one,two]);assert(JSON.stringify(await storage.recover(leader))===JSON.stringify(image(5)),'queued commits or recovered callers mutated the cached base');await storage.commit(leader,{image:image(5)});
const oldError=await new Promise(resolve=>{const request=indexedDB.open(databaseName,1);request.onerror=event=>{event.preventDefault();resolve(request.error.name);};request.onsuccess=()=>{request.result.close();resolve('accepted');};});assert(oldError==='VersionError','old database reader accepted the delta format');
}finally{raw?.close();await leader?.release();storage.close();indexedDB.deleteDatabase(databaseName);}
}
async function importBackpressureCase(name,assert){
const root=crypto.randomUUID(),workspace=crypto.randomUUID(),registry=createRegistry(builtinComponents()),database=`${name}-import-pressure`,storage=createBrowserStorage({name:database}),original=createDocument(root,[{id:root,parent:null}],[]);let document=original,versions=new Map([[root,1]]),engine,calls=0,release;const gate=new Promise(resolve=>{release=resolve;});
const transport=async request=>{calls++;const body=JSON.parse(request.body);for(const[id,version]of Object.entries(body.expected_versions))assert(version===(versions.get(id)??0),'pressure lost a version fence');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,(versions.get(id)??0)+1);const participants=Object.keys(body.expected_versions);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(row=>row.id),model:{documentFormatVersion:1,projectId:root,complete:false,removedCanvasIds:[],canvases:[...document.canvases.values()],elements:[...document.elements.values()],tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))}}};};
const options={scope:{accountId:crypto.randomUUID(),workspaceId:workspace,projectId:root},registry,document:original,versions:new Map(versions),storage:{...storage,async commit(owner,batch){if(batch.files?.length)await gate;return storage.commit(owner,batch);}},persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);await engine.persist();const id=engine.imports.startNdjson(new Blob([Array.from({length:500},(_,index)=>JSON.stringify({kind:'element',key:`n-${index}`,record:{id:`n-${index}`,incarnation:1,componentId:'text',dataVersion:1,structure:'node',ownerCanvasId:'$target',data:{title:`n-${index}`,text:''},geometry:{x:0,y:0,width:200,height:100,rotation:0}}})).join('\n')]),root);
await new Promise((resolve,reject)=>{const stop=engine.editor.subscribe((_,snapshot)=>{if(snapshot.rootRevision-engine.getDurability().durableRootRevision>=32){clearTimeout(timer);stop();resolve();}}),timer=setTimeout(()=>{stop();reject(Error('browser import did not reach its durability budget'));},5000);});
const count=engine.imports.getJobs().get(id).committedSourceKeys.length;assert(count>1&&count<500&&calls===0,'source durability did not bound speculative progress or fence the network');await new Promise(resolve=>setTimeout(resolve,20));assert(engine.imports.getJobs().get(id).committedSourceKeys.length===count,'backpressure did not stop the producer');release();await engine.settle();assert(document.elements.size===500&&engine.imports.getJobs().get(id).status==='completed','pressure release did not complete all records');await engine.close();engine=await openProjectEngine(options);assert(engine.getSnapshot().document.elements.size===500&&engine.imports.getJobs().get(id).committedSourceKeys.length===500,'IndexedDB lost the completed prefix');
}finally{release();await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
async function referenceCycleCase(name,assert){
const root=crypto.randomUUID(),workspace=crypto.randomUUID(),registry=createRegistry(builtinComponents()),database=`${name}-reference-cycle`,storage=createBrowserStorage({name:database}),original=createDocument(root,[{id:root,parent:null}],[]);
let document=original,versions=new Map([[root,1]]),engine;
const transport=async request=>{const body=JSON.parse(request.body);for(const[id,version]of Object.entries(body.expected_versions))assert(version===(versions.get(id)??0),'cycle lost a version fence');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,(versions.get(id)??0)+1);const participants=Object.keys(body.expected_versions);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(row=>row.id),model:{documentFormatVersion:1,projectId:root,complete:false,removedCanvasIds:participants.filter(id=>!document.canvases.has(id)),canvases:[...document.canvases.values()].filter(row=>participants.includes(row.id)),elements:[...document.elements.values()].filter(row=>participants.includes(row.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))}}};};
const options={scope:{accountId:crypto.randomUUID(),workspaceId:workspace,projectId:root},registry,document:original,versions:new Map(versions),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
const node=(id,owner,componentId='text')=>({kind:'element',key:id,record:{id,incarnation:1,structure:'node',componentId,dataVersion:1,ownerCanvasId:owner,data:{title:id,text:'',...(componentId==='frame'?{ports:[{id:'in',direction:'input',nodeId:'A',handle:'left'}]}:{})},geometry:{x:0,y:0,width:200,height:100,rotation:0}}});
try{
engine=await openProjectEngine(options);const imported=engine.imports.startNdjson(new Blob([[node('F','$target','frame'),{kind:'canvas',key:'S',record:{id:'S',parent:{canvasId:'$target',entryElementId:'F'}}},node('A','S')].map(row=>JSON.stringify(row)).join('\n')]),root);await engine.settle();const first=engine.imports.getJobs().get(imported),F=first.stableIds['element:F'],A=first.stableIds['element:A'];assert(first.status==='completed'&&first.committedSourceKeys.length===3,'native cycle did not complete atomically');assert(document.elements.get(F).data.ports[0].nodeId===A,'native port kept the source ID');
const copied=await engine.imports.duplicate([{id:F,incarnation:1}]);await engine.settle();const job=engine.imports.getJobs().get(copied),F2=job.stableIds[`element:${F}`],A2=job.stableIds[`element:${A}`];assert(job.status==='completed'&&job.committedSourceKeys.length===3,'copy cycle lost a member');assert(document.elements.get(F2).data.ports[0].nodeId===A2&&A2!==A,'copy port attached to the original child');
await engine.close();engine=await openProjectEngine(options);assert(engine.getSnapshot().document.elements.get(F2).data.ports[0].nodeId===A2,'IndexedDB lost the copied reference');assert(engine.editor.history.undo().status==='applied','cycle undo failed');await engine.settle();assert(!document.elements.has(F2)&&!document.elements.has(A2)&&document.elements.has(F),'cycle undo exposed half a group');assert(engine.editor.history.redo().status==='applied','cycle redo failed');await engine.settle();await engine.close();engine=await openProjectEngine(options);assert(engine.getSnapshot().document.elements.get(F2).data.ports[0].nodeId===A2&&engine.getSnapshot().document.elements.get(A2).incarnation===2,'cycle redo/reopen lost the restored identity');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
async function duplicateRecoveryCase(name,assert){
const root=crypto.randomUUID(),child=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),database=`${name}-duplicate`,storage=createBrowserStorage({name:database});
const node=(id,owner=root,componentId='text')=>({id,incarnation:1,structure:'node',ownerCanvasId:owner,componentId,dataVersion:1,data:{title:id,text:'',...(componentId==='image'?{assetId:'shared',src:'asset:shared',runId:'do-not-run'}:{})},geometry:{x:0,y:0,width:200,height:100,rotation:0}});
const edge={id:'incoming',incarnation:1,structure:'connection',ownerCanvasId:root,componentId:'connection',dataVersion:1,source:{elementId:'C'},target:{elementId:'F'},path:null,data:{title:''}},original=createDocument(root,[{id:root,parent:null},{id:child,parent:{canvasId:root,entryElementId:'F'}}],[node('C'),node('F',root,'frame'),node('A',child,'image'),edge]);
let document=original,versions=new Map([[root,1],[child,1]]),engine;
const model=participants=>({documentFormatVersion:1,projectId:root,complete:false,removedCanvasIds:[],canvases:[...document.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))});
const transport=async request=>{const body=JSON.parse(request.body);for(const[id,version]of Object.entries(body.expected_versions))assert(version===(versions.get(id)??0),'copy lost expected version');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,(versions.get(id)??0)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(Object.keys(body.expected_versions))}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map(versions),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
let queryMismatch=false,queryPublications=0;
const open=async()=>{const current=await openProjectEngine(options);current.editor.subscribe((_,snapshot)=>{queryPublications++;try{const queries=current.editor.getQueries(snapshot.document);if(queries.document!==snapshot.document||queries.revision!==snapshot.revision)queryMismatch=true;}catch{queryMismatch=true;}});return current;};
try{
engine=await open();const id=await engine.imports.duplicate([{id:'F',incarnation:1},{id:'A',incarnation:1}]);
await engine.imports.pause(id);await engine.settle();assert(engine.imports.getJobs().get(id).source.kind==='duplicate-snapshot','copy did not expose its preparing source');await engine.close();engine=await open();assert(engine.imports.getJobs().get(id).status==='paused'&&engine.imports.getJobs().get(id).preparation.selection.length===2,'preparing copy source/selection did not survive IndexedDB');await engine.imports.resume(id);
await new Promise(resolve=>{if(engine.imports.getJobs().get(id).committedSourceKeys.length){resolve();return;}const release=engine.editor.subscribe((_,snapshot)=>{if(snapshot.runtime.imports.get(id).committedSourceKeys.length===1){release();resolve();}});});await engine.imports.pause(id);await engine.settle();
const before=engine.imports.getJobs().get(id),copied=before.stableIds['element:F'];assert(before.status==='paused','copy pause lost');engine.editor.dispatch({type:'element.data',target:{id:copied,incarnation:1},patch:{title:'edited copy'}});await engine.settle();await engine.close();engine=await open();
const restored=engine.imports.getJobs().get(id);assert(restored.source.kind==='duplicate-plan'&&JSON.stringify(restored.stableIds)===JSON.stringify(before.stableIds),'copy source or mapping changed on reopen');await engine.imports.resume(id);await engine.settle();const job=engine.imports.getJobs().get(id),image=document.elements.get(job.stableIds['element:A']),incoming=document.elements.get(job.stableIds['element:incoming']);
assert(job.status==='completed'&&job.failures.length===0,'copy did not complete after reopening');assert(document.elements.get(copied).data.title==='edited copy','copy replay overwrote an edit');assert(image.data.assetId==='shared'&&!Object.hasOwn(image.data,'runId'),'copy reissued image execution');assert(incoming.source.elementId==='C'&&incoming.target.elementId===copied&&incoming.ownerCanvasId===root,'copy lost upper incoming source');assert(document.elements.has('incoming')&&document.canvases.has(job.stableIds[`canvas:${child}`]),'copy changed originals or lost its child canvas');const queries=engine.editor.getQueries();assert(queries.members(job.stableIds[`canvas:${child}`]).includes(job.stableIds['element:A']),'recovered query lost copied member');assert(queries.relations.incidentEdges.get('C').has(job.stableIds['element:incoming']),'query lost upper incoming edge');
assert(engine.editor.history.undo().status==='applied','copy undo failed');await engine.settle();const count=document.elements.size;await engine.close();engine=await open();await engine.settle();assert(document.elements.size===count&&!document.elements.has(job.stableIds['element:A']),'terminal copy revived undone records');assert(!engine.editor.getQueries().relations.childCanvasByEntry.has(copied),'undo retained a stale child binding');assert(!queryMismatch&&queryPublications>0,'publication exposed a document/index revision mismatch');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
async function duplicatePlannerRecoveryCase(name,assert){
const root=crypto.randomUUID(),child=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),database=`${name}-duplicate-planner`,storage=createBrowserStorage({name:database});
const node=(id,owner,componentId='text')=>({id,incarnation:1,structure:'node',ownerCanvasId:owner,componentId,dataVersion:1,data:{title:id,text:''},geometry:{x:0,y:0,width:200,height:100,rotation:0}});
const original=createDocument(root,[{id:root,parent:null},{id:child,parent:{canvasId:root,entryElementId:'F'}}],[node('F',root,'frame'),...Array.from({length:500},(_,i)=>node(`member-${i}`,child))]);
let document=original,versions=new Map([[root,1],[child,1]]),engine;
const transport=async request=>{const body=JSON.parse(request.body);for(const[id,version]of Object.entries(body.expected_versions))assert(version===(versions.get(id)??0),'planner recovery lost expected version');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,(versions.get(id)??0)+1);const participants=Object.keys(body.expected_versions);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:{documentFormatVersion:1,projectId:root,complete:false,removedCanvasIds:[],canvases:[...document.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))}}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map(versions),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);const id=await engine.imports.duplicate([{id:'F',incarnation:1}]);
await new Promise((resolve,reject)=>{const timer=setTimeout(()=>{release();reject(new Error('no durable DFS checkpoint'));},30000);const release=engine.editor.subscribe((_,snapshot)=>{const planner=snapshot.runtime.imports.get(id)?.preparation?.planner;if(planner?.phase==='dfs'&&planner.work.length){clearTimeout(timer);release();resolve();}});});
await engine.imports.pause(id);await engine.settle();const before=engine.imports.getJobs().get(id),saved=JSON.stringify(before.preparation.planner);assert(before.preparation.version===4&&before.preparation.planner.phase==='dfs','did not pause within DFS');assert(before.preparation.planner.sourceToken===before.source.sha256,'checkpoint lost source binding');assert(before.committedSourceKeys.length===0,'preparation published content prematurely');await engine.close();engine=await openProjectEngine(options);
assert(JSON.stringify(engine.imports.getJobs().get(id).preparation.planner)===saved,'IndexedDB changed the DFS checkpoint');let resumed=false;const release=engine.editor.subscribe((_,snapshot)=>{const planner=snapshot.runtime.imports.get(id)?.preparation?.planner;if(planner){assert(planner.steps>=before.preparation.steps,'resume regressed the cursor');assert(!['selection','canvases','members','roots','seed'].includes(planner.phase),'resume repeated an earlier preparation phase');resumed=true;}});
await engine.imports.resume(id);await engine.settle();release();const job=engine.imports.getJobs().get(id);assert(resumed&&job.status==='completed'&&job.failures.length===0,'DFS recovery did not complete');assert(job.committedSourceKeys.length===502&&Object.keys(job.stableIds).length===502,'DFS recovery lost a source record');assert(document.elements.size===1002&&document.canvases.size===3,'DFS recovery duplicated or lost content');const copiedChild=job.stableIds[`canvas:${child}`];for(let i=0;i<500;i++)assert(document.elements.get(job.stableIds[`element:member-${i}`])?.ownerCanvasId===copiedChild,'DFS sibling lost its copied owner');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
async function nativeImportRecoveryCase(name,assert){
const root=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),original=createDocument(root,[{id:root,parent:null}],[]),database=`${name}-native-import`,storage=createBrowserStorage({name:database}),calls=[];let document=original,versions=new Map([[root,1]]),engine;
const model=(complete,participants=[...versions.keys()])=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:participants.filter(id=>!document.canvases.has(id)),canvases:[...document.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))});
const transport=async request=>{calls.push(request);const body=JSON.parse(request.body);for(const[id,version]of Object.entries(body.expected_versions))assert(version===(versions.get(id)??0),'import lost the version fence');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,(versions.get(id)??0)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false,Object.keys(body.expected_versions))}};};
const node=id=>({kind:'element',key:id,record:{id,incarnation:5,structure:'node',ownerCanvasId:'$target',componentId:'text',dataVersion:1,data:{title:id,text:'',taskId:'not copied'},geometry:{x:0,y:0,width:200,height:100,rotation:0}}}),edge={kind:'element',key:'edge',record:{id:'edge',incarnation:1,structure:'connection',componentId:'connection',dataVersion:1,ownerCanvasId:'$target',source:{elementId:'first'},target:{elementId:'second'},path:null,data:{}}},text=[{...node('first'),record:{...node('first').record,componentId:'frame'}},edge,{...node('second'),record:{...node('second').record,ownerCanvasId:'child'}},{kind:'canvas',key:'child',record:{id:'child',parent:{canvasId:'$target',entryElementId:'first'}}}].map(value=>JSON.stringify(value)).join('\n')+'\n{bad}';
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map([[root,1]]),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);const id=engine.imports.startNdjson(new Blob([text]),root);
await new Promise(resolve=>{const release=engine.editor.subscribe((_,snapshot)=>{if(snapshot.runtime.imports.get(id).committedSourceKeys.length===1){release();resolve();}});});await engine.imports.pause(id);await engine.settle();
const first=engine.imports.getJobs().get(id).stableIds['element:first'],fileId=engine.imports.getJobs().get(id).source.fileId;engine.editor.dispatch({type:'element.data',target:{id:first,incarnation:1},patch:{title:'user edit'}});await engine.settle();await engine.close();engine=await openProjectEngine(options);
assert(engine.imports.getJobs().get(id).status==='paused','paused import did not survive IndexedDB');await engine.imports.resume(id);await engine.settle();const job=engine.imports.getJobs().get(id);
assert(job.status==='partial'&&job.committedSourceKeys.length===4&&job.failures.length===1,'partial native import lost progress or failure report');assert(job.stableIds['element:first']===first&&document.elements.get(first).data.title==='user edit','import reallocated an identity or overwrote user content');assert(document.canvases.size===2&&document.elements.size===3&&engine.getSnapshot().runtime.runs.size===0,'native import duplicated an object or launched a run');assert(!Object.hasOwn(document.elements.get(first).data,'taskId'),'native import retained execution metadata');
const leaderRead=await (async()=>{await engine.close();const leader=await storage.claim(options.scope);try{return await storage.readFile(leader,fileId);}finally{await leader.release();}})();assert(await leaderRead.text()===text,'original source bytes were not durably stored');engine=await openProjectEngine(options);
assert(engine.editor.history.undo().status==='applied','import batch undo failed');await engine.settle();assert(document.elements.size===1&&document.elements.has(first),'undo did not preserve the interrupted user edit');await engine.close();engine=await openProjectEngine(options);await engine.settle();assert(document.elements.size===1&&engine.getSnapshot().document.elements.size===1,'terminal job revived records removed by undo');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
async function elisionRecoveryCase(name,assert){
for(const structural of [false,true]){
const root=crypto.randomUUID(),child=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),node=(id,ownerCanvasId=root)=>({id,incarnation:1,ownerCanvasId,componentId:'text',dataVersion:1,structure:'node',data:{title:id,text:''},geometry:{x:0,y:0,width:200,height:100,rotation:0}}),original=createDocument(root,[{id:root,parent:null}],[node('a')]),database=`${name}-elision-${structural}`,storage=createBrowserStorage({name:database}),calls=[];let document=original,versions=new Map([[root,1]]),engine;
const model=(complete,participants=[...versions.keys()])=>({documentFormatVersion:1,projectId:root,complete,removedCanvasIds:participants.filter(id=>!document.canvases.has(id)),canvases:[...document.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...document.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(document.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)]))});
const transport=async request=>{calls.push(request);if(request.method==='GET')return{status:200,body:{model:model(true)}};const body=JSON.parse(request.body);for(const[id,version]of Object.entries(body.expected_versions))assert(version===(versions.get(id)??0),'elision restore lost the actual server version');for(const operation of body.operations)document=applyChanges(document,operation.changes);for(const id of Object.keys(body.expected_versions))versions.set(id,(versions.get(id)??0)+1);return{status:200,body:{protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false,Object.keys(body.expected_versions))}};};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document:original,versions:new Map(versions),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
const reopen=async()=>{await engine.close();engine=await openProjectEngine(options);};
try{
engine=await openProjectEngine(options);const created=engine.editor.transaction(tx=>{tx.dispatch({type:'element.create',element:structural?{...node('temporary'),componentId:'frame'}:node('temporary')});if(structural){tx.dispatch({type:'canvas.create',canvas:{id:child,parent:{canvasId:root,entryElementId:'temporary'}}});tx.dispatch({type:'element.create',element:node('member',child)});}}),removed=engine.editor.dispatch({type:'element.remove',target:{id:'temporary',incarnation:1}});await engine.settle();
const ids=[...created.operationIds,...removed.operationIds];assert(ids.length===2&&calls.length===0,'closed unsent lifetime still sent a request');assert(engine.getSnapshot().runtime.ledger.requests.size===0&&engine.getSnapshot().history.undo.length===2,'local elision forged a request or collapsed history');
await reopen();for(const id of ids)assert(engine.getSnapshot().runtime.ledger.operations.get(id).resolution==='elided','local completion proof did not survive IndexedDB');assert(engine.getSnapshot().document.tombstones.get('temporary')===1&&!engine.getSnapshot().runtime.acknowledged.tombstones.has('temporary'),'local lifecycle contaminated the server baseline');
const localUndo=engine.editor.history.undo(),localRedo=engine.editor.history.redo();assert(localUndo.status==='applied'&&localRedo.status==='applied','local traversal pair failed');ids.push(...localUndo.operationIds,...localRedo.operationIds);await engine.settle();await reopen();assert(calls.length===0&&engine.getSnapshot().document.tombstones.get('temporary')===2,'repeated local elision lost its next incarnation');
engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{title:'unrelated ACK'}});await engine.settle();const session=await engine.prepareRecovery();assert((await session.apply()).status==='applied','canonical refresh after elision failed');await engine.settle();await reopen();assert(engine.getSnapshot().document.tombstones.get('temporary')===2,'ACK or fresh canonical read lost the local high-water mark');
assert(engine.editor.history.undo().status==='applied','unrelated undo failed');await engine.settle();assert(engine.editor.history.undo().status==='applied','elided deletion undo failed');await engine.settle();assert(document.elements.get('temporary').incarnation===3,'undo reused the canceled incarnation');if(structural)assert(document.canvases.has(child)&&document.elements.get('member').incarnation===3,'partition undo lost its members or lifecycle');
assert(engine.editor.history.redo().status==='applied','elided deletion redo failed');await engine.settle();assert(!document.elements.has('temporary')&&document.tombstones.get('temporary')===3,'redo did not archive the fresh life');await reopen();assert(engine.getSnapshot().document.tombstones.get('temporary')===3,'server/local lifecycle merge regressed after reopening');assert(calls.filter(call=>call.method==='POST').length===4,'elision history was retransmitted');for(const request of engine.getSnapshot().runtime.ledger.requests.values())assert(!request.operationIds.some(id=>ids.includes(id)),'locally elided IDs appeared in a backend request');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
}
async function batchRecoveryCase(name,assert){
const root=crypto.randomUUID(),workspace=crypto.randomUUID(),account=crypto.randomUUID(),registry=createRegistry(builtinComponents()),node={id:'a',incarnation:1,ownerCanvasId:root,componentId:'text',dataVersion:1,structure:'node',geometry:{x:0,y:0,width:200,height:100,rotation:0},data:{title:'a',text:''}},document=createDocument(root,[{id:root,parent:null}],[node]);
const firstTitle='first '.repeat(100),secondTitle='second '.repeat(100);
const database=`${name}-batch-recovery`,storage=createBrowserStorage({name:database}),calls=[],receipts=new Map();let server=document,version=1,lose=true,engine;
const transport=async request=>{
calls.push(request);
if(request.method==='GET')return{status:200,body:receipts.get(request.path.split('/').at(-1))};
const body=JSON.parse(request.body);assert(body.expected_versions[root]===version,'batch lost the server version fence');
for(const operation of body.operations)server=applyChanges(server,operation.changes);version++;
const receipt={protocol_version:2,request_id:request.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:{documentFormatVersion:1,projectId:root,complete:false,removedCanvasIds:[],canvases:[...server.canvases.values()],elements:[...server.elements.values()],tombstones:Object.fromEntries(server.tombstones),versions:{[root]:version}}};receipts.set(request.requestId,receipt);
if(lose){lose=false;return{status:502,body:{error:'lost_batch_ack'}};}return{status:200,body:receipt};
};
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document,versions:new Map([[root,1]]),storage,persistence:createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport})};
try{
engine=await openProjectEngine(options);
const first=engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{title:firstTitle}}),second=engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{title:secondTitle}});
assert(engine.getSnapshot().document.elements.get('a').data.title===secondTitle,'local publication waited for batch preparation');await engine.settle();
const frozen=[...engine.getSnapshot().runtime.ledger.requests.values()][0];
assert(calls.length===1&&frozen.operationIds.length===2&&frozen.operationIds[0]===first.operationIds[0]&&frozen.operationIds[1]===second.operationIds[0],'logical edits were not covered by one ordered request');assert(version===2,'batch advanced a canvas version per operation');const wire=JSON.parse(frozen.body).operations,raw=frozen.operationIds.map(id=>({id,changes:engine.getSnapshot().runtime.ledger.operations.get(id).intent.changes}));assert(JSON.stringify(wire).length<JSON.stringify(raw).length,'eligible long field batch was not compacted');assert(wire.at(-1).changes[0].before==='a'&&wire.at(-1).changes[0].after===secondTitle,'wire projection did not retain original before and final after');
await engine.close();engine=await openProjectEngine({...options,clock:{now:()=>Date.now()+100000,setTimer:(fn,ms)=>setTimeout(fn,ms),clearTimer:id=>clearTimeout(id)}});await new Promise(resolve=>setTimeout(resolve,10));await engine.settle();
assert(calls.length===2&&calls[1].method==='GET'&&calls[1].path.endsWith(frozen.id),'batch recovery sent a new request');
assert(engine.getSnapshot().runtime.ledger.requests.get(frozen.id).body===frozen.body&&engine.getSnapshot().history.undo.length===2,'batch changed frozen bytes or collapsed history units');
for(const id of frozen.operationIds)assert(engine.getSnapshot().runtime.ledger.operations.get(id).phase==='resolved','batch ACK omitted a logical operation');
assert(engine.editor.history.undo().status==='applied','second edit undo failed');await engine.settle();assert(server.elements.get('a').data.title===firstTitle,'batch undo lost the intermediate historical value');
assert(engine.editor.history.undo().status==='applied','first edit undo failed');await engine.settle();assert(server.elements.get('a').data.title==='a','batch first inverse failed');
assert(engine.editor.history.redo().status==='applied','first edit redo failed');await engine.settle();assert(server.elements.get('a').data.title===firstTitle,'batch redo skipped an independent history unit');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
async function protocolRecoveryCase(name,assert){
const root='10000000-0000-4000-8000-000000000001',workspace='10000000-0000-4000-8000-000000000003',account='10000000-0000-4000-8000-000000000004';
const registry=createRegistry(builtinComponents()),document=createDocument(root,[{id:root,parent:null}],[{id:'a',incarnation:1,ownerCanvasId:root,componentId:'text',dataVersion:1,structure:'node',geometry:{x:0,y:0,width:260,height:200,rotation:0},data:{title:'a',text:''}}]);
const calls=[],receipts=new Map();let server=document;
const transport=async input=>{
calls.push(input);
if(input.method==='POST'){
const batch=JSON.parse(input.body);server=applyChanges(server,batch.operations[0].changes);
receipts.set(batch.request_id,{protocol_version:2,request_id:batch.request_id,status:'applied',operation_ids:batch.operations.map(operation=>operation.id),model:{documentFormatVersion:1,projectId:root,canvases:[...server.canvases.values()],elements:[...server.elements.values()],tombstones:{},versions:{[root]:2},complete:false,removedCanvasIds:[]}});
return{status:502,body:{error:'upstream_timeout'}};
}
return{status:200,body:receipts.get(input.path.split('/').at(-1))};
};
const storage=createBrowserStorage({name:`${name}-protocol`}),persistence=createFoundryPersistence({workspaceId:workspace,rootId:root,registry,transport});
const options={scope:{accountId:account,workspaceId:workspace,projectId:root},registry,document,versions:new Map([[root,1]]),storage,persistence};let engine;
try{
engine=await openProjectEngine(options);engine.editor.dispatch({type:'element.data',target:{id:'a',incarnation:1},patch:{title:'durable-v2'}});await engine.settle();
const request=[...engine.getSnapshot().runtime.ledger.requests.values()][0];assert(request.protocolVersion===2,'new protocol metadata was replaced by a legacy default');await engine.close();
engine=await openProjectEngine({...options,clock:{now:()=>Date.now()+100000,setTimer:(callback,delay)=>setTimeout(callback,delay),clearTimer:handle=>clearTimeout(handle)}});
await new Promise(resolve=>setTimeout(resolve,10));await engine.settle();
assert(calls.length===2&&calls[1].method==='GET','protocol 2 recovery must query the original request');
assert(engine.getSnapshot().document.elements.get('a').data.title==='durable-v2'&&engine.getSnapshot().history.undo.length===1,'protocol recovery changed working content or duplicated history');
const recovered=engine.getSnapshot().runtime.ledger.requests.get(request.id);assert(recovered.body===request.body&&recovered.outcome==='applied','protocol recovery rewrote request bytes or lost the receipt');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-protocol`)}
}
async function engineRecoveryCase(name, assert) {
const scope = { accountId: 'account', workspaceId: 'workspace', projectId: 'project' }, registry = createRegistry(builtinComponents());
const document = createDocument('project', [{ id: 'canvas', parent: null }], [{ id: 'element', incarnation: 1, ownerCanvasId: 'canvas', componentId: 'text', dataVersion: 1, structure: 'node', geometry: { x: 0, y: 0, width: 100, height: 100, rotation: 0 }, data: { title: 'initial', text: '' } }]);
let server = document; const receipts = new Map(), calls = [];
const port = {
compile(intent, requestId, snapshot) { return { payload: { intent, requestId }, canvasIds: intent.canvasIds, expectedVersions: Object.fromEntries(snapshot.runtime.versions) }; },
async submit(request) { calls.push(['submit', request]); const intent = JSON.parse(request.body).intent; server = applyChanges(server, intent.changes); receipts.set(request.id, { document: server, versions: new Map([['canvas', 2]]) }); return { outcome: 'unresolved' }; },
async query(request) { calls.push(['query', request]); return { outcome: 'applied', canonical: { receiptId: request.id } }; },
canonical(request) { return receipts.get(request.id); },
};
const storage = createBrowserStorage({ name: `${name}-engine`, checkpointEvery: 4 }), options = { scope, registry, document, versions: new Map([['canvas', 1]]), storage, persistence: port };
let engine;
try {
engine = await openProjectEngine(options);
const detach = engine.sessions.attach('view');
engine.editor.dispatch({ type: 'element.data', target: { id: 'element', incarnation: 1 }, patch: { title: 'durable edit' } });
detach(); await engine.settle();
assert(calls.length === 1, 'detaching last view must not abort persistent work');
const request = calls[0][1];
await engine.close();
const futureClock = { now: () => Date.now() + 100_000, setTimer: (callback, delay) => setTimeout(callback, delay), clearTimer: handle => clearTimeout(handle) };
engine = await openProjectEngine({ ...options, clock: futureClock });
await new Promise(resolve => setTimeout(resolve, 10)); await engine.settle();
assert(calls.length === 2 && calls[1][0] === 'query', 'recovered dispatched request must query before a new submit');
assert(calls[1][1].id === request.id && calls[1][1].body === request.body, 'request ID/body must survive real IndexedDB recovery exactly');
assert(engine.getSnapshot().document.elements.get('element').data.title === 'durable edit', 'recovered pending changes must not replay twice');
assert(engine.getSnapshot().history.undo.length === 1 && engine.getSnapshot().operations.size === 1, 'history and ledger must recover the same state cut');
assert(engine.sessions.getSnapshot().views.size === 0, 'transient views must not be recovered as document data');
assert(engine.getDurability().rootRevision === engine.getDurability().durableRootRevision, 'final receipt and replay must cross the durable barrier');
} finally { await engine?.close(); storage.close(); indexedDB.deleteDatabase(`${name}-engine`); }
}
async function loadingImportRecoveryCase(name,assert){
const root=crypto.randomUUID(),child=crypto.randomUUID(),scope={accountId:crypto.randomUUID(),workspaceId:crypto.randomUUID(),projectId:root},registry=createRegistry(builtinComponents()),node=(id,owner=root)=>({id,incarnation:1,ownerCanvasId:owner,componentId:'text',dataVersion:1,structure:'node',data:{title:id,text:''},geometry:{x:0,y:0,width:100,height:80,rotation:0}}),frame={...node('F'),componentId:'frame'},canvases=[{id:root,parent:null},{id:child,parent:{canvasId:root,entryElementId:'F'}}],full=createDocument(root,canvases,[frame,node('A'),node('C',child)]),prefix=createDocument(root,canvases,[frame,node('C',child)]),loading={version:5,sourceToken:'browser-import-cut',declaredCanvasIds:[root,child],sourceCanvases:canvases,receivedElements:2,totalElements:3,canvasElementCounts:{[root]:2,[child]:1},elementOwners:{F:root,A:root,C:child}},database=name+'-loading-import',storage=createBrowserStorage({name:database}),versions=new Map([[root,1],[child,1]]);let server=full,engine;const calls=[];
const persistence=createFoundryPersistence({registry,workspaceId:scope.workspaceId,rootId:root,transport:async input=>{assert(input.method==='POST','unexpected import read');calls.push(input);const body=JSON.parse(input.body),participants=Object.keys(body.expected_versions);for(const id of participants)assert(body.expected_versions[id]===versions.get(id),'stale partial import version');server=applyChanges(server,body.operations.flatMap(operation=>operation.changes));for(const id of participants)versions.set(id,(versions.get(id)??0)+1);return{status:200,body:{protocol_version:2,request_id:input.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:{documentFormatVersion:1,projectId:root,complete:false,canvases:[...server.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...server.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(server.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)])),removedCanvasIds:[]}}};}});
const options={scope,registry,storage,persistence,document:prefix,versions:new Map(versions),loading};
try{engine=await openProjectEngine(options);assert(!engine.imports.canStart(root).enabled&&engine.imports.canStart(child).enabled,'import admission ignored coverage');const id=engine.imports.startNdjson(new Blob([JSON.stringify({kind:'element',key:'imported',record:node('imported','$target')})+'\n']),child);await engine.imports.pause(id);await engine.settle();await engine.close();engine=await openProjectEngine(options);assert(engine.imports.getJobs().get(id).status==='paused','paused v11 task did not reopen');await engine.imports.resume(id);await engine.settle();const job=engine.imports.getJobs().get(id),mapped=job.stableIds['element:imported'];assert(job.status==='completed'&&server.elements.has(mapped),'recovered source bytes did not import before tail');assert(engine.loading.getState().version===5,'import replaced the partial read');await engine.close();engine=await openProjectEngine(options);assert(engine.imports.getJobs().get(id).stableIds['element:imported']===mapped,'reopening reallocated imported identities');assert(engine.editor.history.undo().status==='applied','partial import history did not reopen');await engine.settle();assert(!server.elements.has(mapped),'partial import undo left a server object');assert(calls.length===2,'import recovery submitted duplicate operations');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
async function loadingAssetRecoveryCase(name,assert){
const root=crypto.randomUUID(),child=crypto.randomUUID(),scope={accountId:crypto.randomUUID(),workspaceId:crypto.randomUUID(),projectId:root},registry=createRegistry(builtinComponents()),node=(id,owner=root)=>({id,incarnation:1,ownerCanvasId:owner,componentId:'text',dataVersion:1,structure:'node',data:{title:id,text:''},geometry:{x:0,y:0,width:100,height:80,rotation:0}}),frame={...node('F'),componentId:'frame'},canvases=[{id:root,parent:null},{id:child,parent:{canvasId:root,entryElementId:'F'}}],full=createDocument(root,canvases,[frame,node('A'),node('C',child)]),prefix=createDocument(root,canvases,[frame,node('C',child)]),loading={version:6,sourceToken:'browser-import-cut',declaredCanvasIds:[root,child],sourceCanvases:canvases,receivedElements:2,totalElements:3,canvasElementCounts:{[root]:2,[child]:1},elementOwners:{F:root,A:root,C:child}},database=name+'-loading-asset',storage=createBrowserStorage({name:database}),versions=new Map([[root,1],[child,1]]);let server=full,engine;const calls=[];
const persistence=createFoundryPersistence({registry,workspaceId:scope.workspaceId,rootId:root,transport:async input=>{assert(input.method==='POST','unexpected import read');calls.push(input);const body=JSON.parse(input.body),participants=Object.keys(body.expected_versions);for(const id of participants)assert(body.expected_versions[id]===versions.get(id),'stale partial import version');server=applyChanges(server,body.operations.flatMap(operation=>operation.changes));for(const id of participants)versions.set(id,(versions.get(id)??0)+1);return{status:200,body:{protocol_version:2,request_id:input.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:{documentFormatVersion:1,projectId:root,complete:false,canvases:[...server.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...server.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:Object.fromEntries(server.tombstones),versions:Object.fromEntries(participants.map(id=>[id,versions.get(id)])),removedCanvasIds:[]}}};}});
let time=0;const receipts=new Map(),uploads=[],assets={sourceURL:id=>`https://assets.example/${id}`,displayURL:id=>`/assets/${id}`,async upload(request,bytes){uploads.push(request);const body=JSON.parse(request.body),stored={id:body.resource_id,url:assets.sourceURL(body.resource_id),bytes:bytes.size,width:20,height:10,mime_type:bytes.type};receipts.set(request.id,stored);throw Error('lost image ACK');},async query(request){return{outcome:'applied',canonical:receipts.get(request.id)};},accept:(_request,status)=>status.canonical},clock={now:()=>time,setTimer:()=>0,clearTimer(){}};
const options={scope,registry,storage,persistence,assets,clock,document:prefix,versions:new Map(versions),loading};
try{engine=await openProjectEngine(options);assert(!engine.canStageFiles(root).enabled&&engine.canStageFiles(child).enabled,'image admission ignored coverage');let resource;const result=engine.stageFiles([{name:'picture.png',bytes:new Blob(['durable image bytes'],{type:'image/png'})}],([ref],tx)=>{resource=ref;tx.dispatch({type:'element.create',element:{...node('picture',child),componentId:'image',data:{title:'Picture',text:'',assetId:ref.id,src:ref.src}}});});assert(result.status==='applied'&&engine.getSnapshot().document.elements.has('picture'),'image did not display locally');await engine.settle();assert(uploads.length===1&&calls.length===0,'image write bypassed upload dependency');const request=uploads[0],display=await engine.displayResource(resource.id);assert((await(await fetch(display.url)).blob()).size>0,'saved Blob could not display');display.release();await engine.close();time=10_000;engine=await openProjectEngine(options);await engine.settle();assert(uploads.length===1&&engine.getSnapshot().runtime.ledger.requests.get(request.id).body===request.body,'lost ACK changed the upload identity');assert(server.elements.get('picture').data.assetId===resource.id,'reopened upload did not release the image document');assert(engine.editor.history.undo().status==='applied','image undo failed');await engine.settle();assert(!server.elements.has('picture'),'image undo left a server element');assert(engine.editor.history.redo().status==='applied','image redo failed');await engine.settle();assert(server.elements.get('picture').incarnation===2&&uploads.length===1,'image redo uploaded again or reused the deleted incarnation');assert((await engine.loading.complete(full,new Map([[root,1],[child,1]]),{sourceToken:loading.sourceToken})).status==='applied','old tail could not complete with stored image');await engine.settle();assert(engine.getSnapshot().document.elements.get('picture').incarnation===2&&engine.getSnapshot().runtime.resources.get(resource.id).stored,'old tail lost resource/history');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(database);}
}
async function loadingRecoveryCase(name,assert,coverage=false,scoped=false,structural=false){
const format=structural?10:scoped?9:coverage?8:7;name+=structural?"-structural":scoped?"-scoped":coverage?"-coverage":"-legacy";
const root=crypto.randomUUID(),scope={accountId:crypto.randomUUID(),workspaceId:crypto.randomUUID(),projectId:root},registry=createRegistry(builtinComponents()),node=id=>({id,incarnation:1,ownerCanvasId:root,componentId:'text',dataVersion:1,structure:'node',data:{title:id,text:'original'},geometry:{x:0,y:0,width:120,height:80,rotation:0}}),a=node('A'),b=node('B'),canvases=[{id:root,parent:null}],prefix=createDocument(root,canvases,[a]),full=createDocument(root,canvases,[a,b]),versions=new Map([[root,1]]),loading={...(coverage?{version:structural?4:scoped?3:2,canvasElementCounts:{[root]:2},...(structural?{elementOwners:{A:root,B:root}}:{})}:{version:1}),sourceToken:'browser-captured-read',declaredCanvasIds:[root],receivedElements:1,totalElements:2};
const adapter=createBrowserStorage({name:`${name}-loading`,checkpointEvery:3});let ownerForTest;const storage={...adapter,async claim(scope){ownerForTest=await adapter.claim(scope);return ownerForTest;}};const calls=[],receipts=new Map();let server=full,version=1,engine;
const model=(complete=true)=>({documentFormatVersion:1,projectId:root,complete,canvases,elements:[...server.elements.values()],tombstones:{},versions:{[root]:version},removedCanvasIds:[]});
const transport=async input=>{if(input.method==='GET')return{status:200,body:{model:model()}};calls.push(input);const body=JSON.parse(input.body);assert(body.expected_versions[root]===version,'loading writer used a stale version');server=applyChanges(server,body.operations.flatMap(operation=>operation.changes));version++;const receipt={protocol_version:2,request_id:input.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(false)};receipts.set(input.requestId,receipt);return{status:200,body:receipt};};
const persistence=createFoundryPersistence({registry,workspaceId:scope.workspaceId,rootId:root,transport}),open=()=>openProjectEngine({scope,registry,document:prefix,versions,loading,storage,persistence,createId:()=>crypto.randomUUID()});
try{engine=await open();const target={id:'A',incarnation:1};assert(engine.editor.dispatch({type:'element.data',target,patch:{text:'local before tail'}}).status==='applied','loading field rejected');await engine.settle();assert(calls.length===0&&engine.getSnapshot().runtime.ledger.requests.size===0,'loading prefix froze or sent a request');await engine.close();
const owner=await storage.claim(scope),image=await storage.recover(owner);assert(image.formatVersion===format,'loading state did not require its versioned recovery envelope');decodeRecovery(image,registry);await owner.release();engine=await open();assert(engine.getSnapshot().document.elements.get('A').data.text==='local before tail','loading edit lost after IndexedDB reopen');assert(engine.editor.history.undo().status==='applied'&&engine.editor.history.redo().status==='applied','loading history did not reopen');await engine.settle();assert(calls.length===0,'history traversal escaped the loading hold');
const before=engine.getDurability().durableRootRevision,nativePut=IDBObjectStore.prototype.put;let injected=false,failure;
IDBObjectStore.prototype.put=function(value,key){const request=nativePut.call(this,value,key);if(!injected&&this.name==='checkpoints'&&value?.image?.formatVersion!==format){injected=true;throw Error('loading_full_cut_abort');}return request;};
try{await engine.loading.complete(full,versions,{sourceToken:loading.sourceToken});}catch(error){failure=error.message;}finally{IDBObjectStore.prototype.put=nativePut;}
assert(injected&&failure==='loading_full_cut_abort','completion did not reach forced checkpoint rollback');const partial=await adapter.recover(ownerForTest);assert(partial.rootRevision===before&&partial.formatVersion===format&&calls.length===0,'aborted full checkpoint released loading writes or lost the partial cut');
await engine.persist();await engine.settle();
assert(!engine.loading.getState()&&server.elements.get('A').data.text==='local before tail'&&engine.getSnapshot().document.elements.size===2,'full tail overwrote the local prefix');await engine.close();engine=await open();await engine.settle();assert(!engine.loading.getState()&&engine.getSnapshot().document.elements.size===2,'completed recovery reopened as partial');
const database=await new Promise((resolve,reject)=>{const request=indexedDB.open(`${name}-loading`);request.onsuccess=()=>resolve(request.result);request.onerror=()=>reject(request.error);});
try{const transaction=database.transaction(['checkpoints','journal'],'readonly'),read=store=>new Promise((resolve,reject)=>{const request=transaction.objectStore(store).getAll();request.onsuccess=()=>resolve(request.result);request.onerror=()=>reject(request.error);});const cuts=(await Promise.all([read('checkpoints'),read('journal')])).flat();assert(cuts.every(cut=>(cut.image?.formatVersion??cut.header?.formatVersion)<7),'completed storage still requires a partial journal base');}finally{database.close();}
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(`${name}-loading`);}
}
async function scopedAckRecoveryCase(name,assert,structural=false){
name+=structural?"-structural":"-fields";
const root=crypto.randomUUID(),child=crypto.randomUUID(),scope={accountId:crypto.randomUUID(),workspaceId:crypto.randomUUID(),projectId:root},registry=createRegistry(builtinComponents()),node=(id,owner=root)=>({id,incarnation:1,ownerCanvasId:owner,componentId:'text',dataVersion:1,structure:'node',data:{title:id,text:'original'},geometry:{x:0,y:0,width:120,height:80,rotation:0}}),a=node('A'),b=node('B'),f={...node('F'),componentId:'frame'},c=node('C',child),edge={id:'E',incarnation:1,ownerCanvasId:root,componentId:'connection',dataVersion:1,structure:'connection',data:{},source:{elementId:'A'},target:{elementId:'C'},path:null},canvases=[{id:root,parent:null},{id:child,parent:{canvasId:root,entryElementId:'F'}}],prefix=createDocument(root,canvases,[a,f,c]),full=createDocument(root,canvases,[a,b,f,c,edge]),versions=new Map([[root,1],[child,1]]),loading={version:structural?4:3,...(structural?{elementOwners:{A:root,B:root,F:root,C:child,E:root}}:{}),sourceToken:'scoped-browser-source',declaredCanvasIds:[root,child],receivedElements:3,totalElements:5,canvasElementCounts:{[root]:4,[child]:1}},storage=createBrowserStorage({name:name+'-scope-ack'}),receipts=new Map(),bodies=[];
let server=full,currentVersions=new Map(versions),time=0,engine;
const model=(participants,complete=false)=>({documentFormatVersion:1,projectId:root,complete,canvases:[...server.canvases.values()].filter(canvas=>participants.includes(canvas.id)),elements:[...server.elements.values()].filter(element=>participants.includes(element.ownerCanvasId)),tombstones:{},versions:Object.fromEntries(participants.map(id=>[id,currentVersions.get(id)])),removedCanvasIds:[]});
const transport=async input=>{if(input.method==='GET')return{status:200,body:receipts.get(input.path.split('/').at(-1))};const body=JSON.parse(input.body),participants=Object.keys(body.expected_versions);bodies.push(input.body);for(const id of participants)assert(body.expected_versions[id]===currentVersions.get(id),'scoped request used a stale owner version');server=applyChanges(server,body.operations.flatMap(operation=>operation.changes));for(const id of participants)currentVersions.set(id,currentVersions.get(id)+1);const receipt={protocol_version:2,request_id:input.requestId,status:'applied',operation_ids:body.operations.map(operation=>operation.id),model:model(participants)};receipts.set(input.requestId,receipt);if(bodies.length===1)throw Error('lost scoped ACK');return{status:200,body:receipt};};
const persistence=createFoundryPersistence({registry,workspaceId:scope.workspaceId,rootId:root,transport}),clock={now:()=>time,setTimer:()=>0,clearTimer(){}},open=()=>openProjectEngine({scope,registry,document:prefix,versions,loading,storage,persistence,clock});
try{engine=await open();engine.editor.dispatch({type:'element.data',target:{id:'A',incarnation:1},patch:{text:'root waits'}});engine.editor.dispatch(structural?{type:'element.create',element:{...node('born-browser',child),data:{title:'Born',text:'child applied'}}}:{type:'element.data',target:{id:'C',incarnation:1},patch:{text:'child applied'}});await engine.settle();assert(bodies.length===1&&server.elements.get(structural?'born-browser':'C')?.data.text==='child applied'&&server.elements.get('A').data.text==='original','incomplete owner blocked another certified owner or sent prematurely');const request=[...engine.getSnapshot().runtime.ledger.requests.values()][0];await engine.close();time=1000;engine=await open();await engine.settle();assert(bodies.length===1&&engine.getSnapshot().runtime.ledger.requests.get(request.id).body===request.body,'reopen repeated the lost-ACK request');assert(engine.getSnapshot().runtime.versions.get(child)===2&&engine.getSnapshot().runtime.loadingRead.versions.get(child)===1,'read and ACK baselines did not recover independently');
assert(engine.loading.accept(full,versions,{...loading,receivedElements:5}).status==='applied','old source tail did not reconcile');await engine.settle();assert(bodies.length===2&&server.elements.get('A').data.text==='root waits','newly complete owner did not wake');assert((await engine.loading.complete(full,versions,{sourceToken:loading.sourceToken})).status==='applied','original source could not complete after scoped ACKs');await engine.settle();assert(engine.getSnapshot().runtime.loadingRead===undefined&&engine.getSnapshot().document.elements.get(structural?'born-browser':'C')?.data.text==='child applied'&&engine.getSnapshot().document.elements.get('A').data.text==='root waits','old completion replaced ACKs or retained the source');
}finally{await engine?.close();storage.close();indexedDB.deleteDatabase(name+'-scope-ack');}
}
export async function run() {
const results = [], name = `headless-test-${crypto.randomUUID()}`;
const scope = { accountId: 'test-account', workspaceId: 'test-workspace', projectId: 'test-project' };
const storage = createBrowserStorage({ name, checkpointEvery: 3 });
const image = revision => ({ formatVersion: 1, rootRevision: revision, localRevision: Math.min(revision, 1), payload: { document: { title: `v${revision}` }, ledger: { frozenBody: '{"request_id":"stable"}' }, history: ['history-id'] } });
const assert = (condition, message) => { if (!condition) throw new Error(message); };
const rejects = async (callback, message) => { try { await callback(); } catch (error) { assert(error.message === message, `expected ${message}, received ${error.message}`); return; } throw new Error(`expected ${message}`); };
let leader, other;
try {
await depotAdoptionRaceCase(assert);results.push('real IndexedDB GC and a gated older Root cannot consume or release bytes belonging to a newer adoption cut');
await depotFilesCase(assert);results.push('stored uploads use shared-byte file pointers with original MIME, atomic promotion rollback, logical inventory and independent identity eviction/rehydration');
await depotFilesUpgradeCase(assert);results.push('DB v3 original files/checkpoints upgrade to v4 and lazily promote through an idempotent Root cut');
await assetAdoptionStorageCase(assert);results.push('cross-project adoption uses fresh authorization, survives workspace/IndexedDB reopening and undo/redo, and preserves closed destination pins without project-local byte copies');
await assetDepotStorageCase(assert);results.push('workspace depot atomically shares digest bytes, preserves closed-project pins, blocks unknown/stale coverage and rolls back failed Root cuts');
leader = await storage.claim(scope);
assert(await storage.recover(leader) === null, 'empty scope must be distinguishable from a corrupt checkpoint');
await rejects(() => storage.claim(scope), 'storage_scope_in_use');
await storage.commit(leader, { image: image(0), files: [{ id: 'asset', bytes: new Blob(['original']) }] });
assert((await storage.recover(leader)).rootRevision === 0, 'initial checkpoint should recover');
assert(await (await storage.readFile(leader, 'asset')).text() === 'original', 'committed bytes should survive');
results.push('initial checkpoint and file bytes commit together; duplicate leader rejected');
await storage.commit(leader, { image: image(1) });
await storage.commit(leader, { image: image(1) });
await rejects(() => storage.commit(leader, { image: { ...image(1), payload: null } }), 'storage_revision_conflict');
await rejects(() => storage.commit(leader, { image: image(3) }), 'storage_revision_gap');
await rejects(() => storage.commit(leader, { image: { ...image(2), formatVersion: 14 } }), 'unsupported_recovery_version');
results.push('same-cut retry is idempotent; conflicting, skipped and future-format cuts reject');
// The first put succeeds before a later duplicate resource aborts the whole transaction.
await rejects(() => storage.commit(leader, { image: image(2), files: [{ id: 'orphan', bytes: new Blob(['must-rollback']) }, { id: 'asset', bytes: new Blob(['replacement']) }] }), 'resource_identity_conflict');
assert(await storage.readFile(leader, 'orphan') === undefined, 'aborted resource bytes must roll back');
assert((await storage.recover(leader)).rootRevision === 1, 'aborted document/history/ledger cut must not advance');
assert(await (await storage.readFile(leader, 'asset')).text() === 'original', 'existing resource bytes must remain immutable');
results.push('late transaction failure rolls back new bytes and the entire state cut');
await storage.commit(leader, { image: image(2) });
const originalFence = leader.fence;
await leader.release();
other = createBrowserStorage({ name, checkpointEvery: 3 });
const next = await other.claim(scope);
await rejects(() => storage.commit(leader, { image: image(3) }), 'storage_leader_lost');
assert(next.fence > originalFence, 'reacquire must advance fencing generation');
assert((await other.recover(next)).rootRevision === 2, 'new adapter must replay the durable journal');
await other.commit(next, { image: image(3) });
assert((await other.recover(next)).payload.document.title === 'v3', 'compacted checkpoint must cover the same document/history/ledger cut');
leader = next;
results.push('fresh adapter replays journal; released handle is fenced; checkpoint compaction preserves latest state');
// Deliberately remove a journal item to exercise the corruption boundary, not an empty fallback.
await other.commit(next, { image: image(4) });
const database = await new Promise((resolve, reject) => { const request = indexedDB.open(name); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); });
const storageKey = [JSON.stringify([scope.accountId, scope.workspaceId, scope.projectId]), 4];
const tamper = database.transaction('journal', 'readwrite'), read = tamper.objectStore('journal').get(storageKey);
let intact;
read.onsuccess = () => { intact = read.result; const corrupted = structuredClone(intact); corrupted.image.payload.document.title = 'altered'; tamper.objectStore('journal').put(corrupted, storageKey); };
await new Promise((resolve, reject) => { tamper.oncomplete = resolve; tamper.onabort = () => reject(tamper.error); });
await rejects(() => other.recover(next), 'recovery_checksum_mismatch');
results.push('well-formed data tampering is detected by the stored checksum');
const transaction = database.transaction('journal', 'readwrite');
transaction.objectStore('journal').delete(storageKey);
await new Promise((resolve, reject) => { transaction.oncomplete = resolve; transaction.onabort = () => reject(transaction.error); });
database.close();
await rejects(() => other.recover(next), 'recovery_journal_gap');
results.push('journal gap is reported as corruption and never converted to an empty document');
await engineRecoveryCase(name, assert);
results.push('project engine reopens real IndexedDB with one history unit and the original request; ACK resolves without duplicate edit');
await atomicResourceDeletionCase(name,assert);
results.push('atomic local file collection rolls back injected failure after IDB delete, captures descriptors, rejects missing markers and survives reopening');
await importSourceCollectionCase(name,assert);
results.push('terminal import source GC atomically rolls back a post-delete fault, retries its v4 cut, preserves partial reports and reopens with undo/redo independent of source bytes');
await orphanInventoryCase(name,assert);
results.push('scoped paged inventory finds legacy orphans without hiding references; orphan v5 deletion rolls back faults, retries, isolates projects and forbids byte identity reuse');
await resourceRecoveryCase(name, assert);
results.push('staged file and image recover from real IndexedDB; lost upload ACK queries stable resource identity');
await loadingRecoveryCase(name,assert);
results.push('recovery v7 preserves loaded-field edits/history in IndexedDB; an injected full-checkpoint failure retains the partial cut and zero writes, then retry removes v7 journal dependencies before releasing requests');
await loadingRecoveryCase(name,assert,true);
results.push('recovery v8 preserves certified scope coverage and loading edits; full-checkpoint fault rolls back atomically, retry retires all partial journal dependencies before sending');
await loadingRecoveryCase(name,assert,true,true);
results.push('recovery v9 retains the immutable read baseline and defers incomplete owners; full-checkpoint failure preserves the partial source/ledger and retry retires its journal before writes');
await scopedAckRecoveryCase(name,assert);
results.push('scoped v9 requests survive lost ACK and IndexedDB reopening with exact body/key; querying confirms the original child write, old tail wakes the parent and completion preserves both advanced owners');
await loadingRecoveryCase(name,assert,true,true,true);
results.push('recovery v10 preserves the source identity manifest and atomically retires its checkpoint/journal dependencies before releasing a completed model');
await scopedAckRecoveryCase(name,assert,true);
results.push('v10 creates a new element before the unrelated tail, recovers its lost ACK by the original key through IndexedDB and retains the birth across old-source completion');
await loadingAssetRecoveryCase(name,assert);
results.push('v12 scoped images save Blob/history/upload together, recover a lost ACK through IndexedDB without reupload, preserve undo/redo and survive old-source completion');
await loadingImportRecoveryCase(name,assert);
results.push('v11 partial import persists its source Blob through IndexedDB pause/reopen, preserves mapped IDs and undo while unrelated model records remain unloaded');
await scopedNetworkLoaderCase(assert);
results.push('scoped loader shares one real streamed fetch, exposes the first chunk before tail release, keeps the other subscriber alive after cancellation and preserves partial data after a malformed tail');
await defaultImageSizeCase(name, assert);
results.push('real image Worker enriches creation history in recovery v6; IndexedDB reopening queries a lost size ACK; concurrent views share one read/URL, retain leases incrementally and release only the final display pin before sized undo/redo');
await imageDecodeWorkerCase(assert);
results.push('real module image workers decode intrinsic PNG dimensions with bounded concurrency, reject bad bytes, abort safely and release their bitmap workers');
await digestWorkerCase(assert);
results.push('bounded real module workers hash chunked Blobs with native SHA256 parity, queued abort and close terminate work without a late result');
await ownedImageStorageCase(name,assert);
results.push('owned immutable recovery images survive IndexedDB checksums; mutable images and file descriptors are captured before asynchronous work');
await cooperativeCopyCase(assert);
results.push('cooperative duplicate planning yields to actual browser tasks and cancellation without mutating the source');
await sourceIntegrityCase(name,assert);
results.push('source SHA256 detects same-length Blob tampering before recovery adds or sends a tail; exact bytes resume with the original prefix and IDs');
await deltaJournalCase(name,assert);
results.push('delta journals migrate legacy checkpoints, retain atomic files, verify hashes/base chains, reject old readers and compact through serialized immutable commits');
await importBackpressureCase(name,assert);
results.push('blocked source durability permits a bounded local prefix, fences all network writes and recovers all records after real IndexedDB resumes');
await referenceCycleCase(name,assert);
results.push('native frame reference cycles and copied port mappings commit atomically through protocol 2 and real IndexedDB undo/redo/reopening');
await duplicatePlannerRecoveryCase(name,assert);
results.push('preparation v4 persists a DFS stack/cursor in real IndexedDB and resumes 500 siblings without restarting scans or duplicating identities');
await duplicateRecoveryCase(name,assert);
results.push('deep copy source/mapping survive IndexedDB pause/reopen/resume, retain C to F-prime and shared assets, preserve edits and do not revive undone content');
await nativeImportRecoveryCase(name,assert);
results.push('native import source/cursor/IDs survive real IndexedDB pause/reopen/resume, preserve user edits, report partial failures and do not revive undone records');
await elisionRecoveryCase(name,assert);
results.push('unsent node/partition create-delete lifetimes make zero requests, retain local proofs through IndexedDB/ACK/fresh reads, and undo restores new incarnations');
await batchRecoveryCase(name,assert);
results.push('ordered mutable batches retain every logical ID and independent undo unit through a lost ACK, real IndexedDB reopening and exact receipt query');
await protocolRecoveryCase(name,assert);
results.push('protocol 2 service adapter recovers exact frozen bytes and reconciles a lost ACK through real IndexedDB');
await runRecoveryCase(name,assert);
results.push('AI admission and RunRecord recover through real IndexedDB; cursor and generated content share a durable cut without a second invocation');
await canonicalRefreshCase(name,assert);
results.push('quiescent canonical refresh preserves original requests and history; the new base/document/version cut survives IndexedDB reopening');
await conflictReplanCase(name,assert);
results.push('conflict replan persists its audit/history/new identity; a lost replacement ACK recovers by the exact key and undo restores the reviewed server value');
await appliedReceiptRecoveryCase(name,assert);
results.push('a persisted successful receipt with failed coordination adopts a fresh base and replan in one cut, releases its writer and never repeats the original write');
await structureRecoveryCase(name,assert);
results.push('structural replan restores an atomic partition/member tree through IndexedDB, undo/archive and redo/new incarnation without changing the rejected request');
await scaledContainerCase(name,assert);
results.push('scaled container units preserve nested transforms and typography through atomic protocol writes, IndexedDB reopening, undo and redo');
await mixedHistoryCase(name,assert);
results.push('mixed acknowledged entry/pending partition history persists one undo unit, preserves original request facts and restores fresh lifecycles after reopening');
await traversalRecoveryCase(name,assert);
results.push('rejected partition undo recovers archived scopes and history through IndexedDB; redo/undo advance lifecycles without repeating the old request');
await historyEvidenceCase(name,assert);
results.push('history transition evidence survives redo invalidation and IndexedDB reopening, reconstructs prior stacks without document mutation and never enters service payloads');
await journalRecoveryCase(name,assert);
results.push('native history events recover a rejected undo after a later confirmed branch cleared redo; IndexedDB reopening retains the branch identity and sends its content once');
await userJournalRecoveryCase(name,assert);
results.push('pure user group recovery preserves a later acknowledged contribution across keep/discard, IndexedDB reopening and atomic undo/redo without retransmission');
await confirmedTraversalCase(name,assert);
results.push('later confirmed undo/redo restorations replay history only, preserve request bytes through reopening and remap older and remaining history lifecycles');
await adoptionEvidenceCase(name,assert);
results.push('adoption anchor and event evidence survive IndexedDB reopening and compose mixed geometry/content choices exactly without extra wire writes');
await adoptionConsumerCase(name,assert);
results.push('adoption evidence is consumed across repeated recovery/reopening, preserving old requests, latest reviewed field baselines and structural lifecycles');
return results;
} finally { await leader?.release(); other?.close(); storage.close(); indexedDB.deleteDatabase(name); }
}