Files
patbond-doc/docs/development/iterations/iteration-4/04-reality-check.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

52 KiB
Raw Blame History

04 M4 开工现实核查(Reality Check

作者:Reality Checker 日期:2026-09-14 输入:三仓工作区实测(api 3cd8005 / flutter fbcd734 / doc 5cc6361,均 tag v0.4.0); docs/database/patbond_postgresql.sql 正典模型;device-verification.mdreleases.md server-exposure.md;Gitea API 实时提交状态。 同批 07 号(Evidence Collector)已独立清点基线数字,本报告复用其结论并不重复清点 本报告的增值面是 M4 规划的未证前提狙击跨文档一致性

总体裁决:NEEDS WORK。 基线代码质量与契约纪律确实扎实(32/45/75 契约、四模块字节级快照、 381 + 597 双绿、CI 三仓已恢复全绿),但 M4「AI 创作」的三个核心前提全部无证据支撑 无任何 AI 服务商 endpoint / 凭证 / 额度(.envinit-secrets.sh 共 4 个密钥,无一条与 AI 相关); 服务端完全不具备写对象存储的能力(全代码库 S3 调用仅 createBucket/headBucket/headObject/closeputObject、零 getObject),故「媒体链路可直接复用」只对读侧与客户端上传侧成立、 Worker 产出物落盘是净新增;且挂起的真机项已达 10 项(非 8 项),M4 若沿现有模式将再批量产出一批。 唯一的好消息是 Worker 队列不需要 Redis/MQ——正典模型已给出 Postgres FOR 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 测试全绿 3813+131+40+100+107),BUILD SUCCESSFailures 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.032 路径 / 45 操作 / 75 schema info.version=1.4.0paths 32、operations 45、schemas 75逐格相符
四模块字节级快照锁 md5 = a7081fb84f1207eef579ab94025f5801doc 正典 + auth/user/pet/community 四份快照五处完全相同
契约矩阵 181 格零漂移 181 这个数字只存在于报告散文iteration-3.5/04:96releases.md:39feature-checklist.md:242);代码里唯一被钉住的规模断言是 MediaContractConformanceTest.java:250assertThat(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);:95ix_posts_generation_job:47 注释明写「FK to creation.generation_jobs strippedM4 补回)」
六容器部署(含自托管 MinIO docker-compose.yml services = postgres, minio, user, auth, pet, community,恰 6
埋点事件白名单 42 条 41 条EventDictionary.javaMap.ofEntriesMap.entry 41 个,去重后仍 41 不对,见问题 P3
ADR-001~022 decisions.md 22 个 ## ADR-0xx 标题,无缺号无重号
E2E 四份脚本 42 场景 脚本自报计数相加恰好 42M1 [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 = 210M1 无 check(、用 16 处 卫语句 → 合计 226。差 8 处无法机械复现(疑为 assertMeShape 等辅助函数内部断言另计) ⚠️ 数量级可信、精确值未取证
mkdocs 可构建、导航无死链 mkdocs build --strict exit 0Documentation 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}/status2026-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 教训的同型复发,值得单独记录):

  1. 源头releases.md:121 写「真机验证四项挂起(媒体上传弱网、乐观更新手感、Feed 图片加载、社区事件落库);另有 M2 两项」= 登记 6 项整段漏掉 M3.5 的四项
  2. 我的开工任务书据此写「真机四项验证」(只剩 M3 的 4)。
  3. 协调者更正为「8 项 = M2 两项 + M3 四项 + M3.5 两项」——方向对了,但 M3.5 又少计 2。
  4. 实测 = 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_HOMEPATHplatform-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-218width_px integer, height_px integer MediaAssetResponse.java:18-19PostMediaItemResponse.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 上仅两条 UPDATEmarkReadySET status='ready', ready_at=now(), updated_at=now())与 markFailedSET status='failed'), 都不碰尺寸列

结论:生产链路上这两列永久为 NULL,没有任何代码能写入。 裁决 NEEDS WORK(确认遗留)

⚠️ 陷阱提示PostMediaAttachIntegrationTest.java:44-45 断言 widthPx==640 / heightPx==480 是绿的 因为 CommunityTestData.java:55 在测试夹具里直接 INSERT 了这两列。 测试绿 ≠ 生产有值 —— 这与 M3.5「无 nickname 字段」同型:断言测的是夹具,不是产品。任何规划不得据此认为该字段可用。

2.4 eventVersion 口径仍未定型 —— 且服务端根本不校验

  • 契约要求必填:openapi.yaml TrackedEvent.requiredeventVersion
  • 契约描述已过期openapi.yaml:2341-2343description: 事件 schema 版本(**字典 v1 全部为 1**)EventDictionary.java 类注释开头即 Event dictionary **v3**
  • 服务端零校验grep -rn "eventVersion" --include=*.javaEventDictionary.javaAnalyticsService.java 中命中 0 次。唯一处理是 TrackEventsRequest.java:38@NotNullAnalyticsRepository.java:26/48 的原样落库。
  • 库侧只有宽约束:V2__create_platform_product_events.sql:11 event_version smallint NOT NULL DEFAULT 1 :22 CHECK (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_snapshotprovider_model_snapshotmodel_version_snapshot:620-622)。 任何一次入队都必须填出这三个值 —— 接 fixture 也要填,这是契约级强制,不能含糊。

裁决:BLOCKED。

这直接击穿「本迭代端到端可验证」的说法。 两条路,必须现在拍板选一条,不能含混:

  • 路 A(推荐,可 CERTIFIEDM4 明确只做 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 具备队列所需全部列:statusqueued/running/succeeded/failed/cancelled)、 priorityattempt_count/max_attemptsnext_attempt_atlease_ownerlease_expires_atprogressversion

    • 两个专用偏索引: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 NULLsucceeded 必须 progress=100 AND output_asset_id IS NOT NULL)。 这是很强的资产:状态机由数据库强制,Worker 写错就报错

  • 调度侧也有现成先例:UserApplication.java:7 已有 @EnableScheduling SessionCleanupJob.java:32 已有一个 @Scheduled 任务在生产运行。轮询式 Worker 与它同构。

裁决:CERTIFIED(基础设施无需扩容)。 这是 M4 少有的真·已就位项。 明确建议不要引入 Redis/MQ —— 会凭空增加一个容器、一套运维面、一份 ADR,而正典设计已否决其必要性。

⚠️ 但两个未证的衍生点必须写进规划:

  1. lease_expires_at 的回收者不存在。 设计给了 ix_generation_jobs_running 索引, 却没有任何代码回收超租约的僵尸 job。这与已登记的「uploading 超时清理任务」是同型缺口 而那一项至今未做(releases.md:123 已登记)。M4 若不写回收器,running 的 job 崩了就永久卡死。
  2. 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-petpatbond-community 侧则只有 S3Presigner 做本地 SigV4 签名(MediaUrlSigner.java),连网络调用都没有。

复用面 结论 证据
预签名 GET 读取(帖图/头像展示) 真可复用 MediaUrlSigner.java 在 pet/community 两处已复制运行
客户端上传三段(createUpload → 直传 → complete 真可复用 MediaService.java + /media/uploads 两端点已在契约内
桶初始化 可复用 S3ObjectStorage.ensureBucket():66
服务端写对象(Worker 产出物落盘) 净新增,零基础 putObject
服务端读对象字节(把输入图喂给模型) 净新增,零基础 getObjectheadObject 只取元数据)
purpose 白名单 ⚠️ 需扩展 application.yml.sample:59 = post_image,user_avatar,pet_avatarMediaProperties.java:66 同值。无 AI 输入/输出用途
产物尺寸写入 media.assets 不可复用 见 §2.3insertUploading 无尺寸列、无 UPDATE 路径

裁决:NEEDS WORK(该表述必须在规划里改写)。

准确表述应为:「媒体的读链路与客户端上传链路可直接复用;服务端读写对象字节的能力为零,是 M4 的净新增工作。」

好消息是 purpose 扩展很便宜 —— MediaProperties.java:61 注释明写该白名单是 「configuration + contract-enum change, never a migration」,即改配置 + 改契约枚举即可,不需要迁移。

⚠️ 一个具体的下游矛盾generation_jobs 自己存了 width_px/height_px:616-617CHECK … 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:53 status 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_pickerflutter_image_compress 确实已实现media_picking.dart:28 SystemMediaImagePickermedia_compression.dart:42 FlutterImageCompress.compressWithList)—— 但 media_uploader.dart:570-571app.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 内做,不必等真机):

  1. Worker 状态机做纯后端集成测试Testcontainers + fixture provider)—— 入队、SKIP LOCKED 并发领取、租约过期回收、重试退避、max_attempts 耗尽转 failed、幂等键去重。 这些全部不需要客户端、不需要真模型、不需要真机,可 100% 进 mvnw test 门禁。这是 M4 最该先建的护栏。
  2. platform 枚举扩容拍板:若想让桌面参与埋点验证,就在契约 enumlinux(或加通用 desktop)。 这是一行契约改动 + 快照同步,能一次性解掉 M2/M3/M3.5 遗留下来的「事件落库只能真机验」死结, 收益跨三个迭代。若决定不加,则必须承认 M4 的埋点验证同样挂起,并写进清单。
  3. 入队/轮询/取消的契约面进 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_exposedEventDictionary.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 必然需要 V6creation 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/ 实测返回 200nginx 在服;文档站按 sites-enabled 另一域名分流,我未探测其域名, 未取证:文档站实际 URL 与是否真的无鉴权)。
  • §1 端口表「最后确认」全部停在 2026-09-11。而 server-exposure.md 自订纪律 ② 是「每次迭代收官核对一遍,更新『最后确认』」,M3.5 收官与 v0.4.0 发布均发生在 09-14 该列未更新。按同文档纪律 ⑤「本页与实际不符即为缺陷」,这本身是一处待补。

我的评估与建议(只取证与建议,不动服务器)

风险等级判,倾向「应当加访问控制」,理由是三条已发生的事实叠加:

  1. 文档站内容包含 server-exposure.md 本身 —— 即一份完整的对外端口清单与常驻服务清单 还包含 ci-runner-setup.mdCI 拓扑)、decisions.md(全部 22 条架构决策)、 数据库正典 SQL(全表结构与约束)。这是一份给攻击者的现成侦察报告
  2. 本项目刚刚发生过一次真实入侵尝试(iteration-3.5/07Gitea gitconfig 注入), server-exposure.md 的缘起就是「Nacos 在 ADR-002 移除后仍暴露公网近两个月」—— 说明本环境的暴露面治理确实曾经失守过,不是理论风险。 3 该站与 Gitea 同一台机器、同一个 nginxsites-enabled/ 同级分流)。文档站的任何 nginx 配置失误 与代码托管共享爆炸半径。

建议:加 IP 白名单或 basic auth(二者皆可,basic auth 更省事),并把结论写成 ADR 或在 server-exposure.md 的「归属决策」列填上,把状态从「待评估」改成终态。 成本约十分钟 nginx 配置,不应再挂第四个迭代

⚠️ 但需注意一条执行顺序约束:若加 basic authmkdocs 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=42EventDictionary.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:35feature-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.gitignoresite/ 仅靠全局 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:48REFERENCESV5: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 01.71s
CI 三仓门禁 CERTIFIED 五个上下文全 success09-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 必然需要 V6creation 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 WORKM4 前必须定) 定「版本号随字典版本」还是「随单事件 schema」;同步改契约描述(现称「字典 v1 全部为 1」);补服务端校验
429 限流 🟠 NEEDS WORKM4 强相关) 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(低) 仓库根加 .gitignoresite/;当前仅靠 ~/.gitignore_global:183 屏蔽

7. 给 M4 的最小可信化建议

不改变 M4 的产品目标,只让它可被证明。按优先级:

  1. 先拍 AI provider 的板(路 A / 路 B),并写进 ADR。 这是唯一的阻断项,其他一切排期都依赖它。 若一周内拿不到真模型凭证,就走路 A —— fixture 路完全可以交付一个诚实的、全自动验证的 M4
  2. 第一波先建后端 Worker 的自动化护栏,不碰客户端。 用 Testcontainers 覆盖:入队幂等、 SKIP LOCKED 并发领取、租约过期回收、重试退避、max_attempts 耗尽、五状态机流转。 这些零外部依赖、零真机、可进 CI,是 M4 唯一能靠自动化门禁守住的部分,应当先做厚。
  3. 顺手修两个近乎零成本的历史遗留media.assets 尺寸写入(Worker 本来就知道), 以及白名单数量断言。都是一次改动换一个永久防漂。
  4. 拍板 platform 枚举。linux/desktop 能一次解掉横跨 M2/M3/M3.5/M4 的 「事件落库只能真机验」死结,收益跨四个迭代,成本是一行契约 + 快照同步。
  5. 本迭代内清掉 M2 两项真机验证(09-21 时限,模拟器 1 小时即可),别让北极星首批读数标「未验收」。
  6. 修掉发布流程的两处(P5 门禁上下文、P8 双份/四份),否则 M4 发布会重复本次的漏洞。
  7. 不要引入 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 --strictGitea 五个 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 断言精确值 机械可数 226210 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 / pushmkdocs build --strict 重建了被全局 gitignore 屏蔽的 patbond-doc/site/,三仓 git status 仍为空。