WIP-371 · 335 第二幕 2.5 字段级终判 —— 验收裁定书 v1

验收官:波波 | 时间:2026-09-16 23:47 | commit:fe2f4c2d | host:192.168.1.7
命令源:docs/plans/335-drill-playbook.md §2 / 2.5(v1.6.1)
判据器:scripts/check_332_fields.py(本人 2026-09-14 efb99df3 编制)
四态真源:剧本 §7;证据五要素真源:剧本 §8

〇、一句话结论

2.5 字段级终判 = BLOCKED-证据不足(不是 FAIL)。

理由一句话:check_332_fields.py 报的 3 条 ERROR 全部且仅是「缺少证据」,零条「值不匹配」——

即 没有任何一条字段级断言被真值推翻,是证据链缺 3 处回包,不是功能缺陷。

按剧本 §7 四态定义「环境/依赖/证据未齐 ⇒ BLOCKED,不得冒充 PASS,也不得降为 FAIL」。


一、执行记录(可复现)

1.1 按指令原样直跑 site.json(假 FAIL,先自曝)

$ python3 scripts/check_332_fields.py docs/delivery/evidence/335/02-config/site.json
332 字段级联调验收 —— 证据 site.json
  [WARN]  S5c 缺回退后 C 端证据(建议补:应等于目标版本快照的 brandName)
  [ERROR] S1 缺少证据(GET /admin/config/current)
  [ERROR] S2 缺少证据(PUT /config/site/brand)
  [ERROR] S3 缺少证据(匿名 GET /config/site)
  [ERROR] S4 缺少证据(POST /admin/config/publish)
  [ERROR] S4c 缺少证据(publish 后再读 C 端——本轮修复的正是这一环)
  [ERROR] S5 缺少证据(POST /admin/config/rollback)
  判定:FAIL(ERROR 6 / WARN 1)
EXIT=1

这个 FAIL 是假的,不能拿来判"第二幕不成立"。 原因:判据器契约要求 steps{S1..S5c,N1,N2} 聚合结构,

而 site.json 是一次 GET 的原始 HTTP 回包,没有 steps 键 ⇒ 所有步骤判「缺少证据」。

这是输入形态不匹配,与功能无关。

1.2 判据器反证(证明不是脚本坏了)

$ python3 scripts/check_332_fields.py --selftest
  ideal  → ERROR 0 / WARN 0(应 0/0)
  broken → ERROR 4 / WARN 0(应 ≥4)
      [ERROR] S1 data.version 应为 int(不是 versionId/version_id/current.id),实得 None
      [ERROR] S3 C 端 data.brandName=None ≠ 期望 '神山秘境-L3测试'
      [ERROR] S4 themePrimary 必填且须 #RRGGBB,实得 'red'
      [ERROR] S5 请求字段应为 toVersion(int≥1),实得 None
  自检判定:PASS
EXIT=0

判据器自身有效(ideal 过、broken 拦得住)。

1.3 如实组装后重跑(关键)

把 02-config/ 下磁盘上真实存在的原始回包装配成契约结构 → docs/delivery/evidence/335/02-config/fields-input.json

(内置 _provenance 逐项标注来源;缺失步骤显式留空,不代补)。

$ python3 scripts/check_332_fields.py docs/delivery/evidence/335/02-config/fields-input.json
332 字段级联调验收 —— 证据 fields-input.json
  [ERROR] S1 缺少证据(GET /admin/config/current)
  [ERROR] S2 缺少证据(PUT /config/site/brand)
  [ERROR] S3 缺少证据(匿名 GET /config/site)
  判定:FAIL(ERROR 3 / WARN 0)
EXIT=1

分野在此:ERROR 从 6 降到 3,而剩余 3 条全部是"缺回包",且 S4 / S4c / S5 / S5c 四项零 ERROR 全过。


二、已有回包部分的实测判定:4/4 真 PASS

步骤证据文件断言实测判定
S4 治理发布publish.jsonok:true + data.version 为 int{"ok":true,"data":{"version":2}} req_id=28c622650fc1✅
S4c 发布后 C 端生效site.jsondata.brandName = 快照值演示实例(335 演练)v2 req_id=2a1cb6aa3076✅
S5 治理回退rollback.json L1toVersion(int) + 回退生成前向新版本(version > toVersion)toVersion=1 → version=3 req_id=d961fd6ff374✅
S5c 回退后读源rollback.json 尾裸串回到目标版本值brandName=演示实例(335 演练)(v1 值,非 v2)✅

这条链是 332 的核心修复点(_sync_brand 断点),四步实测全通 —— 本轮第二幕最有价值的正向证据,不应被上面的 ERROR 数掩盖。

另:版本链自洽 —— publish→v2 / rollback(toVersion=1)→v3(前向版本化正确)/ 复原 publish→v4。


三、缺口清单(判 BLOCKED 的唯一原因)

缺现象定性
S1 GET /admin/config/current02-config/ 全目录零 HTTP 回包命中;texts.txt 只有一行脚本 stdout ✅ 品牌种子 v1 已发布脚本自述 ≠ HTTP 回包,不满足五要素
S2 PUT /api/miniapp/config/site/brand全目录零回包缺失 ⇒ _v0 基线未知,version +1 链式断言无法验证
S3 publish 之前匿名读源site.json 时序在 publish(v2) 之后,语义属 S4c 不属 S3缺对照组 ⇒ 无法区分"publish 生效"与"PUT 直写就生效"(此缺口同时卡死 Q-B,见 §五)

附带瑕疵(不阻断,须登记)

该脚本第 3 步内嵌判据为 brandName == "t_demo 演练租户",实测值是 演示实例(335 演练)v2 ⇒ 脚本会在第 3 步 exit 5,第 4 步 summary 不生成。

⇒ 与实际执行痕迹吻合:执行方是手工 curl 取回包,不是脚本跑通。README 的 ✅ 是人工改判,非脚本产出。


四、最小补齐集(补完即可判 PASS,估 5 分钟内)

需 stdout+回包原文同时落盘(满足五要素:命令/回显/时间戳/host/request_id)。

脚本已置于 scripts/run_371_fields_gap.sh,由可达 staging 的机位执行(本工位不可达 100.64.1.230:8102,实测 timeout 124,不伪造回显):

# 0) 健康闸(沿用 1.11)
curl -s -o /dev/null -w '%{http_code}\n' --max-time 10 "$BASE/health"      # 须 200

# 1) S1 治理基线(admin token)
curl -s -w '\nHTTP=%{http_code}\n' -H "Authorization: Bearer $TOK" \
     "$BASE/api/miniapp/admin/config/current"            → s1.json

# 2) S3 publish 前匿名读源(对照组 —— 无 token)
curl -s -w '\nHTTP=%{http_code}\n' "$BASE/api/miniapp/config/site"  → s3.json

# 3) S2 328 直写(staff|admin token)
curl -s -X PUT -w '\nHTTP=%{http_code}\n' -H "Authorization: Bearer $TOK" \
     -H 'Content-Type: application/json' \
     -d '{"value":{"brandName":"371-字段级终判-X","slogan":"<旧的slogan>"}}' \
     "$BASE/api/miniapp/config/site/brand"               → s2.json

# 4) S3' 直写后再匿名读源(**Q-B 判定实验**,关键)
curl -s -w '\nHTTP=%{http_code}\n' "$BASE/api/miniapp/config/site"  → s3p.json

补齐后把 S1/S2/S3 回填 fields-input.json 重跑,预期 ERROR 0 / exit 0 ⇒ 2.5 判 PASS。


五、Q-B(PUT vs publish 收敛)裁决输入 —— 验收官独立意见

触发:R1 在 02-config/README.md 末尾的裁决输入=「建议收敛 publish 唯一出口(PUT 保留草稿语义)」。

静态核证事实(不看报告,只认代码)

路径文件SQL 落点
GET /api/miniapp/config/siteminiapp_site.py:48-86SELECT key,value FROM site_config → conf["brand"]
PUT /api/miniapp/config/site/{key}miniapp_site.py:87-117INSERT INTO site_config ... ON CONFLICT(key) DO UPDATE(写生产读源行)
publish / rollbackminiapp_config_governance.py:126 / :143 → _sync_brand (:68)INSERT INTO site_config (key='brand') ... ON CONFLICT DO UPDATE(同一张表同一行)

grep -rn "_sync_brand" 全仓仅命中 miniapp_config_governance.py 三处(定义+publish+rollback);

miniapp_site.py 的 PUT 路径不含任何版本化逻辑。

判据(对 R1 建议的直接回应)

  1. 「PUT 保留草稿语义」在当前代码语义下不成立:PUT 写的是 site_config 生产读源行,

⇒ PUT 直写即时对 C 端生效,它不是草稿。 要让它变成草稿,必须改代码(PUT 改存草稿区 / 或强制产出草稿版本),

不能只靠剧本一句话定稿 —— 否则剧本与实现对不上,下次演练照样踩。

  1. 双路并存是真风险,不是"未冲突":两条路写同一行,存在破坏版本一致性的场景 ——

```

publish v1 → site_config = A,versions{1:A}

PUT brand → X ⇒ site_config = X,但 versions 最新仍是 {1:A}(无痕)

此时 rollback toVersion=1 ⇒ _sync_brand(A) 覆盖 ⇒ X 静默丢失

```

反向也失真:不回退时 site_config=X,但版本历史里查不到 X ⇒ 审计链路断裂。

README 写「实测 PUT 与 publish 双路并存未冲突」只因为没在同一时间窗做交叉操作,属未测到的场景,不是证无。

  1. 收敛方向认可,处置等级要上调:支持「publish 为唯一出口」,但这是代码级收敛项(需改 PUT 落点),

建议号双轨登记:①剧本 2.1-2.2 定稿措辞;②研发项:PUT 落点改造(避免审计失真)。

终裁建议(供 R1 拍板)

选项做法代价我的意见
APUT 只写草稿表,publish 唯一出口需改造 PUT + 前端改用 publish✅ 推荐(与 R1 方向一致,且真正闭环)
B保留双路,PUT 直写并强制生成版本需改 PUT 调 _insert_version次选:不打断现有前端,但版本表会变吵
C维持现状 + 剧本标注"勿交叉操作"零改造❌ 不推荐:靠人守纪律,审计仍会断

六、裁定

项判定
2.5 字段级终判BLOCKED-证据不足(缺 S1/S2/S3 三处 HTTP 回包;已有 S4/S4c/S5/S5c 四项全 PASS)
第二幕整体不成立待补(不等于第二幕失敗;其余 2.1/2.2/2.3/2.3b/2.4/2.6 已由各自席位 PASS,2.5 补齐即可收口)
补充编号 V-371-01fields-input.json 组装输入(含 provenance)
补充编号 V-371-02check_332_fields.py --selftest PASS 反证
补充编号 V-371-03补齐后回填重跑,目标 exit 0

不具备销项条件:在 S1/S2/S3 回包落盘前,任何人不得把 2.5 记作 PASS。

-- 署名:波波(验收官)· 2026-09-16 23:47