许可证正文**逐字采用** 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 个导出不变。
19 KiB
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 与工作树上完全一致)才定位到是口径差异。 报告里凡出现行数处已统一标注口径。
建议(按优先级)
| 优先级 | 事项 | 理由 |
|---|---|---|
补 LICENSE |
已补 Apache-2.0(逐字采用 ASF 原文,仅填 APPENDIX 版权行) | |
| 提交并同步到云端 | 3 个提交已推送到 origin/main(快进,非 force) |
|
| 中 | 把 baknret.key 移出仓库目录 |
虽未被跟踪,工作区放口令文件是卫生问题 |
| 中 | 确认 Write-BakNRetRunSummary 的 $Mode 意图 |
未使用参数 + 未被调用 |
| 低 | 在 VM 内补跑修复后的三套件 | 本次因提权被禁未取得(宿主侧已全绿) |
| 低 | 启动时打印 7-Zip 版本 | 便于排障 |
本次流水线的诚实边界
- VM 测试未跑全:会话审批策略改为
never后gsudo提权被自动拒绝,而 Hyper-V 与 PowerShell Direct 都需要管理员。已核实的部分(同步成功、VM 内 Pester 183 通过)如实记录; 零依赖与端到端套件在 VM 内的结果本次未取得,不做推断。宿主侧同等套件已全部通过。 - 静态分析未压到 0:剩余 80 条与仓库已声明的偏离冲突,按你的决定登记而不改, 逐条附依据。严格模式的「零容忍」在这里与仓库自身配置相抵,选择尊重仓库约定。
- 阶段 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 的报告生成方独立渲染并留证 |