验收官:波波 | 时间: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」。
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 键 ⇒ 所有步骤判「缺少证据」。
这是输入形态不匹配,与功能无关。
$ 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 拦得住)。
把 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 全过。
| 步骤 | 证据文件 | 断言 | 实测 | 判定 |
|---|---|---|---|---|
| S4 治理发布 | publish.json | ok:true + data.version 为 int | {"ok":true,"data":{"version":2}} req_id=28c622650fc1 | ✅ |
| S4c 发布后 C 端生效 | site.json | data.brandName = 快照值 | 演示实例(335 演练)v2 req_id=2a1cb6aa3076 | ✅ |
| S5 治理回退 | rollback.json L1 | toVersion(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。
| 缺 | 现象 | 定性 |
|---|---|---|
S1 GET /admin/config/current | 02-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,见 §五) |
rollback.json 非标准单回包:三行 shell 输出粘连,第 1 行尾拼接了 echo 裸串。建议重跑为独立文件。run_2.3_site.sh 的产出 site.summary.txt 与 findings.txt 缺失(find evidence/335/02-config 仅 4 个 json/txt)。 该脚本第 3 步内嵌判据为 brandName == "t_demo 演练租户",实测值是 演示实例(335 演练)v2 ⇒ 脚本会在第 3 步 exit 5,第 4 步 summary 不生成。
⇒ 与实际执行痕迹吻合:执行方是手工 curl 取回包,不是脚本跑通。README 的 ✅ 是人工改判,非脚本产出。
需 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。
触发:R1 在 02-config/README.md 末尾的裁决输入=「建议收敛 publish 唯一出口(PUT 保留草稿语义)」。
| 路径 | 文件 | SQL 落点 |
|---|---|---|
GET /api/miniapp/config/site | miniapp_site.py:48-86 | SELECT key,value FROM site_config → conf["brand"] |
PUT /api/miniapp/config/site/{key} | miniapp_site.py:87-117 | INSERT INTO site_config ... ON CONFLICT(key) DO UPDATE(写生产读源行) |
| publish / rollback | miniapp_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 路径不含任何版本化逻辑。
site_config 生产读源行,⇒ PUT 直写即时对 C 端生效,它不是草稿。 要让它变成草稿,必须改代码(PUT 改存草稿区 / 或强制产出草稿版本),
不能只靠剧本一句话定稿 —— 否则剧本与实现对不上,下次演练照样踩。
```
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 双路并存未冲突」只因为没在同一时间窗做交叉操作,属未测到的场景,不是证无。
建议号双轨登记:①剧本 2.1-2.2 定稿措辞;②研发项:PUT 落点改造(避免审计失真)。
| 选项 | 做法 | 代价 | 我的意见 |
|---|---|---|---|
| A | PUT 只写草稿表,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-01 | fields-input.json 组装输入(含 provenance) |
| 补充编号 V-371-02 | check_332_fields.py --selftest PASS 反证 |
| 补充编号 V-371-03 | 补齐后回填重跑,目标 exit 0 |
不具备销项条件:在 S1/S2/S3 回包落盘前,任何人不得把 2.5 记作 PASS。
-- 署名:波波(验收官)· 2026-09-16 23:47