/** * 部分加载v6的上传/跨项目资源采纳准入证据。资源存在或URL相同不代表当前源可安全使用, * 必须绑定 sourceToken、认证canvas范围和对应逻辑操作;原上传body/key也要与资源事实一致。 */ import type {Document,Id,Registry,OperationIntent} from '../canvas-core/types.ts'; import {equal,fault} from '../canvas-core/values.ts'; import type {RuntimeState} from './contracts.ts'; import {structuralRequestScope} from './loading.ts'; import {referencedResources,type LoadingAssetAdmission} from './resources.ts'; /** Proven local upload identities; never infer authorization from an arbitrary URL. */ // source身份+各proof身份一致时复用资源URL表;不因浏览器缓存有URL就授予准入。 const namespaces=new WeakMap;urls:ReadonlyMap}>(); /** 从已记录loadingAdmission派生resource→规范来源URL,只在v6和保留源存在时提供。 */ export function loadingAssetURLs(runtime:RuntimeState):ReadonlyMap{ const source=runtime.loadingRead;if(!source||runtime.loading?.version!==6)return new Map(); const proofs=new Map();for(const resource of runtime.resources.values())if(resource.loadingAdmission)proofs.set(resource.id,resource.loadingAdmission); for(const [id,binding]of runtime.assetBindings??[])if(binding.loadingAdmission)proofs.set(id,binding.loadingAdmission); const previous=namespaces.get(source);if(previous&&previous.proofs.size===proofs.size&&[...proofs].every(([id,proof])=>previous.proofs.get(id)===proof))return previous.urls; const urls=new Map([...proofs].map(([id,proof])=>[id,proof.sourceURL]));namespaces.set(source,{proofs,urls});return urls; } /** 联合验证上传/采纳与源范围、操作顺序、引用和stored证明;最后核对冻结上传协议字段。 */ export function validateLoadingAssetAdmissions(runtime:RuntimeState,registry:Registry):void{ const loading=runtime.loading;if(loading?.version!==6)return; // upload先分配资源,后续document.change才能引用;早于/同于upload的引用不具备可靠身份因果。 for(const resource of runtime.resources.values()){ const proof=resource.loadingAdmission,record=runtime.ledger.operations.get(resource.operationId); if(!proof||proof.version!==1||proof.sourceToken!==loading.sourceToken||typeof proof.sourceURL!=='string'||!proof.sourceURL||proof.sourceURL.length>8192||!Array.isArray(proof.canvasIds)||!proof.canvasIds.length||new Set(proof.canvasIds).size!==proof.canvasIds.length||proof.canvasIds.some(id=>typeof id!=='string'||!structuralRequestScope(runtime,runtime.loadingRead!,id))||record?.intent.operationType!=='asset.upload'||record.intent.resourceId!==resource.id||resource.stored&&record.resolution!=='applied')throw fault('invalid_loading_asset',[resource.id]); if([...runtime.ledger.operations.values()].some(candidate=>candidate.intent.operationType==='document.change'&&candidate.intent.sequence<=record.intent.sequence&&referencedResources(candidate.intent.changes,registry).has(resource.id)))throw fault('invalid_loading_asset',[resource.id]); if(![...runtime.ledger.operations.values()].some(candidate=>candidate.intent.operationType==='document.change'&&candidate.intent.sequence>record.intent.sequence&&referencedResources(candidate.intent.changes,registry).has(resource.id)))throw fault('invalid_loading_asset',[resource.id]); } // adoption绑定自己的文档意图,sourceURL等于已授权asset.url,不凭后来的另一个引用补造准入。 for(const [id,binding]of runtime.assetBindings??[]){ const proof=binding.loadingAdmission,record=runtime.ledger.operations.get(binding.operationId); if(!proof||proof.version!==1||proof.operationId!==binding.operationId||proof.sourceToken!==loading.sourceToken||proof.sourceURL!==binding.asset.url||!Array.isArray(proof.canvasIds)||!proof.canvasIds.length||new Set(proof.canvasIds).size!==proof.canvasIds.length||proof.canvasIds.some(owner=>!structuralRequestScope(runtime,runtime.loadingRead!,owner))||record?.intent.operationType!=='document.change'||!referencedResources(record.intent.changes,registry).has(id))throw fault('invalid_loading_asset',[id]); if([...runtime.ledger.operations.values()].some(candidate=>candidate.intent.operationType==='document.change'&&candidate.intent.sequence>>(); /** 资源首次出现在逻辑回放中时,实际owner集合须精确匹配准入范围;seen只在本次验证中去重。 */ export function validateLoadingAssetUse(intent:OperationIntent,document:Document,runtime:RuntimeState,registry:Registry,seen:Set):void{ if(runtime.loading?.version!==6)return; for(const id of referencedResources(intent.changes,registry)){ const proof=runtime.resources.get(id)?.loadingAdmission??runtime.assetBindings?.get(id)?.loadingAdmission;if(!proof||seen.has(id))continue;seen.add(id); let documents=ownership.get(registry);if(!documents){documents=new WeakMap();ownership.set(registry,documents);}let index=documents.get(document); if(!index){const owners=new Map>();for(const element of document.elements.values())for(const ref of referencedResources([{kind:'element',id:element.id,before:null,after:element}],registry)){const scopes=owners.get(ref)??new Set();scopes.add(element.ownerCanvasId);owners.set(ref,scopes);}index=new Map([...owners].map(([id,scopes])=>[id,[...scopes].sort()]));documents.set(document,index);} if(!equal(index.get(id)??[],[...proof.canvasIds].sort()))throw fault('invalid_loading_asset',[id]); } }