Files
pure-note/docs/acceptance.md
T
wangairnan 412821684b docs: 新增第 2 轮评审报告、D36 决策记录与 serve 旧词收尾
review-round2.md 全量修复清单;decisions.md 澄清 D6、新增 D36;design/acceptance 残留 serve → start(P2-14)。
2026-09-08 17:32:57 +08:00

92 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 最终验收报告
> 依据:docs/design.md(v1.1);决策记录:docs/decisions.md
> 日期:2026-09-08
## 一、验收结论
**全部通过。** 设计文档定义的功能(§2.1 F1–F8)、安全设计(§9)、测试策略(§13)
与里程碑验收门(§12 M0–M4)均已实现并有自动化测试或实测证据支撑。
`go vet` / `govulncheck` / `npm audit` 干净;全量测试绿;构建冒烟 19 项断言全过;
真实浏览器端到端走查(博客浏览 / 登录 / 编辑器 / 回收站)通过。
## 二、§9.3 上线前安全检查单(逐项)
| 检查项 | 状态 | 证据 |
| --- | --- | --- |
| go vet + govulncheck 干净;npm audit 干净 | ✅ | `go vet ./...` 通过;`govulncheck ./...`:0 个影响本代码的漏洞;`npm audit --omit=dev`:0 vulnerabilities |
| 可见性矩阵自动化用例全绿(列表/详情/标签/RSS/sitemap/图片直链/HTML meta/管理接口) | ✅ | `TestVisibilityMatrix`(表驱动:{匿名,管理员} × {公开,私有,回收站,不存在} × 7 出口 + 图片缓存头分流 + 404 防枚举一致性) |
| 恶意 Markdown 渲染无脚本执行 | ✅ | 前端 vitest 冒烟(script/iframe/事件属性/javascript:/style/SVG 全拦截 + schema 快照);服务端 goldmark 转义 + bluemonday(`TestRenderEscapesDangerousHTML`);CSP `script-src 'self'` 兜底(冒烟断言) |
| 跨站伪造 POST(含 login/logout)被拒;Cookie 属性与 `__Host-` 前缀;刷新后 CSRF 经 /api/me 重取 | ✅ | `TestCSRF`(无 token/错 token/跨源 login/logout/缺 Origin+Referer);`TestLoginFlow`(/api/me 返回 csrf_token);实测 Set-Cookie:`__Host-pn_session=…; Path=/; Max-Age=604800; HttpOnly; Secure; SameSite=Lax` |
| 伪造扩展名/SVG/超限文件被拒 | ✅ | `TestUploadValidation`(魔数不符 415、SVG 拒绝、声明类型白名单、空文件 400、>5MB 413、同字节去重同 id) |
| 登录爆破触发 429;失败日志含 IP | ✅ | `TestLoginRateLimit`(第 6 次失败 429 + Retry-After、正确口令在桶空时同样 429、成功不计费);实测日志 `login_failed ip=127.0.0.1 ip_bucket_left=9 account_bucket_left=4` |
| settings 响应不含口令哈希;PUT 无法写非白名单键;改密通过 | ✅ | `TestSettingsWhitelist`(GET 不含 password/argon2、未知键 400、page_size 生效);`TestPasswordChange`(旧错 401/弱新 400/成功/旧口令失效/当前会话保持) |
| 图片:私有与不存在统一 404;缓存头 immutable vs no-store | ✅ | `TestVisibilityMatrix` 图片出口(公开 immutable / 非公开 no-store / 匿名 404 响应体一致防枚举) |
| 安全头逐项确认(含 HSTS) | ✅ | 冒烟脚本断言 HSTS/X-Frame-Options/nosniff/CSP;全套头由 SecurityHeaders 中间件统一下发(curl 实测) |
| 备份还原演练(停服→删 wal/shm→替换 db→恢复);备份 0600 | ✅ | 演练实测:备份文件 `-rw-------`(0600);删 wal/shm → 换备份 → 起服 → 数据完整可读 |
| 回收站恢复用例;gc dry-run→commit;7 天内孤儿图片不删 | ✅ | `TestTrashLifecycleAndGC` + CLI 实测(dry-run 报告、commit 执行、宽限期保留) |
| user_version 越界启动被拒;--allow-newer 放行 | ✅ | `TestMigrationNewerRejected` + CLI 实测(拒启 exit=1;--allow-newer 后 /api/health 200) |
| 构建冒烟测试通过 | ✅ | `make smoke`(npm ci → vite build → sync-assets → CGO_ENABLED=0 交叉构建 → 起服务 19 项断言:SPA 资源 200+MIME、meta 注入、安全头、私有不可见) |
| Caddy 仅开放 80/443、程序监听 127.0.0.1 | ✅(配置交付) | deploy/Caddyfile + pn.service(`--addr 127.0.0.1:8080 --behind-proxy`)——本机无生产部署环境,按配置文件验收 |
## 三、§13 测试策略落地
| 测试组 | 用例 | 位置 |
| --- | --- | --- |
| 可见性矩阵 | 表驱动全出口(§13 矩阵全覆盖,含图片并集语义与缓存头分流) | internal/httpapi/visibility_test.go |
| 迁移 | 空库→最新幂等;越界拒启;allow-newer 放行;外键级联 | internal/store/store_test.go |
| 认证会话 | 登录成功/失败统一文案、429、改密三态、轮换后 CSRF 不变、/api/me 两态、登出 | internal/httpapi/api_test.go |
| CSRF | 无/错 token 403、跨源(含 login/logout)、缺 Origin+Referer | 同上 |
| 上传 | 魔数/伪造扩展/SVG/超限/空/去重 | 同上 |
| 回收站 | 删→不可见→恢复→可见;图片引用保全;30 天 gc;7 天宽限 | 同上 |
| gc | dry-run 不改数据、过期会话清理 | 同上 |
| slug | 中文 post-YYYYMMDD、同日冲突 -2、回收站占用、手改 409 字段级错误、非法格式 400 | 同上 |
| 元信息注入 | 公开注入笔记 meta;私有/不存在回退站点默认;html/template 转义 | visibility_test.go + webui_test.go |
| 前端 vitest | sanitize schema 快照 + 恶意 Markdown 冒烟 + GFM 回归 + node prop 泄漏检查 | web/tests/markdown.test.tsx(6/6) |
**回归命令**:`make test`(go vet + go test ./... + vitest)与 `make smoke`。
## 四、里程碑验收门(§12)
| 里程碑 | 验收门 | 状态 |
| --- | --- | --- |
| M0 骨架 | `make build` 产出可运行二进制;start 起服务;/api/health 200 | ✅ |
| M1 公开浏览 | 笔记/标签 API + 博客三页 + 渲染管线 + RSS/sitemap + meta 注入;公开侧可见性用例;构建冒烟 | ✅ |
| M2 管理端 | 登录/会话/CSRF + CRUD + 回收站 + 图片上传 + 编辑器 + 改密;完整矩阵 + 各组用例随功能交付 | ✅ |
| M3 安全加固 | §9.3 全项通过(见上表);CSP nonce 硬化按设计留 M3+ 待办 | ✅ |
| M4 运维 | systemd ×3 + Caddyfile + 备份恢复演练真还原 | ✅(压测 hey 未做:单机个人规模非验收必需,工具未安装;`SetMaxOpenConns(1)` 串行模型按设计实现) |
## 五、真实浏览器走查(补充验收)
以内置浏览器对 `make build` 产物做黑盒走查,全部通过:
1. **博客首页**:暗色主题、站点标题、置顶排序、标签云计数、页脚 RSS/Sitemap;
2. **详情页**:面包屑、标题/日期/标签、GFM 表格与任务列表(disabled checkbox)渲染、上一篇/下一篇;
3. **登录**:错误口令提示、成功跳转 /admin;
4. **管理列表**:公/私徽标、slug/时间/标签、状态切换/编辑/删除;
5. **编辑器**:CodeMirror 输入、分屏实时预览、保存后自动生成 slug("go")、摘要自动截取、URL 迁移到 /notes/3/edit;
6. **回收站**:删除确认弹窗 → 列表移除 → 回收站出现 → 恢复 → 公开页重新可见(标签页标题即 meta 注入验证)。
## 六、交付物清单
```
pn(单二进制,CGO_ENABLED=0,go:embed 前端产物)
├── cmd/pn/ start / init / passwd / backup / gc / version
├── internal/{config,store,auth,markdown,middleware,httpapi,webui}
├── web/ React 19 + Vite 8 + Tailwind 4(vitest 6 用例)
├── deploy/ pn.service、pn-maint.{service,timer}、Caddyfile
├── scripts/smoke.sh 构建冒烟(make smoke)
├── Makefile / README.md / .gitignore
└── docs/{design.md, review-round1.md, decisions.md, acceptance.md}
```
依赖面:Go 直接依赖 6 个(预算 ≤6);npm 生产依赖与设计一致,0 漏洞。
锁定文件 go.sum / package-lock.json 入库。
## 七、遗留事项(不阻塞验收)
1. CSP `style-src` nonce 硬化 —— 设计明示 M3+ 待办(§9.2/§14),v1 保留 'unsafe-inline';
2. hey 压测 —— M4 可选项,需生产规格环境;
3. `vite dev` 开发态标签页标题显示模板占位符(D23,仅开发态外观);
4. shadcn CLI 未使用,按「复制式组件」理念手写等价最小组件集(D22)。