/** * CodeMeta — v1.6.0 协议核心 * * report.code 是业务命运的单一信息源;status / level / semantic 全部由此派生。 * * 设备端调用 dispatchMessage(code, data) 时,协议层自动按 code 推导 status / level, * 集成方接收消息后调用 resolveCodeMeta(code) 查表得到 semantic / requiresErrorBlock 等。 * * 设计依据:claude-docs/protocol-upgrade-proposal-v1.6.0.md */ // v1.6.0: 使用 type-only import 避免与 index.ts 的循环依赖 // index.ts 在 MessageFactory.dispatchMessage 中 import code-meta, // code-meta 本文件如果运行时 import ./index 会触发 TDZ。 // 使用 type import 仅在编译期解析,运行时不产生 cyclic require。 import type { ReportLevel, MessageType, CommandType, ProgressPhase, ProgressSourceType } from './index'; /** v1.6.0:单一 status enum,合并 v1.5.0 的 CommandStatus + ProgressStatus */ export enum MessageStatus { IN_PROGRESS = 'IN_PROGRESS', COMPLETED = 'COMPLETED', FAILED = 'FAILED', CANCELLED = 'CANCELLED', } /** 业务语义分类——终态语义 + 进度语义 */ export type CodeSemantic = // 终态语义(仅在 isTerminal=true 时出现) | 'success' | 'partial' | 'failure' | 'no-op' | 'cancel' | 'busy' // 进度语义(isTerminal=false) | 'phase_start' | 'phase_end' | 'phase_failed' | 'step_success' | 'step_failed' // v1.8.0: R1/R2 上下文降级时, 终态 failure 映射到 step_failed (与 phase_failed 区分 — step 级失败不必然父命令失败) | 'step_skipped' | 'step_progress' | 'retry' | 'retry_pending' | 'warning'; /** * 单一 code 的元数据描述符 * * 派生字段(status / level)会序列化进消息; * 客户端字段(semantic / requiresErrorBlock / isTerminal)仅供查表,不进消息。 */ export interface CodeMeta { isTerminal: boolean; status: MessageStatus; /** * v1.10.3: 支持 array 类型 (partial-failure 多 level 允许) * - string (e.g., 'INFO'): Edge validator 严格相等校验 (大多数 entry) * - readonly ReportLevel[] (e.g., ['INFO','WARNING','ERROR']): Edge validator includes 校验 (partial-failure 场景) */ level: ReportLevel | readonly ReportLevel[]; semantic: CodeSemantic; requiresErrorBlock: boolean; /** * v1.8.0: 上下文派生应用的规则名(debug + dashboard 用),不参与序列化 */ appliedRules?: string[]; /** * v1.8.0: R3 invariant 破坏标记 — Edge 收到此返回值时强制 reject (即使 messageType=PROGRESS_UPDATE) * 仅在 R3 (EDGE_CACHE_* phase + sourceType !== EDGE) 触发,其它规则不会设置 */ violationReason?: string; } /** * v1.8.0: resolveCodeMeta 派生上下文 (5 维 tuple) * * 详见 upgrade-guide-v1.8.0.md §3.2 R1-R7 派生规则。 * * - messageType: wire `type` 字段 * - parentCommandType: 父命令 commandType (Edge/Backend 查 requestRef 关联的 CommandMessage 获得) * - phase: wire `phase` 字段 (PROGRESS_UPDATE 含) * - sourceType: wire `sourceType` 字段 * - subCommandType: wire `command.commandType` (BATCH 子项时一般 SIMPLE) */ export interface CodeMetaContext { messageType?: MessageType; parentCommandType?: CommandType; phase?: ProgressPhase | string; sourceType?: ProgressSourceType; subCommandType?: CommandType; } /** * 终态 code 显式 META 表 * * 这些 code 是业务命运决断点,每个语义都是独特的,**不能用后缀规则推导**。 * 设备端发送终态消息时,dispatchMessage 在此表查询,自动填充消息字段。 */ export const EXPLICIT_META: Record = { // ═══════════ PROGRAM 节目部署 ═══════════ PROGRAM_COMPLETED: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'INFO' as ReportLevel, semantic: 'success', requiresErrorBlock: false, }, // v1.7.4: rename PROGRAM_PARTIALLY_SUCCEEDED → PROGRAM_PARTIAL_SUCCESS // 副词 PARTIALLY 是 PROGRAM 域单独的历史语法异类(其它域用形容词 PARTIAL) // 与 SYNC_MONITORING_TABLE_READ_PARTIAL_SUCCESS 语法对齐 // QUICKLY_DETECTION_PARTIAL_ONLINE 保留 (outcome axis 是在线度,不是 success/fail 维度) PROGRAM_PARTIAL_SUCCESS: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'WARNING' as ReportLevel, semantic: 'partial', requiresErrorBlock: false, }, PROGRAM_ALL_FAILED: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, PROGRAM_NO_PROGRAMS: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'INFO' as ReportLevel, semantic: 'no-op', requiresErrorBlock: false, }, PROGRAM_UPLOAD_FAILED: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, PROGRAM_UPLOAD_BUSY: { isTerminal: true, status: MessageStatus.FAILED, level: 'WARNING' as ReportLevel, semantic: 'busy', requiresErrorBlock: true, }, PROGRAM_UPLOAD_CANCELLED: { isTerminal: true, status: MessageStatus.CANCELLED, level: 'WARNING' as ReportLevel, semantic: 'cancel', requiresErrorBlock: true, }, // ═══════════ QUICKLY_DETECTION 一键检测 ═══════════ QUICK_DETECTION_ALL_ONLINE: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'INFO' as ReportLevel, semantic: 'success', requiresErrorBlock: false, }, QUICK_DETECTION_PARTIAL_ONLINE: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'WARNING' as ReportLevel, semantic: 'partial', requiresErrorBlock: false, }, // v1.8.0: status COMPLETED → FAILED (Q5 — 语义打架修复) // 触发:upgrade-guide-v1.8.0.md Round 1 Q5 设备端审计 / Backend & Edge & Gateway 无现存代码依赖 // status=COMPLETED, 改 FAILED 与 ALL_FAILED 语义对齐,避免 dashboard 绿勾 + ERROR 字打架 QUICK_DETECTION_ALL_OFFLINE: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, QUICK_DETECTION_FAILED: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, QUICK_DETECTION_BUSY: { isTerminal: true, status: MessageStatus.FAILED, level: 'WARNING' as ReportLevel, semantic: 'busy', requiresErrorBlock: true, }, // ═══════════ SYNC_MONITORING_TABLE 监播导出 ═══════════ // v1.7.4: 三态终态(SUCCESS / PARTIAL_SUCCESS / FAILED) // PARTIAL_SUCCESS 场景: 部分天读取成功部分天 timeout — 真实业务结局,二态化会失真 SYNC_MONITORING_TABLE_READ_SUCCESS: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'INFO' as ReportLevel, semantic: 'success', requiresErrorBlock: false, }, SYNC_MONITORING_TABLE_READ_PARTIAL_SUCCESS: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'WARNING' as ReportLevel, semantic: 'partial', requiresErrorBlock: false, }, SYNC_MONITORING_TABLE_READ_FAILED: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, // ═══════════ DEVICE_LOG_RETRIEVE 设备日志获取 ═══════════ // v1.12.3: SIMPLE READ 命令, 一次性返回日志 (gzip+base64) DEVICE_LOG_RETRIEVE_SUCCESS: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'INFO' as ReportLevel, semantic: 'success', requiresErrorBlock: false, }, DEVICE_LOG_RETRIEVE_FAILED: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, // ═══════════ EDGE 代理层合成的终态拒绝 ═══════════ // 当 Edge 在转发命令到设备的过程中失败(设备不在线、命令格式错、转发失败等), // Edge 合成一个终态消息发回 Gateway 让命令生命周期闭环。 // 具体失败原因通过 data.error.{phase, step, category, detail} 表达。 EDGE_COMMAND_REJECTED: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, EDGE_PROGRAM_REJECTED: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, // v1.10.0 §3: BARGRAPH_SYNC_FRAME_PLAYED_COUNTER 显式 EXPLICIT_META entry // ⚠️ 与 BARGRAPH_TRAIN_LENGTH 命名相近但语义截然不同, 长效防误用: // - BARGRAPH_SYNC_FRAME_PLAYED_COUNTER (本 entry): 硬件已播放帧累积计数器 (READ-only observed) // - BARGRAPH_TRAIN_LENGTH: 列车长度配置 (configure, WRITE, 米→帧换算) // 命名相近源于历史 spec drift, v1.10.0 §2 路径 b 删 raw `BARGRAPH_PLAY_FRAMES_NUMBER` 后双方共识 BARGRAPH_SYNC_FRAME_PLAYED_COUNTER: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'INFO' as ReportLevel, semantic: 'success', requiresErrorBlock: false, }, // v1.10.2 §1 + v1.10.3 §1: PROGRAM_COMPLETE_FINAL + QUICK_DETECTION_COMPLETE_FINAL 显式 EXPLICIT_META // 允许 partial-failure 语义: level=INFO (全成功) / WARNING (部分失败但整体完成) / ERROR (完全失败) // requiresErrorBlock=false → optional error block, partial-failure 时 emit 可带 error block // v1.10.3 §1: level 改 array (从 'INFO' 单值锁死 → ['INFO','WARNING','ERROR']) — Edge validator 兼容 includes // 修真 v1.10.1 staging 14 TOL 残留 (Self-reflection #12 落地) // 镜像 invariant: 双 entry 必须完全相同 (v1.10.0 §1 EmitProgramCompletePhase ↔ v1.9.0 §B EmitDetectCompletePhase) PROGRAM_COMPLETE_FINAL: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: ['INFO', 'WARNING', 'ERROR'] as readonly ReportLevel[], semantic: 'phase_end', requiresErrorBlock: false, }, QUICK_DETECTION_COMPLETE_FINAL: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: ['INFO', 'WARNING', 'ERROR'] as readonly ReportLevel[], semantic: 'phase_end', requiresErrorBlock: false, }, }; /** * 进度 code 后缀规则 * * 进度消息有数百个 code(按 PROGRAM_FETCH_DOWNLOAD_PROGRESS 这种命名规范), * 不可能逐一罗列 META,但 level / status / semantic 完全可从后缀派生。 * * 优先级从高到低,首次命中即返回。 */ // v1.7.1: 删除 infix 字段 — 协议层规范化为 suffix-only 派生规则 // (消除"_WARN infix 与 _FAILED suffix 双命中"的设计 bug,确保一码一义) // v1.7.6: meta 含完整 CodeMeta(含 isTerminal),不再硬编码 isTerminal: false // 新增 _READ_SUCCESS / _WRITE_SUCCESS 终态规则(SIMPLE 命令成功响应) interface SuffixRule { /** 后缀匹配(endsWith)*/ suffix: string; meta: CodeMeta; } // 注意:endsWith 按顺序首次命中即返回,所以**更长的后缀必须放在更短后缀之前**: // _READ_SUCCESS / _WRITE_SUCCESS 必须在 _SUCCESS 之前 // _RETRY_PENDING 必须在 _RETRY 之前(已有约定) const SUFFIX_RULES: SuffixRule[] = [ // v1.7.7: SIMPLE 命令失败终态 + BATCH 三态终态(对称侧补完,v1.7.6 留漏) // 命名约定(设备端 canonical doc §3.x): // SIMPLE: __FAILED e.g. SYNC_FUNCTIONS_SWITCH_READ_FAILED // BATCH: BATCH___ e.g. BATCH_BARGRAPH_LED_SWITCH_WRITE_ALL_FAILED // 必须放在 _FAILED / _SUCCESS 之前(endsWith 长后缀优先命中) // 触发:v1.7.6 wire 联调 req_1778824929909_jlvome (BARGRAPH_MISALIGNMENT_READ_FAILED) // 被误命中 _FAILED → IN_PROGRESS phase_failed → Edge 等不到 terminal → 30s 假 timeout { suffix: '_READ_FAILED', meta: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, }, { suffix: '_WRITE_FAILED', meta: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, }, { suffix: '_ALL_FAILED', meta: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, }, // v1.7.8: ECAN 广播 WRITE 终态(对称侧补完,v1.7.7 留漏) // 命名约定(设备端 IndependentEcanActionProvider.cs:459,513): // _WRITE_BROADCAST_SUCCESS / _FAILED // 5 个光柱命令:BARGRAPH_LED_SWITCH / BARGRAPH_PLAYBACK_FORBID / BARGRAPH_MISALIGNMENT // / BARGRAPH_TRAIN_LENGTH / BARGRAPH_PROGRAM_PLAY_IMMEDIATELY // v1.10.0 §2: 删 raw `BARGRAPH_PLAY_FRAMES_NUMBER` (dead spec, 设备端从未 emit), 公开命名唯一 `BARGRAPH_TRAIN_LENGTH` // 必须放在 _FAILED 之前(长后缀优先) // 触发:v1.7.7 wire 联调 req_1778824943659_4gaxtv (BARGRAPH_MISALIGNMENT_WRITE_BROADCAST_SUCCESS) // 命中 _SUCCESS → IN_PROGRESS step_success → 与 wire COMPLETED 终态不符 { suffix: '_BROADCAST_FAILED', meta: { isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, }, }, // v1.8.0: BATCH/COMPLEX 子项进度失败 — caller 优先用此显式后缀 (Q9 设备端约定) // 与 _FAILED 区别:level=WARNING (子项失败不必触发 ERROR 告警, 父命令终态再决定整体严重度) + 不强制 errorBlock { suffix: '_STEP_FAIL', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'WARNING' as ReportLevel, semantic: 'phase_failed', requiresErrorBlock: false, }, }, { suffix: '_FAILED', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'ERROR' as ReportLevel, semantic: 'phase_failed', requiresErrorBlock: true, }, }, { suffix: '_RETRY_PENDING', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'WARNING' as ReportLevel, semantic: 'retry_pending', requiresErrorBlock: false, }, }, { suffix: '_RETRY', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'WARNING' as ReportLevel, semantic: 'retry', requiresErrorBlock: false, }, }, { // v1.7.1: _WARN 改 suffix(与其他 8 条规则对齐,消除 infix 设计噪点 + 修双关键字 bug) // 命名约定: PROGRAM_UPLOAD_CAN_BUS_ALARM_WARN(非 PROGRAM_UPLOAD_WARN_CAN_BUS_ALARM) suffix: '_WARN', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'WARNING' as ReportLevel, semantic: 'warning', requiresErrorBlock: false, }, }, { suffix: '_SKIPPED', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'INFO' as ReportLevel, semantic: 'step_skipped', requiresErrorBlock: false, }, }, { suffix: '_COMPLETED', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'INFO' as ReportLevel, semantic: 'phase_end', requiresErrorBlock: false, }, }, // v1.10.1 §1: _FINAL 显式派生 (vs fallback step_progress) // 配合 v1.9.0 §B QUICK_DETECTION_COMPLETE_FINAL + v1.10.0 §1 PROGRAM_COMPLETE_FINAL // 失败路径 level='ERROR' 由设备端 EmitProgramCompletePhase/EmitDetectCompletePhase helper 显式覆盖 // Edge validator 用 isOrchestratorCodedWire 判 4 字段强制 (不依赖此 SUFFIX_RULES) { suffix: '_FINAL', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'INFO' as ReportLevel, semantic: 'phase_end', requiresErrorBlock: false, }, }, { suffix: '_START', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'INFO' as ReportLevel, semantic: 'phase_start', requiresErrorBlock: false, }, }, { suffix: '_PROGRESS', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'INFO' as ReportLevel, semantic: 'step_progress', requiresErrorBlock: false, }, }, // v1.7.6: SIMPLE 命令终态成功响应(READ/WRITE 操作完成) // 命名约定: __SUCCESS — 设备端 canonical doc §3.x 规范 // 必须在 _SUCCESS 之前(更长后缀优先) // 修复 v1.7.0–v1.7.5 协议大坑:SUFFIX_RULES 缺此规则导致设备端所有 SIMPLE 命令的 // _READ_SUCCESS / _WRITE_SUCCESS 终态响应被 Edge 错误降级为 IN_PROGRESS step_success // → Edge 协议校验拒收 → 假 timeout(30s 超时后 Edge 合成 timeout 上报 Gateway)。 // 触发: v1.7.5 wire 联调 req_1778811285514_oan6qa (SYNC_FUNCTIONS_SWITCH_READ_SUCCESS) { suffix: '_READ_SUCCESS', meta: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'INFO' as ReportLevel, semantic: 'success', requiresErrorBlock: false, }, }, { suffix: '_WRITE_SUCCESS', meta: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'INFO' as ReportLevel, semantic: 'success', requiresErrorBlock: false, }, }, // v1.7.7: BATCH 三态终态(对称侧补完) // 必须在 _SUCCESS 之前(_ALL_SUCCESS / _PARTIAL_SUCCESS 是 BATCH 终态,_SUCCESS 是阶段成功) // 触发:v1.7.6 wire 联调 BATCH_BARGRAPH_MISALIGNMENT_READ_PARTIAL_SUCCESS { suffix: '_ALL_SUCCESS', meta: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'INFO' as ReportLevel, semantic: 'success', requiresErrorBlock: false, }, }, { suffix: '_PARTIAL_SUCCESS', meta: { isTerminal: true, status: MessageStatus.COMPLETED, level: 'WARNING' as ReportLevel, semantic: 'partial', requiresErrorBlock: false, }, }, // v1.7.8: ECAN 广播 WRITE 终态(对称 _BROADCAST_FAILED) // 必须放在 _SUCCESS 之前(长后缀优先),与 _READ_SUCCESS / _WRITE_SUCCESS / _ALL_SUCCESS 同优先级层 // v1.10.4: level 扩展为 array (INFO | WARNING) — 支持设备端 Q4-v2 (76d7ae2) Legacy SG7Driver 3 阶段对齐后的 partial-success // 形态: 全成功 → INFO; 部分 switch Ping 跳过或 send 失败但其余全成功 → WARNING + data.{successSwitches, skippedSwitches, failedSwitches, totalSwitchCount, framesSent} // 镜像 v1.10.3 Bug #3 修法 (PROGRAM_COMPLETE_FINAL + QUICK_DETECTION_COMPLETE_FINAL level array), Edge validator 已 v1.10.3 加 Array.isArray + .includes 兼容路径 { suffix: '_BROADCAST_SUCCESS', meta: { isTerminal: true, status: MessageStatus.COMPLETED, level: ['INFO', 'WARNING'] as readonly ReportLevel[], semantic: 'success', requiresErrorBlock: false, }, }, // v1.8.0: BATCH/COMPLEX 子项进度成功 — caller 优先用此显式后缀 (Q9 设备端约定) // 与 _SUCCESS 行为等价 (都 step_success/IN_PROGRESS/INFO), 但命名更清晰表达"step 级"语义 { suffix: '_STEP_OK', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'INFO' as ReportLevel, semantic: 'step_success', requiresErrorBlock: false, }, }, { suffix: '_SUCCESS', meta: { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'INFO' as ReportLevel, semantic: 'step_success', requiresErrorBlock: false, }, }, ]; /** * 兜底 META(未知 code 时返回) * * 接收方收到未知 code 时降级到 step_progress,避免阻塞协议处理。 */ const FALLBACK_META: CodeMeta = { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: 'INFO' as ReportLevel, semantic: 'step_progress', requiresErrorBlock: false, }; /** * v1.8.0: 上下文派生规则 — applyContextRules * * 详见 upgrade-guide-v1.8.0.md §3.2 R1-R7 派生规则。 * * 优先级 (高 → 低): * R3: phase ∈ EDGE_CACHE_* + sourceType !== EDGE → invariant 破坏标记 (Edge 强制 reject) * R1: messageType === PROGRESS_UPDATE + base.isTerminal === true → 降级 step META * R2: parentCommandType ∈ {BATCH, COMPLEX} + subCommandType === SIMPLE + base 终态 → 降级 (R1 双保险) * R4: phase ∈ PROGRAM_* + code 不以 PROGRAM_ 开头 → WARN log (不改 META) * R5: phase ∈ DETECT_* + code 不以 QUICK_DETECTION_ 开头 → WARN log (不改 META) * R6: 与 R1 行为合并 — EXPLICIT_META 终态 code 在 PROGRESS_UPDATE 中自动降级 (R1 覆盖) * R7: phase === SYNC_EXPORT + code 不以 SYNC_MONITORING_ 开头 → WARN log (不改 META) */ function applyContextRules(base: CodeMeta, code: string, ctx: CodeMetaContext): CodeMeta { const appliedRules: string[] = []; const PROGRAM_PHASES = new Set([ 'PROGRAM_INIT', 'PROGRAM_FETCH', 'PROGRAM_EXTRACT', 'PROGRAM_PREPROCESS', 'PROGRAM_COMPILE', 'PROGRAM_UPLOAD', 'PROGRAM_STATS', 'PROGRAM_COMPLETE', ]); // v1.9.0: detection 7 phase 加 QUICK_DETECTION_ 前缀, 与 ProgressPhase enum / ERROR_STEP_BY_PHASE matrix 镜像 const DETECT_PHASES = new Set([ 'QUICK_DETECTION_INIT', 'QUICK_DETECTION_SWITCH_DETECT', 'QUICK_DETECTION_SWITCH_CONFIG_READ', 'QUICK_DETECTION_SYNC_DETECT', 'QUICK_DETECTION_BARGRAPH_DETECT', 'QUICK_DETECTION_SYNC_RECOVER', 'QUICK_DETECTION_COMPLETE', ]); // R3 (优先级最高): EDGE_CACHE_* + sourceType !== EDGE → invariant 破坏 // 设备端永远不该 emit EDGE_CACHE_* phase, 协议方 hard reject 防御未来误用 (Q8) if (ctx.phase && typeof ctx.phase === 'string' && ctx.phase.startsWith('EDGE_CACHE_') && ctx.sourceType !== 'EDGE') { return { ...base, isTerminal: true, status: MessageStatus.FAILED, level: 'ERROR' as ReportLevel, semantic: 'failure', requiresErrorBlock: true, appliedRules: ['R3'], violationReason: `EDGE_CACHE phase (${ctx.phase}) 必须 sourceType=EDGE — 协议层 invariant 破坏 (R3)`, }; } let derived: CodeMeta = base; // R1: messageType=PROGRESS_UPDATE + isTerminal=true → 降级 step META // (覆盖 R6: 任何 EXPLICIT_META 终态 code 在 PROGRESS_UPDATE 中合法降级) if (ctx.messageType === ('PROGRESS_UPDATE' as MessageType) && base.isTerminal) { derived = { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: base.level, // level 保留 (设备端可能 emit 业务 level) semantic: base.status === MessageStatus.COMPLETED ? 'step_success' : 'step_failed', requiresErrorBlock: base.requiresErrorBlock, // 强制 errorBlock 保留 (失败需 phase/step/category/detail) }; appliedRules.push('R1'); } // R2: parentCommandType ∈ {BATCH, COMPLEX} + subCommandType === SIMPLE + base 终态 // → 双保险, 即使 ctx.messageType 缺失也能降级 (Edge findRequestRef miss 兜底) // R2 不限 phase (Q13 设备端反馈 — 允许 PROGRAM_UPLOAD 等业务 phase 也触发) if (base.isTerminal && !appliedRules.includes('R1') && ctx.parentCommandType && (ctx.parentCommandType === ('BATCH' as CommandType) || ctx.parentCommandType === ('COMPLEX' as CommandType)) && ctx.subCommandType === ('SIMPLE' as CommandType)) { derived = { isTerminal: false, status: MessageStatus.IN_PROGRESS, level: base.level, semantic: base.status === MessageStatus.COMPLETED ? 'step_success' : 'step_failed', requiresErrorBlock: base.requiresErrorBlock, }; appliedRules.push('R2'); } // R4/R5/R7: 命名空间合规 — 不改 META, 仅在 appliedRules 标记 (Edge 看到时 log warn) if (ctx.phase && typeof ctx.phase === 'string') { if (PROGRAM_PHASES.has(ctx.phase) && !code.startsWith('PROGRAM_')) { appliedRules.push('R4-violation'); } if (DETECT_PHASES.has(ctx.phase) && !code.startsWith('QUICK_DETECTION_')) { appliedRules.push('R5-violation'); } if (ctx.phase === 'SYNC_EXPORT' && !code.startsWith('SYNC_MONITORING_')) { appliedRules.push('R7-violation'); } } return appliedRules.length > 0 ? { ...derived, appliedRules } : derived; } /** * 单一 code 解析入口 (v1.8.0 升维双签名) * * 解析顺序: * 0. v1.8.0 上下文派生 (applyContextRules) — 仅在 ctx 提供时 * 规则 R1-R7 详见 upgrade-guide-v1.8.0.md §3.2 * 1. 终态显式表(EXPLICIT_META) — 17 entries closed enum * (PROGRAM 7 + QUICK_DETECTION 5 + SYNC_MONITORING_TABLE 3 + EDGE 2) * 2. 后缀规则(SUFFIX_RULES) — 20 条规则覆盖数百个进度 + 终态 code * (v1.7.6: +_READ_SUCCESS / _WRITE_SUCCESS SIMPLE 终态后缀) * (v1.7.7: +_READ_FAILED / _WRITE_FAILED + BATCH _ALL_SUCCESS / _PARTIAL_SUCCESS / _ALL_FAILED 终态) * (v1.7.8: +_BROADCAST_SUCCESS / _BROADCAST_FAILED ECAN 广播 WRITE 终态) * (v1.8.0: +_STEP_OK / _STEP_FAIL BATCH/COMPLEX 子项进度显式后缀 — Q9) * 3. 兜底(FALLBACK_META) — 未知 code 降级 * * @param code report.code 字符串 * @param ctx (可选) v1.8.0 上下文 — 不提供时与 v1.7.8 行为兼容 * @returns CodeMeta — status / level / semantic / requiresErrorBlock / isTerminal [+ appliedRules + violationReason] */ export function resolveCodeMeta(code: string, ctx?: CodeMetaContext): CodeMeta { // Step 1+2: 单维 base 派生 (v1.7.8 行为) let base: CodeMeta; if (EXPLICIT_META[code]) { base = EXPLICIT_META[code]; } else { // v1.7.1: suffix-only 派生(删除 infix 分支) // v1.7.6: isTerminal 不再硬编码 false,从 rule.meta 派生 let matched = false; base = FALLBACK_META; for (const rule of SUFFIX_RULES) { if (code.endsWith(rule.suffix)) { base = rule.meta; matched = true; break; } } if (!matched) base = FALLBACK_META; } // Step 0 (v1.8.0): 上下文派生 if (ctx) { return applyContextRules(base, code, ctx); } return base; } /** * 判断 code 是否已知(命中显式表或后缀规则,不走兜底) */ export function isKnownCode(code: string): boolean { if (EXPLICIT_META[code]) return true; // v1.7.1: suffix-only 派生(删除 infix 分支) for (const rule of SUFFIX_RULES) { if (code.endsWith(rule.suffix)) return true; } return false; }