三方库适配完成后,代码看似稳定,依赖来源却可能发生变化。一个模块的声明版本范围没有修改,实际解析的内容仍可能因安装环境、锁文件更新或本地模块调整而不同。对于持续交付的项目,除了检查接口与业务行为,还要把这次使用的具体包、取得路径、文件摘要和许可证信息连成一份可复核的清单。LibraryBridge在前六篇中已经建立了依赖画像、模块适配边界、统一异步结果和资源清理契约,现在需要补齐这条供应链记录。

当前工程继续使用@ohos/lottie的声明范围^2.0.0与本地bridge_utils,依赖画像revision=1。上篇比较了稳定记录2.0.0和待评估记录2.1.0,五项契约检查有四项通过,错误映射仍为REVIEW,所以升级决策保持HOLD。asset-hero的资源租约已在epoch6收敛,动态操作数为0,静态海报路线STATIC_POSTER保持可用。供应链核查不应修改这些已经确立的业务状态。

供应链校验与许可证清单

一、把声明依赖与实际取得的内容分开

oh-package.json5负责描述工程希望引入哪些依赖,oh-package-lock.json5则是需要纳入版本核查的解析记录。华为开发者联盟添加依赖项文档明确介绍了dependencies、devDependencies、dynamicDependencies的用途,以及远端三方包、本地模块、HAR/HSP的不同写法。此处仅把远端依赖和本地模块作为业务清单的两个条目,不将本地路径误当成远端仓库地址。

清单最少需要name、declaredRange、resolvedVersion、sourceKind、locator与digest。其中版本范围只是约束,解析版本是某一次依赖解析的结果,摘要与文件字节绑定。对本地模块,还要留意同一路径的内容会继续变化;路径字符串相同不能说明模块内容一定相同。把来源与摘要一起保存,才能在后续审查中说明“为什么这次构建读到了这些内容”。

许可证则是另一条独立数据线。包描述里的license字段可以作为收集入口,但字段缺失、格式含糊或者文本与实际分发要求不一致时,都应标记为待复核。许可证名称、许可证文本的取得地址和人工复核结果应分别记录。不能用文件摘要相同推断许可证已经核对通过,也不能因为许可证字段为空就把包直接当成允许任意分发。

const baseline = {
  project: 'LibraryBridge', revision: 1,
  dependency: '@ohos/lottie', range: '^2.0.0',
  resolved: '2.0.0', candidate: '2.1.0', upgradeGate: 'HOLD',
  local: 'file:../bridge_utils', leaseEpoch: 6,
  activeLeases: 0, posterRoute: 'STATIC_POSTER'
};
const inventory = [
  { name: '@ohos/lottie', resolved: '2.0.0',
    sourceKind: 'REGISTRY', locator: 'locked-registry-record',
    licenseState: 'PENDING', digestState: 'PENDING' },
  { name: 'bridge_utils', resolved: '1.0.0',
    sourceKind: 'LOCAL', locator: 'file:../bridge_utils',
    licenseState: 'PENDING', digestState: 'PENDING' }
];
console.log(baseline.upgradeGate, inventory.length);

二、摘要比对必须绑定具体输入

SHA-256可用来识别内容变化,但计算对象必须写清楚:整个归档包、某个HAR文件、清单文件,还是本地目录按规则生成的规范化快照。比较不同对象的哈希,即使恰巧相等也没有工程意义。实践中为每种对象保存digestScope、algorithm、expectedDigest和actualDigest,并把输入路径解析到受控工作目录内部,防止任意相对路径逃逸到其他文件位置。

对归档文件可直接逐字节计算摘要;对文件夹则需要先确定稳定的遍历顺序、忽略清单、路径编码和文件边界。若目录快照规则发生变化,新的摘要就不适合直接与旧规则下的摘要比较。Node.js业务逻辑可以采用内置crypto计算输入字节的摘要,真正接入安装产物时由文件读取层提供字节,不需要假设某个HarmonyOS SDK包含同名方法。

const { createHash } = require('node:crypto');
function sha256(bytes) {
  return createHash('sha256').update(bytes).digest('hex');
}
function checkDigest(record, bytes) {
  if (!record.expectedDigest || !Buffer.isBuffer(bytes)) {
    return { state: 'REVIEW', reason: 'MISSING_REFERENCE' };
  }
  const actual = sha256(bytes);
  return {
    state: actual === record.expectedDigest ? 'MATCH' : 'MISMATCH',
    actualDigest: actual, digestScope: record.digestScope
  };
}
const content = Buffer.from('librarybridge-record-v1', 'utf8');
const record = { digestScope: 'SNAPSHOT_BYTES',
  expectedDigest: sha256(content) };
console.log(checkDigest(record, content).state);

一份摘要结果的价值在于可重复核查。清单中的expectedDigest应来自先前确认的基线记录,而不是在比对函数里临时生成并替代基线。上面的文本字节用于说明函数的入参和返回值;接入工程后要从受控归档获取期望摘要,从对应的候选文件读取实际内容。若匹配失败,应保留文件类型、范围和差异状态,不把不一致的候选直接覆盖旧清单。

依赖清单与摘要检查的开发结构

三、许可证登记与摘要状态独立推进

在LibraryBridge里,许可证登记包含declaredLicense、noticeLocation、reviewerState以及reviewedRevision。对于尚未取得明确许可证文字的记录,采用PENDING;对于许可证标识已经确认、但通知文本或分发条款仍待落实的记录,可以采用REVIEW。只有声明信息、对应文本与分发要求均被核对,才允许应用层把该条目标为ACCEPTED。这些是工程自己的治理状态,不改变包管理器对依赖的安装行为。

许可证不能简单按名称去重。一个依赖可能同时包含多个开源组件,单个仓库也可能因目录不同出现不同许可要求。对于本地bridge_utils,应追踪其维护位置与代码来源;对于远端包,应记录此次取得的具体归档和版本。把所有相关文本汇成发布附录时,还要保留每一项与具体依赖记录之间的关联,便于将来的安全审查和版本复盘。

华为的ohpm clean文档提及安装产物目录与锁文件可以被清理,且提供保留锁文件的选项。这说明工作台不宜把临时安装目录作为唯一审计存储。规范化快照与复核记录应保存在有版本管理的独立位置,并且避免将可重新生成的安装缓存与证据文件混在一起。

四、四道门槛合成一个可解释的决策

候选能否进入升级步骤,至少需要同时满足四个条件:依赖解析记录明确;核心文件摘要与预期一致;许可证及通知项核对完成;上一章的接口与错误契约检查通过。哪一项缺失,都应保留具体原因。此处候选2.1.0的错误映射尚待复核,两个许可证记录也仍为PENDING,因此整体门禁继续HOLD,而不是因为某项摘要匹配就改变前一篇的升级结论。

function decideSupplyGate(items, contract) {
  const blockers = [];
  for (const item of items) {
    if (item.digestState !== 'MATCH') blockers.push(item.name + ':DIGEST');
    if (item.licenseState !== 'ACCEPTED') blockers.push(item.name + ':LICENSE');
  }
  if (contract.errorMapping !== 'PASS') blockers.push('ERROR_MAPPING');
  return { status: blockers.length ? 'HOLD' : 'READY', blockers };
}
const items = [
  { name: '@ohos/lottie', digestState: 'MATCH', licenseState: 'PENDING' },
  { name: 'bridge_utils', digestState: 'MATCH', licenseState: 'PENDING' }
];
const result = decideSupplyGate(items, { errorMapping: 'REVIEW' });
console.log(result.status, result.blockers.length);

这个汇总函数只接受已经形成的检查结果。记录由谁取得、对应哪个候选、采用哪一套摘要范围,都应在调用前绑定到快照。为了防止跨版本结果串联,可以把revision + packageName + resolvedVersion + digestScope组合为复核索引。同一候选重复检查时更新状态记录的版本,而不持续追加相同阻塞项。倘若接口回归结论后来改变,供应链报告仍须保留原先的摘要和许可证记录,不能覆盖历史候选的复核过程。

五、让检查面板说清哪些事项尚待完成

页面可以把依赖清单与升级门禁并列展示,但要区分“摘要相符”“许可证待核对”和“升级已允许”三种语义。当前清单包含两项依赖:@ohos/lottie与bridge_utils。应用层对指定样本字节的摘要校验返回MATCH,两项许可证仍为PENDING;上一章的五项契约回归保留四项通过、一项复核,总门禁继续HOLD,稳定解析版本2.0.0不动。界面不应把两项摘要校验成功展示为整个候选升级成功。

对于摘要不一致,可在详情页显示期望摘要与实际摘要的前后片段、文件范围及复核时间;对于许可证信息待补齐,则展示责任人可访问的文本位置与明确待办。敏感目录、用户名和私有仓库凭据应排除在普通列表之外。使用blockedReasons显示完整的阻塞原因,比只用黄色提示标记更利于追踪改进。页面刷新、依赖重选与用户切换版本时还要隔离不同请求的结果代次,避免新候选被旧候选的响应覆盖。

供应链完整性与许可证待办状态

六、在可控的依赖基线上继续推进

供应链治理的收获不在于多生成一张清单,而在于建立有明确对象、稳定标识和复核依据的记录。声明版本、解析版本、摘要范围、许可证状态和适配器契约应分开保存,并通过同一候选快照关联。此刻的结论很具体:LibraryBridge的稳定依赖仍保持2.0.0,候选2.1.0尚有错误映射和许可证待办,升级暂停;bridge_utils本地路径继续沿用,epoch6的资源租约处于已释放状态。下一篇可以在此基础上设计升级开关和回退策略,避免候选出现问题时再重新追查依赖来源。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐