Files
patbond-doc/docs/development/iterations/iteration-4/07-evidence-baseline-audit.md
T
lixi 79b33dba31 docs: M4「AI 创作」开工分析八份报告 + 汇总拍板页入档挂导航
八角色并行开工分析,合计 7448 行;另出 00 汇总页(跨角色收敛结论、
13 项待拍板、6 项待仲裁分歧、未取证项汇总),挂第四迭代导航最前。
mkdocs build --strict 通过。

基线实测修正(文档与实况不符):
- api 测试 381(releases.md 记 379,成因待仲裁)
- 埋点白名单 41(四份文档记 42,experiment_exposed 重复计数)
- 真机验证挂起 10 项(转述链 4→6→8→10 每跳丢项)
- E2E 断言机械可数 226(声称 234 无可复核来源)
- v0.4.0 实际发布 09-14 11:17;CI 非红,三仓五上下文全绿

多方独立收敛(无需拍板):
- 队列用 Postgres SKIP LOCKED + 租约列,不引入 Redis/MQ
- 服务端零对象写能力(ObjectStorage 无 put/get),M4 立足点缺地基
- 「四模块字节级快照锁 CI」不存在,实际门禁仅结构断言
- 定稿模型 input_asset_id NOT NULL,即图生图不支持文生图
- 跨 schema 外键补回是 V5 自身指令,裁剪理由已不成立

阻塞项与安全缺口:
- AI provider BLOCKED:零 SDK/endpoint/额度,正典种子即 fixture
- 分支保护必需上下文选错触发器:(push) 限定 branches:[dev],
  致「推 dev 即满足门禁」且「非 dev 分支 PR 永久无法合并」
- check-secrets.sh 对 sk-/sk-ant- 零覆盖,须先于任何 AI key 落地
- 北极星 09-21 窗口已于 09-13 关闭,补救无从下手,建议改事件驱动

本批核心教训:13 处文档/注释与代码相反且多已被下游采信,其中
5 处造成实际规模误判(widthPx M→S、数据模型早已定稿 L→M、
社区侧 purpose 校验实际不存在等)。汇总页 §0 立转述纪律。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 15:48:56 +08:00

39 KiB
Raw Blame History

M4 开工基线证据审计(07 号报告)

任务:M4「AI 创作」开工前对全部声称基线逐条亲自取证。每条结论附实际命令输出或 文件:行号。 执行人:Evidence Collector 取证日期:2026-09-14(工作机本地检出,三仓均停在 v0.4.0) 纪律:只读取证 + 只写本报告。未修改任何生产代码、mkdocs.yml、服务器配置;未执行 git commit / push

结论先行10 条基线中 6 条完全对上3 条对不上1 条无法静态复核。 三个专项均发现实质问题:真机挂起项实际登记 10 项而非 8 项(差的 2 项是 M3.5 的 caregiver 改宠物头像与获赞数对账,登记在常设清单里但未进功能清单跟踪); 发布 checklist 仍写着「M2+M3 两份」E2E,从未改成四份;文档站待评估项只以 表格单元格内的一句 ⚠️ 存在,无归属人、无时限、无 ADR。 埋点白名单实为 41 条(非 42),且没有任何测试锁住这个数。 api 测试实为 381(非 379)——v0.4.0 发布门禁表里的数字是旧的。


0. 取证环境事实

证据
取证时刻 2026-09-14 14:09:28 +0800 date '+%Y-%m-%d %H:%M:%S'
默认 JDK openjdk 26.0.2.1 (2026-08-18) java -version
可用 JVM java-11-openjdk / java-17-openjdk / java-26-openjdk ls /usr/lib/jvm/
JAVA_HOME 未设置 echo $JAVA_HOME(unset)
后端容器 全部未运行 docker compose ps 输出为空

说明:git-workflow.mddevice-verification.md 都写 JAVA_HOME=/usr/lib/jvm/java-17-openjdk 该路径确实存在ls -d 成功),故文档前置有效;但本次基线测试在默认 JDK 26 下跑, 仍 381/381 全绿(surefire 日志内 using Java 26.0.2.1)。两条通路都可用,非缺陷,仅记录口径差异。

后端容器未运行是本次唯一的取证硬约束——它导致 E2E 断言数无法运行时复核(见基线 9)。


1. 基线逐条对账表

# 基线 声称值 实测值 证据(命令 / 文件:行号) 裁决
1a 三仓工作区干净 全部干净 全部干净 git -C <仓> status --porcelain 三仓均空输出 对上
1b 三仓停在 v0.4.0 git tag --points-at HEAD 三仓均返回 v0.4.0 对上
1c api HEAD dev@3cd8005 dev@3cd8005 3cd80055779db2d52cf8cc1af425d06131f7e41dHEAD -> dev, tag: v0.4.0, origin/main, origin/dev 对上
1d flutter HEAD dev@fbcd734 dev@fbcd734 fbcd73468805e95b8055395654ca1014e0d6c13c,同点位含 origin/main 对上
1e doc HEAD main@5cc6361 main@5cc6361 5cc63615345fc30cb342a336292d7decdffea98f 对上
2a api 测试数 379 381 ./mvnw -B test exit 0surefire XML 聚合:auth 40 + common 3 + community 107 + pet 100 + user 131 = 381failures=0 errors=0 skipped=0 对不上(+2
2b flutter 测试数 597 597+2 skipped flutter test01:03 +597 ~2: All tests passed! exit 0 ⚠️ 数字对上,但 2 项被跳过未披露
3a 契约版本 v1.4.0 1.4.0 openapi.yaml:4version: 1.4.0 对上
3b 契约规模 32 路径 / 45 操作 / 75 schema 32 / 45 / 75 Python + PyYAML 机械计数(逐路径列出,见 §2.1) 对上
3c 四模块字节级快照锁 四模块一致 5 份全同md5 a7081fb84f1207eef579ab94025f5801 md5sum 于 doc 正典 + auth/community/pet/user 四份 openapi-v1.4.0.yaml 对上(与 04 号报告的 a7081f…5801 吻合)
4 契约矩阵 181 格零漂移 181 格 表列合计 24+83+66+8 = 1814 测试类 40 个 @Test 全绿 iteration-3.5/04-contract-freeze-v140.md:96 合计行;4 个 *ContractConformanceTest.java 全在 381 内 0 失败 ⚠️ 口径自洽,但「181」不可机械计数(见 §2.2
5a Flyway V1~V5 V1~V5 恰好 V1~V5无 V6 find -name "V*.sql" → 5 个源文件;测试日志 Successfully applied 5 migrations ... now at version v5 对上
5b posts.generation_job_id 裸列无 FK 裸列、无外键 列存在且确无 FK V5__community_baseline.sql:48generation_job_id uuid,(无 REFERENCES);:47 注释明写 FK ... stripped (M4 补回);全文件 15 处 REFERENCES 无一涉及该列 对上
6 埋点白名单 42 条 42 41 EventDictionary.java 单一 WHITELIST MapMap.entry( 计数 = 41,去重事件名 = 41 对不上(−1
7 ADR 001~022 齐全无缺号 001~022 22 条,001~022 连续无缺号 decisions.md 标题计数 = 22grep -o "ADR-[0-9]\{3\}" | sort -u 输出 001…022 连续 对上
8 部署六容器(含自托管 MinIO 6 6 docker-compose.yml services = postgres(postgres:18)、minio(minio/minio:RELEASE.2025-04-22)、userauthpetcommunity;另有 2 个 volumepgdata/minio-data,非容器) 对上(MinIO 自托管确认,ADR-016
9a E2E 脚本 4 份 仓库根 4 份「脚本」 4 份,位于 patbond-flutter 仓库根,为 .dart.sh patbond-flutter/test_e2e_{,m2_,m3_,m35_}manual.dart;工作区根 ls *.sh → 无此文件 对上(措辞校正见 §2.3
9b E2E 42 场景 42 427+11+14+10 各脚本自述与收尾断言:test_e2e_m2_manual.dart:767 $_passed/11test_e2e_m3_manual.dart:1345 $_passed/14test_e2e_m35_manual.dart:1139 $_passed/10M1 脚本 [N/7] 分段 对上
9c E2E 234 断言 234 静态调用点仅 207M2 41 / M3 83 / M3.5 83+ M1 另一体例 grep -c "check(" 去定义行;脚本内无断言计数器_passed 只数场景) 未取证 / 不可静态复核(见 §2.3
9d v0.4.0 发布时零失败 零失败 本次未复跑(后端六容器未运行) docker compose ps ⚠️ 未取证(沿用 iteration-3.5/08 记录,非本次实证)
10a mkdocs 可构建 可构建 exit 0,零 warning mkdocs build --strict -d /tmp/mkdocs-audit-siteDocumentation built in 1.66 secondsMKDOCS_EXIT=0 对上
10b 导航无死链 无死链 0 条 nav 指向缺失文件 脚本比对:nav 引用 102 个 .md,全部存在 对上
10c 无孤立文档 无孤立 0 个孤立 docs/ 下 102 个 .mdnav 引用 102 个,差集为空 对上

2. 关键条目的取证细节

2.1 契约规模(基线 3b)——三个数字全部机械复核

用 PyYAML 解析后按 OpenAPI 方法名白名单统计操作数:

info.version = 1.4.0
paths = 32
operations = 45
schemas = 75

32 条路径的操作分布(逐条列出以便 M4 增量时对照):/auth/{login,logout,refresh,register} 各 1、 /breeds 1、/care-reminders/{reminderId} 1、/comments/{commentId} 1、/events 1、/feed 1、 /health-events/{eventId} 1、/me 2/me/{bookmarks,community-stats,posts} 各 1、 /media/uploads 1、/media/uploads/{assetId}/complete 1、/pets 2/pets/{petId} 2/pets/{petId}/{care-reminders,health-events,vaccinations,weights}2/pets/{petId}/summary 1、 /posts 1、/posts/{postId} 3/posts/{postId}/{bookmark,comments,like}2/users/{userId}/follow 2/users/{userId}/follow-stats 1、/vaccinations/{vaccinationId} 1、 /vaccine-catalog 1。

2.2 契约矩阵 181 格(基线 4)——为什么标「不可机械计数」

iteration-3.5/04-contract-freeze-v140.md:88-96 的表格自身算术正确:

模块 测试类 操作 单元格
patbond-auth AuthContractConformanceTest 7 24
patbond-pet ContractConformanceTest 18 83
patbond-community CommunityContractConformanceTest 18 66
patbond-user MediaContractConformanceTest 2 8
合计 4 类 45 181

四个类都存在且全绿,@Test 方法数为 10 / 12 / 13 / 5 = 40。 但「单元格」是报告里人工定义的「操作 × 响应类」概念单位,与 @Test 方法数不是一对一 (一个 @Test 内常覆盖多格,如 §3.1 里 404 一格含 40400/40405 两码)。 代码侧没有任何断言锁住「181」这个总数。 故本条裁决为:「零漂移」已由 40 个绿测试实证;「181 格」只能信报告口径,无法独立机械复核。

2.3 E2E 场景与断言(基线 9)——场景可证,断言不可

场景数 42 = 已机械证实。 每份脚本收尾都打印 $_passed/N,且 _passed++ 出现次数与 N 相符 (如 test_e2e_m35_manual.dart_passed++ 恰 10 次,行 343/391/431/493/618/690/745/880/943/1136)。

断言数 234 = 无法复核。 三点证据:

  1. 脚本里没有断言计数器_passed 只在场景末自增,数的是场景不是断言 (test_e2e_m35_manual.dart:78 int _passed = 0;,无 _asserts 之类)。 报告 iteration-3.5/08:18-24 的「断言数」列(M1 7 / M2 44 / M3 88 / M3.5 95)是人工清点值 非程序输出。

  2. 静态调用点少于声称值

    脚本 报告声称断言 check() 静态调用点
    test_e2e_manual.dartM1 7 check() 体例16 处 失败守卫) 体例不同
    test_e2e_m2_manual.dart 44 41 3
    test_e2e_m3_manual.dart 88 83 5
    test_e2e_m35_manual.dart 95 83 12

    差值可由循环内断言解释M3.5 的 710/717/899 行 check() 位于 for 循环内,运行时会执行多次), 但报告未说明「断言数」是运行时计数,读者无从判断。

  3. 口径不统一:M1 记 7(≈ 每场景 1 条),而 M2/M3/M3.5 按单条 check() 记。 把两种口径相加得到的 234 不是一个同质量纲的数

结论42 场景可直接引用;234 断言建议停止作为门禁数字引用,或改为脚本自打印 (在 check() 内加一个 _asserts++ 并在收尾打印,一行改动即可让此数自证)。 后端六容器未运行,本次未复跑任何 E2E 脚本,「零失败」沿用旧记录、非本次实证。

2.4 埋点白名单 41 vs 42(基线 6)——差在哪里

唯一权威源是 patbond-api/patbond-user/src/main/java/com/patbond/patbond/user/analytics/EventDictionary.java (单一 WHITELISTMap.ofEntries(...)isKnownEvent() 直接查它,无第二个注册表)。

  • grep -c "Map.entry("41
  • 去重事件名 → 41(逐条已列,此处略)

分域核算:auth 11 + page_viewed 1 + pet 3 + health_record 7 = 22(= v2 基线,与文档一致); post 域 8(发布漏斗 5 + 草稿/删除 + 媒体三段中的 started/succeeded/failed)、feed 2、互动 8 = community 增量 18;再加 experiment_exposed 122 + 18 + 1 = 41

文档口径错在把 community 增量记为 19。 三处文档同错(转述扩散):

  • feature-checklist.md:218 → 「community 域 19 + experiment_exposed」「EventDictionary 22→42
  • iteration-3/29-m3-summary.md:20 → 「42 事件(+19 community 域 + experiment_exposed)」
  • iteration-3/27-wave3-closure.md:17,27iteration-3/index.md:47 → 「22→42

EventDictionary.java类 Javadoc 自身是对的——它写 post domain (8...)feed domain (2...)interactions (8...),合计 18,另记 experiment_exposed。即:代码注释 = 41,四处文档 = 42

放大这个问题的根因EventDictionaryTest.java155 行、13 个 @Test)逐事件断言 props 键集, 但没有任何一条断言锁住白名单的总条数(无 hasSize / size() 断言)。 所以「文档 42 / 实现 41」这类漂移不会被任何门禁拦住。


3. 专项 A:真机验证挂起项逐项清点

声称:共 8 项(M2 两项 + M3 四项 + M3.5 两项)。 实测docs/development/device-verification.md 实际登记 10 项

迭代 # 项目 步骤完整性 前置齐备 执行记录
M2 1 验证一:Android 事件真实落库(~10 min 4 步 + 3 条通过标准 + psql 命令 待填
M2 2 验证二:SessionTracker 30 分钟后台换会话(~45 min) 4 步 + 2 条通过标准 + 巡检 SQL 兜底 (接验证一同一登录) 待填
M3 1 媒体上传弱网表现 (a)~(e) 五档 明写 PATBOND_MINIO_PUBLIC_ENDPOINT 必配 待补
M3 2 乐观更新真机手感 (a)~(e) 含造数指引 待补
M3 3 Feed 图片加载 (a)~(e) 含「先发 ≥26 帖」造数 待补
M3 4 社区事件落库 步骤 + (a)~(f) 六条标准 + psql 待补
M3.5 1 头像上传弱网表现 (a)~(f) 六档 待补
M3.5 2 头像缓存表现 (a)~(d) 待补
M3.5 3 caregiver 账号改宠物头像 (a)~(c) 前置不可执行(见下) 待补
M3.5 4 获赞数与帖子点赞数对账 (a)~(d) ⚠️ 无「前置」小节1/2/3 都有) 待补

A.1 「8 项」是怎么来的——两份文档对不上

「8」出自 功能完成清单,不是真机清单本身:

  • feature-checklist.md:221 → 「Android 真机验证(M2 两项 + M3 四项)」= 6
  • feature-checklist.md:244 → 「真机项(头像上传弱网、头像缓存)」= 2
  • 合计 8。releases.md 的 v0.4.0「已知遗留」同样只写「M3.5 新增(头像上传弱网、头像缓存)」。

device-verification.md 的 M3.5 节开篇明写「下列四项是桌面替代不了的部分」, 并编号登记了 4 项。

⚠️ 实质风险M3.5 的第 3 项(caregiver 改宠物头像)与第 4 项(获赞数对账) 只存在于常设真机清单,没有进入功能完成清单的跟踪行。 功能清单是收官时的销账依据——这两项当前处于「已登记但无人跟踪」状态, 按现有流程走完 M4 收官也不会有人发现漏了它们。

A.2 M3.5 第 3 项前置不可执行(唯一的硬阻塞)

原文:

前置:两个账号 Aowner/ Bcaregiver),B 对 A 的宠物有 caregiver 角色 (关系授予入口尚未开放,按 pet_health 的协作表直接造数据,收口时补 SQL)。

「收口时补 SQL」从未补——文件里没有任何造数 SQL。执行人拿到这一项无法开工。

取证:所需信息其实都已就位,写出这段 SQL 没有障碍

  • 表与角色枚举存在:V3__pet_health_baseline.sql:97 CREATE TABLE pet_health.pet_owners ( :104 CONSTRAINT ck_pet_owners_role CHECK (role IN ('owner', 'caregiver', 'viewer'))
  • 后端授予路径已被测试覆盖:PetAvatarIntegrationTest.java:214 void caregiverMayChangeTheAvatarButNotTheProfile():218 grantRole(petId, caregiver, "caregiver")

建议:把 grantRole 的等价 INSERT INTO pet_health.pet_owners (...) 写进该项前置,此项即可执行。

A.3 有没有「其实已做过但没销账」的项?

没有任何一项已完整完成——四处执行记录(M2 节、M3 节、M3.5 节)全部为「待填 / 待补」。 但有两项的后端部分已被证实,真机清单里没有反映

  1. M3.5 第 4 项(获赞数对账)的后端口径已证 iteration-3.5/08-release-e2e-regression.md:302 → 「其中『获赞数对账』的后端口径已在场景 10 证明(详情 likeCount == receivedLikeCount), 真机侧待验的只剩 UI 呈现。」 → 该项 4 个勾选框在 device-verification.md 里仍全空,未标注「(a)(b) 后端已证、真机只验 UI」。
  2. M3.5 第 3 项(caregiver)的后端行为已被单测覆盖 iteration-3.5/08 §未覆盖范围 → 「后端已有 PetAvatarIntegrationTest 三段断言覆盖」, 本次已直接在源码核实(见 A.2)。 → 真机清单同样未标注,真机侧实际只需验 UI 角标可见性分档WRITE vs MANAGE)。

判断:不是「做完没销账」,而是**「已缩小的范围没有回写」**——真机项的实际剩余工作量 比清单表面看起来小,但清单不写,下一个执行人会重复评估。

A.4 M2 两项的时限已进入最后一周

device-writer.md 内的时限提醒(引自 iteration-3/06):

时限提醒:建议在 2026-09-21(北极星首次出数日)前完成,否则首批读数只能标「未验收」。

今天 2026-09-14,剩 7 天。两项合计耗时自述 ~55 分钟(10 min + 45 min,其中 40 min 是等待), 唯一前置是一台 Android 真机或模拟器。步骤、psql、通过标准全部齐备,可立即执行。

M2 验证二的巡检 SQL 兜底(count(DISTINCT session_id)/count(*) 应远小于 0.9) 是防「每事件一个 sessionId」缺陷复发的,与北极星读数直接相关——这也是时限挂在 09-21 的原因。

A.5 其它文档债(不阻塞,记录备查)

  1. 通用前置的 flutter run 只传 3 个 base URL,靠一句注解补 community: 「M3 起若新增服务端口(如 community :8084),相应补 PATBOND_COMMUNITY_API_BASE_URL」。 结果是 M3/M3.5 的每一项都要重复叮嘱「四个 base URL 全传」。建议把通用前置的命令块直接补全到四个。
  2. 勾选框体例不一致M3 的第 1/2/3 项用 - a 普通列表,M3 第 4 项与 M3.5 全部用 - [ ] 勾选框。 真机执行时无法在前三项上打勾销账。

4. 专项 B:发布流程文档一致性

B.1 「发布后生效的纪律」现状——存在、基本可执行,但有一处已过期

该节位于 docs/development/releases.mdv0.3.0 小节末尾(不在 v0.4.0 小节)。内容核实:

条目 内容 裁决
main 禁直推 一切影响 main 的变更走 PR + CI 状态检查 已生效并已被 v0.4.0 实证
五步 PR 流程 ①完成 checklist 1~2 步 → ②建 dev → main PR → ③等状态检查绿 → ④Gitea 合并(应为快进)→ ⑤续 checklist 5、7 步 ⚠️ 第 ① 步文字过期(见下)
checklist 第 4 步作废 本地 merge --ff-only && push 仅适用首次发布 已明确记录
checklist 第 3、6 步一次性 不再重复 已明确记录
分叉即排查 PR 显示无法快进 → 先查明原因,不用合并提交掩盖 清晰

过期点:五步流程第 ① 步写「完成 checklist 第 1~2 步(三仓 CI 绿 + E2E 双份回归 PASS)」。 而同一文件 v0.4.0 小节的「checklist 变更(下次发布适用)」已宣布 「回归清单由两份改为四份M1/M2/M3/M3.5」。 同一份 releases.md 内自相矛盾v0.3.0 节的纪律说双份,v0.4.0 节说四份。

B.2 checklist 里没有写成四份(核心发现)

用户问「核实 checklist 里是否真的写成了四份」——答案:没有,仍写着两份。

releases.md 顶部「维护约定」指向的权威 checklist 是 iterations/iteration-3/08-git-workflow-plan.md §3.3。原文第 2 步(该文件 84 行):

  1. 实测compose 全栈起,跑 M2+M3 两份 E2E 烟囱脚本,全场景 PASS,证据入波次报告;

这份 checklist 自 M3 起从未被修改。 八步中有 4 步已与现实脱节

原文要点 现状 修正记录在哪
2 M2+M3 两份 E2E 应为 四份M1/M2/M3/M3.5 只在 releases.md v0.4.0「checklist 变更」段
3 命名统一 + api main 重建 一次性项,已完成 只在 releases.md v0.3.0 纪律段
4 本地 checkout main && merge --ff-only && push 已失效main 受保护会被拒) 只在 releases.md v0.3.0 纪律段
6 Gitea 开启分支保护 一次性项,已完成 只在 releases.md v0.3.0 纪律段

即:checklist 本体是 M3 时代的原稿,全部修正只散落在发布记录页的散文里。 下次发布若有人照 §3.3 逐步执行,会跑错 E2E 份数、并撞上一条已被分支保护拒绝的 git 命令。

B.3 checklist 至今仍是「草案」,且从未固化进常设文档

iteration-3/08-git-workflow-plan.md §3.3 的标题原文:

3.3 发布 checklist 草案(首次发布用,验证后固化进 git-workflow.md

这个固化动作从未发生。核实 docs/development/git-workflow.md64 行,常设文档):

grep -n "checklist\|发布\|E2E\|四份\|两份\|PR" docs/development/git-workflow.md
→ (无任何输出)

该常设文档里完全没有发布流程、没有 checklist、没有 PR 纪律、没有「main 受保护」的说法。 它的「禁止事项」只说「不 force push 共享分支(dev / main)」,仍是保护启用前的口径。

⚠️ 实质风险:唯一的权威发布 checklist 是一份标着「草案」的 iteration-3 迭代报告 内容已 4 步过期;而常设的 git-workflow.md 对发布流程完全沉默。 两次发布的经验只以散文形式沉在 releases.md 的两个版本小节里。

B.4 附带核实:doc 仓 main 未受保护

releases.md v0.3.0 的分支保护表(经 Gitea API 核实的记录):

仓库 protected 状态检查上下文
patbond-api true CI / backend-test (push)
patbond-flutter true CI / flutter-gates (push)
patbond-doc 未启用 ——

即「main 受保护禁直推」只适用 api 与 flutter 两仓doc 仓 main 仍是直推流 git-workflow.md 亦写「patbond-doc:直接提交 main」)。本报告即直接写在 doc 仓 main 工作区。 这是按规划的有意安排(doc main 即日常分支、不承担发布分支语义),非缺陷,仅澄清口径 ——避免「下次发布必须走 PR」被误读为三仓皆然。


5. 专项 C:文档站公开可访问 / 访问控制待评估项

纪律声明:本节纯取证 + 建议。未连接服务器、未读取或修改任何 nginx / 防火墙配置。 下述服务器侧事实均引自仓库内的 server-exposure.md 登记(该文件本身即为核对产物), 并明确标注哪些是「文档记载」而非「本次实测」。

C.1 待评估项的登记现状——只有一句话,在表格单元格里

文件确认存在:docs/development/server-exposure.md(常设文档,2026-09-11 因安全事件新建)。

待评估项的全部原文,位于 §2「常驻服务」表格的「状态」列单元格内:

文档站(patbond-doc 经 nginx 443 mkdocs 构建产物,含架构/部署/迭代全部文档 | —— | 运行;⚠️ 公开可访问,待评估是否加 basic auth 或 IP 白名单(无凭证内容,但暴露内部架构细节)

releases.md v0.4.0 末尾有一行呼应:

待评估:文档站公开可访问是否加访问控制(见服务器暴露面清单)。

登记质量问题(三项俱缺):

  1. 无归属决策:该行的「归属决策」列是 ——(空)。同表其它服务都指向 ADR 或 CI 手册 gitea → CI Runner 手册、dockerd → ADR-006)。文档站没有任何 ADR 覆盖它的暴露决策
  2. 无时限、无归属人:不像真机 M2 两项挂着明确的 2026-09-21
  3. 不在处置记录里:§5「处置与核对记录」只有 2026-09-11 一行(安全事件处置), 待评估项未登记为待办事项,只以表格内 ⚠️ 存在——检索性差,收官核对时极易滑过。

C.2 当前暴露面(据 server-exposure.md 登记,2026-09-11 核对)

对公网开放端口 4 项22SSH)、80(跳 443)、443HTTPS)、ICMP。 其中 443 由 nginx 按域名分流,sites-enabled/ 内两个站点:git.patbond.cnpatbond-doc

即:文档站与 Gitea 共用 443,靠域名分流,无任何认证层。

已于 2026-09-11 关闭并记录防重开:3000(Gitea 直连,本次事件入口)、8848/9848NacosADR-002 已移除)、 2222(无服务监听的空规则)。

「最后确认」列全部为 2026-09-11server-exposure.md 自身的维护约定 ② 要求 「每次迭代收官核对一遍,更新『最后确认』」——M3.5 于 09-11 收官、v0.4.009-14 发布, 发布当日未再核对。属轻微逾期(3 天),M4 开工正是补这次核对的时机。

C.3 暴露内容评估(本次实测部分)

我可以实测的是文档站会发布什么内容(构建产物侧),这部分不需要碰服务器:

  • 构建产物含 102 个页面mkdocs build --strict 全量成功,nav 102 项零孤立)。
  • 内容面包括:完整 OpenAPI 契约(docs/api/openapi.yaml,32 路径全部端点与错误码)、 数据库全量 DDL(docs/database/patbond_postgresql.sql)、22 条 ADR、 server-exposure.md 本身(服务器端口与常驻服务清单)ci-runner-setup.md(CI runner 部署细节)、以及含安全事件复盘的 iteration-3.5/07-security-incident-20260911.md
  • 凭证面:三仓 check-secrets.sh --all 在 v0.4.0 门禁 exit 0(引自 releases.md,本次未复跑)。 故「无凭证内容」的判断有依据。

⚠️ 值得指出的悖论:因安全事件而建立的 server-exposure.md——那份逐条列出 「哪些端口开着、哪些服务在跑、内部 API 走哪条路径、凭证轮换触发条件」的清单—— 本身正随文档站公开发布。同理还有 ci-runner-setup.md 与安全事件复盘全文 (后者详述了注入路径与处置手法)。 这不是凭证泄漏,但恰好是攻击者做侦察最想读的三份文档,且描述的是刚被攻击过的这台主机。 这一点在待评估项的括注「无凭证内容,但暴露内部架构细节」里被显著低估了。

C.4 建议(不改配置,仅供拍板)

优先级排序:

  1. 先把待评估项从表格单元格提成一条正式待办(§5 处置记录里加一行,或建 M4 工单), 补齐归属人 + 时限。当前形态检索不到,等于没登记。
  2. 推荐方案:IP 白名单 优先于 basic auth。 理由:读者集合当前就是维护者本人; nginx 侧 allow/deny 比 basic auth 少一套凭证要管(而按纪律 6,新增凭证还要走「不回显」流程)。 若将来需给外部评审看,再叠加 basic auth。
  3. 若维持公开,则做内容分层:把 server-exposure.mdci-runner-setup.md、 安全事件复盘三份移出公开构建(mkdocs exclude_docs 或独立私有站), 契约与 ADR 保持公开。这三份的敏感度与其余 99 份不在一个档位。
  4. 无论选哪条,补一条 ADR,让文档站的暴露决策像其它服务一样有归属决策可查 ——这正是 server-exposure.md 缘起段所说「代码侧有 ADR 防『决策变了实现没跟上』, 服务器侧此前无任何对应机制」要补的那一环。
  5. 顺手更新 §1/§2 的「最后确认」到本次核对日期。

6. 所有对不上的差异(按严重度排序)

# 严重度 差异 声称 实测 证据
D1 发布 checklist 未更新为四份 E2E,且 4/8 步已脱节 「已由两份改为四份」 checklist 本体仍写「M2+M3 两份」 iteration-3/08-git-workflow-plan.md:84
D2 真机挂起项数量 8 项 10 项M2 2 + M3 4 + M3.5 4 device-verification.md M3.5 节「下列四项」+ 4 条编号项;对比 feature-checklist.md:221,244
D3 埋点白名单条数 42 条 41 条 EventDictionary.java Map.entry( = 41;错误口径见 feature-checklist.md:218iteration-3/29:20iteration-3/27:17,27iteration-3/index.md:47
D4 api 测试基线 379 381 surefire 聚合 40+3+107+100+131iteration-3.5/04 自身已写「379→381」,但 releases.md v0.4.0 门禁表与 tag 表仍写 379
D5 releases.md 内部自相矛盾 —— v0.3.0 纪律段写「E2E 双份回归」,v0.4.0 段写「改为四份 releases.md 两节并存
D6 flutter「597 全绿」未披露跳过项 597 双绿 597 passed + 2 skipped flutter test+597 ~2;跳过项为 test/smoke/media_upload_smoke_test.dart:133test/smoke/detail_interactions_smoke_test.dart:173(需 compose 后端 + 环境变量)
D7 E2E 脚本形态措辞 「仓库根 4 份脚本」 patbond-flutter 仓库根,为 .dartdart run)非 .sh;工作区根无 *.sh ls /home/lx/workspace/patbond/*.sh → 无此文件

7. 所有未取证项(诚实清单)

# 为何未取证 补证方式
U1 E2E 234 断言 脚本无断言计数器,静态调用点 207 与声称值不符;差值疑为循环内执行但报告未说明口径 check() 内加 _asserts++ 并于收尾打印;或明确标注为运行时计数
U2 E2E「v0.4.0 零失败」 后端六容器未运行(docker compose ps 空),本次未复跑四份脚本 docker compose up -d 后串行跑四份(须串行,M3 场景 10 与 M3.5 场景 10 的全量翻页比对会被并发发帖污染)
U3 契约矩阵「181 格」 「单元格」是报告人工定义单位,代码侧无断言锁总数(4 类共 40 个 @Test,非一对一) 若要可复核,在契约测试里加一条总数断言;否则明确标注为文档口径
U4 CI 三仓绿 未查 Gitea commit status API(本次为本地取证,且不宜在 8 agent 并发时打服务器) GET /repos/{owner}/{repo}/commits/{sha}/status 三仓各一次
U5 服务器侧实际暴露面 按任务纪律不碰服务器配置;C.2 全部引自 server-exposure.md 登记(2026-09-11),非本次实测 按该文件 §3「核对方法」在服务器上执行 ss -tlnp 等四条命令
U6 check-secrets.sh --all 三仓 exit 0 未复跑(v0.4.0 门禁记录为绿,本次未验) 三仓各 sh scripts/check-secrets.sh --all
U7 零迁移在存量库上实证 需 compose 起于既有 pgdata volume;容器未运行 与 U2 同一轮 compose 启动时看 Flyway 日志

8. 找到的真实问题清单(按严重度排序)

P1(高)发布 checklist 是 M3 时代原稿,4/8 步已脱节,且从未固化进常设文档

  • 证据iteration-3/08-git-workflow-plan.md:84 仍写「M2+M3 两份 E2E」; §3.3 标题仍标「草案(首次发布用,验证后固化进 git-workflow.md)」; grep "checklist\|发布\|E2E\|PR" docs/development/git-workflow.md零输出
  • 影响:下次发布照单执行会 ①漏跑 M1/M3.5 两份回归、②撞上第 4 步已被分支保护拒绝的 push origin main。修正只以散文存在于 releases.md 两个版本小节。
  • 建议:把 checklist 固化进 git-workflow.md 并就地改正 4 步(2 改四份、3/6 标一次性已完成、 4 换 PR 流程),iteration-3/08 §3.3 加一行「已被 git-workflow.md 取代」。M4 开工前即可完成。

P2(高)真机挂起项漏跟踪 2 项,且其中 1 项前置不可执行

  • 证据device-verification.md 登记 10 项,feature-checklist.md:221,244 只跟踪 8 项 ——M3.5 的 caregiver 改宠物头像、获赞数对账未进功能清单。 且 caregiver 项前置写「收口时补 SQL」,SQL 从未补(造数所需的 pet_health.pet_owners 表与 caregiver 角色枚举早已存在:V3:97,104 授予写法可照抄 PetAvatarIntegrationTest.java:218grantRole)。
  • 影响:两项在现行销账流程外,M4 收官不会被发现;caregiver 项即便有人接手也无法开工。
  • 建议:功能清单补齐 4 项;把 INSERT INTO pet_health.pet_owners 造数 SQL 写进前置。

P3(中)埋点白名单实为 41 条,四处文档写 42,且无测试锁总数

  • 证据EventDictionary.java Map.entry( = 41(类 Javadoc 自身的分域说明 8+2+8 也等于 18, 与 41 自洽);文档四处写 42(feature-checklist.md:218iteration-3/29:20iteration-3/27:17,27iteration-3/index.md:47),错在把 community 增量记为 19(实为 18)。 EventDictionaryTest.java13 个 @Test无任何总数断言
  • 影响:M4 要新增 AI 创作域事件,增量必然基于错误基数计算 (「42 + N」会与实现的「41 + N」持续差 1)。这正是 M3.5 教训里「转述即污染」的同型问题。
  • 建议M4 开工前先修基数——四处文档改 41,并在 EventDictionaryTest 加一条 assertThat(WHITELIST).hasSize(41) 级别的断言(需暴露 size 或用 isKnownEvent 遍历),让此数此后自证。

P4(中)v0.4.0 发布门禁表引用的 api 测试数(379)低于实际(381)

  • 证据surefire 聚合 381、0 失败(在 tag v0.4.0 = 3cd8005 工作区实测); releases.md v0.4.0 的 tag 表与门禁表均写 379; 而 iteration-3.5/04-contract-freeze-v140.md:8 自己写的是「379→381」。
  • 影响:门禁证据数字与被 tag 的代码不符。数字虽小,但发布记录是跨迭代对账基准 ——M4 收官时「381 → N」的增量核算会从错的起点算。
  • 建议releases.md 两处 379 改 381(历史记录订正加脚注说明,不静默改)。

P5(中)4 份 integration_test/ 活体测试完全在自动化门禁之外

  • 证据flutter test 的 597 项不含 integration_test/(日志内 integration_test/ 出现 0 次); 该目录下 4 个文件(profile_avatar_live_test.dartfeed_live_test.dartclient_ux_live_test.dartpublish_live_test.dart)均 skip: !enabled 且需真后端。
  • 影响iteration-3.5/05 号报告用 integration_test/profile_avatar_live_test.dart 作为「桌面全链路已通」的证据,而这份证据不被任何门禁重跑,回归时不会报警。 另有 2 个 test/smoke/* 在 597 里被静默跳过(D6)。
  • 建议:要么在发布 checklist 里把这几份列为手动必跑项(与 E2E 同级), 要么明确标注为「一次性取证、非回归资产」,避免后续报告继续把它当活证据引用。

P6(中)doc 仓无 .gitignoresite/ 不入库的纪律仅靠开发者机器的全局配置兜着

  • 证据patbond-doc/.gitignorecat .gitignore → 无此文件); git check-ignore -v site//home/lx/.gitignore_global:183:/site ——即 site/ 只被用户级全局 gitignore 忽略。 而 git-workflow.md 的门禁表明确要求「不把 site/ 落进仓库」。 取证期间 site/ 目录 mtime 为 14:10(本次会话期间被并发 agent 重建,本报告未触碰它)。
  • 影响:换一台机器、或 CI 里执行 mkdocs build(默认输出 site/)后跑 git add -A, 102 个页面的构建产物会直接入库。纪律写在文档里,仓库侧零强制。
  • 建议patbond-doc/ 加一份最小 .gitignore(至少 site/)。一行改动消除整类风险。 (本次未代改——按纪律只写本报告。)

P7(低)安全事件催生的三份服务器文档,正随公开文档站发布

  • 证据server-exposure.md §2 登记文档站「公开可访问」且「归属决策」列为空; 该文件本身、ci-runner-setup.mditeration-3.5/07-security-incident-20260911.md 均在 mkdocs.yml nav 的 102 项之内(零孤立,即全部发布)。
  • 影响:端口清单 / 常驻服务 / 内部 API 路径 / 凭证轮换触发条件 / 刚发生的注入路径与处置手法, 对这台刚被攻击过的主机构成现成侦察材料。待评估项的括注 「无凭证内容,但暴露内部架构细节」低估了这一点。
  • 建议:见 §5.C.4——优先把待评估项提成正式待办(当前只是表格单元格里的一句 ⚠️,检索不到), 倾向 IP 白名单;若维持公开则把这三份移出公开构建。不要在 M4 里继续挂着不决。

9. 给 M4 开工的直接结论

可以放心引用的基线(本次机械复核通过):

  • 三仓 v0.4.0 干净同点位:api 3cd8005 / flutter fbcd734 / doc 5cc6361
  • flutter 597 测试(记得注明另有 2 项跳过)
  • 契约 v1.4.032 路径 / 45 操作 / 75 schema,五份副本字节级同一(md5 a7081fb8…5801
  • Flyway V1~V5community.posts.generation_job_id 裸列已就位、确无 FK V5:48)——M4 补 FK 时新增 V6,不得改 V5git-workflow.md 禁改已推送迁移)
  • ADR 001~022 连续无缺号 → M4 首个新决策为 ADR-023
  • 部署 六容器postgres:18 + MinIO + auth/user/pet/community
  • E2E 42 场景M1 7 / M2 11 / M3 14 / M3.5 10
  • 文档站:mkdocs build --strict exit 0 零 warning102 页 nav 全覆盖、零孤立、零死链

开工前建议先修的三个基数/流程问题(都是小改动,但会污染 M4 全程估算):

  1. 埋点白名单 41 而非 42(P3)——M4 必然新增 AI 创作域事件,基数错则增量全错
  2. 发布 checklist 改四份 E2E 并固化进 git-workflow.mdP1)——M4 收官要发版
  3. api 测试基线 381 而非 379P4)——M4 增量核算的起点

时限压力:真机 M2 两项挂着 2026-09-21(北极星首次出数),今天 09-14,剩 7 天; 两项合计 ~55 分钟且步骤齐备,唯一缺一台 Android 设备。逾期则首批北极星读数只能标「未验收」。

本报告未做的事:未 commit / push;未改 mkdocs.yml (本文件需由 mkdocs.yml 负责人挂进 iteration-4 导航节,否则不进文档站); 未改任何生产代码、未碰服务器配置。


取证人Evidence Collector 取证日期2026-09-14 基线点位api 3cd8005 / flutter fbcd734 / doc 5cc6361(三仓均 v0.4.0 裁决汇总:基线 10 条 → 对上 6 / 对不上 3 / 不可静态复核 1;专项 A/B/C 各有实质发现; 真实问题 7 个(高 2 / 中 4 / 低 1);未取证项 7 个