fix(web): 空/非空/不可读生产目标分开呈现 #15

Open
zheromiku wants to merge 5 commits from fix/library-empty-target-ui into test/publish-http-single-scenario
Owner

F1 修复(本包唯一范围):没有读到目标观测时不再误报"目标非空"

  • 修复:web/src/ReleaseTargets.tsx —— overview 缺失时 production 保持 null,分类走 no-observation;
    四类有效响应(known / 已读且为空 / 非空无版本 / 不可读)的判定与建基线入口不变;
    刷新失败但保留旧观测时标"上次观测 · 刷新失败,可能已过期"。
  • 测试:ReleaseTargets.test.tsx 复用审核者三个真实 SSR 反例 + 旧观测标记;
    新增 ReleaseTargets.mount.test.tsx(同一挂载 + 可控 fetch:首次读取未返回 / 空→不可读→恢复 /
    刷新失败→恢复 / 内外切换 / overview 属于别的范围)。撤掉"三次 SSR 当刷新"的用例。
  • 依赖:新增 jsdom 30.1.3(仅 vitest 的 DOM 环境;npm ci 与 lock 已核,manifests-locked 通过)。
  • 后端零改动:git diff 6827fa9..47be4ae -- internal cmd 为空。
  • 本地门禁 16 项 success(tests-web 504 passed,tsc -b 无错);CI run 323 / job 338 success,
    head_sha=47be4ae502b728222b219aeb1d630ad273690eb5,identity=success。
  • 证据:~/.hermes/handoffs/develop-release-contract/empty-target-no-observation-fix.md / .json;
    RED(旧实现 5 failed / 变异 3 failed)与 GREEN 42/42 的日志都在同目录。
  • 边界:未改 F2(历史指纹语义)、未登录、未改候选库/进程、未部署 8088、未合并。
F1 修复(本包唯一范围):没有读到目标观测时不再误报"目标非空" - 修复:`web/src/ReleaseTargets.tsx` —— overview 缺失时 production 保持 null,分类走 `no-observation`; 四类有效响应(known / 已读且为空 / 非空无版本 / 不可读)的判定与建基线入口不变; 刷新失败但保留旧观测时标"上次观测 · 刷新失败,可能已过期"。 - 测试:`ReleaseTargets.test.tsx` 复用审核者三个真实 SSR 反例 + 旧观测标记; 新增 `ReleaseTargets.mount.test.tsx`(同一挂载 + 可控 fetch:首次读取未返回 / 空→不可读→恢复 / 刷新失败→恢复 / 内外切换 / overview 属于别的范围)。撤掉"三次 SSR 当刷新"的用例。 - 依赖:新增 jsdom 30.1.3(仅 vitest 的 DOM 环境;`npm ci` 与 lock 已核,manifests-locked 通过)。 - 后端零改动:`git diff 6827fa9..47be4ae -- internal cmd` 为空。 - 本地门禁 16 项 success(tests-web 504 passed,tsc -b 无错);CI run 323 / job 338 success, head_sha=47be4ae502b728222b219aeb1d630ad273690eb5,identity=success。 - 证据:`~/.hermes/handoffs/develop-release-contract/empty-target-no-observation-fix.md` / `.json`; RED(旧实现 5 failed / 变异 3 failed)与 GREEN 42/42 的日志都在同目录。 - 边界:未改 F2(历史指纹语义)、未登录、未改候选库/进程、未部署 8088、未合并。
zheromiku added 1 commit 2026-11-02 10:56:45 +08:00
后端 overview 早已区分 known / unknown+empty_baseline / unknown 非空 / unreachable,
页面只判断 state === 'known',把"已证空"和"读不到"都写成"未知",并在两种情况下
都给出"建立目标权威基线"入口——而该接口对空目标返回 409
library_baseline_target_empty、对读不到的目标返回 503 library_baseline_unprobeable。
按后端真实契约做最小分类:

- LibraryKnown 补 empty_baseline 与 unreachable 两个已存在的字段/状态;
- 页面按四类分别呈现:known 显示生效版本+快照摘要;空目标写明"已读且为空/首次发布/
  写前仍生成完整预备份";非空缺权威版本才指向基线入口;不可读照实说读取失败;
- baselineNeeded 从"非 known"收窄为"非空且无权威版本",不动授权、写闸、预备份、
  容量与 worker 执行准入等任何后端检查,也不放宽后端的 409。

行为测试新增四类状态、空→不可读→空刷新不残留旧状态、空目标无候选时按钮禁用;
原"非 known 一律未知"的断言随文案更正。本地门禁 16 项 success
(tests-web 498 passed,tsc -b 无错)。
zheromiku added 2 commits 2026-11-02 11:04:12 +08:00
父要在真实浏览器里点"签发授权 → 启动同步",需要一个目标确实为空、来源确实存在
的候选环境。两个 test-only 入口(默认 skip,已登记 scripts/test-scope.json 的可选范围):

- TestCandidateEmptyTargetFixture:用已有 DS02/r38 夹具装配十表全非空的独立 fullshape
  测试来源并封存,登记为 test_sealed;同时用产品自己的 ScopeContentEmpty 证明目标范围
  为空(KB/tenant 骨架在、十表内容 0 行),并另建同 mapping 的运行期索引(夹具索引会在
  测试结束时删除)。目标指纹用夹具真实桶名重算,保证与 API/worker 同一份 env 一致。
- TestCandidateAttestation:签发/续签 object-gateway 审计(有效期上限 1 小时),
  四个证据摘要绑真实文件(写入者清单、只读策略、reader 拒写探测、真实排空记录)。

不改产品代码、不放宽任何准入;来源明确标 fixture-assembled-legal-fullshape,
不冒称原 C01 两份封存或 Capture/Export HTTP。
上一笔把 scripts/test-scope.json 整体重排成缩进格式,diff 变成 196 行噪音。
这里恢复文件原本的一行一条格式,只保留新增的 TestCandidateEmptyTargetFixture /
TestCandidateAttestation 两条;内容(112 条可选)未变。
zheromiku added 1 commit 2026-11-02 11:12:29 +08:00
首轮试跑暴露三处夹具缺陷(都在 test-only 文件里,产品代码未动):
- 目标骨架只核 knowledge_bases:最小夹具 schema 没有 tenants 表,改为按 KB 行数与
  产品 ScopeContentEmpty 的十表计数证空;
- 运行期索引曾把 GET _mapping 的结果再包一层 mappings,ES 400;
  现在直接回放原 mapping 文档(properties 层级与封存时完全一致);
- 目标指纹改在夹具分配桶名之后用同一份 env 计算,运行器再把桶名写进 API/worker env。

试跑两次均按 cleanup_empty_target_ui.py 精确清理并读回(resources 全不存在)。
zheromiku added 1 commit 2026-11-02 12:29:56 +08:00
根因:ReleaseTargetsView 在 overview 缺失时自己补 {state:'unknown'},而
productionTargetClass 把"unknown 且没有 empty_baseline"一律当成非空。后端对
"已探测且非空"发的正是这个形状(Go struct 上 omitempty),所以页面本地默认值
与真实观测在 JSON 上无法区分:首次加载、整库状态读取失败、当前范围没有数据
三条路径都会显示"目标非空,尚无权威版本",管理员还会拿到会返回 409 的
"建立目标权威基线"入口。

修法:没有 overview 时 production 保持 null,分类走新的 no-observation 分支;
四类有效响应(known / 已读且为空 / 非空无版本 / 不可读)的判定与入口不变。
刷新失败保留上一份 overview 时标注"上次观测 · 刷新失败,可能已过期",不把旧
结果当成刚验证的状态。后端 gate、权限、预备份与 API 拒绝一律未改。

测试:把旧"三次 SSR 当刷新"的用例撤掉,改为同一挂载 + 可控 fetch 的真实行为
测试(首次读取未返回 / 空→不可读→恢复 / 刷新失败保留旧观测 / 内外切换 /
overview 属于别的范围);缺观测的三条路径另以 SSR 负例复用审核者的反例。
新增 jsdom 仅作为 vitest 的 DOM 环境(商品代码零改动,npm ci 与锁文件已核)。

验证:make verify 16 项 success(tests-web 504 passed、tsc 无错、npm audit 0);
新用例先在原实现上红(断言收到"目标非空"),修后绿。未部署、未合并、未改
候选运行栈。
Some checks are pending
build / build (push) Successful in 1m46s
This pull request can be merged automatically.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/library-empty-target-ui:fix/library-empty-target-ui
git checkout fix/library-empty-target-ui
Sign in to join this conversation.