Files
BakNRet/.scratch/ci-cd/cicd_report_20260928.md
Shuery 2cbcaaee48 chore(license): 补 Apache License 2.0
许可证正文**逐字采用** ASF 的 LICENSE-2.0.txt,只把 APPENDIX 里的版权占位行填成
"Copyright 2026 Shuery" —— 正文一个字节都没改。

为此先下载规范文本再打补丁,而不是凭记忆抄一遍:许可证抄错一个词就不再是那个许可证了。
校验方式是逐行对比本仓 LICENSE 与规范文本,结果只差版权那一行(规范文本 11358 字节、
SHA256 cfc7749b…;本仓 11337 字节,差额 21 字节正好等于两行文本的长度差)。

选 Apache-2.0 而非 MIT,主要差别在第 3 节的**明示专利授权**:贡献者授予专利许可,
而不只是版权许可。

同时:
  * README 的许可证章节从「尚未声明」改成实际条款摘要 + 徽章(License: Apache-2.0)
  * BakNRet.psd1 的 PrivateData.PSData 登记 LicenseUri 与 Copyright,Import-Module
    之后可直接读到许可信息
  * 不加 NOTICE:第 4(d) 节只在作品本身带 NOTICE 时才要求向下传递,而本仓库不分发第三方
    代码(Pester / PSScriptAnalyzer 只放在 .tools/ 供本地测试,既进 gitignore 也不进分发产物)
  * .scratch/ci-cd 的两份报告补了带日期的「后续更新」注记,关掉许可证合规的 L-1 与依赖
    安全扫描的 R-1,避免后来的人读到「主许可证未声明」这个已经过期的结论

验收:test.ps1 9/9 全绿(5.1 与 7);静态分析 80 条、0 Error(与改动前持平);
README 相对链接 0 失效、新增外部链接 2/2 返回 200;双宿主解析 psd1 零错、89 个导出不变。
2026-10-02 01:34:45 +08:00

19 KiB
Raw Permalink Blame History

BakNRet CI/CD 流水线报告

  • 报告时间:2026-09-28
  • 项目:BakNRet(Windows 备份 / 恢复工具,PowerShell 5.1 + 7.x)
  • 分支:refactor/ms-conventions
  • 测试环境:Hyper-V VM BakNRet-Lab(Gen2 / 8 vCPU / 12 GB)+ 宿主 STRIX-X870A
  • 流水线入口:/ci-cd-pipeline(用户指定:测试环境为 Hyper-V 虚拟机)

Note

后续更新(2026-10-02):本报告里的两条许可证相关待办已关闭 —— 补 LICENSE (Apache-2.0,正文逐字采用 ASF 的 LICENSE-2.0.txt,仅填 APPENDIX 版权行)已落地, 对应许可证合规的 L-1 与依赖安全扫描的 R-1。报告正文保留为流水线运行当时的记录。


阶段总览

阶段 名称 状态 关键结果
0.1 依赖安全扫描 ✅ 通过 零运行时依赖;0 致命/严重;2 条一般级卫生风险
0.2 许可证合规 ⚠️ 通过(有缺口) 无 copyleft 冲突;主许可证未声明
0.3 语法检查与修复 ✅ 通过 131/131 双宿主解析零错;修 2 处排版 + 1 处自伤;分析器 84→80 条、0 Error
1 黑盒测试 ✅ 通过 11 条用例:9 通过、2 失败(0 致命/严重)
2 缺陷修复与回归 ✅ 通过 修 1 个真实缺陷;回归 12/12;Pester 183→192 全绿
3 测试增强与性能 ✅ 通过 性能基线首次建立、无退化;单测增强给出优先级、未擅自新增
4 代码规范化 ✅ 通过 分析器格式规则门禁通过(0 Error);仓库自有格式门禁为准
5 文档生成与质量检查 ✅ 通过 PROJECT_REPORT.md 532 行;markdownlint 0 错;死链 0
6 数据库迁移审查 ⏭️ 不适用 全仓无数据库、无 SQL、无迁移脚本
7 生产部署 ⛔ 停在人工确认门 需你确认(见文末)
8 收尾 ✅ 完成 本报告

阶段 0:静态分析与修复

0.1 依赖安全扫描

项目 结论
依赖管理文件 不存在(无 package.json / requirements.txt / go.mod / pom.xml / Cargo.toml)
运行时依赖 0 个模块(只需 PowerShell 5.1 或 7.x + 7-Zip)
CVE 无可报(没有任何被本仓库固定版本的第三方库)

风险:R-1 无 LICENSE(一般)· R-2 工作区存在口令文件 baknret.key(一般,未被 git 跟踪, .gitignore:18 *.key 已忽略;全程未读取其内容)· R-3 不检查 7z 版本(建议)· R-4 口令经命令行传给 7z(建议,上游限制)。

0.2 许可证合规

组件 许可证 是否随分发
本项目 未声明 —
Pester 5.9.1 Apache-2.0 否(.tools/,gitignore)
PSScriptAnalyzer 1.25.0 MIT 否
Newtonsoft.Json MIT 否
7-Zip 26.03 LGPL-2.1 + BSD-3 + unRAR 限制 否(用户自备)

无 copyleft 传染。7-Zip 的 unRAR 限制只禁止「用其代码还原 RAR 压缩算法」,本项目不触发。

0.3 语法检查与自动修复

修复的真实缺陷

文件:行 规则 修复
Backup.ps1:42 PSUseConsistentWhitespace @($Rest)+ @(...) → @($Rest) + @(...)
Restore.ps1:42 同上 同上

修复过程中的自伤与恢复(重要教训)

编辑这两个文件后 UTF-8 BOM 被抹掉,导致 5.1 立即报 The string is missing the terminator: '., 而 PowerShell 7 完全正常。用 UTF8Encoding($true) 重写后恢复,双宿主 parse_bad=0。 这条恰好实证了本仓库 Encode 层门禁的价值,也说明「编辑 PowerShell 源文件必须保留 BOM」是硬约束。

未修复项:80 条全部为风格类(0 Error),按用户决定登记为「仓库已声明的偏离」,逐条附依据: 行长 160 是配置里写明的有意偏离(PSScriptAnalyzerSettings.psd1 + ADR-0008); PSPlaceCloseBrace 等集中在 Run-Tests.ps1 的测试夹具字符串内(改了会改变断言语义); PSUseSupportsShouldProcess 已全局排除属已知噪声;空 catch 是有意为之(注释已写明理由)。

待人工确认的观察项:Write-BakNRetRunSummary 的 $Mode 参数声明了 ValidateSet 与 Mandatory 但函数体内从未读取,且该函数未被仓库内任何代码调用(仅对外导出)。判断为未完工实现, 删除会破坏公共 API 参数契约,故登记而不改动。


阶段 1 → 2:黑盒测试与缺陷修复

测试环境确认(技能强制)

项目 事实
目标 Hyper-V VM BakNRet-Lab(Gen2 / 8 vCPU / 12 GB / Default Switch 内部 NAT)
检查点 自动检查点、clean-baseline、sandbox-ready
数据隔离 仓库自述:全部操作在 VM 内进行,宿主机仓库 / Backups\ / logs\ 不被写入
生产暴露 无(无生产服务器、无域名、无对外服务)

已获用户明确确认后开始测试。

用例与结果

编号 用例 阶段 1 阶段 2 回归
BB-01 真实备份:退出码 0 + 产出归档 + manifest PASS PASS
BB-02 排除模式真的把内容挡在归档之外(逐个归档内容核对) PASS PASS
BB-03 -DryRun 一个字节都不写(manifest SHA256 前后一致) PASS PASS
BB-04 源不存在记 missing-source,不算失败 PASS PASS
BB-05 -Only 只处理匹配的条目 PASS PASS
BB-06 显式清单不存在 → 必须报失败 FAIL PASS(已修)
BB-06b 对照:未显式指定时首次运行仍建模板 — PASS
BB-07 旧名字垫片转发且退出码原样传递 PASS PASS
BB-08 5.1 与 7.x 行为一致 PASS PASS
BB-09 含 & 与 # 的路径(整行引号写法) PASS PASS
BB-10 无控制台且无 -InputScript 时报错退出,绝不挂起 PASS PASS
BB-11 孤儿归档审计 FAIL(夹具错误) PASS
  • 阶段 1:11 条 → 通过 9、失败 2、致命/严重 0
  • 阶段 2 回归:12 条 → 全部通过

缺陷清单

ID 严重程度 标题 根因 修复
DEF-01 一般 显式指定的清单不存在时静默成功 「首次运行引导」与「路径写错」两条语义共用一条代码路径 按 $PSBoundParameters.ContainsKey('BackupListPath') 分流:显式指定 → ERROR + exit 1;未指定 → 保留引导
DEF-02 提示 两处运算符前后缺空格 手写拼接 补空格(分析器该规则归零)
DEF-03 提示 孤儿审计在干跑下不报告 测试夹具错误(非产品缺陷) 修正夹具为真实运行

DEF-01 的影响:计划任务里 -BackupListPath 写错(或相对路径按了别的工作目录解析)时, 脚本会在错误位置凭空建一份模板并 exit 0;任务计划程序读到「上次运行结果 = 成功」, 而实际一个条目都没处理。这与本仓库已修过的「27 条被静默跳过、退出码仍是 0」是同一类缺陷, 因此判为需要修复。Restore-Data.ps1 有对称问题(生成清单 + exit 0),一并修复。

DEF-03 的处置值得记录:BB-11 初判 FAIL,复核后确认是用例设计错了(用了 -DryRun, 而孤儿审计只在真实运行里报告)。保留该记录,因为它说明「失败用例必须先复核再归因」, 否则会把测试自身的问题记到产品头上。

回归证据

BakNRet 验收:层次 [Parse, Unit, Smoke, E2E],宿主 [7, 5.1]
  [PASS] Encode any  受管 133 个文件:缺 BOM 0、CRLF 0、制表符 0
  [PASS] Parse 7    解析通过 131/131 个文件
  [PASS] Unit  7 / Smoke 7 / E2E 7
  [PASS] Parse 5.1  解析通过 131/131 个文件
  [PASS] Unit  5.1 / Smoke 5.1 / E2E 5.1
验收全部通过:9 项(宿主 7 + 5.1)
Tests Passed: 192, Failed: 0, Skipped: 0, Inconclusive: 0, NotRun: 0
  [+] [回归] 显式指定的清单不存在时报失败(退出码 1),且不在错误位置建模板 1.43s
  [+] [回归] 未显式指定清单时的首次运行仍建模板并退出 0(引导不能被误伤) 1.44s

新增的 2 条用例是成对的:只测一条的话,「把所有缺失都改成 exit 1」这种错误实现也能骗过测试。


阶段 3:测试增强与性能验证

单元测试增强(可选步骤)

指标 数值
对外函数 89(与 FunctionsToExport 白名单逐一核对一致)
测试中直接点名 70(78.7%)
未直接点名 19(其中多条由集成路径间接覆盖)

建议补测(本轮未擅自新增,因用户选择了最小侵入姿态,且该步骤标注为可选): Move-BakNRetArchiveIntoPlace(原子替换,历史上真出过「先删后移」缺陷)、 Resolve-BakNRetRootedPath(相对路径按仓库根解析这条计划任务关键约定)。 安全描述符相关的 4 个函数不建议补单元测试 —— 它们需要真实提权与真实 NTFS, 仓库已用 tools/lab/Lab.ps1 acl-test(含负对照)在真 VM 里覆盖,那是更正确的层次。

性能基线(首次建立)

环境:宿主 STRIX-X870A,PowerShell 7.7.0-preview.5,7-Zip 26.03 (x64)

指标 采样 min 中位数 max
模块导入 5 628.3 ms 631.0 ms 2052.8 ms
清单解析 ×50 轮 5 2535.9 ms 2546.9 ms 2691.5 ms
只读干跑(真实 28 条清单) 5 9839.9 ms 9869.2 ms 12900.8 ms
源树扫描(200 文件 ×3 轮) 1 — 18.2 ms —

7z 吞吐(固定夹具 200 × 32 KB 随机数据):-mx=0 46.7 ms / -mx=5 224.2 ms / -mx=9 225.2 ms。 夹具为不可压缩数据,故体积不随级别变化 —— 该夹具测的是吞吐与 I/O,不是压缩率。

判定:首次基线,无历史可比,无退化。基线文件:.scratch/ci-cd/20260928-001722/perf_baseline.json。 跨机比较无意义,仅同机同宿主下对比。


阶段 4:代码规范化

  • 仓库自有的格式门禁是 PSScriptAnalyzerSettings.psd1 打开的 6 条格式规则 (括号 / 缩进 / 空格 / 对齐 / 大小写)+ 160 字符行长,通过 tools/Invoke-Analyzer.ps1 执行。
  • 本次:0 条 Error,格式类告警从 4 条(whitespace)降到 0 条。
  • PowerShell 没有官方「Google 风格」格式化器;本仓库的等价物就是上面这套规则集, 故阶段 4 以仓库门禁为准,未引入外部格式化器(避免制造与仓库约定冲突的大 diff)。
  • README.md / PROJECT_REPORT.md 由 Prettier 3.9.9 格式化并通过 --check。

阶段 5:文档生成与质量检查

产物 规模 质量门禁
README.md 925 行 Prettier ✅;markdownlint 17 → 10 错(全为 MD051 假阳性);外部链接 11/11 → 200;内部相对链接全通;63 个标题锚点全部可解析;3 张 Mermaid 图在真实浏览器中解析通过
PROJECT_REPORT.md 532 行 Prettier ✅;markdownlint 0 错;相对链接 18/18 有效;外部链接 3/3 → 200

MD051 假阳性说明:README 的标题带 emoji(如 ## 🚀 快速开始),GitHub 生成的锚点是 #-快速开始(剥离 emoji 后前导连字符)。markdownlint-cli2 的锚点生成器不做 emoji 剥离, 因此把这类链接报为无效。已用独立脚本按 GitHub 规则复算全部锚点,63 个全部可解析, 故不修改标题(emoji 标题是本次文档改造的要求之一)。

死链检查:全部外部链接逐个 HEAD 请求验证,返回 200;相对链接逐个 Test-Path 验证存在。


阶段 6:数据库迁移审查 —— 不适用

检查 结果
*.sql / *.db / *.sqlite 文件 无
Invoke-Sqlcmd / SqlConnection / CREATE TABLE / SELECT ... FROM 无命中
迁移脚本目录 不存在

本项目是本地 CLI 工具,不涉及任何数据库,该阶段跳过。


阶段 7:生产部署 —— 停在人工确认门

Caution

未执行任何部署动作。 本项目的「生产部署」= 在真实机器上注册计划任务并让它按计划跑备份, 属于对用户个人环境产生持久副作用的操作,必须由你确认。

部署前状态

项目 状态
代码门禁 ✅ 9/9(双宿主)· Pester 192/192 · 黑盒 12/12
工作树 6 个文件已改(+796 / −202),新增 PROJECT_REPORT.md(未提交)
运行前提 PowerShell 5.1/7.x ✅ · 7-Zip 26.03 ✅
阻塞项 会话审批策略为 never,gsudo 提权被自动拒绝(退出码 999);注册计划任务需要管理员

建议的部署步骤(待你确认后执行)

# 1. 先确认清单与口令就位(口令应在仓库外)
.\Backup-Data.ps1 -DryRun            # 只读,先看计划与空间预估
.\Backup-Data.ps1 -Only 'Edge'       # 先只备一项,验证端到端
.\Restore-Data.ps1 -VerifyOnly       # 只校验,不解压

# 2. 注册计划任务(需要管理员)
.\tools\Register-BackupTask.ps1 -At '21:30' -DryRun   # 先看将要注册什么
.\tools\Register-BackupTask.ps1 -At '21:30'           # 确认后注册

# 3. 部署后验证
Get-ScheduledTaskInfo -TaskName 'BakNRet Backup'      # 上次运行结果
Start-ScheduledTask   -TaskName 'BakNRet Backup'      # 手动跑一次

回滚

.\tools\Register-BackupTask.ps1 -Remove

风险:注册计划任务本身可逆(-Remove);但首次真实备份会写入归档目录并消耗磁盘 (README 已有空间预估与逐条目守卫,不够时只告警不中断)。


阶段 8:关键指标与建议

关键指标

指标 数值
验收门禁 9/9 PASS(PS 7.7.0-preview.5 + 5.1.26100.9502 各一遍)
解析 131/131 文件双宿主零错
Pester 192 通过 / 0 失败 / 0 跳过(183 → 补 2 条回归用例后 185 → 再补 7 条后 192)
黑盒回归 12/12
静态分析 80 条 / 0 Error(修复前 84)
运行时依赖 0
致命 / 严重缺陷 0
修复的真实缺陷 1(DEF-01)+ 2 处排版
代码变更 6 文件,+796 / −202
文档产物 README.md 925 行 · PROJECT_REPORT.md 1004 行

后续轮次已执行(用户回复「继续」之后)

事项 结果
补单元测试(原建议 2 条) ✅ 实际补 7 条断言:原子替换 3 条(含 AST 判定 File.Replace 与 [NullString]::Value)、路径解析 4 条(含 AST 判定「模块里不得引用 $PSScriptRoot」)。Pester 185 → 192,0 失败
修正 README 的零依赖套件条数 ✅ 实测为 128 项(原写 111,已改正)。这是上一轮 README 重写留下的事实错误,由 PROJECT_REPORT 的核对暴露出来
复核 PROJECT_REPORT.md 的数字 ✅ 行数口径标注为「非空行」;Pester 计数更新为 192;修正一处过度断言(原文称「修复后代码在 VM 内验证可用」,实际只取到修复前的 Pester 快照)
补 LICENSE / 移出 baknret.key / 确认 $Mode 意图 ⏸️ 仍需你决定

指标口径说明(本次踩到的坑):工程规模按非空行统计是 14389 行,按含空行统计是 16805 行。 两者都对,但必须写明口径。本次就因为口径不同(我用含空行、报告用非空行),一度把一份正确的数字 误判为错误,直到逐目录复算(模块 4369 非空行在 HEAD 与工作树上完全一致)才定位到是口径差异。 报告里凡出现行数处已统一标注口径。

建议(按优先级)

优先级 事项 理由
高 ✅ 已完成 2026-10-02 补 LICENSE 已补 Apache-2.0(逐字采用 ASF 原文,仅填 APPENDIX 版权行)
高 ✅ 已完成 2026-10-02 提交并同步到云端 3 个提交已推送到 origin/main(快进,非 force)
中 把 baknret.key 移出仓库目录 虽未被跟踪,工作区放口令文件是卫生问题
中 确认 Write-BakNRetRunSummary 的 $Mode 意图 未使用参数 + 未被调用
低 在 VM 内补跑修复后的三套件 本次因提权被禁未取得(宿主侧已全绿)
低 启动时打印 7-Zip 版本 便于排障

本次流水线的诚实边界

  1. VM 测试未跑全:会话审批策略改为 never 后 gsudo 提权被自动拒绝,而 Hyper-V 与 PowerShell Direct 都需要管理员。已核实的部分(同步成功、VM 内 Pester 183 通过)如实记录; 零依赖与端到端套件在 VM 内的结果本次未取得,不做推断。宿主侧同等套件已全部通过。
  2. 静态分析未压到 0:剩余 80 条与仓库已声明的偏离冲突,按你的决定登记而不改, 逐条附依据。严格模式的「零容忍」在这里与仓库自身配置相抵,选择尊重仓库约定。
  3. 阶段 7 未执行:等你在下一轮明确确认。

附件索引

所有中间产物在 .scratch/ci-cd/:

文件 内容
20260928-001722/dependency_security_report.md 依赖安全扫描报告
20260928-001722/license_check_report.md 许可证合规报告
20260928-001722/stage0_static_analysis.md 阶段 0 完整报告(含 BOM 自伤记录)
20260928-001722/stage3_tests_and_perf.md 阶段 3 报告(覆盖率分析 + 性能基线)
20260928-001722/analyzer_raw.txt 修复前静态分析原始输出(84 条)
20260928-001722/analyzer_after.txt 修复后静态分析输出(80 条)
20260928-001722/pester_after_fix.txt 修复后 Pester 详细输出(185 通过;本轮补测后 192)
20260928-001722/perf_baseline.json 性能基线数据
20260928-001722/blackbox_results.json 阶段 1 黑盒结果(11 条)
20260928-001722/blackbox_regression.json 阶段 2 回归结果(12 条)
blackbox-tests.ps1 黑盒用例脚本(可重跑)
blackbox-regression.ps1 回归脚本(可重跑)
perf-baseline.ps1 性能基线脚本(可重跑)
../_verify/mermaid.png README 三张 Mermaid 图的浏览器渲染截图
../_verify/report_mermaid.png PROJECT_REPORT 四张图的渲染截图(本次独立复验;与 20260928-001722/report_mermaid.png 是两次独立渲染,字节不同属正常)
20260928-001722/report_mermaid.png 同上四张图,由阶段 5 的报告生成方独立渲染并留证