# 22: 前端执行票 —— 真实响应信封消费与业务错误映射修复 Type: task Status: resolved Assignee: codex Category: frontend-execution ## 进度 **状态:修复已实施,待验证项 1–4 全部取得真实证据,本票收口(2026-10-12)。** Codex 按本地 tracker 的 `Status:` 约定认领(deck 的 assignees 写入读回失败,以 `Assignee:` 字段为准)。核心信封解包由并行改动 `ead39442` 提供,非本票实施;本轮**新实施**的是同一条业务错误映射链上的两处白名单继承键缺口,提交 `86db4d4`。 ## 状态:缺陷真实存在,已被并行改动修复,待验证 **本票一度被我错误撤销,此处更正。** ### 缺陷(真实存在,2026-10-10 10:25 实测证实) `cockpitApi.js` 原实现直接 `return { data: payload.data }` —— 而 `payload.data` 是 `readJsonPayload` 返回的**整个 AjaxResult 信封** `{code, msg, data}`,业务载荷从未被解包。后果: 1. **真实数据被读成空**:`fetchBaseArchives` 对真实 `/infrastructure/base-archives`(`facilityCount=1006`)返回 `state=empty`;`fetchMapSnapshot` 对真实 `/map-snapshot`(`total=13`)返回 `total=null`、`snapshotAt=''`。 2. **业务错误被读成成功**:`/events/current?mode=ALARM` 业务 `code=500`,前端分区判为 `success` —— 故障被伪装成正常空态。 实测证据:`.scratch/cockpit-real-data-wayfinding/verify/artifacts/t18/live-audit-results.json`(写入时间 10:25),三项检查 `frontend-envelope-archive`、`frontend-envelope-map`、`frontend-business-error-not-success` 均为 `failed`。 ### 修复现状(并行改动,非本票实施) 工作区已存在对应修复(`src/modules/cockpit/services/cockpitApi.js`,文件修改时间 **14:38:36**):新增信封拆解分支——若载荷含数值型 `code`,则 `code !== 200` 判失败(401/403 映射 `UNAUTHORIZED`,其余 `UNAVAILABLE`),缺 `data` 字段判 `BAD_PAYLOAD`,`code === 200` 才解包 `body.data`。 该修复于 14:38 落地,**晚于**上述 10:25 的失败实测,**早于**我 15:50 的复核。复核因此在已修复代码上进行并返回正常。 ### 我的错误(记录在案) 我 15:58 撤销本票时,用**修复后**的代码复核得到「适配层正常」,据此判定缺陷「不成立」并关闭票据。这是推理错误:**修复后的通过不能否定修复前已证实的缺陷**。正确处置是标记为「已修复待验证」,而非撤销。相关术语纪律:验证缺陷必须确认**被测代码版本**与**复现时版本**一致。 ## 本轮实施(Codex,2026-10-12):业务错误映射的白名单继承键缺口 核验已有修复时发现**同类缺陷的第二、第三处**,都在本票“业务错误映射”范围内: `readBusinessReason`(`cockpitApi.js`)用普通对象下标查 `BACKEND_ERROR_CATEGORY`。白名单只有一个条目,但下标查找会**命中 `Object.prototype` 的继承键**——后端随便回一个 `category: "toString"`,失败原因就成了函数而不是契约值。与文件里既有的注释纪律(「只认白名单里这一个取值……不让后端塞任意原因进结果」)直接冲突。 对抗式自查时用 `node` 探针确认第二处同源缺陷:`sectionStateFromBackend('toString')` 与 `sectionStateFromFreshness('toString')` 返回的是**函数**,会原样漏进 `data-section-state`。 修复(提交 `86db4d4`,两个文件共 5 行):三处改为只认白名单**自有键**。这是最小改动,不新增抽象,沿用本文件既有的 `Object.prototype.hasOwnProperty.call` 写法。 离线回归:新增 [`verify/t22-envelope-frontend.mjs`](../verify/t22-envelope-frontend.mjs),不联网、注入 `fetchImpl`,覆盖三项真实失败检查对应的**同一批领域出口**(`fetchBaseArchives` / `fetchMapSnapshot` / `fetchCurrentEvents`)、HTTP 与业务两级失败、mock 分支、裸载荷、白名单继承键、后端分区状态映射 —— **19/19 通过**(修复前 5 项继承键检查为红,已实测复现)。 既有离线回归无回退:`t48-real-envelope` 5/5、`t04-config`、`t05-adapter` 283/283、`t07-coordinate` 39/39、`t08-map-summary` 44/44 全绿。 **既存失败(与本票无关,已用基线对照排除)**:`t03-pole-probe`、`t28-fill-container` 缺 puppeteer-core;`t06-assets` JSON import 属性;`t11-equipment-detail` F7;`t17-infrastructure-panels` / `t18-monitoring-panels` 引用已被片区改造删除的 `MANAGEMENT_REGION_GAP` / `REGION_MAP_GAP`;`t37-area-limits`、`t38-fog` 断言。将两处改动临时还原为基线后复跑,**9 项全部同样失败**,故与本轮改动无因果关系。 **事故记录**:批量复跑时误执行了 `t20-mutations.mjs`(故意改坏源码再还原的变异测试),它在还原阶段 `copyfile` 失败,把 `OVERVIEW_RANGE` 留成 `''` 并留下 `.bak`。已从 git 恢复 `overviewApi.js`(该文件在会话开始时干净)并删除 `.bak`,工作区已复原为仅含本票改动。 ## 待验证项与真实复验结果(2026-10-12,用户明确授权后执行) 复验前先保全历史失败证据:原产物另存为 [`live-audit-results-2026-10-10T0225Z-pre-fix.json`](../verify/artifacts/t18/live-audit-results-2026-10-10T0225Z-pre-fix.json)(脚本会覆盖同名文件,不保全就会抹掉缺陷证据)。 1. ✅ **通过** —— 重跑 `verify/t18-real-audit.mjs`(116 次只读 GET,真实后端 `192.168.110.96:8301`)。三项 `frontend-*` 全部 `passed`: | 检查 | 修复前(2026-10-10 02:25Z) | 修复后(2026-10-12 01:23Z) | | --- | --- | --- | | `frontend-envelope-archive` | failed `{state:"empty", raw:1006}` | **passed** `{state:"success", adapted:5278, raw:5278}` | | `frontend-envelope-map` | failed `{adaptedTotal:null, rawTotal:13, snapshot:""}` | **passed** `{adaptedTotal:13, rawTotal:13, snapshot:"2026-10-12T09:23:47…"}` | | `frontend-business-error-not-success` | failed `{rawCode:500, adaptedState:"success"}` | **passed** `{rawCode:500, adaptedState:"failed"}` | 第三项是本票要害:业务 `code=500` 现在被判为 `failed`,不再是 `success`。新产物 [`live-audit-results.json`](../verify/artifacts/t18/live-audit-results.json)。 该次审计 116 请求 / 85 通过 / **33 失败**,失败项**全部不属于本票**:`map-eight-status-buckets` 与 `pointStatus` 回显(归后端[票 29](29-29.md))、设备维 events/history、work-orders 的业务 500、`monitoring/*` 缺 `createdAt`(归后端/票 18 阻断项 5)。本票不得据此宣称真实联调整体收口。 2. ✅ **通过** —— 真实浏览器(`yarn dev` → `http://localhost:8001/#/cockpit`,`public/cockpit/config.json` 为 `dataSource: real`)逐模式确认: - **总体概览**:态势总览「设施底数 **5276** 处」、「数据已就绪」;基础设施「设施总数 **5276**」「行业构成 燃气 537 / 供水 1004 / 排水 3735」「管线里程 67.60944km」——修复前这些面板是空的。截图 [`real-overview-envelope-consumed.png`](../verify/artifacts/t22/real-overview-envelope-consumed.png)。 - **预警处置**:`/events/current?mode=ALARM`(业务 `code=500`)对应的「当前预警」分区显示 **「数据加载失败 · 数据源暂不可用 · 该分区加载失败(其它分区不受影响)」**,红标,非 success、非空白;历史分析/预警效率/预警分析同样。地图区如实说「异常设备数据未就绪,预警处置不猜测异常点」。截图 [`real-business-error-failed-section.png`](../verify/artifacts/t22/real-business-error-failed-section.png)。 - 浏览器实际发出的请求已抓取确认打到了真实后端(含 `events/current?mode=ALARM`、`map-snapshot` 7 页均带 `snapshotAt`)。 3. ✅ **通过** —— mock 分支与裸载荷未被误伤:`t22` 的 mock 用例证明 mock 处理器返回值不经信封分支(且刻意让 mock 返回 `code:500` 仍判成功),裸载荷原样透传;`t48` 第 5 项同证。 4. ✅ **通过** —— 改动归属已登记:并行修复 `ead39442`(Git 作者 Kazerin),本轮修复 `86db4d4`。 ## 收口后的去向 本票四项验收全部取得真实证据,故按自身交付物收口(`Status: resolved`)。 **不要据此认为真实联调整体完成**:本次审计仍有 33 项失败(八桶 `pointStatus`、设备维事件/工单业务 500、`monitoring/*` 缺 `createdAt`),它们属于[票 29](29-29.md)、[票 23](23-23-token-pointstatus.md)与[票 18](18-frontend-real-integration-gate.md) 的范围,仍按各自的阻断项推进。 用户已另有标签停在端口 8000 的同应用标签页;本轮自建的 8001 dev server 与浏览器会话均已关闭,用户原有标签页未被借用或改动。 ## 附:我在复核中发现的另一项真实缺陷 即使信封已修好,运行监测-监测点台账仍显示「已列出 0 个点位 · 本端点序列共 **undefined** 条」—— 缺失值被直接渲染。已另立 [票 27](27-27.md)。 ## 关联 发现于票 18。与 [票 28](28-28.md)(后端静默丢弃监测点)分属前后端,修复路径互不依赖。 ## Comments ### local — 2026-10-12T01:07:04.396Z 按用户本次指派认领,进入已有修复的核验阶段。当前状态 fixed-pending-verification,无 needs-triage,无 blockedBy。本轮先核验已提交代码、历史失败证据、mock/裸载荷与业务失败分区路径;遵守 AGENTS.md,不自动运行真实审计、构建或浏览器调试,缺失的真实验收证据会如实保留。 ### local — 2026-10-12T01:08:29.659Z 源码核验事实:核心信封解包与业务 code 判断已提交在 ead39442db43aecf33f009c522e61a7a4c354c2b(Git 作者 Kazerin);本轮 HEAD=777f0614db95daf180336bc9fc9f11cbaf063133,cockpitApi.js 无工作区差异。本轮不认领已有修复为新实施。仍有明确缺口:t48-real-envelope 只覆盖地图,不验证档案、事件顶层失败与 mock;readBusinessReason 用普通对象下标查白名单,category=toString/__proto__/constructor 等继承键会返回非 REQUEST_REASON 值,与只认白名单的既定约束冲突。方案:独立离线回归验证三个实际领域出口、HTTP/业务错误和 mock/裸载荷;以自有键查找修复映射,保留既有真实审计失败证据;真实复验需用户明确授权 build/debug 例外。