Files
Endoleap/src/lib/canvas-runtime/import-verification.ts
T

44 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 导入源完整性证据的拥有权与发送屏障。块读取证明允许先显示/编辑合法前缀,
* 但source SHA必须在可靠Root中封存后才能发送受影响修改;普通内存布尔值不能冒充durable证据。
*/
import type {Id} from '../canvas-core/types.ts';
import {equal,fault} from '../canvas-core/values.ts';
import {importNeedsSeal,validateImportJob,type ImportJob} from './imports.ts';
import {validateImportReadProof} from './import-read-proof.ts';
/** A derived durable-cut index, never an in-memory permission boolean. */
/** 从 integrity-v2、带 SHA 且无 integrityError 的 job 派生 fileId→SHA Map,不扫描字节或读取存储。
* 调用方必须传已持久 cut;函数不证明其来源可靠,也不独立调用 validateImportJob 验摘要。
* 返回实际可变 Map,若重复 fileId 后项覆盖前项;文件身份唯一性由外围约束保证。 */
export function importSeals(jobs:ReadonlyMap<Id,ImportJob>|undefined){
const seals=new Map<Id,string>();for(const job of jobs?.values()??[])if(job.source?.integrityVersion===2&&job.source.sha256&&!job.source.integrityError)seals.set(job.source.fileId,job.source.sha256);return seals;
}
/** 整个 job 表的发送闸门:每个非豁免 integrity-v2 源都须无需 seal 且与 seals 中 SHA 相等。
* 取消且 committedSourceKeys 为空者豁免;不按本次请求挑选 job,也不重验块证明/实际存储事务。
* 其它 integrityVersion 在此跳过,是否支持/合法由 job 解释器先校验,不能把 true 当所有源都已验证。 */
export function importsMaySend(jobs:ReadonlyMap<Id,ImportJob>|undefined,seals:ReadonlyMap<Id,string>){
for(const job of jobs?.values()??[])if(job.source?.integrityVersion===2&&!(job.status==='cancelled'&&!job.committedSourceKeys.length)&&(importNeedsSeal(job)||seals.get(job.source.fileId)!==job.source.sha256))return false;return true;
}
/** Only engine-owned publication paths may advance read evidence or source verification. */
/** 比较前后 job 表保护 integrity-v2 证据,owner 是引擎传入的当前任务 ID,不是外部权限令牌。
* 同一表引用直接返回,依赖不可变状态合同;v2 job 不得删除或降级,即使 owner 匹配也不例外。
* 每个 next v2 job 验结构且要求 proof 索引为从 0 开始的连续前缀,比通用 seek reader 更严格。
* owner 可推进证据/执行事实,仍不能改 fileId/长度/已有 SHA 或丢改旧块;此处不重算字节或检查持久水位。 */
export function assertImportEvidence(previous:ReadonlyMap<Id,ImportJob>|undefined,next:ReadonlyMap<Id,ImportJob>|undefined,owner:Id|undefined){
if(previous===next)return;
for(const[id,job]of previous??[])if(job.source?.integrityVersion===2&&!next?.has(id))throw fault('import_integrity_owned',[id]);
/** 比较执行事实时仅剔除 job.generation 和当前 source.retirement;previousFiles 等内容仍参与比较。
* 允许普通保留流程调整这些排除字段,不在这里证明 generation/退休时钟单调,交其它发布校验处理。 */
const execution=(job:ImportJob|undefined)=>{if(!job)return job;const{generation:_,source,...body}=job;if(!source)return body;const{retirement:__,...identity}=source;return {...body,source:identity};};
for(const[id,job]of next??[]){const source=job.source,old=previous?.get(id)?.source;if(source?.integrityVersion!==2){if(old?.integrityVersion===2)throw fault('import_integrity_owned',[id]);continue;}
validateImportJob(job);const proof=validateImportReadProof(source.readProof,source.fileId,source.byteLength);
// engine的解析进度要求证明为从0开始的连续块前缀,比通用seek reader的证明形状更严格。
if(proof.blocks.some((block,index)=>block.index!==index))throw fault('invalid_import_read_proof');
const changed=!equal(old?.readProof,proof)||old?.sha256!==source.sha256||old?.integrityError!==source.integrityError;
if(owner!==id&&(changed||!equal(execution(previous?.get(id)),execution(job))))throw fault('import_integrity_owned',[id]);
// 相同fileId/长度和已存在SHA不可改变,旧块摘要也必须原样保留,不能用“重算成功”覆盖冲突源。
if(old?.integrityVersion===2){if(old.fileId!==source.fileId||old.byteLength!==source.byteLength||old.sha256&&old.sha256!==source.sha256)throw fault('import_source_changed',[id]);const hashes=new Map(proof.blocks.map(block=>[block.index,block.sha256]));for(const block of old.readProof!.blocks)if(hashes.get(block.index)!==block.sha256)throw fault('import_source_changed',[id]);}
}
}