23-23-token-pointstatus.md 9.5 KB

23: 前端执行票 —— 地图快照分页 token 与 pointStatus 八桶契约对齐

进度

已关闭。两个曾被 #29 阻塞的验收项(状态栏/饼图真实八桶计数、多选筛选后点数变化)已由后端票 29 在同一份真实运行环境(dataSource=real → http://192.168.110.96:8301)上以 real-probe-new-runtime.json(45/45)与 browser-verification.md 收口:八桶 EMPTY=6、UNASSOCIATED=7 其余 0,状态栏与饼图同源;筛选 13→1(ALARM)→2(ALARM,WARNING),全局计数不变;BOGUS/ALARM,BOGUS 两端 HTTP 400 INVALID_REQUEST;分页 token 保留(map-page2-token-retained)。本会话在不重复触发构建/浏览器的前提下复核:① 前端源码未变(仅 domain/mapSummary.js:toStatusDistribution 八桶完整性 + 装载器缺 token 停住 / 第 2 页起必带锁定 snapshotAt / SNAPSHOT_EXPIRED 保留旧对象);② filterOptions.js 的 statusFilterQuery 白名单去重、丢弃 BOGUS,保证「不把脏值发给后端」;③ 重跑 t23-map-contract 11 项断言全过(token 传递与转义、八桶完整性、脏值剔除、缺 token 停住且保留对象、快照失效不归零)。按项目规则本会话未自动运行 build/dev/browser;真实浏览器验收未在本会话重跑,其在 #29 会话中的记录结果判定有效。

来源

票 18 真实联调与浏览器取证(2026-10-10)。

现象

  1. 分页快照 token 丢失:浏览器实测出现 GET /map-snapshot?page=2&pageSize=2(不带 snapshotAt),后端按契约返回 400 SNAPSHOT_EXPIRED,地图随即进入「快照已失效 · 请刷新」与 0 点位。首屏 GET /map-snapshot?page=1&pageSize=2 是正常签发 snapshotAt 的。
  2. 状态筛选契约不一致:真实 map-snapshot 响应没有 pointStatusDistribution,mapFilter.pointStatus 也不回显;传 pointStatus=ALARM 与不传返回完全相同的 total=13(入参被忽略)。UNKNOWN 视为非法值返回 INVALID_REQUEST;/monitoring/distribution 对 UNKNOWN 与 UNASSOCIATED 均返回 400。非法值 BOGUS 在 map-snapshot 上返回 200 而不是 400(与前端白名单预期不一致)。
  3. 界面表现(真人走查第 1 项):状态栏八个计数全部显示 —,分布面板提示「全局状态分布未取到(后端尚未发布八桶全局计数)」。

范围

  1. 前端分页:第 2 页起必须携带首屏锁定的 snapshotAt;快照失效按既有恢复纪律处理,不把整张图清空成 0 点位。
  2. 与后端对齐八桶状态契约:六态 + UNKNOWN + UNASSOCIATED,多值逗号分隔、OR;map-snapshot 与 /monitoring/distribution 两端行为一致;非法值两侧一致拒绝。
  3. 状态栏与分布面板在未取到全局计数时如实降级,取到后共用同一份数据(同源,决策 02 已冻结)。

验收

浏览器实测:地图自动分批加载到完成且不出现快照失效;状态栏与分布面板显示真实八桶计数;多选状态筛选后地图点数随之变化;后端返回 INVALID_REQUEST 时前端不把脏值发出去。探针 map-eight-status-buckets、status-*、invalid-* 相关检查项全部通过。

关联

发现于票 18;承接决策票 02 与规格 07。

Answer

诊断结论(2026-10-10 复核,两项现象归属不同)

① 分页 token:前端已正确传递,历史故障不再复现;本次仅补防护与回归。

真实只读探针 [t23-real-probe](../verify/t23-real-probe.mjs)(http://192.168.110.96:8301,仅 GET)实测:

检查 实测 判定
第 1 页签发 token snapshotAt=2026-10-10T17:18:53.1160157+08:00,total=13 通过
第 2 页带 token HTTP 200 / code=200 通过
第 2 页不带 token HTTP 400 / category=SNAPSHOT_EXPIRED 后端规则通过
前端装载器是否漏带 createMapSummaryLoader 第 2 页起必带 state.snapshot(domain/mapSummary.js) 票 18 第 3 点「上次故障未复现」属实

因此原现象 1 属历史已消失的故障,不能当成仍存在的缺陷;但缺 token 时前端会带着空 token 继续翻页这一点没有防护,本轮已补。

② 八桶契约缺口在服务端,前端无法单方面交付。

真实实测 结果 与前端契约的差距
map-snapshot 响应含 pointStatusDistribution 无 状态栏与饼图只能如实降级(决策 02 已允许 available:false)
mapFilter 回显键 industry/district/districtName/districtFilter/deviceType/pointCodes/deviceCodes 无 pointStatus
pointStatus=ALARM 与不传 total 均为 13 入参被完全忽略,筛选不生效
map-snapshot?pointStatus=UNKNOWN HTTP 200 规格要求可接受,但实为忽略而非匹配
map-snapshot?pointStatus=UNASSOCIATED HTTP 200 同上
map-snapshot?pointStatus=BOGUS HTTP 200 违反规格 07「认不出的值 400 INVALID_REQUEST」
/monitoring/distribution?pointStatus=UNKNOWN HTTP 400 该端点只认六态单值

服务端源码复核一致:CockpitMapSnapshotResponse 无 pointStatus 入参与 MapFilter.pointStatus(mapFilter 六个键);CockpitMonitoringDistributionService.resolvePointStatus 只认六态单值。前端已做的白名单过滤(isKnownPointStatusBucket)保证不会把脏值发出去,但真实环境「不传脏值」与「后端拒绝脏值」两件事无法由前端同时成立。

本轮前端交付

  1. 缺 token 不再翻页:装载器在 nextPage > 1 且无锁定 snapshotAt 时进入失败态并停住(reason='snapshot-token-missing'),不发无效分页请求,也不清空已加载对象。
  2. 八桶计数完整性校验:toStatusDistribution 由「有数组即算已发布」改为八桶齐全且计数为非负整数才 available:true;缺桶、空数组、重复桶、非法桶、非整数或负数一律 available:false。这消除了「部分桶被当作真实零值」的风险——正是状态栏显示 — 的场景里最容易出现的口径造假。
  3. 可复跑回归 [t23-map-contract](../verify/t23-map-contract.mjs):首屏不带 token、第 2 页必带(经真实 transport 拼串断言,含 +///: 这类需要转义的 token)、BOGUS 被白名单剔除、缺桶/重复桶/负数不被接受、缺 token 停住且保留已加载对象、SNAPSHOT_EXPIRED 保留旧对象不归零。

后端边界(按维护者裁定:本票不改后端仓库)

八桶的入参、回显、全局计数与非法值拒绝,全部落在 E:\pipe-ner-server 的响应类与服务类。前端无法在不伪造的前提下让界面显示真实八桶计数——本地过滤代替后端筛选已被本 map 的 Out of scope 明文禁止。因此真实八桶验收项由新开的后端执行票承接,前端验收只到「如实降级 + 不发脏值 + token 正确」。

本票在真实八桶计数落地前不关闭。

授权的真实浏览器验收(2026-10-10)

环境 http://localhost:8001,dataSource: real(本机 gitignored public/cockpit/config.json 指向 http://192.168.110.96:8301/api/cockpit/v1);捕获 d57c3eed5ebd1,440 请求、0 错误。

验收项 实测 判定
自动分批到完成 5 个刷新周期,每周期 page=1(不带 token)→ page=2…7(全部带 snapshotAt),全部 HTTP 200 通过
token 转义正确 snapshotAt=2026-10-10T17%3A38%3A15.997194%2B08%3A00(:→%3A、+→%2B) 通过
不出现快照失效 界面「地图摘要加载完成 · 已上图 0 个 · 无地图位置 13 个」;全程无 SNAPSHOT_EXPIRED、无 expired 态 通过
状态栏与饼图同源降级 八桶计数全部显示 —(未取到),未把缺失桶画成 0 通过
多选状态筛选后点数变化 无法验证:pointStatus 被服务端忽略,ALARM 与不传均 total=13 受阻于 29
不把脏值发给后端 契约回归断言 BOGUS 被白名单剔除(真实环境服务端对 BOGUS 回 200,前端侧仍不发) 通过

同屏真实读数印证接线正常:设施底数 5276、管线里程 67.60944 km、行业数 4(燃气/供水/排水/窨井)、片区「未分区」5278。

回归与零回归证明

  • 新增 [t23-map-contract](../verify/t23-map-contract.mjs):11 项断言通过(token 传递与转义、八桶完整性、BOGUS 剔除、缺 token 停住且保留已加载对象、SNAPSHOT_EXPIRED 不归零)。
  • 本轮源码只改 domain/mapSummary.js(+19/−5)。
  • 全量逻辑闸门 50 个脚本:41 通过、9 失败;把这一个文件的改动 git stash 后重跑,失败集合完全相同(缺 puppeteer-core 模块、票 15 遗留的 MANAGEMENT_REGION_GAP 断言等),即本轮零回归。
  • 关键脚本:t08 逻辑 44/44、t15 11/11、t16、t23 全通过。

下一步

等待 29 落地服务端八桶契约后,重跑 [t23-real-probe](../verify/t23-real-probe.mjs) 与浏览器验收,补「状态栏/饼图显示真实八桶计数」与「多选筛选后点数变化」两项,再关闭本票。

Status: resolved Assignee: codex Type: task Blocked by: #29 Labels: ready-for-agent, frontend-execution, wayfinder:task

Comments

Answer