八角色并行开工分析,合计 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>
39 KiB
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.md与device-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 |
3cd80055779db2d52cf8cc1af425d06131f7e41d,HEAD -> 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 0;surefire XML 聚合:auth 40 + common 3 + community 107 + pet 100 + user 131 = 381,failures=0 errors=0 skipped=0 |
❌ 对不上(+2) |
| 2b | flutter 测试数 | 597 | 597(+2 skipped) | flutter test → 01:03 +597 ~2: All tests passed! exit 0 |
⚠️ 数字对上,但 2 项被跳过未披露 |
| 3a | 契约版本 | v1.4.0 |
1.4.0 |
openapi.yaml:4 → version: 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 = 181;4 测试类 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:48 → generation_job_id uuid,(无 REFERENCES);:47 注释明写 FK ... stripped (M4 补回);全文件 15 处 REFERENCES 无一涉及该列 |
✅ 对上 |
| 6 | 埋点白名单 42 条 | 42 | 41 | EventDictionary.java 单一 WHITELIST Map,Map.entry( 计数 = 41,去重事件名 = 41 |
❌ 对不上(−1) |
| 7 | ADR 001~022 齐全无缺号 | 001~022 | 22 条,001~022 连续无缺号 | decisions.md 标题计数 = 22;grep -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)、user、auth、pet、community;另有 2 个 volume(pgdata/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 | 42(7+11+14+10) | 各脚本自述与收尾断言:test_e2e_m2_manual.dart:767 $_passed/11;test_e2e_m3_manual.dart:1345 $_passed/14;test_e2e_m35_manual.dart:1139 $_passed/10;M1 脚本 [N/7] 分段 |
✅ 对上 |
| 9c | E2E 234 断言 | 234 | 静态调用点仅 207(M2 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-site → Documentation built in 1.66 seconds,MKDOCS_EXIT=0 |
✅ 对上 |
| 10b | 导航无死链 | 无死链 | 0 条 nav 指向缺失文件 | 脚本比对:nav 引用 102 个 .md,全部存在 |
✅ 对上 |
| 10c | 无孤立文档 | 无孤立 | 0 个孤立 | docs/ 下 102 个 .md,nav 引用 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 = 无法复核。 三点证据:
-
脚本里没有断言计数器。
_passed只在场景末自增,数的是场景不是断言 (test_e2e_m35_manual.dart:78int _passed = 0;,无_asserts之类)。 报告iteration-3.5/08:18-24的「断言数」列(M1 7 / M2 44 / M3 88 / M3.5 95)是人工清点值, 非程序输出。 -
静态调用点少于声称值:
脚本 报告声称断言 check()静态调用点差 test_e2e_manual.dart(M1)7 无 check()体例(16 处✗失败守卫)体例不同 test_e2e_m2_manual.dart44 41 −3 test_e2e_m3_manual.dart88 83 −5 test_e2e_m35_manual.dart95 83 −12 差值可由循环内断言解释(M3.5 的 710/717/899 行
check()位于for循环内,运行时会执行多次), 但报告未说明「断言数」是运行时计数,读者无从判断。 -
口径不统一: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
(单一 WHITELIST 为 Map.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 1 → 22 + 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,27与iteration-3/index.md:47→ 「22→42」
而 EventDictionary.java 的类 Javadoc 自身是对的——它写 post domain (8...)、feed domain (2...)、
interactions (8...),合计 18,另记 experiment_exposed。即:代码注释 = 41,四处文档 = 42。
放大这个问题的根因:EventDictionaryTest.java(155 行、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 四项)」= 6feature-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 项前置不可执行(唯一的硬阻塞)
原文:
前置:两个账号 A(owner)/ B(caregiver),B 对 A 的宠物有 caregiver 角色 (关系授予入口尚未开放,按
pet_health的协作表直接造数据,收口时补 SQL)。
「收口时补 SQL」从未补——文件里没有任何造数 SQL。执行人拿到这一项无法开工。
取证:所需信息其实都已就位,写出这段 SQL 没有障碍:
- 表与角色枚举存在:
V3__pet_health_baseline.sql:97CREATE TABLE pet_health.pet_owners (,:104CONSTRAINT ck_pet_owners_role CHECK (role IN ('owner', 'caregiver', 'viewer')) - 后端授予路径已被测试覆盖:
PetAvatarIntegrationTest.java:214void caregiverMayChangeTheAvatarButNotTheProfile(),:218grantRole(petId, caregiver, "caregiver")
建议:把 grantRole 的等价 INSERT INTO pet_health.pet_owners (...) 写进该项前置,此项即可执行。
A.3 有没有「其实已做过但没销账」的项?
没有任何一项已完整完成——四处执行记录(M2 节、M3 节、M3.5 节)全部为「待填 / 待补」。 但有两项的后端部分已被证实,真机清单里没有反映:
- M3.5 第 4 项(获赞数对账)的后端口径已证:
iteration-3.5/08-release-e2e-regression.md:302→ 「其中『获赞数对账』的后端口径已在场景 10 证明(详情likeCount==receivedLikeCount), 真机侧待验的只剩 UI 呈现。」 → 该项 4 个勾选框在device-verification.md里仍全空,未标注「(a)(b) 后端已证、真机只验 UI」。 - 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 其它文档债(不阻塞,记录备查)
- 通用前置的
flutter run只传 3 个 base URL,靠一句注解补 community: 「M3 起若新增服务端口(如 community :8084),相应补PATBOND_COMMUNITY_API_BASE_URL」。 结果是 M3/M3.5 的每一项都要重复叮嘱「四个 base URL 全传」。建议把通用前置的命令块直接补全到四个。 - 勾选框体例不一致:M3 的第 1/2/3 项用
- (a)普通列表,M3 第 4 项与 M3.5 全部用- [ ]勾选框。 真机执行时无法在前三项上打勾销账。
4. 专项 B:发布流程文档一致性
B.1 「发布后生效的纪律」现状——存在、基本可执行,但有一处已过期
该节位于 docs/development/releases.md 的 v0.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 行):
- 实测: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.md(64 行,常设文档):
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 末尾有一行呼应:
待评估:文档站公开可访问是否加访问控制(见服务器暴露面清单)。
登记质量问题(三项俱缺):
- 无归属决策:该行的「归属决策」列是
——(空)。同表其它服务都指向 ADR 或 CI 手册 (gitea → CI Runner 手册、dockerd → ADR-006)。文档站没有任何 ADR 覆盖它的暴露决策。 - 无时限、无归属人:不像真机 M2 两项挂着明确的
2026-09-21。 - 不在处置记录里:§5「处置与核对记录」只有 2026-09-11 一行(安全事件处置),
待评估项未登记为待办事项,只以表格内
⚠️存在——检索性差,收官核对时极易滑过。
C.2 当前暴露面(据 server-exposure.md 登记,2026-09-11 核对)
对公网开放端口 4 项:22(SSH)、80(跳 443)、443(HTTPS)、ICMP。
其中 443 由 nginx 按域名分流,sites-enabled/ 内两个站点:git.patbond.cn 与 patbond-doc。
即:文档站与 Gitea 共用 443,靠域名分流,无任何认证层。
已于 2026-09-11 关闭并记录防重开:3000(Gitea 直连,本次事件入口)、8848/9848(Nacos,ADR-002 已移除)、 2222(无服务监听的空规则)。
「最后确认」列全部为 2026-09-11。 而 server-exposure.md 自身的维护约定 ② 要求
「每次迭代收官核对一遍,更新『最后确认』」——M3.5 于 09-11 收官、v0.4.0 于 09-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 建议(不改配置,仅供拍板)
优先级排序:
- 先把待评估项从表格单元格提成一条正式待办(§5 处置记录里加一行,或建 M4 工单), 补齐归属人 + 时限。当前形态检索不到,等于没登记。
- 推荐方案:IP 白名单 优先于 basic auth。 理由:读者集合当前就是维护者本人;
nginx 侧
allow/deny比 basic auth 少一套凭证要管(而按纪律 6,新增凭证还要走「不回显」流程)。 若将来需给外部评审看,再叠加 basic auth。 - 若维持公开,则做内容分层:把
server-exposure.md、ci-runner-setup.md、 安全事件复盘三份移出公开构建(mkdocsexclude_docs或独立私有站), 契约与 ADR 保持公开。这三份的敏感度与其余 99 份不在一个档位。 - 无论选哪条,补一条 ADR,让文档站的暴露决策像其它服务一样有归属决策可查
——这正是
server-exposure.md缘起段所说「代码侧有 ADR 防『决策变了实现没跟上』, 服务器侧此前无任何对应机制」要补的那一环。 - 顺手更新 §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:218、iteration-3/29:20、iteration-3/27:17,27、iteration-3/index.md:47 |
| D4 | 中 | api 测试基线 | 379 | 381 | surefire 聚合 40+3+107+100+131;iteration-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:133、test/smoke/detail_interactions_smoke_test.dart:173(需 compose 后端 + 环境变量) |
| D7 | 低 | E2E 脚本形态措辞 | 「仓库根 4 份脚本」 | 在 patbond-flutter 仓库根,为 .dart(dart 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:218的grantRole)。 - 影响:两项在现行销账流程外,M4 收官不会被发现;caregiver 项即便有人接手也无法开工。
- 建议:功能清单补齐 4 项;把
INSERT INTO pet_health.pet_owners造数 SQL 写进前置。
P3(中)埋点白名单实为 41 条,四处文档写 42,且无测试锁总数
- 证据:
EventDictionary.javaMap.entry(= 41(类 Javadoc 自身的分域说明 8+2+8 也等于 18, 与 41 自洽);文档四处写 42(feature-checklist.md:218、iteration-3/29:20、iteration-3/27:17,27、iteration-3/index.md:47),错在把 community 增量记为 19(实为 18)。EventDictionaryTest.java(13 个@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.mdv0.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.dart、feed_live_test.dart、client_ux_live_test.dart、publish_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 仓无 .gitignore,site/ 不入库的纪律仅靠开发者机器的全局配置兜着
- 证据:
patbond-doc/根无.gitignore(cat .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.md、iteration-3.5/07-security-incident-20260911.md均在mkdocs.ymlnav 的 102 项之内(零孤立,即全部发布)。 - 影响:端口清单 / 常驻服务 / 内部 API 路径 / 凭证轮换触发条件 / 刚发生的注入路径与处置手法, 对这台刚被攻击过的主机构成现成侦察材料。待评估项的括注 「无凭证内容,但暴露内部架构细节」低估了这一点。
- 建议:见 §5.C.4——优先把待评估项提成正式待办(当前只是表格单元格里的一句
⚠️,检索不到), 倾向 IP 白名单;若维持公开则把这三份移出公开构建。不要在 M4 里继续挂着不决。
9. 给 M4 开工的直接结论
可以放心引用的基线(本次机械复核通过):
- 三仓
v0.4.0干净同点位:api3cd8005/ flutterfbcd734/ doc5cc6361 - flutter 597 测试(记得注明另有 2 项跳过)
- 契约 v1.4.0:32 路径 / 45 操作 / 75 schema,五份副本字节级同一(md5
a7081fb8…5801) - Flyway V1~V5;
community.posts.generation_job_id裸列已就位、确无 FK (V5:48)——M4 补 FK 时新增V6,不得改 V5(git-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 --strictexit 0 零 warning,102 页 nav 全覆盖、零孤立、零死链
开工前建议先修的三个基数/流程问题(都是小改动,但会污染 M4 全程估算):
- 埋点白名单 41 而非 42(P3)——M4 必然新增 AI 创作域事件,基数错则增量全错
- 发布 checklist 改四份 E2E 并固化进
git-workflow.md(P1)——M4 收官要发版 - api 测试基线 381 而非 379(P4)——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 个