Skip to content

Session 完整性与回放确定性:manifest、hash、seal 的准确边界 ​

先看结论

  • 当前实现是 append-only 写入 + SHA-256 摘要校验,属于"篡改可被检测",不是密码学签名意义上的"不可伪造"。 没有对 manifest/Raw 文件做数字签名,篡改后重新计算哈希是理论可行的;这里的保护目标是防止意外损坏与静默数据丢失,不是对抗主动伪造者。
  • 文件级原子替换不等于整场事务或断电耐久性。 manifest.json/checkpoint 采用同目录临时文件写入 + flush/fsync + 原子替换,但目录级发布不是单次原子操作——进程如果恰好在搬运阶段崩溃,可能留下不完整的目标目录(不会被后续流程覆盖,需要人工另选新目录重试)。
  • Raw checkpoint 不等于 Metric 引擎 checkpoint。 检查点只是 Raw 前缀的凭证(序号、字节边界、累计哈希),恢复后仍需要显式 finalize 离线重建 Metric,不会复用崩溃时的指标文件。
  • 回放一致性验证的是"多轮独立回放产出语义相同的派生结果",不是"设备真机断电/拔线"级别的持久化证明,两者是不同强度的证据。

1. Raw 层:append-only 写入与 SHA-256 摘要 ​

RawSessionRecorder(MacApp/Sidecar/perfowl_collector_core/recording.py)以单线程所有权模型 append-only 写入 raw-events.jsonl,写入的同时用 hashlib.sha256() 同步累加摘要(self._raw_digest),停止时把 rawSHA256、rawByteCount、eventCount、schema 等字段原子写入 manifest.json。validate_recorded_session() 在回放前重新计算文件哈希并与 manifest.rawSHA256 比对,不一致则 fail-closed 拒绝回放(ReplayValidationError),不会把损坏或不完整的 Raw 数据当作可信输入静默接受。

2. Checkpoint:周期凭证与显式恢复 ​

  • 周期 checkpoint:RawSessionRecorder.checkpoint() 在同一个 Raw writer 锁内同步 Raw 与(如有)CoreProfile blob,复制当前序号/字节边界/累计哈希/计数,原子发布独立的 raw-checkpoint.json。检查点不证明设备侧没有丢失数据,只是"截至这一刻已确认写入的前缀"的凭证。
  • 显式恢复:recover-checkpoint SOURCE NEW_DESTINATION CLI 只复制检查点声明的 Raw/blob 前缀到一个新目录(拒绝已存在的目标路径、路径穿越与符号链接),复制同时重新哈希,再做一次独立的全量 Raw 回放/逐块校验,序号与事件数匹配后才发布新目录;失败则清理临时目录,原始会话始终不被修改。检查点之后的尾段数据不猜测、不自动补齐,标记为 partial 并在 recovery.json 记录被排除的字节数;恢复后的身份状态是 identity unresolved,不会被自动提升为 verified。
  • Metric 侧独立:恢复只处理 Raw 层;要得到可用的指标数据,还需要对恢复出的新目录显式执行 finalize,离线重建 Metric——这是与"崩溃时的 Metric 文件"完全独立的一次新计算,不是断点续算。

(p4-r3-checkpoint-recovery-result.md:300 MiB 输入、单进程 30 块 × 10 MiB,checkpoint 模式每 10 块显式同步一次;20 轮对照下 baseline/checkpoint 写入耗时中位 654.950ms / 663.782ms,额外开销约 +1.35%;这只代表本机本次离线基准模型,不外推为周期采集时延预算。)

3. Metric 封存(Seal)一致性 ​

IncrementalMetricRecorder 的封存要求 consumedRawEventCount = lastConsumedRawSequence = raw.eventCount 三者严格相等——即 append 必须从序号 1 连续消费,无指标输出的 Raw 事件也要推进消费位置;重复、乱序、缺口或写入失败都会使该 recorder 失效,不允许继续封存。封存顺序是先 flush/fsync 指标字节,再发布 manifest,持久化失败会向上传播,不产生"看起来成功"的回包。Finalizer 复用增量缓存时,还要求 Session ID、Raw 哈希与两个消费边界同时匹配;没有封存凭证的历史缓存会从已验证的 Raw 重新构建,不会把旧的、未经证明完整的缓存直接升级为"已证明完整"。

4. Online Report 的 seal(实时会话收口) ​

Online Report 的 seal()(MacApp/Sidecar/perfowl_collector_core/live_seal.py::seal_live_session)是一次性能力:必须绑定同一个仍然健康(未 poisoned)的 writer,重复调用返回同一个已缓存结果而不会重新计算。如果 writer 因为之前的 append/checkpoint 失败被标记为 failed(poisoned),seal() 会直接拒绝——这与第 1 节 SessionWriter.append() 的失败即整体失败设计一致:一旦 Raw/Metric/Report 任一环节出现部分写入后的失败,不允许在同一个 writer 上继续假装可以恢复。

5. 存储格式与回放确定性的多轮验证(R3 批次证据) ​

R3 批次(p4-r3-format-replay-20-rounds-result.md / p4-r3-seal-consistency-result.md)对"legacy 内联格式"与"外置 blob 格式"各做了多轮 AB/BA 交替回放,比较 metrics-derived、frame-events、frame-report、report-stat、startup-report、launch-spans 六类派生产物的语义哈希:

  • 首批 80 轮回放(新旧格式各 20 轮 × 两份样本)六类产物语义哈希全部一致;
  • 后续追加 40 轮同类回放同样全部一致,且产品源码 SHA-256 前后未变;
  • 大样本(约 267 MB 原始 Raw)下,外置 blob 格式相比 legacy 内联格式的 finalizer 耗时中位从约 2363–2557ms 降至约 583–625ms,进程峰值 RSS 同步下降;小样本(约 18.5 MB)下外置格式耗时中位反而略增约 1.6%——不能因为大样本受益就宣称所有负载都会变快。

这组证据证明的是"同一份 Raw 输入,经过存储格式变更后,多轮独立回放仍然产出语义一致的派生结果"(E2,确定性重放),不是该 Session 在真实断电、拔线或磁盘故障下的数据持久化保证——后者仍是开放门禁(详见 Session 架构演进与实验 第 11 节)。

6. Screenshot 与其余采集的隔离边界 ​

Screenshot 使用独立子进程与独立 DVT 长连接(com.apple.instruments.server.services.screenshot),其 persistScreenshotSequence 策略字段不进入 CapturePlan.hash、CollectorReadiness 或 prewarmGeneration 的计算——勾选与否不会触发 Collector 重启或重新 Prepare App。这是刻意的"绝对物理隔离"设计:截图侧任何超时、重连或崩溃都不应影响 Performance DTX 或其他指标采集。manifest 记录 screenshotPreviewEnabled、screenshotPersistenceEnabled、screenshotObservedCount、screenshotPersistedCount 四类审计字段;Review 侧区分 not-requested/available/partial/requested-but-failed 四种截图序列状态,不把"部分失败"汇报成"完全可用"。

7. 当前证据与仍未关闭的门禁 ​

  • 存储格式回放一致性:E2,多轮(累计超过 100 轮)验证通过;
  • CoreProfile 存储分级(derivedOnly/full):E4 近 5 分钟真机磁盘验证通过(Build 58),30 分钟长测与 full 模式真机对照仍待执行;
  • Checkpoint/恢复:E2 通过(264/264),无物理断电场景的 E4 结论;
  • Screenshot 长连接架构:E2 通过(Build 59),E4 真机 follow-up 曾发现 PNG payload 损坏等实现偏差,修复状态见演进文章;
  • Developer ID、公证、ticket stapling 与 GPL-3.0 法务合规结论,本领域范围内继续开放,不因内部测试包交付而视为完成。

8. 常见误区 ​

  • 把"篡改可被检测"当作"篡改不可能发生";
  • 把 manifest/checkpoint 的文件级原子替换理解为整个 Session 目录的事务性发布;
  • 把 Raw checkpoint 当作 Metric 引擎的断点续算凭证;
  • 把多轮回放语义哈希一致,当作真机断电/拔线级别的持久化证明;
  • 把 E2 自动化验证的存储/回放结论,当作已完成 E4 真机验收。

8.1 手机自有Lab原件迁移的检查边界 · 2026-10-18 ​

手机自有Lab的封口记录和Mac Session使用各自的schema。检查器若读取不存在的summary.runName,会在服务可读时误报失败;应以当前FixtureLabCore.swift、FixtureLabRecords.swift核定字段:Lab runName在effective-config.explicitLaunchRequest,版本与来源摘要也在config。不要复用旧startup命名空间、32容量或另一种文件合同。

保全时先冻结实际目录集合与封口/释放状态,分块读取原件并核源前后stat、源SHA、host fsync后独立复读SHA,以及manifest各pin。导出的五件对象再与原件语义比较,events按行核顺序、数量与字节;一次只解析一份export,避免同时常驻Raw数组和导出数组。本次63记录、378文件的字节核对通过E3,离线174,218条事件一致性通过E2,峰值112,377,856B;这只是保管一致性,未重验历史指标,也不证明配方可用。

副本齐全不自动释放手机容量。归位需另有独占计划与journal、保全后同容器保留式rename、实际再枚举及真实容量收据。目录读取不能证明自有进程已退出;进程存在也不自动证明活负载。适用范围限本次自有Lab合同,不扩展为其他App迁移、完整Mac Session恢复或物理耐久性结论。来源:新机手机保全记录。

2026-10-18增量(E3):五份原件保留式归位后,实际活动63→58,独立25文件SHA/目录守恒核同;这只释放活动命名槽位,物理释放字节为0。64是host执行器的准入上限,App写入器本身未强制64条;不要把列表展示20条误认成真实容量,也不要在活动根内新增retired目录而增加准入计数。之后只读又见不同的自有生命,且旧记录全部封口释放/无pending;原因未知。前一次正常退出不能当作下一阶段一直空闲的凭证,仍须新鲜完整PID/executable/bundle/startDate绑定、封口与待执行请求检查。只处理已确认的自有生命,遇新生命或未知状态停止;不复用旧PID。来源同上及新机参考资格。

2026-10-23 · 树项上限与原件字节预算分别核对(E3/E4) ​

长期保留失败与验收证据后,目录+文件总项可能先触及保护上限,文件字节仍低于预算。本次只读枚举在3626文件+470目录处触及4096项,不能因此判为通道断开或数据损坏。先保留失败收据,再明确增加有界树项参数(默认4096、显式8192),保持深度、单目录、字节、超时、内存和类型/SHA保护;重新实枚举得到3963文件/5,915,576,254B,后全源与主机备份核同,单次自有App升级后集合与全部SHA不变。

复用备份可节省重复存储,仍须读取当前手机全源并核SHA;仅mtime相同不能代替当前来源证明。历史expired请求是已保全的未消费请求归档,按nonce及封包过期时间核对,不能删掉它来迁就只接受claimed/intent的旧检查器,也不能重放。此结论限自有App此次稳定Documents及同址升级,不是第三方App迁移或断电耐久性保证。源读取263秒、升级与全源后核252秒,均在有界预算内;指标验收仍单列。方案、准备失败与恢复决策。

9. 版本历史与来源批次 ​

版本日期修订要点
v1(本页)2026-09-18新增页面,从 session-artifacts.md 中拆出 manifest/hash/seal/checkpoint/replay 的完整证据边界,纠正旧版"可复现且不可篡改"的密码学级别暗示。
v1.12026-10-18增补自有Lab schema、源/host字节与导出语义核对、内存控制及容量收据边界;历史指标结论不变。
v1.22026-10-18增补保留归位的活动槽位/物理字节区别、host64准入与App展示限制,以及退出后新生命的重新绑定边界。
v1.32026-10-23树项/字节门分开、全源SHA与备份复用、历史expired请求保留;Lab32同址升级证据,不计指标验收。

10. 继续阅读 ​

2026-10-23 · QueueShutDown不能直接当根因(E4失败取证) ​

一次新Session在约12.8秒、11样本时以QueueShutDown终止,Raw只有launch事件、没有帧源;独立手机负载却完整运行140秒并释放。传输诊断在队列异常前先记录TimeoutError/errno60和No route to host/errno65,因此该例能确认连接读取/路由中断先于消息队列关闭,不能从队列错误直接推出阈值公式、手机锁屏或硬件问题。预热连接年龄在成功轮也有约69–107秒,本例约112秒,年龄相关性尚不足以确定根因。

判断时保全失败manifest、transport-diagnostics、全部Session源与手机负载原件,分别核系统流和自有工具终态;失败轮不得用“手机任务已完成”或后续通道恢复改成通过。安全恢复先停止后续队列,空闲客户端正常退出/重建自身连接、新鲜容量与全旧SHA核同,随后另开诊断与全新正式attempt,不拼接旧通过轮。适用于本例原生RSD /DTX路径;此方法不等于已修复底层路由故障。来源与恢复决策。

2026-10-25 · 备份候选缺少metadata时的受限恢复(E3) ​

备份catalog只有path/bytes/SHA、缺mtime时,严格匹配器可能把可复用旧文件判为未命中,导致本地重复镜像触及新增字节上限;该失败不能直接归因为手机数据损坏。本次保全在新增约2GiB后安全停止,已核文件与partial日志全部保留,手机动作0。

恢复时保留原预算和时效门:以完整当前inventory元数据选择候选,对未核的手机原件仍流式重读SHA,并与本机独立复读SHA比较;metadata只决定尝试复用哪个文件,不能证明内容相同。已核partial仅在同目录集合、同stat、host SHA及有限来源时效全部成立时承接。此次最终4,998件/6,407,426,060B全源及host SHA一致,单版本升级后集合与全部SHA不变;这是自有测试App的数据保全证据,不代表GPU指标通过。来源:Lab34持续负载方案与保全记录。

版本日期修订与来源
v1.42026-10-25备份候选metadata仅用于选择、partial保全受限恢复;完整手机源与host SHA及升级后全树核同,见Lab34方案。

Perfowl · Performance Observer