Files
soft_quay/docs/tasks/T-613.md

9.6 KiB

id, title, phase, deps, status, created, issue, context_ref, claim_branch, work_branch, write_paths
id title phase deps status created issue context_ref claim_branch work_branch write_paths
T-613 建立安装文件与目录事务耐久顺序 1
T-612
DONE 2026-07-18 null 0f69fa3bb4d00e6c79938c0a7bc66f5e0faec09e null agent/codex/T-613
docs/tasks/T-613.md
core/installer/
docs/api.md
docs/04-architecture.md
docs/05-coding-rules.md
docs/review/phase1-security-review.md
docs/00-ai-start-here.md
docs/06-tasks.md
docs/current-state.md

问题 / 背景

T-103 已建立 staging → current → backup 的进程崩溃恢复状态机,但它只证明了关键步骤之间的进程退出可恢复,没有建立真实断电时的数据与目录项耐久顺序。当前 Extractor 在 io.Copy 后直接关闭 payload 文件;transaction.go 虽会同步临时 journal 内容,但 journal rename、journal 删除、目录 rename 以及 RemoveAll 后都没有父目录持久化栅栏。发生掉电时,committed journal、payload 数据、current/backup 目录名和清理动作可能以不安全的相对顺序落盘。

根据 docs/review/phase1-security-review.md 最终处理顺序第 3 项,这是 T-302 的独立阻断项。Windows 不存在与 POSIX 完全等价的目录 fsync;本任务必须把可用的 Windows 目录句柄 FlushFileBuffers 策略落实为可测试的实现,且在无法建立所需栅栏时 fail closed。单元测试能够证明调用顺序和错误传播,不能声称替代 Windows VM/真机的物理断电、文件锁或杀毒软件故障注入。

方案

  1. 在 core/installer 建立单一、可测试的内部耐久接口/栅栏,由 Extractor、transaction、Switcher、Recovery 和受控删除路径复用:
    • 普通文件在写入、CRC/长度校验成功后必须 Sync 再关闭;Sync/Close 任一失败使本次 staging 删除并返回错误,不得报告可安装结果。
    • staging 完整提取后,按子目录到根目录的顺序同步目录树,保证已创建文件、目录和 metadata 在 Switcher 写 prepared journal 前已经经过目录项栅栏。
    • 写 journal 继续采用临时文件→内容 Sync→Close→rename;目标/backup rename、backup 删除和最终 journal 删除后都要同步 app root。writeTransaction 成功的语义必须是对应 phase 已经完成文件与目录栅栏。
    • 每次 current ↔ backup ↔ staging 目录 rename、rollback/recovery rename 和 removeManagedDirectory 完成后,先同步 app root,再运行测试钩子或写下一 phase journal。已 committed 并持久化前不得删除 backup;删除 backup 与 transaction 后也要完成 root 栅栏。
  2. 用 build tag 分离目录栅栏实现,不引入第三方依赖:
    • 非 Windows 使用打开目录后的 File.Sync。
    • Windows 使用 syscall.CreateFile 的 FILE_FLAG_BACKUP_SEMANTICS 打开目录句柄并调用 FlushFileBuffers;只使用 Win7 已存在的 API,不让现代 API 进入导入表。目录打开、flush 或 close 失败必须返回稳定 installer 耐久错误,不得静默降级为 no-op。
    • 所有真实文件系统操作保持在 installer 内,不把平台 API 泄露给 domain/application 或 Gio。测试用可注入 fake fence 精确断言顺序和故障传播;Windows 原生测试必须验证真实目录 fence 可执行。
  3. 固化顺序与恢复语义:
    1. 每个 payload 的内容已验证、Sync、Close;
    2. staging 子目录至根目录已 Sync;
    3. prepared journal 以临时文件内容 Sync + root 栅栏持久化;
    4. current→backup rename + root 栅栏 → current_backed_up journal + root 栅栏;
    5. staging→current rename + root 栅栏 → staging_activated journal + root 栅栏;
    6. health 成功后 committed journal + root 栅栏,才清理 backup 和 transaction;health 失败/Recovery 的反向 rename 与清理也遵循同一栅栏。
  4. 新增耐久错误、单元与原生测试:覆盖 payload Sync/Close、staging tree、journal replace/remove、每个切换/rollback/recovery rename、目录清理的调用顺序;注入每一栅栏失败并断言不越过下一个 phase、现有恢复不变式仍成立、无误报成功。Windows 用例在真实目录上验证目录句柄 Flush;不支持/失败的文件系统必须显式失败。保留既有阶段崩溃、路径、ZIP、CRC 与恢复矩阵。
  5. 同步 API、架构、编码规则和审核记录,明确“耐久栅栏”相对于“物理掉电证明”的边界。T-302/T-601 仍需在目标 NTFS/Windows VM 或真机执行断电、杀毒/文件锁等故障注入;本任务不把单元测试描述为完整硬件级持久化担保。

验收要点

  • 每个成功提取的 payload 文件在 CRC/大小校验后、返回 ExtractResult 前已 Sync 并正确 Close;Sync/Close 失败删除 staging,不返回成功,不留下可被 Switcher 使用的半持久 payload。
  • staging 目录树在 Extractor 成功返回前按子→父顺序经过目录栅栏;失败 fail closed。prepared journal 只可在该栅栏之后写入。
  • 所有 journal replace/remove、current/backup/staging rename 和受控目录删除都使用单一耐久路径;每次可见目录变更先有 root 栅栏,再可写后续 phase/运行 afterStep。committed journal 经过 root 栅栏后才允许清理 backup 和 transaction。
  • Windows 目录栅栏通过 FILE_FLAG_BACKUP_SEMANTICS 目录句柄 + FlushFileBuffers 实现且可在原生测试运行;不支持或 flush 失败返回稳定耐久错误,绝不静默跳过。非 Windows 无头测试继续可运行。
  • fake fence 覆盖成功顺序与每一类栅栏故障:payload、staging tree、journal、rename、rollback/recovery、cleanup;故障不推进 phase,既有 Recover 状态机仍恢复或 fail closed。
  • 文档准确区分“代码已建立并测试耐久顺序”与“尚需 T-302/T-601 在 Windows VM/真机完成物理断电、文件锁/杀毒干扰故障注入”;不再把目前单元测试写成硬件掉电证明。
  • Go 1.20.14 + GOWORK=off 下 go vet ./installer、go test -count=10 ./installer 通过;当前 Windows 主机上的 installer 原生测试、完整 ./scripts/verify_phase0.ps1、python scripts/validate_agent_context.py、python scripts/validate_harness_governance.py 与提交前差异检查通过。

边界(不改什么)

  • 不实现 T-302 的下载、SHA-256、Catalog identity、app.json/files.json 校验、安装事件或生产 cmd 编排;不把现有 installer 原型宣称为完整用户安装链。
  • 不修改 ZIP 协议、Catalog/下载队列、路径安全、中央目录预扫描、health-check 定义、UI、Schema、Go/Gio 版本或 workspace。
  • 不引入第三方 durability 库,不使用 Win10+ API;Windows 只用 Win7 已有的 CreateFile/FlushFileBuffers 路径。
  • 不声称单元测试、Sync 或 FlushFileBuffers 覆盖电源切断、硬件缓存、网络文件系统、文件锁或杀毒软件的所有行为;这类目标环境故障注入仍须 T-302/T-601 单独验证。
  • 不修改、提交或删除用户的 soft_quay.code-workspace,不启动子 Agent,不在本任务外落成 T-614 或恢复 T-302。

协作约束

  • 按仓库当前规则由单 Agent 串行执行,不启动子 Agent。
  • 本任务只允许修改 frontmatter 中的 write_paths;若需要新的安装协议、下载链接线、外部 VM/真机基础设施或第三方依赖,必须停止并另立任务,不得在 T-613 内扩边。
  • T-613 完成、完整验证并提交前,不落成或领取签名向量整改、T-302 或其他后续任务。

执行记录

  • 2026-07-18:根据 docs/review/phase1-security-review.md 最终处理顺序第 3 项落成任务;现有全局最大任务为 T-612,因此取 T-613,依赖已完成的 T-612。
  • 2026-07-18:本地代码检索确认 Extractor 在 io.Copy 后仅 Close payload,replaceFileWithBackup 只 Sync 临时 journal 内容,Switcher/Recovery 的目录 rename 与 removeManagedDirectory 后均没有父目录栅栏。任务因此覆盖 payload、目录树、journal、切换/恢复 rename 与清理的统一顺序,不只修单个 Sync()。
  • 2026-07-18:Windows 策略限定为 Win7 已有的目录句柄 CreateFile(FILE_FLAG_BACKUP_SEMANTICS) + FlushFileBuffers,失败 fail closed;物理掉电/锁/杀毒故障注入的实测证据仍明确后置到 T-302/T-601。
  • 2026-07-18:在 core/installer 落地单一内部耐久接口。Extractor 现在在每个 payload 的 CRC/长度检查后 Sync、Close,并按 staging 子目录→根→父目录建立栅栏;transaction 的临时 journal 先 Sync/Close,全部 journal rename/remove 都同步 app root。Switcher、rollback、Recovery 与受控目录删除复用同一路径,每次目录可见变更完成 root 栅栏后才可进入下一 phase/afterStep。
  • 2026-07-18:Windows 目录实现使用 Go 1.20 标准 syscall.CreateFile 的读写目录句柄、FILE_FLAG_BACKUP_SEMANTICS 和 FlushFileBuffers,没有新增第三方或 Win10+ API;非 Windows 以打开目录的 File.Sync 实现。原生 Windows TestSyncDirectoryPath 通过,打开、flush、close 任一步失败均返回 ErrDurability 错误链。
  • 2026-07-18:新增 fake fence 顺序/失败测试,覆盖 payload、staging tree、journal、rename、rollback、Recovery 和 committed cleanup;失败不越过下一 phase,既有 Recover 仍将可恢复状态收敛。同步完成 API/架构/编码规则/审核结论,明确代码层顺序不替代 T-302/T-601 的 VM/真机物理断电、文件锁与杀毒软件故障注入。
  • 2026-07-18:验证通过:GOWORK=off go -C core vet ./installer、GOWORK=off go -C core test -count=10 ./installer、./scripts/verify_phase0.ps1、python scripts/validate_agent_context.py、python scripts/validate_harness_governance.py 与 git diff --check;完整验证同时覆盖 Go 1.20.14 core、现代版与 Win7 版构建/测试。无 blocker。