八角色并行开工分析,合计 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>
52 KiB
04 M4 开工现实核查(Reality Check)
作者:Reality Checker 日期:2026-09-14 输入:三仓工作区实测(api
3cd8005/ flutterfbcd734/ doc5cc6361,均 tagv0.4.0);docs/database/patbond_postgresql.sql正典模型;device-verification.md;releases.md;server-exposure.md;Gitea API 实时提交状态。 同批 07 号(Evidence Collector)已独立清点基线数字,本报告复用其结论并不重复清点; 本报告的增值面是 M4 规划的未证前提狙击与跨文档一致性。总体裁决:NEEDS WORK。 基线代码质量与契约纪律确实扎实(32/45/75 契约、四模块字节级快照、 381 + 597 双绿、CI 三仓已恢复全绿),但 M4「AI 创作」的三个核心前提全部无证据支撑: 无任何 AI 服务商 endpoint / 凭证 / 额度(
.env与init-secrets.sh共 4 个密钥,无一条与 AI 相关); 服务端完全不具备写对象存储的能力(全代码库 S3 调用仅createBucket/headBucket/headObject/close, 零putObject、零getObject),故「媒体链路可直接复用」只对读侧与客户端上传侧成立、 Worker 产出物落盘是净新增;且挂起的真机项已达 10 项(非 8 项),M4 若沿现有模式将再批量产出一批。 唯一的好消息是 Worker 队列不需要 Redis/MQ——正典模型已给出 PostgresFOR UPDATE SKIP LOCKED租约队列设计,现有 compose 六容器无需扩容。
1. 基线数字逐条核对
同批 07 号报告已逐格清点,此处只列核对结果与我侧独立取证的差异项。
| 声称 | 实测 | 裁决 |
|---|---|---|
三仓工作区干净、全部停在 v0.4.0 |
git status --porcelain 三仓均空输出;git tag --points-at HEAD 三仓均返回 v0.4.0 |
✅ 对 |
api dev@3cd8005 |
3cd80055779db2d52cf8cc1af425d06131f7e41d |
✅ 对 |
| api 379 测试全绿 | 381(3+131+40+100+107),BUILD SUCCESS,Failures 0 / Errors 0 / Skipped 0 |
❌ 不对,见问题 P4 |
flutter dev@fbcd734 |
fbcd73468805e95b8055395654ca1014e0d6c13c |
✅ 对 |
| flutter 597 测试全绿 | 00:39 +597 ~2: All tests passed!,exit 0 |
✅ 对(但含 2 个默认跳过项,见问题 P6) |
doc main@5cc6361 |
5cc63615345fc30cb342a336292d7decdffea98f |
✅ 对 |
| 契约 v1.4.0:32 路径 / 45 操作 / 75 schema | info.version=1.4.0;paths 32、operations 45、schemas 75,逐格相符 |
✅ 对 |
| 四模块字节级快照锁 | md5 = a7081fb84f1207eef579ab94025f5801,doc 正典 + auth/user/pet/community 四份快照五处完全相同 |
✅ 对 |
| 契约矩阵 181 格零漂移 | 181 这个数字只存在于报告散文(iteration-3.5/04:96、releases.md:39、feature-checklist.md:242);代码里唯一被钉住的规模断言是 MediaContractConformanceTest.java:250 的 assertThat(CONTRACT.operations()).hasSize(45)。无任何测试断言「181」 |
⚠️ 不可机械复核,见问题 P5 |
| Flyway V1~V5 | patbond-user/src/main/resources/db/migration/ 恰好 V1~V5,无 V6 |
✅ 对 |
V5 已为 posts.generation_job_id 留裸列(无外键) |
V5__community_baseline.sql:48 = generation_job_id uuid,(零 REFERENCES);:95 建 ix_posts_generation_job;:47 注释明写「FK to creation.generation_jobs stripped(M4 补回)」 |
✅ 对 |
| 六容器部署(含自托管 MinIO) | docker-compose.yml services = postgres, minio, user, auth, pet, community,恰 6 |
✅ 对 |
| 埋点事件白名单 42 条 | 41 条(EventDictionary.java 的 Map.ofEntries 内 Map.entry 41 个,去重后仍 41) |
❌ 不对,见问题 P3 |
| ADR-001~022 | decisions.md 22 个 ## ADR-0xx 标题,无缺号无重号 |
✅ 对 |
| E2E 四份脚本 42 场景 | 脚本自报计数相加恰好 42:M1 [1/7]~[7/7] = 7、M2 $_passed/11、M3 $_passed/14、M3.5 $_passed/10 |
✅ 对 |
| E2E 234 断言 | 机械可数:check( 计数 M2 42 + M3 84 + M3.5 84 = 210;M1 无 check(、用 16 处 ✗ 卫语句 → 合计 226。差 8 处无法机械复现(疑为 assertMeShape 等辅助函数内部断言另计) |
⚠️ 数量级可信、精确值未取证 |
| mkdocs 可构建、导航无死链 | mkdocs build --strict exit 0,Documentation built in 1.71 seconds |
✅ 对 |
| main 分支保护禁直推 + 两个状态检查上下文 | 仅有 releases.md:110-114 表格作为证据(称经 Gitea API 核实);我侧 GET /branch_protections 返回 401(无 token,未取证)。且该上下文的选型本身有洞,见问题 P2 |
⚠️ 配置未复核 / 设计有洞 |
1.1 我侧独立取证:CI 状态比声称的更好(唯一正向偏差)
iteration-3.5/04:8 与 §7.3 记录「⚠️ Gitea CI 仍红——runner 级故障,最后一次 CI 绿是 M3 末的 8089c06」,
且 08-release-e2e-regression.md 全文零处提及 CI/runner/Gitea。若照抄这条结论,会以为 M4 开工时 CI 仍死。
实测(Gitea API GET /repos/{owner}/{repo}/commits/{sha}/status,2026-09-14):
| 仓 | state | 上下文 | 结果 | 上报时间 |
|---|---|---|---|---|
| patbond-api | success |
CI / backend-test (push) |
Successful in 5m31s | 2026-09-14T09:44:05+08:00 |
| patbond-api | success |
CI / backend-test (pull_request) |
Successful in 6m42s | 2026-09-14T09:54:37+08:00 |
| patbond-flutter | success |
CI / flutter-gates (push) |
Successful in 3m46s | 2026-09-14T09:58:23+08:00 |
| patbond-flutter | success |
CI / flutter-gates (pull_request) |
Successful in 3m44s | 2026-09-14T10:02:08+08:00 |
| patbond-doc | success |
CI / docs-build (push) |
Successful in 1m22s | 2026-09-14T11:20:44+08:00 |
结论:runner 已修复,三仓 HEAD 全绿,(push) 与 (pull_request) 双上下文均真实跑过。
iteration-3.5/04 §7.3 的「CI 仍红」是已过期的历史状态,M4 开工不必把它当风险。
(此项 07 号列为「未取证」,本报告补齐。)
2. 遗留与挂起项的当前真实状态
纪律:本节每一项都从源码/迁移文件直接取证,不采信任何报告结论。
2.1 真机挂起项:实际 10 项,且「转述污染」链条已可完整还原
device-truth:打开 docs/development/device-verification.md 逐节清点顶层验证项:
| 迭代 | 节标题 | 顶层项数 | 明细 | 执行记录 |
|---|---|---|---|---|
| M2 | 「M2 挂起项(2026-09-08 登记,待执行)」 | 2 | 验证一 Android 事件真实落库;验证二 SessionTracker 30 分钟后台换会话 | _(待真机到位后填写…)_ → 未做 |
| M3 | 「M3 预登记(社区)」 | 4 | 媒体上传弱网;乐观更新真机手感;Feed 图片加载;社区事件落库 | _(待补)_ → 未做 |
| M3.5 | 「M3.5 预登记(用户资料与头像)」 | 4 | 头像上传弱网;头像缓存;caregiver 改宠物头像;获赞数对账 | _(待补)_ → 未做 |
| 合计 | 10 | 0 项已执行 |
M3.5 节的正文自证是四项:「下列四项是桌面替代不了的部分」。
污染链条(这正是 M3.5 教训的同型复发,值得单独记录):
- 源头:
releases.md:121写「真机验证四项挂起(媒体上传弱网、乐观更新手感、Feed 图片加载、社区事件落库);另有 M2 两项」= 登记 6 项,整段漏掉 M3.5 的四项。 - 我的开工任务书据此写「真机四项验证」(只剩 M3 的 4)。
- 协调者更正为「8 项 = M2 两项 + M3 四项 + M3.5 两项」——方向对了,但 M3.5 又少计 2。
- 实测 = 10 项。
即:同一事实经过三次转述,出现 4 → 6 → 8 → 10 四个不同数字,每一次转述都在丢项。
device-verification.md 是唯一正确的原始文件,任何规划都必须直接读它。
2.2 M2 两项的 2026-09-21 时限:可行,且「等真机」是个伪阻塞
device-verification.md 的 M2 节挂着时限提醒「建议在 2026-09-21(北极星首次出数日)前完成」。今天 09-14,剩 7 天。
关键取证——该清单明写「Android 真机(推荐)或 Android 模拟器」,而本机模拟器链路完整可用:
$ ls ~/.android/avd/ → Pixel_7.avd Pixel_7.ini
$ ~/Android/Sdk/emulator/emulator -list-avds → Pixel_7
$ ls -la /dev/kvm → crw-rw-rw- 1 root kvm (全员可读写,无需加组)
$ ls ~/Android/Sdk/system-images → android-36
$ ls ~/Android/Sdk → build-tools cmake cmdline-tools emulator ndk platforms platform-tools skins …
flutter devices 当前只看到 Linux/Chrome,原因是 ANDROID_HOME/ANDROID_SDK_ROOT 未设置且
adb/emulator 不在 PATH(实测 which adb → not found),不是缺硬件。
裁决:时限可行(CERTIFIED-可行)。 M2 两项的净工作量是「验证一 ~10 分钟 + 验证二 ~45 分钟(其中 35 分钟是纯等待)」,
合计约 1 小时挂钟时间,其中人工操作不足 15 分钟。前置只差三条命令(导出 ANDROID_HOME、
PATH 加 platform-tools/emulator、起 Pixel_7)。7 天时限绰绰有余;把它记作「待真机到位」已经拖了 6 天,属登记口径错误而非资源不足。
解除条件:起 Pixel_7 模拟器 + compose 六容器,跑完 M2 两项,填 device-verification.md 的「M2 项执行记录」,
并把 feature-checklist.md 第 9 节由 🟡 改 ✅。
但需同时纠正一处过度乐观:10 项里只有一部分模拟器可替代。按各项通过标准的物理依赖分类:
| 可用模拟器完成(6 项) | 必须真实硬件(4 项) |
|---|---|
M2 两项(platform=android 落库、SessionTracker 30min) |
M3-1 媒体上传弱网(需蜂窝/飞行模式掐断、中端机压缩耗时 ≤2s) |
| M3-4 社区事件落库(只要 platform 枚举命中即可) | M3-2 乐观更新手感(需中低端机真实帧率判「无可见掉帧」) |
| M3.5-3 caregiver 改宠物头像(纯权限档位) | M3-3 Feed 图片加载(含「蜂窝 vs Wi-Fi 可达性差异」) |
| M3.5-4 获赞数对账(纯数字口径) | M3.5-1 头像上传弱网(含 iPhone HEIC —— 模拟器与 Android 都造不出) |
| M3.5-2 头像缓存(跨页命中可在模拟器观察) |
另注(07 号已取证,此处只标注影响):M3.5-3 caregiver 项的前置写着「按 pet_health 的协作表直接造数据,收口时补 SQL」,
该 SQL 从未补 → 此项当前不可执行,即便有设备也跑不了。解除条件是先补造数 SQL。
2.3 widthPx / heightPx 仍恒 null —— 结构性,且 M4 会被它直接绊到
取证(不看文档,看写路径):
- 声明侧存在:
V1__identity_media_baseline.sql:217-218有width_px integer, height_px integer;MediaAssetResponse.java:18-19、PostMediaItemResponse.java:15-16都外露这两个字段。 - 写路径不存在:
MediaAssetRepository.insertUploading()的 INSERT 列清单是(id, owner_user_id, kind, purpose, storage_type, bucket, object_key, mime_type, byte_size, sha256, status)—— 不含 width_px / height_px。 - 更新路径也不存在:全库
grep width_px命中的UPDATE语句 0 条;media.assets上仅两条 UPDATE, 即markReady(SET status='ready', ready_at=now(), updated_at=now())与markFailed(SET status='failed'), 都不碰尺寸列。
结论:生产链路上这两列永久为 NULL,没有任何代码能写入。 裁决 NEEDS WORK(确认遗留)。
⚠️ 陷阱提示:PostMediaAttachIntegrationTest.java:44-45 断言 widthPx==640 / heightPx==480 是绿的,
因为 CommunityTestData.java:55 在测试夹具里直接 INSERT 了这两列。
测试绿 ≠ 生产有值 —— 这与 M3.5「无 nickname 字段」同型:断言测的是夹具,不是产品。任何规划不得据此认为该字段可用。
2.4 eventVersion 口径仍未定型 —— 且服务端根本不校验
- 契约要求必填:
openapi.yamlTrackedEvent.required含eventVersion。 - 契约描述已过期:
openapi.yaml:2341-2343写description: 事件 schema 版本(**字典 v1 全部为 1**), 而EventDictionary.java类注释开头即Event dictionary **v3**。 - 服务端零校验:
grep -rn "eventVersion" --include=*.java在EventDictionary.java与AnalyticsService.java中命中 0 次。唯一处理是TrackEventsRequest.java:38的@NotNull与AnalyticsRepository.java:26/48的原样落库。 - 库侧只有宽约束:
V2__create_platform_product_events.sql:11event_version smallint NOT NULL DEFAULT 1,:22CHECK (event_version > 0)。
结论:客户端可对任意事件上报任意正整数版本号并被静默接受;字典已到 v3 而契约仍宣称「全部为 1」。 裁决 NEEDS WORK(确认遗留)。M4 若新增事件,必须先定这个口径,否则新事件版本号写什么都「对」。
2.5 429 限流仍缺 —— 后端与契约双零命中
$ grep -rn "429|RateLimit|rateLimit|TOO_MANY" patbond-api --include=*.java --include=*.yml → 0 行
$ grep -n "429" patbond-doc/docs/api/openapi.yaml → 0 行
结论:不仅未实现,契约里连 429 这个响应码都没声明,即系统在任何路径上都不可能返回 429。 裁决 NEEDS WORK(确认遗留)。
连带影响:iteration-3/03:151 规划过「429 按 Retry-After 退避……Retry-After 留接线点」。
客户端那条分支永远走不到,属当前无法被任何测试覆盖的死分支。
M4 上 AI 生成必然要限流(见 §3.5),届时这条分支才第一次有意义。
2.6 「我的收藏与草稿」页仍缺 —— 后端就绪、数据层就绪、UI 零
| 层 | 状态 | 证据 |
|---|---|---|
| 契约 | ✅ 有 | openapi.yaml 含 /api/v1/me/bookmarks [GET] 与 /api/v1/me/posts [GET] |
| Flutter 数据层 | ✅ 有 | community_repository.dart:185 listMyPosts(带 status 过滤)、:279 listMyBookmarks |
| Flutter UI | ❌ 无 | find lib -iname "*bookmark*" -o -iname "*draft*" -o -iname "*favorite*" → 零结果;`grep -rn "bookmark |
| 入口 | 假入口 | profile_page.dart:49 菜单项「我的收藏与草稿」仍走演示提示;:45-46 注释自承「后端能力已就位(/me/bookmarks、/me/posts),但列表页本单未做」 |
死代码取证:listMyBookmarks 的生产调用方 0 个(grep 仅命中 community_repository.dart 自身与 3 个测试文件)。
listMyPosts 有 1 个生产调用方:post_compose_page.dart:196 的 _restoreLatestDraft(),
其注释自承「草稿恢复(最小实现:最新一条)」,limit: 1, status: PostStatus.draft。
结论:裁决 NEEDS WORK(确认遗留)。收藏列表完全无 UI 且数据层是死代码;草稿只有「恢复最新一条」,无列表、无自动保存。 这一条对 M4 直接相关 —— 见 §3.4。
3. M4 规划未证前提狙击
本节是本报告的重心。每条前提给出它需要什么证据与实测到的证据。
3.1 【最危险】真实 AI 模型服务:endpoint / 凭证 / 额度三者全无
需要的证据:一个可调用的模型服务地址、一份可用凭证、一个已确认的额度或计费口径。
实测:
$ grep -rniE "openai|anthropic|stability|replicate|dashscope|volcengine|comfyui|sdxl|api_key|apiKey" \
patbond-api --include=*.java --include=*.yml --include=*.yaml --include=*.sample
→ 零命中(排除 gen_random / generated / generation_job_id 等同形词后)
$ grep -oE "^[A-Z_]+" patbond-api/.env
→ PATBOND_DB_PASSWORD / PATBOND_INTERNAL_TOKEN / PATBOND_MINIO_ROOT_USER / PATBOND_MINIO_ROOT_PASSWORD
$ grep -oE "PATBOND_[A-Z_]+" patbond-api/deploy/init-secrets.sh | sort -u
→ 同上 4 个,无第五个
即:整个项目的密钥面共 4 项(DB 口令、服务间 token、MinIO 用户/口令),无一条与任何 AI 服务商相关; 没有任何 HTTP 客户端、SDK 依赖、配置占位符指向任何模型服务。
更关键的一条反证 —— 正典模型自己就只设想了 fixture 提供方:
patbond_postgresql.sql:1495-1499 的种子数据:
INSERT INTO creation.generation_models
(id, code, display_name, provider_code, provider_model_name, media_kind, sort_order)
VALUES
(…, 'patbond-v1', 'Patbond-V1', 'fixture', 'patbond-image-v1', 'image', 10),
(…, 'patbond-v1', 'Patbond-V1', 'fixture', 'patbond-video-v1', 'video', 10);
provider_code = 'fixture';:1510 的样例 job 亦为 provider_code_snapshot='fixture'、
provider_request_id='fixture-provider-job-1'。设计者从未假设 M4 接真模型。
同时注意 generation_jobs 有三个 NOT NULL 的快照列:
provider_code_snapshot、provider_model_snapshot、model_version_snapshot(:620-622)。
任何一次入队都必须填出这三个值 —— 接 fixture 也要填,这是契约级强制,不能含糊。
裁决:BLOCKED。
这直接击穿「本迭代端到端可验证」的说法。 两条路,必须现在拍板选一条,不能含混:
- 路 A(推荐,可 CERTIFIED):M4 明确只做 fixture/stub provider,与正典种子一致。 「端到端可验证」重新定义为「入队 → 租约领取 → 状态机流转 → 产物落 MinIO → 挂帖」全链路可验, 产物是 stub 图(例如把输入图做一次确定性变换)。这条路的每一环都能被自动化测试覆盖,无外部依赖、无额度风险、CI 可跑。
- 路 B(需先解阻塞):接真模型。开工前必须先有:服务商选定 + endpoint + 凭证注入方案
(
init-secrets.sh增第 5 项 + compose 环境变量 + ADR)+ 额度/计费上限 + 失败与超时口径 + ADR 记录「凭证不入库」如何保证。这些一件都还没有。
⚠️ 若规划文本同时写「fixture 兜底」又写「端到端接通真实模型」,那是自相矛盾, 按本报告纪律判 NEEDS WORK,必须二选一并写进 ADR。
3.2 【前提是伪命题】Worker 队列不需要 Redis / MQ
这条前提我给出的是否证:任务书假设「Worker 队列需要 Redis 或 MQ」,实测该假设本身不成立。
- 现有 compose 六服务 =
postgres, minio, user, auth, pet, community。无 Redis、无 RabbitMQ/Kafka、无任何 broker。 - 但正典模型已经给出了完整的 Postgres 租约队列设计,不需要 broker:
-
generation_jobs具备队列所需全部列:status(queued/running/succeeded/failed/cancelled)、priority、attempt_count/max_attempts、next_attempt_at、lease_owner、lease_expires_at、progress、version。 -
两个专用偏索引:
ix_generation_jobs_queue ON (priority DESC, next_attempt_at, created_at, id) WHERE status='queued'与ix_generation_jobs_running ON (lease_expires_at, id) WHERE status='running'(:712-715)。 -
patbond_postgresql.sql:1896-1917直接给出了多 Worker 并发领取的标准写法,注释即 「AI worker claim pattern for multiple concurrent workers」:WITH picked AS ( SELECT id FROM creation.generation_jobs WHERE status='queued' AND attempt_count < max_attempts AND next_attempt_at <= now() ORDER BY priority DESC, next_attempt_at, created_at, id FOR UPDATE SKIP LOCKED LIMIT 1 ) UPDATE creation.generation_jobs j SET status='running', started_at=now(), progress=1, attempt_count=attempt_count+1, lease_owner=:worker_id, lease_expires_at=now()+interval '2 minutes', version=version+1 FROM picked WHERE j.id=picked.id RETURNING j.*; -
ck_generation_jobs_state是一条五分支状态机 CHECK,把每个 status 允许的字段组合钉死 (如running必须lease_owner IS NOT NULL、succeeded必须progress=100 AND output_asset_id IS NOT NULL)。 这是很强的资产:状态机由数据库强制,Worker 写错就报错。
-
- 调度侧也有现成先例:
UserApplication.java:7已有@EnableScheduling,SessionCleanupJob.java:32已有一个@Scheduled任务在生产运行。轮询式 Worker 与它同构。
裁决:CERTIFIED(基础设施无需扩容)。 这是 M4 少有的真·已就位项。 明确建议不要引入 Redis/MQ —— 会凭空增加一个容器、一套运维面、一份 ADR,而正典设计已否决其必要性。
⚠️ 但两个未证的衍生点必须写进规划:
lease_expires_at的回收者不存在。 设计给了ix_generation_jobs_running索引, 却没有任何代码回收超租约的僵尸 job。这与已登记的「uploading超时清理任务」是同型缺口, 而那一项至今未做(releases.md:123已登记)。M4 若不写回收器,running的 job 崩了就永久卡死。- Worker 放哪个模块未定。
@EnableScheduling只在patbond-user。新建patbond-creation模块意味着第 7 个容器(compose 从 6 → 7),需 ADR。若塞进现有模块,则 AI 长任务会与在线请求争线程池。
3.3 【半真半假,最易误判】「媒体链路 M3 已就位可直接复用」
这句话必须按方向拆开验,因为读侧成立、客户端写侧成立、服务端写侧完全不存在。
取证 —— 全代码库 S3 调用面:
$ grep -rhoE "\b(s3|client|s3Client)\.[a-zA-Z]+\(" patbond-user/src/main/java/com/patbond/patbond/user/
→ client.close( client.createBucket( client.headBucket( client.headObject(
即整个后端对对象存储的能力只有:建桶、探桶、探对象元数据、关闭客户端。
零 putObject、零 getObject。 patbond-pet 与 patbond-community 侧则只有 S3Presigner
做本地 SigV4 签名(MediaUrlSigner.java),连网络调用都没有。
| 复用面 | 结论 | 证据 |
|---|---|---|
| 预签名 GET 读取(帖图/头像展示) | ✅ 真可复用 | MediaUrlSigner.java 在 pet/community 两处已复制运行 |
| 客户端上传三段(createUpload → 直传 → complete) | ✅ 真可复用 | MediaService.java + /media/uploads 两端点已在契约内 |
| 桶初始化 | ✅ 可复用 | S3ObjectStorage.ensureBucket():66 |
| 服务端写对象(Worker 产出物落盘) | ❌ 净新增,零基础 | 无 putObject |
| 服务端读对象字节(把输入图喂给模型) | ❌ 净新增,零基础 | 无 getObject(headObject 只取元数据) |
purpose 白名单 |
⚠️ 需扩展 | application.yml.sample:59 = post_image,user_avatar,pet_avatar;MediaProperties.java:66 同值。无 AI 输入/输出用途 |
产物尺寸写入 media.assets |
❌ 不可复用 | 见 §2.3,insertUploading 无尺寸列、无 UPDATE 路径 |
裁决:NEEDS WORK(该表述必须在规划里改写)。
准确表述应为:「媒体的读链路与客户端上传链路可直接复用;服务端读写对象字节的能力为零,是 M4 的净新增工作。」
好消息是 purpose 扩展很便宜 —— MediaProperties.java:61 注释明写该白名单是
「configuration + contract-enum change, never a migration」,即改配置 + 改契约枚举即可,不需要迁移。
⚠️ 一个具体的下游矛盾:generation_jobs 自己存了 width_px/height_px(:616-617,
带 CHECK … BETWEEN 64 AND 8192),也就是 AI 产物的尺寸在 creation schema 里是已知的;
但 media.assets 的同名列永远为 NULL(§2.3)。若 Worker 不顺手把尺寸写进 media.assets,
客户端渲染 AI 产物会继续回落 4:3 占位(这正是已登记的「单图帖回落 4:3」遗留)。
M4 有一次几乎零成本修掉它的机会(Worker 本来就知道尺寸),建议顺带修掉,不要再滚一轮。
3.4 「社区草稿已就位可复用」—— 只有一半
- ✅ 库侧就位:
V5__community_baseline.sql:53status varchar(16) NOT NULL DEFAULT 'draft'。 - ✅ 契约就位:
/api/v1/me/posts支持status过滤;Post.category枚举已含ai_creation。 - ✅ M4 的读侧钩子已预留:契约明写
ai_creation 为 M4 预留值,M3 不开放写入(提交 400/40000), 且有测试钉住 ——PostLifecycleIntegrationTest.java:102-104断言 M3 提交category: ai_creation被拒。 M4 要做的是把这个拒绝改成放行,需同步改契约、快照、该测试。 - ❌ 草稿 UI 只有「恢复最新一条」:见 §2.6。无草稿列表、无自动保存(
releases.md:123已登记为遗留)。
裁决:NEEDS WORK。 「AI 创作产物存草稿再发布」这条产品路径依赖草稿列表,而列表不存在。 规划若假设「用户可以把多个 AI 产物存成草稿再挑一个发」,那是未证前提 —— 当前只能恢复最新一条,多草稿会互相覆盖。
3.5 【高危】长耗时异步任务在桌面端基本无法验证,M4 极可能再产出一批挂起项
这是我对 M4 最强的风险判断,有三条硬证据。
证据一:埋点在桌面端结构性不可用。
openapi.yaml:2373-2375:
platform:
type: string
enum: [android, ios]
枚举只有两个值。Linux 桌面上报 platform=linux → 整批 400。这不是缺陷而是契约内行为,
device-verification.md 通用前置已写明「桌面/Web 不可用……platform 值不在契约枚举内会被服务端整批拒绝」。
推论:M4 新增的任何创作漏斗事件(生成发起/成功/失败/耗时分桶),其「落库」验证只能在 Android 上做 → 又是一批真机挂起项。
证据二:选图与压缩在 Linux 桌面无原生实现,桌面实测走的是替身。
image_picker 与 flutter_image_compress 确实已实现(media_picking.dart:28 SystemMediaImagePicker、
media_compression.dart:42 FlutterImageCompress.compressWithList)——
但 media_uploader.dart:570-571 与 app.dart:53 都注明「Linux 桌面既无 image_picker 也无
flutter_image_compress 的原生实现,桌面真链路只替换选图与压缩两层」。
(顺带纠正我方任务书的一处错误表述:并非「无 image_picker/compress 实现」,
而是实现有、Linux 平台支持无。这是原始文件与转述的又一处偏差。)
推论:AI 创作必然以「选一张宠物照」开头 → 该入口在桌面永远是替身 → 首步就无法真实验证。
证据三:现有 E2E 与 integration_test 都不在门禁里。
四份 test_e2e_*_manual.dart 是手工脚本,需 compose 全栈在位,不在 flutter test 内、CI 不会跑
(07 号亦独立取证 integration_test/ 下 4 个真机测试完全在 flutter test 之外)。
flutter test 的 2 个 skip 也正是需要后端的 smoke(PATBOND_MEDIA_SMOKE=1 / PATBOND_DETAIL_SMOKE=1)。
推论:长耗时异步任务(入队→轮询→完成,正典租约 2 分钟)如果照现有模式验证,
只会再产出一份「手工脚本 + 真机清单」,自动化门禁覆盖率为零。
裁决:NEEDS WORK,且这是 M4 最可能失控的一面。
可解除的具体做法(这些都能在桌面/CI 内做,不必等真机):
- Worker 状态机做纯后端集成测试(Testcontainers + fixture provider)——
入队、SKIP LOCKED 并发领取、租约过期回收、重试退避、
max_attempts耗尽转 failed、幂等键去重。 这些全部不需要客户端、不需要真模型、不需要真机,可 100% 进mvnw test门禁。这是 M4 最该先建的护栏。 platform枚举扩容拍板:若想让桌面参与埋点验证,就在契约enum加linux(或加通用desktop)。 这是一行契约改动 + 快照同步,能一次性解掉 M2/M3/M3.5 遗留下来的「事件落库只能真机验」死结, 收益跨三个迭代。若决定不加,则必须承认 M4 的埋点验证同样挂起,并写进清单。- 入队/轮询/取消的契约面进 E2E 脚本(第五份),并同步更新回归清单(见 §4.1)。
3.6 其余未证前提(一次列清)
| 前提 | 实测 | 裁决 |
|---|---|---|
| 「V5 已留裸列,M4 补外键很轻」 | 裸列确实在(V5:48)。但 V6 需 CREATE SCHEMA creation + 3 张表 + ~12 索引 + 3 触发器 + 补 1 个外键,不是轻量迁移 |
NEEDS WORK(工作量被低估) |
| 「跨 schema 外键照正典补回即可」 | V5:20-30 注释确立的纪律是「未迁移 schema 的跨库外键一律裁剪」。generation_jobs 自身引用 identity.users/pet_health.pets/media.assets(均已存在,可保留),但它还被 marketplace 之外的 platform.regions 牵连口径 —— 需逐条确认哪些保留哪些裁剪,无现成结论 |
未取证,需 API/DBA 角色出定型表 |
| 「A/B 实验前置已就位」 | 服务端字典有 experiment_exposed(EventDictionary.java:103,注释自承「dictionary ahead of its M4 first use」)。但 Flutter 侧零引用:grep -rn "experiment_exposed|experimentKey" lib/ test/ → 零命中。即无任何客户端能发这个事件 |
NEEDS WORK(仅服务端半就位) |
| 「北极星 2026-09-21 首次出数」 | 依赖 M2 两项真机验证(§2.2)。技术上可行,但至今 0 项执行,且 platform=linux 死结未解 |
NEEDS WORK(可行但已拖期) |
| 「零迁移」惯例可延续 | M3.5 做到零迁移是因为所需列 V1/V3/V5 已存在。M4 必然需要 V6(creation schema 完全不存在),零迁移惯例在 M4 必然中断 |
需明确写进规划,别延续错误预期 |
4. 发布流程与服务器侧登记项(协调者追加三项)
按分工,此处不重复 Evidence Collector 对 checklist 的逐步清点,只判「规划是否站得住」。
4.1 发布流程:文档内部自相矛盾,且状态检查上下文的选型有洞
(a)同一份 releases.md 自己打自己(回归清单份数)
| 位置 | 表述 |
|---|---|
releases.md:36 |
「E2E 回归(四份,同环境串行)……M1 7/7 + M2 11/11 + M3 14/14 + M3.5 10/10 = 42/42 场景」 |
releases.md:56 |
「回归清单由两份改为四份:M1/M2/M3/M3.5」 |
releases.md:131 |
「1. 完成 checklist 第 1~2 步(三仓 CI 绿 + E2E 双份回归 PASS)」 |
:131 位于「发布后生效的纪律」一节 —— 也就是给下一次(即 M4)发布看的那段前瞻指令,写的是「双份」。
:36/:56 是本次回顾记录,写的是「四份」。前瞻指令与本次结论矛盾,且错在前瞻侧。
照 :131 执行 M4 发布,会只跑 2 份、漏掉 M3/M3.5 两份共 24 个场景。
叠加 Evidence Collector 独立取证的「发布 checklist 正文(iteration-3/08:84)仍写着 M2+M3 两份、
8 步里 4 步过期、标题至今是『草案』从未固化进 git-workflow.md」——
即『四份』这个正确结论只活在回顾表格里,两处可执行入口(checklist 正文 + 纪律段)都还是旧的。
裁决 NEEDS WORK。解除条件:把 releases.md:131 的「双份」改「四份」,
同步 iteration-3/08 checklist 正文,并把 checklist 从「草案」固化进 git-workflow.md。
(b)状态检查上下文选了 (push),与「等 PR 检查转绿」的指令不是一回事
releases.md:110-114 登记的必需上下文是:
| 仓库 | protected | 状态检查上下文 |
|---|---|---|
| patbond-api | true |
CI / backend-test **(push)** |
| patbond-flutter | true |
CI / flutter-gates **(push)** |
| patbond-doc | 未启用 | —— |
而 releases.md:133 的指令是「3. 等 PR 的 CI 状态检查转绿(即上表的 status_check_contexts)」。
这两者不等价。 我实测(§1.1)同一个 commit 上 (push) 与 (pull_request) 是两个独立上下文:
CI / backend-test (push) success 5m31s 09:44:05
CI / backend-test (pull_request) success 6m42s 09:54:37
api 工作流是 on: push: branches: [dev]。因此 dev → main 的 PR 场景下,
(push) 状态是推 dev 时就已经写好的,PR 开出来之前它就是绿的。
结论:这个门禁实际由「推 dev」满足,而不是由 PR 满足。
若某次 PR 的 (pull_request) 检查失败、而先前推 dev 的 (push) 是绿的,合并仍会被放行 —— 门禁形同虚设。
releases.md:116 自承选择显式写死上下文是为了消除「留空 = 空集为真反而放行」的歧义,
方向正确,但挑错了上下文:要真正在 PR 时把关,必需上下文应是 (pull_request)(或两者都要求)。
裁决 NEEDS WORK(真实的门禁漏洞)。解除条件:把必需上下文改为
CI / backend-test (pull_request) / CI / flutter-gates (pull_request),或两个上下文都列为必需,
并重新用 Gitea API 核实后更新 releases.md 表格。
(注:我侧 GET /branch_protections 返回 401,无 token 故无法复核当前实际配置,
上述判断基于文档登记值 + 我实测到的上下文命名事实。修正前必须先用有权限的 token 核一遍实际配置。)
(c)doc 仓的口径需要说清楚,避免误读
releases.md:128 的「main 已禁止直接推送——影响 main 的一切变更一律走 PR」是全局语气,
但 :114/:116 明确 doc 仓 main 未启用保护、「即日常分支、不参与发布分支语义,按规划不设保护」。
两处并存容易被后续 agent 误读成「doc 也要走 PR」。且 doc 工作流是 on: push: branches: [main],
若哪天真给 doc main 加上保护并要求 (push) 上下文,会直接死锁(推 main 被禁 → (push) 永不产生 → PR 永不可合)。
建议在 :128 加一句限定「(api 与 flutter 两仓;doc 仓 main 保持直推)」。裁决 NEEDS WORK(表述风险)。
4.2 服务器侧:文档站公开可访问,登记项仍悬空
server-exposure.md §2 常驻服务表中该行原文:
文档站(patbond-doc) | 经 nginx 443 | mkdocs 构建产物,含架构/部署/迭代全部文档 | —— | ✅ 运行;⚠️ 公开可访问,待评估是否加 basic auth 或 IP 白名单(无凭证内容,但暴露内部架构细节)
现状核实:
- 该项仍是「待评估」,无结论、无归属决策(「归属决策」列为空
——)、无 ADR 记录。 https://git.patbond.cn/实测返回 200(nginx 在服;文档站按sites-enabled另一域名分流,我未探测其域名, 未取证:文档站实际 URL 与是否真的无鉴权)。- §1 端口表「最后确认」全部停在 2026-09-11。而
server-exposure.md自订纪律 ② 是「每次迭代收官核对一遍,更新『最后确认』」,M3.5 收官与 v0.4.0 发布均发生在 09-14, 该列未更新。按同文档纪律 ⑤「本页与实际不符即为缺陷」,这本身是一处待补。
我的评估与建议(只取证与建议,不动服务器):
风险等级判中,倾向「应当加访问控制」,理由是三条已发生的事实叠加:
- 文档站内容包含
server-exposure.md本身 —— 即一份完整的对外端口清单与常驻服务清单, 还包含ci-runner-setup.md(CI 拓扑)、decisions.md(全部 22 条架构决策)、 数据库正典 SQL(全表结构与约束)。这是一份给攻击者的现成侦察报告。 - 本项目刚刚发生过一次真实入侵尝试(
iteration-3.5/07,Gitea gitconfig 注入),server-exposure.md的缘起就是「Nacos 在 ADR-002 移除后仍暴露公网近两个月」—— 说明本环境的暴露面治理确实曾经失守过,不是理论风险。 3 该站与 Gitea 同一台机器、同一个 nginx(sites-enabled/同级分流)。文档站的任何 nginx 配置失误 与代码托管共享爆炸半径。
建议:加 IP 白名单或 basic auth(二者皆可,basic auth 更省事),并把结论写成 ADR 或在
server-exposure.md 的「归属决策」列填上,把状态从「待评估」改成终态。
成本约十分钟 nginx 配置,不应再挂第四个迭代。
⚠️ 但需注意一条执行顺序约束:若加 basic auth,mkdocs build 的产物是静态站,不受影响;
但若有任何自动化在拉取文档站 URL 做校验,会被 401 打断 —— 我未取证是否存在这类消费方,
落地前应先 grep 三仓有无对文档站 URL 的自动访问。
裁决 NEEDS WORK(登记项悬空,建议本迭代内闭环)。
5. 真实问题清单(按严重度排序)
编号 P1~P12。「新发现」= 本报告首次登记;「已登记」= 文档已知但状态需纠正。
P1 —— 阻断级:M4 无任何 AI 服务商 endpoint / 凭证 / 额度(新发现)
密钥面共 4 项,无一与 AI 相关;代码库零 provider SDK;正典种子 provider_code='fixture'。
影响:「AI 创作端到端可验证」当前是无证据的口号。必须在开工前二选一(fixture 路 / 真模型路,见 §3.1)。
generation_jobs 三个 NOT NULL 快照列迫使这个选择必须显式。
P2 —— 阻断级:服务端零对象写能力,「媒体链路可复用」被高估(新发现)
全库 S3 调用仅 createBucket/headBucket/headObject/close。Worker 落产物需 putObject、
喂输入需 getObject,两者皆为净新增。
影响:M4 媒体侧工作量被系统性低估;规划中「直接复用」的措辞必须改写(§3.3)。
P3 —— 高:埋点白名单实为 41 条,四份文档写成 42,且无测试锁定(已登记数字错误)
实测 Map.entry 41 个。差异来源已定位到 feature-checklist.md:218 的算术错误:
它写「community 域 19 + experiment_exposed」= +20 → 22+20=42;
而 EventDictionary.java 类注释自己的拆分是 post 8 + feed 2 + interactions 8 = 18,
18 + experiment_exposed 1 = 19 增量,22+19 = 41。即「community 域 19」把 experiment_exposed 重复计了一次。
影响:中等(数字失真,不影响运行),但无任何测试断言该数量(唯一规模断言是 operations()==45),
所以这个数字会继续漂。建议加一条 WHITELIST.size() 断言把它钉住,成本一行。
P4 —— 高:v0.4.0 发布记录把 381 测试写成 379(已登记数字错误)
实测 3cd8005 = 381。差异来源已定位:iteration-3.5/04:150-159 明确记录契约冻结
「测试数 379 → 381(+2)」,而 3cd8005 正是那次契约冻结的提交。
releases.md:17、:35、feature-checklist.md:5 三处写 379,都是照抄了冻结前的数字。
影响:releases.md 把 hash 3cd8005 与 379 绑在一行,是内部不自洽的发布记录;
后续任何「测试数应为 379」的回归判断都会误判。
P5 —— 高:门禁漏洞——必需状态检查选了 (push) 而非 (pull_request)(新发现)
见 §4.1(b)。(push) 在 PR 开出前就已绿,门禁实际由推 dev 满足;PR 检查红也能合并。
影响:main 分支保护的实际强度低于文档宣称。这是本次发现的唯一安全/流程类真实漏洞。
P6 —— 中高:M4 极可能再批量产出「只能真机验」的挂起项(新发现)
三条硬约束叠加:platform enum 仅 [android, ios](桌面埋点整批 400);
Linux 桌面无选图/压缩原生实现(走替身);四份 E2E + integration_test/ 4 个测试全在 CI 之外,
flutter test 的 2 个 skip 也是需后端的 smoke。
影响:若不先建后端侧 Worker 状态机自动化测试并拍板 platform 枚举,M4 收官时挂起项会从 10 项继续往上加。
P7 —— 中高:真机挂起项实为 10 项,releases.md 只登记 6 项(已登记但漏项)
releases.md:121 漏掉整个 M3.5 四项。转述链 4→6→8→10 每一跳都在丢项(§2.1)。
其中 M3.5-3 caregiver 项因前置造数 SQL 从未补,当前不可执行。
影响:M4 规划若照 releases.md 估算收尾工作量,会低估 4 项。
P8 —— 中:发布纪律段仍写「E2E 双份回归」,前瞻指令错误(新发现)
releases.md:131 与同文件 :36/:56 矛盾,且错在给 M4 用的前瞻侧;
叠加 checklist 正文(iteration-3/08:84)仍写两份、8 步中 4 步过期、至今是「草案」未固化。
影响:M4 发布会漏跑 24 个 E2E 场景。
P9 —— 中:429 限流在后端与契约双零命中,客户端退避分支是死代码(已登记)
契约里连 429 响应码都未声明。M4 上 AI 生成必然需要配额限流,届时这条分支才第一次有意义(§2.5)。
P10 —— 中:widthPx/heightPx 永久 NULL,且有一条「测试绿但生产空」的陷阱(已登记 + 新发现陷阱)
写路径与更新路径均不存在;PostMediaAttachIntegrationTest:44-45 断言 640/480 靠的是
CommunityTestData:55 的夹具直插。这是 M3.5「nickname」教训的同型复发。
M4 的 Worker 天然知道产物尺寸,有一次近乎零成本修掉的机会(§3.3 末)。
P11 —— 中:eventVersion 服务端零校验,契约描述已过期(已登记)
契约称「字典 v1 全部为 1」,实际字典 v3;服务端仅 @NotNull,任意正整数均被接受(§2.4)。
M4 新增事件前必须定口径。
P12 —— 低:patbond-doc 无 .gitignore,site/ 仅靠全局 gitignore 屏蔽(Evidence Collector 取证,我侧补充成因)
我侧补充证据:git check-ignore -v site 返回 /home/lx/.gitignore_global:183:/site,
即屏蔽规则来自本机用户级全局配置,不在仓库内。
影响:任何新机器/新克隆/CI 容器里跑 mkdocs build 都会让 site/(数百文件)变成未跟踪,
极易被误 commit。修复成本:仓库根加一行 site/ 的 .gitignore。
另记:一处正向偏差(不是问题,但必须纠正认知)
CI 并非「仍红」。三仓 HEAD 全绿、(push) 与 (pull_request) 双上下文均已真实跑过(§1.1)。
iteration-3.5/04 §7.3 的「CI 仍红」是 09-11 的历史快照,M4 开工不应把它当风险项继承。
6. 逐项裁决与解除条件
6.1 基线资产(M4 可以放心站上去的部分)
| 项 | 裁决 | 依据 / 解除条件 |
|---|---|---|
| 契约 v1.4.0 规模与四模块字节级快照锁 | ✅ CERTIFIED | 32/45/75 逐格相符;md5 五处一致。无条件可用 |
Flyway V1~V5 链 + generation_job_id 裸列 |
✅ CERTIFIED | V5:48 零 REFERENCES,V5:95 索引在,无 V6 |
| 六容器编排 | ✅ CERTIFIED | 6 services 实测;且 M4 无需扩容(§3.2) |
| Worker 队列基础设施 | ✅ CERTIFIED(无需 Redis/MQ) | 正典 SKIP LOCKED 租约设计 + 五分支状态机 CHECK + 两个偏索引 + 已有 @EnableScheduling 先例 |
| ADR-001~022 连续无缺号 | ✅ CERTIFIED | 22 个标题实测 |
mkdocs --strict 可构建 |
✅ CERTIFIED | exit 0,1.71s |
| CI 三仓门禁 | ✅ CERTIFIED | 五个上下文全 success(09-14)。注:门禁强度另见 P5 |
| flutter 597 测试 | ✅ CERTIFIED(带注) | 597 passed;注:2 个 env-gated smoke 默认跳过 |
| api 测试全绿 | ✅ CERTIFIED(数字须改 381) | BUILD SUCCESS、0 失败 0 跳过;解除条件:把三处 379 改 381 |
6.2 M4 开工前必须拍板/解阻塞(BLOCKED 与高危 NEEDS WORK)
| 项 | 裁决 | 解除条件 |
|---|---|---|
| AI 模型服务来源 | 🔴 BLOCKED | 二选一并写进 ADR:路 A 只做 fixture provider(推荐,全链路可自动化验证);路 B 接真模型,则须先备齐 endpoint + 凭证注入方案(init-secrets.sh 第 5 项 + compose 环境变量)+ 额度上限 + 超时/失败口径。在此之前不得声称「端到端可验证」 |
| 「端到端可验证」的定义 | 🔴 BLOCKED | 随上一条同时定义。若走路 A,须显式写明「产物为 stub,不验证生成质量」 |
| 服务端对象读写能力 | 🟠 NEEDS WORK | 承认为净新增工作项并排期:putObject(产物落盘)+ getObject/预签名读(输入喂模型)+ purpose 白名单扩 AI 用途(改配置 + 契约枚举,无需迁移) |
| 「媒体链路可直接复用」表述 | 🟠 NEEDS WORK | 规划文本改写为「读链路与客户端上传链路可复用;服务端读写对象为净新增」 |
| Worker 归属模块与容器数 | 🟠 NEEDS WORK | 出 ADR:新建 patbond-creation(compose 6→7)还是并入现有模块(需评估线程池隔离)。当前无结论 |
| 租约超时回收器 | 🟠 NEEDS WORK | 正典给了 ix_generation_jobs_running 索引但无回收代码。M4 必须实现,否则 running job 崩溃即永久卡死。与既有「uploading 超时清理」同型缺口,建议一并做 |
| M4 是否再产出真机挂起项 | 🟠 NEEDS WORK | 三条并行解除:① 后端 Worker 状态机进 mvnw test 门禁(不需真机/真模型,M4 第一优先);② 拍板 platform enum 是否加 linux/desktop;③ 新增第五份 E2E 脚本覆盖入队/轮询/取消契约面 |
| 「零迁移」预期 | 🟠 NEEDS WORK | 明确写进规划:M4 必然需要 V6(creation schema 完全不存在),零迁移惯例在此中断 |
| V6 工作量 | 🟠 NEEDS WORK | 按 CREATE SCHEMA + 3 表 + ~12 索引 + 3 触发器 + 补 1 外键估,不是轻量迁移。需 DBA/API 角色出跨 schema 外键保留/裁剪定型表 |
6.3 遗留项裁决(M4 需明确「修」还是「继续挂」)
| 项 | 裁决 | 解除条件 / 建议 |
|---|---|---|
| 真机挂起 10 项 | 🟠 NEEDS WORK | 先修登记口径(releases.md:121 补 M3.5 四项、feature-checklist.md 补跟踪 2 项);再按 §2.2 分类推进 |
| M2 两项 @ 09-21 时限 | 🟢 可行(判 CERTIFIED-可行) | 本机 Pixel_7 AVD + /dev/kvm 齐备,约 1 小时挂钟即可完成。只差导出 ANDROID_HOME / PATH。「等真机」是伪阻塞 |
| M3.5-3 caregiver 项 | 🔴 BLOCKED | 前置造数 SQL 从未补,有设备也跑不了。解除条件:先补 pet_health 协作表造数 SQL |
widthPx/heightPx 恒 NULL |
🟠 NEEDS WORK(建议 M4 顺带修) | Worker 已知产物尺寸,写入近乎零成本;不修则 AI 产物继续回落 4:3 |
eventVersion 口径 |
🟠 NEEDS WORK(M4 前必须定) | 定「版本号随字典版本」还是「随单事件 schema」;同步改契约描述(现称「字典 v1 全部为 1」);补服务端校验 |
| 429 限流 | 🟠 NEEDS WORK(M4 强相关) | AI 生成需配额限流。落地时须同时在契约声明 429 + Retry-After,客户端死分支才能激活并被测到 |
| 「我的收藏与草稿」页 | 🟠 NEEDS WORK | 若 M4 产品路径含「多个 AI 产物存草稿再挑发」,则草稿列表是前置依赖,当前只能恢复最新一条、多草稿互相覆盖 |
experiment_exposed 客户端缺失 |
🟠 NEEDS WORK | 服务端字典已有,Flutter 零引用。若 M4 要做 A/B,需补客户端发射点 |
| 白名单数量无测试锁定 | 🟠 NEEDS WORK | 加一行 WHITELIST.size() 断言,防数字继续漂 |
6.4 流程与服务器侧
| 项 | 裁决 | 解除条件 |
|---|---|---|
| 必需状态检查上下文选型 | 🟠 NEEDS WORK(真实漏洞) | 改为 (pull_request) 或双上下文皆必需;用有权限 token 复核实际配置后更新 releases.md 表格。我侧 401 未能复核当前配置 |
| E2E 回归份数(前瞻指令) | 🟠 NEEDS WORK | releases.md:131 双份→四份;同步 checklist 正文;从「草案」固化进 git-workflow.md |
| doc 仓 main 口径表述 | 🟠 NEEDS WORK(低) | releases.md:128 加限定「api 与 flutter 两仓;doc 保持直推」。警告:若日后给 doc main 加保护并要求 (push),因工作流是 on: push: branches:[main] 会死锁 |
| 文档站访问控制 | 🟠 NEEDS WORK(建议本迭代闭环) | 加 basic auth 或 IP 白名单;把 server-exposure.md 该行状态从「待评估」改终态并填「归属决策」列 |
server-exposure.md 最后确认日期 |
🟠 NEEDS WORK(低) | 全表停在 09-11,但 v0.4.0 发布在 09-14。按该文档纪律 ②/⑤ 应更新 |
patbond-doc 缺 .gitignore |
🟠 NEEDS WORK(低) | 仓库根加 .gitignore 含 site/;当前仅靠 ~/.gitignore_global:183 屏蔽 |
7. 给 M4 的最小可信化建议
不改变 M4 的产品目标,只让它可被证明。按优先级:
- 先拍 AI provider 的板(路 A / 路 B),并写进 ADR。 这是唯一的阻断项,其他一切排期都依赖它。 若一周内拿不到真模型凭证,就走路 A —— fixture 路完全可以交付一个诚实的、全自动验证的 M4。
- 第一波先建后端 Worker 的自动化护栏,不碰客户端。 用 Testcontainers 覆盖:入队幂等、
SKIP LOCKED 并发领取、租约过期回收、重试退避、
max_attempts耗尽、五状态机流转。 这些零外部依赖、零真机、可进 CI,是 M4 唯一能靠自动化门禁守住的部分,应当先做厚。 - 顺手修两个近乎零成本的历史遗留:
media.assets尺寸写入(Worker 本来就知道), 以及白名单数量断言。都是一次改动换一个永久防漂。 - 拍板
platform枚举。 加linux/desktop能一次解掉横跨 M2/M3/M3.5/M4 的 「事件落库只能真机验」死结,收益跨四个迭代,成本是一行契约 + 快照同步。 - 本迭代内清掉 M2 两项真机验证(09-21 时限,模拟器 1 小时即可),别让北极星首批读数标「未验收」。
- 修掉发布流程的两处(P5 门禁上下文、P8 双份/四份),否则 M4 发布会重复本次的漏洞。
- 不要引入 Redis/MQ。 正典已否决其必要性;引入等于凭空多一个容器、一套运维面、一份 ADR。
附:本报告的取证边界
已亲自取证(命令 + 输出 / 文件 + 行号,均在正文内):三仓 HEAD 与 tag 与洁净度、
mvnw clean test 全量实跑(381)、flutter test 全量实跑(597 + 2 skip)、
openapi 32/45/75 与五处 md5、Flyway V1~V5 与 V5:48、compose 6 服务、
EventDictionary 41 条、ADR 22 条、mkdocs build --strict、
Gitea 五个 CI 上下文实时状态、platform enum、eventVersion 全链路、
429 双零命中、widthPx 写路径缺失、S3 调用面全集、.env/init-secrets.sh 密钥面、
purpose 白名单、收藏/草稿 UI 缺失与死代码、E2E 场景自报计数、
Android SDK/AVD/KVM 可用性、device-verification.md 10 项、releases.md 内部矛盾、
server-exposure.md 登记项、patbond-doc 无 .gitignore 的成因。
未取证(明确列出缺什么):
| 项 | 缺什么 |
|---|---|
| E2E 234 断言精确值 | 机械可数 226(210 check( + 16 ✗)。差 8 处需人工核对 assertMeShape 类辅助函数的内部断言计法 |
| E2E 四份零失败重跑 | 需 compose 六容器起栈 + 串行跑约数十分钟。本次未重跑,仅采信 08 号报告 |
| 契约矩阵 181 格 | 无机械可数来源;代码内唯一规模断言是 operations()==45。需人工按 iteration-3.5/04 §3 表逐格复核 |
| 零迁移在存量库上实证 | 需起 compose 于既有 pgdata volume 观察 Flyway 日志。未执行 |
check-secrets.sh --all |
未执行(三仓) |
main 分支保护实际配置 |
GET /branch_protections 返回 401,无 token。文档登记值未能复核 —— P5 修正前必须先用有权限 token 核实 |
| 服务器侧实际暴露面 | 未登录服务器执行 ss -tlnp;文档站实际域名与是否真无鉴权未探测(仅确认 git.patbond.cn 返回 200) |
| 跨 schema 外键保留/裁剪定型 | 需 API/DBA 角色出定型表,非本报告职责 |
未修改任何生产代码;未改 mkdocs.yml;未执行任何 git commit / push。
mkdocs build --strict 重建了被全局 gitignore 屏蔽的 patbond-doc/site/,三仓 git status 仍为空。