先看结论与判断条件

  • 向量索引不是独立缓存,而是嵌入模型、预处理、语料、切分规则、距离度量和格式共同计算出的派生产物。
  • 模型版本号不足以标识真实输入,manifest 还应保存模型摘要、关联文件、向量维度、归一化方式和运行时约束。
  • 语料快照需要绑定文档集合、内容摘要、权限范围和删除状态,避免旧索引继续返回已撤回或无权访问的片段。
  • 更新流程应先验证签名元数据与整组摘要,在隔离目录完成装载自检,再以单个激活指针切换,失败时保留旧组。
  • 供应链证明可以把索引产物与构建者、构建类型、参数和依赖材料关联,但声明格式本身不能保证事实真实。
  • 验收应同时检查完整性、语义一致性、授权过滤、回滚拒绝和崩溃恢复;没有项目样本时不能宣称准确率或攻击阻断已验证。

先把索引看成模型与语料共同生成的产物

端侧 RAG 常把向量索引当成可以独立替换的缓存文件,应用只要能打开数据库并返回若干近邻就算更新成功。这个判断忽略了索引向量的来源。每个向量都由特定嵌入模型、分词或预处理配置、文档内容、切分边界、归一化规则和数值精度共同决定。任一输入变化,旧向量与新查询向量就可能不再位于同一个可比较空间。

最危险的故障往往不会崩溃。模型升级后维度仍然相同,索引库也能正常读取,距离查询甚至会稳定返回结果,但排序含义已经改变。用户看到的是“像真的一样”的错误引用,而监控只看到查询成功和耗时正常。工程验收必须把语义兼容性放到文件可读性之前,确认查询编码器与文档编码器来自同一绑定组。

绑定组应具有独立身份,例如 bundle_id 与 generation。组内至少包含嵌入模型摘要、预处理与关联文件摘要、语料快照摘要、切分配置摘要、索引格式、向量维度、距离度量、索引文件摘要和授权范围。应用只能激活完整且自洽的一组资产,不能从多个下载批次中挑选“看起来最新”的单个文件拼装。

索引组中每类资产的错配后果
资产负责的语义常见错配可观察结果
嵌入模型把查询与文档映射到向量空间查询模型升级而文档向量未重建查询成功但近邻排序失真
预处理配置分词、归一化与输入张量构造大小写、截断或特殊标记不同部分语料召回异常且难以复现
语料快照定义允许检索的文档集合旧文档未删除或新文档未进入返回过期、缺失或越权内容
切分规则决定文档片段边界和元数据chunk 标识与正文位置变化引用定位错误或片段上下文破裂
索引格式保存向量、图结构或量化数据运行时读取不兼容的格式版本装载失败、静默降级或查询偏差
授权范围约束哪些主体可访问哪些片段索引更新未同步权限快照文件完整却泄露不应展示的结果

用 manifest 固定整组资产的身份

manifest 不能只列文件名和版本号。文件名可被替换,版本号也可能因为构建重跑、渠道差异或人工复制而重复。每个可执行或数据资产都应记录加密摘要,并把决定语义的非文件参数纳入规范化摘要。对于索引,向量维度、距离度量、量化方式、格式版本、文档数量和生成器版本都属于身份的一部分,而不是可忽略的备注。

嵌入模型条目需要区分逻辑模型名、发布版本和实际文件摘要。LiteRT 模型元数据可以携带输入输出描述、归一化参数、标签和关联文件,这说明模型行为并不总由单个权重文件完整定义。若 tokenizer、词表、标签或预处理配置以关联文件存在,它们必须分别保存摘要,再由 bundle binding digest 把它们与主模型合并。

manifest 自身也需要可信来源。最小做法是由发布系统签名,将签名者、生成时间、过期时间和目标渠道写入受保护元数据。客户端先验证签名与期限,再核对每个本地文件摘要,最后重算整组 binding digest。仅在 JSON 内写一个自报摘要没有安全意义,因为替换者可以同时修改字段与摘要。公开检查代码可以验证内部一致性,签名真实性仍要由项目密钥体系完成。

索引 manifest 的关键字段与门禁
字段必须绑定的对象检查方法失败动作
bundle_id 与 generation完整索引组和发布代次与激活记录和策略单调比较拒绝未知组或未批准回滚
model_sha256实际嵌入模型文件流式读取文件并重算摘要停止装载并删除未激活临时组
preprocess_sha256分词与预处理配置核对关联文件和规范化配置要求重新下载完整绑定组
corpus_sha256文档集合与内容快照重建有序文档清单摘要拒绝使用旧权限或旧内容快照
index_format读取器、维度、度量和量化方式与当前运行时能力矩阵比对保持旧组并报告不兼容
index_sha256最终索引文件下载完成后重算文件摘要拒绝激活且不覆盖旧索引
binding_sha256所有语义输入的规范化组合按固定字段顺序重算任一差异都视为整组不一致

把模型运行时约束写进兼容合同

同一个模型文件在不同移动运行时上不一定具有相同可用性。ONNX Runtime Mobile 的集成要考虑目标平台、执行提供程序、模型使用的算子和定制包配置;Core ML 负责 Apple 平台上的模型转换、集成、装载和预测。它们说明运行时能力是交付合同的一部分,但这些框架不会替项目验证索引签名、语料权限或防回滚策略。

兼容合同应把 runtime_family、minimum_runtime_version、execution_provider、vector_dimension、distance_metric、normalization 和 numeric_type 写入 manifest。装载前先比对静态能力,再用公开安全的固定查询做自检,检查输出维度、有限数值、归一化约束和预期近邻集合。自检样本只能证明基本语义未明显错位,不能替代真实业务语料上的召回评估。

模型转换或量化也应产生新身份。即使逻辑模型名和训练版本不变,转换器、算子融合、量化参数或关联文件改变,都可能影响嵌入结果。工程上应将转换后的移动模型当成新的供应链产物,保存产物摘要和生成材料;索引只能绑定这个实际交付文件,不能绑定远端训练模型的名称后假设转换结果等价。

  • 记录实际交付模型文件而非仅记录训练模型名称
  • 固定运行时家族、最低版本和执行提供程序
  • 核对向量维度、距离度量、归一化和数值类型
  • 转换或量化变化时生成新的模型产物身份
  • 关联 tokenizer、词表与预处理文件各自有摘要
  • 固定查询自检只作为兼容支持证据,不冒充召回评测
  • 运行时支持结论不扩大为签名、授权或防回滚结论

让语料快照同时表达内容与权限

语料快照不应只是一个导出日期。生成器需要对每篇文档使用稳定标识,记录内容版本、可见范围、删除状态和切分配置,再按确定顺序生成快照摘要。若只对合并后的文本文件求摘要,团队很难解释是哪篇文档变化,也无法确认权限撤销是否进入索引。结构化清单使增量构建可以审计新增、修改、删除和权限变化。

切分结果也属于快照。chunk_id 应由文档稳定标识、内容版本、切分规则版本和片段位置共同派生,不能只用数组序号。否则文档开头插入一段文字后,所有序号都改变,旧向量、引用坐标和权限元数据可能互相错位。索引条目至少保存 chunk_id、document_id、content_digest、access_scope 与向量位置,查询返回后还要执行最终权限过滤。

权限过滤不能只依赖构建时快照。对于组织成员变更、文档紧急撤回等需要快速生效的场景,端侧应用应有短期授权状态、撤销清单或强制刷新机制。索引内的 access_scope 用于减少候选集合和提供离线边界,服务端或受信策略仍应决定当前主体是否可见。离线窗口越长,旧权限残留风险越大,必须明确记录。

语料快照中需要可追溯的变化
变化类型manifest 表达索引动作验收重点
新增文档新增 document_id 与内容摘要生成新 chunk 与向量文档只出现一次且权限正确
正文修改内容版本和摘要变化旧 chunk 失效并重建旧引用不再返回
文档删除删除记录或新快照中缺失移除全部相关向量缓存和引用元数据同步清理
权限收紧access_scope 版本变化更新过滤元数据或重建索引旧主体无法从候选结果读取
切分规则变化chunking_config_sha256 变化全量或可证明等价的重建chunk_id 与引用位置闭合
预处理变化preprocess_sha256 变化重新编码受影响语料查询与文档编码链一致
顺序变化有序清单规则保持稳定不应产生无意义内容漂移快照摘要可重现

把下载、验证和激活设计成一笔事务

索引更新不应直接覆盖正在使用的目录。客户端先把 manifest、模型关联文件、语料元数据和索引下载到隔离目录,逐项验证签名、版本、过期时间与摘要,再执行运行时装载和固定查询自检。只有所有步骤通过,才用一个原子激活指针切换到新 bundle_id。进程崩溃或设备掉电后,恢复逻辑只承认旧激活组或完整的新组,不扫描目录拼出混合状态。

The Update Framework 的规范把根、时间戳、快照和目标元数据分成不同角色,并通过签名、版本、过期和一致性关系处理回滚、冻结与混搭风险。端侧 RAG 可以借用这些原则发布整组资产:目标元数据列出文件摘要,快照固定目标集合,时间戳提供新鲜度。TUF 不定义向量语义,binding digest 与兼容自检仍由 RAG 交付协议承担。

回滚应当是显式发布动作,而不是客户端看到新组失败后随意选择任意旧目录。允许回滚时,签名策略需要指明可接受的 generation、模型版本、语料快照与到期时间,并生成新的审计事件。紧急恢复旧版本也要确认旧语料权限仍合法。否则一次技术回滚可能让已删除文档重新进入检索,形成完整性通过但授权失效的事故。

原子更新各阶段的成功条件
阶段输入成功条件失败后的安全状态
发现更新签名元数据与当前 generation版本更新且元数据未过期继续使用已验证旧组
隔离下载完整目标清单每个文件长度与摘要一致清理临时文件而不触碰旧组
绑定核对模型、预处理、语料、格式和索引字段binding digest 重算一致拒绝任何跨组拼接
运行时装载目标运行时与模型产物能力矩阵和基本装载通过卸载新组并保留旧实例
语义自检固定查询和已知近邻样本维度、数值和预期关系符合门禁标记新组不合格
原子激活完整新组和持久化指针单次提交后读者只见一个 generation重启仍指向旧组或完整新组
旧组回收新组稳定回执与保留策略无活动读者且回滚窗口已结束延迟回收而非影响当前服务

用只读检查器验证 manifest 内部绑定

下面的 Python 示例读取一份公开安全的索引 manifest,检查模型、预处理、语料、切分配置和索引文件摘要的格式,再按固定字段重算 binding_sha256。它还核对向量维度、距离度量、格式版本和 generation。任何输入派生字段不符合约束都会以非零状态退出,适合放在构建门禁或移动端下载后的诊断流程中。

代码只验证 manifest 内部一致性,没有读取实际模型、语料或索引文件,也没有验证发布签名。生产实现必须流式计算目标文件摘要,并通过平台安全库验证受信签名者和元数据期限。把 manifest 与文件同时替换仍可能通过这段检查器,因此它不能被描述为完整防篡改方案,更不能作为模型运行时不可提取的证据。

规范化规则必须在发布端和验证端共享。示例使用固定字段名、UTF-8 JSON、排序键和紧凑分隔符生成绑定输入;任何协议升级都应改变 schema_version,并让旧验证器明确拒绝未知版本。不要在失败后删除当前激活组,检查器只报告问题,由更新状态机决定清理尚未激活的临时目录。

端侧 RAG 索引 manifest 绑定检查器
#!/usr/bin/env python3
import hashlib
import json
import re
import sys
from pathlib import Path

SHA256_PATTERN = re.compile(r"^[a-f0-9]{64}$")
ALLOWED_METRICS = {"cosine", "dot_product", "euclidean"}
ALLOWED_FORMATS = {"flat-v1", "hnsw-v1", "ivf-v1"}
BINDING_FIELDS = (
    "model_sha256", "preprocess_sha256", "corpus_sha256",
    "chunking_sha256", "index_sha256", "index_format",
    "vector_dimension", "distance_metric", "generation"
)

def require_value(manifest, name):
    value = manifest.get(name)
    if value is None or value == "":
        print("missing field: " + name, file=sys.stderr)
        raise SystemExit(3)
    return value

def validate_digest(manifest, name):
    value = require_value(manifest, name)
    if not isinstance(value, str) or not SHA256_PATTERN.fullmatch(value):
        print("invalid digest: " + name, file=sys.stderr)
        raise SystemExit(4)
    return value

def validate(manifest):
    if manifest.get("schema_version") != 1:
        print("unsupported schema_version", file=sys.stderr)
        raise SystemExit(5)
    for name in ("model_sha256", "preprocess_sha256", "corpus_sha256",
                 "chunking_sha256", "index_sha256"):
        validate_digest(manifest, name)
    index_format = require_value(manifest, "index_format")
    if index_format not in ALLOWED_FORMATS:
        print("unsupported index_format", file=sys.stderr)
        raise SystemExit(6)
    metric = require_value(manifest, "distance_metric")
    if metric not in ALLOWED_METRICS:
        print("unsupported distance_metric", file=sys.stderr)
        raise SystemExit(7)
    dimension = require_value(manifest, "vector_dimension")
    if not isinstance(dimension, int) or dimension <= 0 or dimension > 65536:
        print("invalid vector_dimension", file=sys.stderr)
        raise SystemExit(8)
    generation = require_value(manifest, "generation")
    if not isinstance(generation, int) or generation < 1:
        print("invalid generation", file=sys.stderr)
        raise SystemExit(9)
    binding = {name: manifest[name] for name in BINDING_FIELDS}
    encoded = json.dumps(binding, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
    calculated = hashlib.sha256(encoded.encode("utf-8")).hexdigest()
    expected = validate_digest(manifest, "binding_sha256")
    if calculated != expected:
        print("binding digest mismatch", file=sys.stderr)
        raise SystemExit(10)

def main():
    if len(sys.argv) != 2:
        print("Usage: rag_manifest_check.py MANIFEST_JSON", file=sys.stderr)
        raise SystemExit(2)
    manifest_path = Path(sys.argv[1])
    if not manifest_path.is_file():
        print("manifest file not found", file=sys.stderr)
        raise SystemExit(2)
    try:
        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as exc:
        print("cannot read manifest: " + str(exc), file=sys.stderr)
        raise SystemExit(2)
    if not isinstance(manifest, dict):
        print("manifest must be an object", file=sys.stderr)
        raise SystemExit(2)
    validate(manifest)
    print("manifest binding accepted")

if __name__ == "__main__":
    main()

用 provenance 追溯索引是怎样生成的

摘要能证明当前文件与记录值一致,却不能解释记录值从何而来。SLSA Provenance 把产物主体、构建者、构建类型、外部参数和依赖材料组织为可验证声明。索引构建可以把最终 index artifact 作为 subject,把嵌入模型、语料快照、切分配置和生成器镜像作为 materials,使审计人员能够判断这份索引是否来自批准的流水线与输入集合。

in-toto Attestation Statement v1 使用 subject 摘要把有类型的 predicate 绑定到具体产物。对 RAG 索引而言,predicate 可以描述生成器版本、规范化规则、输入材料引用、测试摘要和策略版本。声明格式只提供结构;签发者是否可信、构建环境是否受控、参数是否真实仍需组织自己的身份、签名和门禁体系证明,不能看到 attestation 文件就自动判定供应链安全。

provenance 还应避免泄露私有语料。材料条目可以使用受控快照标识和摘要,不应把文档正文、客户名称、内部路径或访问令牌写进公开元数据。需要深入复核时,通过权限受控的证据包把摘要映射到构建记录。公开页面只能说明字段与验证方法,没有真实项目回执时应明确标记未接入,而不是虚构某次构建已经通过。

  • 索引产物摘要作为 provenance subject
  • 嵌入模型、语料快照和切分配置作为材料引用
  • 构建者身份与构建类型进入签名声明
  • 外部参数采用规范化且可复核的公开安全字段
  • 私有语料正文、客户信息和内部路径不进入公开声明
  • 声明签名者与发布策略分别校验
  • provenance 通过不扩大为运行时安全通过

用完整性、语义与授权三类证据验收

索引验收应先固定候选 bundle_id、应用版本、签名身份、模型摘要和语料快照。完整性用例验证签名元数据、文件摘要、binding digest、generation 和原子激活;语义用例使用公开安全的固定查询检查维度、有限数值、预期近邻和引用闭合;授权用例确认权限收紧、文档删除、离线窗口和撤销事件都能改变可见结果。三类证据不能互相替代。

负向用例可以替换单个摘要、交换模型与索引、使用旧语料快照、改变距离度量、提供未知格式或模拟激活时崩溃,观察系统是否保持旧组并拒绝混合状态。这些用例验证防御门禁,不需要公开真实模型、文档、包名或生产命令。测试报告应写明在哪一层拒绝、旧组是否继续可用以及临时文件如何处理。

当前没有具体候选包、模型文件、语料快照、索引产物与设备运行回执,因此本文只给出设计、代码和验收方法,不能宣称某个端侧 RAG 已经提升召回率、阻断篡改或消除泄露。落地时应把同一候选身份下的构建证明、文件摘要、负向用例和设备自检装入受控证据包;模型、语料、格式或授权策略变化后重新验收。

端侧 RAG 索引发布前的证据矩阵
证据类别最低检查合格结果不能推出
来源身份签名者、版本、期限和目标渠道元数据来自批准发布链索引语义一定正确
文件完整性模型、配置、语料清单和索引摘要本地文件与目标清单一致文件运行时不可提取
组内绑定binding digest 与 generation不存在跨版本混搭真实业务召回率达标
运行时兼容模型装载、算子、维度和数值目标环境完成基本自检所有设备和系统版本兼容
语义自检固定查询、近邻和引用位置查询编码与文档编码关系未明显错位未知业务问题都能正确回答
授权边界删除、权限收紧、离线和撤销无权主体不获得受限片段设备内历史明文已经抹除
事务恢复中断下载、校验失败与激活崩溃只保留旧组或完整新组后续更新永远不会失败
供应链证明subject、materials、builder 和签名产物可追溯到声明的构建过程声明内容天然真实或运行时安全

事实依据与适用边界

以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。

本文判断事实或工程依据适用限制
移动模型元数据可以描述输入输出、归一化参数、标签和关联文件,因此模型行为不一定只由主权重文件决定。LiteRT model metadata元数据结构不提供索引签名、业务授权或防回滚结论,关联文件仍需单独验证摘要。
ONNX Runtime Mobile 支持在 Android 和 iOS 运行模型,并需要考虑算子、包体与执行提供程序等约束。ONNX Runtime Mobile运行时支持不等于模型与索引语义兼容,也不提供签名、授权或防篡改保证。
Core ML 提供 Apple 平台上的模型转换、集成、装载和预测能力。Apple Core ML框架能力不能证明模型运行时不可提取,也不替项目核对语料、索引和授权范围。
更新客户端可以通过签名、版本、过期时间和一致性关系识别回滚、冻结与目标混搭风险。The Update Framework specificationTUF 处理更新信任链,不定义向量空间兼容、语料权限或移动 RAG 的业务语义。
构建 provenance 可以把产物主体与构建者、构建类型、外部参数和依赖材料关联。SLSA Provenance v1.1provenance 只能证明所声明的构建记录,不能单独证明运行时安全、检索准确或授权正确。
in-toto Statement 使用产物摘要作为 subject,并将其绑定到有明确类型的 predicate。in-toto Attestation Statement v1声明结构不保证内容真实,仍需验证签名者、签名、策略和构建环境。
工程判断:嵌入模型、预处理、语料快照、切分规则、索引格式和授权范围必须共享一个可重算的绑定摘要。工程判断:基于派生产物身份、向量空间兼容和最小授权原则字段集合和格式需要按项目运行时与数据模型调整,不能把示例 manifest 当成通用标准。
当前没有具体候选包、模型、语料、索引和设备回执,不能断言端侧 RAG 的召回、篡改阻断或权限撤销已经验证。项目证据尚未接入文章仅提供公开可复核的设计、检查代码和验收矩阵,不包含客户案例、性能数字或产品实测结论。

工程常见问题

索引文件摘要正确,为什么还可能返回错误结果?

摘要只能证明索引文件没有偏离记录值,不能证明生成它的嵌入模型、预处理、语料和切分规则与当前查询链一致。旧索引与新模型可能使用相同维度并正常查询,却处在不同向量空间。必须验证整组 binding digest 和语义自检。

只绑定嵌入模型版本号是否足够?

不够。相同版本名可能对应重新转换、不同量化或不同关联文件。应记录实际移动模型文件摘要,并绑定 tokenizer、词表、预处理、向量维度、距离度量和运行时合同。版本号用于人类识别,摘要用于锁定真实产物。

文档删除后,为什么必须生成新的语料快照?

索引是文档集合的派生产物。仅从原始库删除文档,不会自动移除设备上的向量、片段正文和引用元数据。新快照需要表达删除状态并让相关 chunk 失效;紧急撤回还应通过短期授权或撤销机制限制旧离线索引。

TUF 能直接解决端侧 RAG 索引完整性吗?

TUF 可以提供签名元数据、版本、过期和一致性关系,帮助防止更新回滚、冻结与混搭,但它不了解向量维度、嵌入空间、切分规则和语料权限。RAG 协议仍需定义 binding digest、兼容合同和语义自检。

供应链证明存在是否代表索引一定安全?

不代表。provenance 与 attestation 让产物、构建者、参数和材料可追溯,但声明可能由不受信签名者签发,构建环境也可能不满足项目策略。验证时还要核对签名身份、门禁规则、文件摘要、运行时兼容和授权结果。

怎样验收索引更新不会造成半新半旧状态?

在隔离目录下载整组资产,验证签名、期限、每个文件摘要和 binding digest,完成装载与固定查询自检后,再原子切换唯一激活指针。测试应覆盖中断下载、摘要失败、格式不兼容和激活时崩溃,重启后只能看到旧组或完整新组。

想用自己的 App 验证?

提交候选包、目标系统和关键业务路径,申请御盾 PoC 与兼容性评估。

继续阅读: 移动 AI 应用的运行时安全边界