# Changelog

## [1.12.3] - 2026-07-31

### Patch — maxProgramSlots 注释修正

修正 `DevicePhysicalParams.maxProgramSlots` 注释：1.12.0 误把 programNo 的 0-based 语义传染给 maxProgramSlots。

- **注释回滚**：`"整数 0-9，设备 0-based"` → `"整数 1-10，表示容量。programNo 合法范围 0..(maxProgramSlots-1)"`
- **validator/test 不动**：`[1,10]` 校验一直是对的
- **无 wire 变化，无 breaking change**

提案：`claude-docs/discussions/protocol-proposal-maxprogramslots-semantics-2026-07-31.md`（方案 A 批准）

## [1.12.2] - 2026-07-29

### Patch — imageProcessConfig + maxProgramSlots comment

## [1.12.0] - 2026-05-31

### Minor — Pre-License 设备身份强化 (Phase 9a/9d, design lock 三方一致)

protocol cycle R0-R6.5 完整收敛 (claude-docs/discussions/upgrade-guide-v1.12.0.md v0.4 + device-team-final-ack-v1.12.0.md), Owner + 协议方 + 设备端三方 mutual accountability 6 处校正 + 0 hard 冲突。

**Schema 变更** (`RegisterMessage` 改 discriminated union):

```diff
+ // v1.12.0 deviceFingerprint — Pre-License 身份强化
+ export interface DeviceFingerprint {
+   type: 'installPubkey';      // L3 strict, 无 L1/L2/L4 fallback
+   value: string;              // "sha256:<hex>" — SHA-256 of SPKI DER (91 bytes for P-256)
+   proof: string;              // base64 ECDSA-SHA256 sign of proofPayload
+   proofPayload: string;       // "${clientId}|${nonce}|${timestamp}"
+   publicKeyPem?: string;      // 首次审批必填; Edge 持久化用于后续验签
+ }

- export interface RegisterMessage extends BaseMessage {
-   ... clientType: ClientType; ...  // 单一 interface
- }

+ // v1.12.0 discriminated union by clientType (Owner+协议方+设备端拍 schema 层 C 方案)
+ export interface DeviceRegisterMessage extends BaseRegisterMessage {
+   clientType: ClientType.DEVICE;
+   deviceFingerprint: DeviceFingerprint;  // runtime required (Edge 强制), schema type-narrow
+ }
+ export interface NonDeviceRegisterMessage extends BaseRegisterMessage {
+   clientType: Exclude<ClientType, ClientType.DEVICE>;
+   deviceFingerprint?: never;             // 类型层挡 — 调用方误带编译期就抓
+ }
+ export type RegisterMessage = DeviceRegisterMessage | NonDeviceRegisterMessage;

+ // MessageFactory.createRegisterMessage overload
+ static createRegisterMessage(clientId, ClientType.DEVICE, {deviceFingerprint, ...}): DeviceRegisterMessage;
+ static createRegisterMessage(clientId, EDGE/BACKEND/GATEWAY/API_CLIENT, options?): NonDeviceRegisterMessage;
```

**v1.12.0 5 子项**:
- **v1.12.0a** RegisterMessage discriminated union + DeviceFingerprint (Phase 9a Owner 拍 L3 installPubkey strict)
- **v1.12.0b** Edge 校验 fingerprint (proof signature verify, SQLite (device_id, fingerprint_hash) UNIQUE)
- **v1.12.0c** Gateway 审批主键升级 (device_approval_requests 表 (edge_id, device_fingerprint_value) UNIQUE)
- **v1.12.0d** Edge 全局 DAK 收紧 (`ALLOW_GLOBAL_DAK_BOOTSTRAP=false` 生产默认; Phase 9b)
- **v1.12.0e** clientId 格式 `^[a-z0-9._:-]{1,64}$` lower-case 强制 (Phase 9d, Edge 接收 trim+toLowerCase)

**Edge 校验序列升级**:
1. clientId 规范化 + 正则 (违规 → reject INVALID_DEVICE_ID)
2. 黑名单 strict (同 deviceId+新 fp / 同 fp+新 deviceId 双向 reject)
3. fingerprint verify (proof ECDSA P-256 + SHA256)
4. 无 fingerprint: grace 内 LEGACY warn / 过 grace reject FINGERPRINT_REQUIRED
5. Grace 默认 ship+90 天, env `LEGACY_DEVICE_GRACE_UNTIL` 可调

**Alert 通道** (Owner 反问触发 E 升级取代 D 外部通道):
- Gateway admin UI 加 "🚨 安全告警" tab + 15s polling (复用 deviceBadgeTimer 模式)
- SQLite admin_security_alerts 表持久化, 0 外部依赖
- Edge push 触发: FINGERPRINT_PROOF_INVALID / INVALID_DEVICE_ID / FINGERPRINT_DUPLICATE_SUSPECT

**Test vector** (协议方 R4 §2, 设备端 R5 openssl byte-perfect verify):
- 固定 P-256 keypair PEM + ECCBLOB 72B + SPKI DER 91B
- fingerprint sha256:35cd33b6743e2fb3c08324a6022efae65881385269717e187204d6c7d5c9d9ed

**Co-evolution cycle 完整记录**:
- R0 协议方 upgrade-guide v0.2 → v0.4 (Owner L3 strict + body sweep + .NET 4.6.1 C# rewrite + nit/minor patch)
- R1-R6.5 详见 claude-docs/discussions/*v1.12.0*.md (8 doc 完整 cycle)

**新增 tests** (213 → 221): register-device-fingerprint-v1.12.0.test.ts (8 tests: discriminated union + Factory overload + runtime guard + backward compat)

## [1.11.0] - 2026-05-29

### Minor — 新增 ACL_INVALIDATED 消息 + ClientType.API_CLIENT（client 动态授权配套）

纯 additive，服务端 client 动态授权系统（claude-docs/client-authorization-design.md）的协议配套：

```diff
// MessageType enum
+  ACL_INVALIDATED = 'ACL_INVALIDATED',   // Gateway → Backend: client ACL 变更时让缓存方清缓存

// ClientType enum
+  API_CLIENT = 'API_CLIENT'              // 对外 /api/v1 客户端

// 新增 interface AclInvalidatedMessage { type, clientId?, jti?, reason? }
// 新增 type guard isAclInvalidatedMessage()
// message-validator / protocol-utils 的 Record<MessageType,_> 映射同步补 ACL_INVALIDATED 条目
```

**收益**:
- Gateway 改/吊销某 client ACL → 推 ACL_INVALIDATED 给 Backend → Backend 清 ACL 缓存 → 近实时生效（替代 TTL 兜底）
- API_CLIENT 正式化（此前服务端以字符串实现）

**设备端影响 = N/A（轻量 co-evolution，仅需 ack 知晓）**:
- ACL_INVALIDATED 只有 Backend（ACL 缓存方）消费；设备端非接收方，**0 代码改动**
- API_CLIENT 是新增 ClientType 值，设备端不签发/不消费此类型，**不影响**
- additive：无字段删除、无 wire 变更、无现有消息结构改动；老 consumer 不受影响

**升级**: 三端 `npm i @thejrsoft/subway-protocol@^1.11.0`；设备端无需动作。

## [1.10.4] - 2026-05-21

### Patch — SUFFIX_RULES._BROADCAST_SUCCESS level 扩展 array (INFO|WARNING) — Option α 镜像 v1.10.3 Bug #3 修法, compressed cycle 第 4 次实证

**底层逻辑**: 设备端 commit Q4-v2 (`76d7ae2`, sg7-31-win sub-repo `feature/捷克联网项目`) Legacy SG7Driver 3 阶段对齐 (`IndependentEcanActionProvider.BroadcastWriteAsync` 加 ResetConnection + PingSwitch + per-switch 容错) 真修 Issue #1 BROADCAST connection staleness, 但引入 partial-success 路径 emit `_BROADCAST_SUCCESS + level=WARNING + data.{successSwitches, skippedSwitches, failedSwitches, totalSwitchCount, framesSent}` 表达"部分 switch 失败但整体可用". 原 SUFFIX_RULES `_BROADCAST_SUCCESS level: 'INFO'` 单值锁死, Edge validator strict equality 会 reject WARNING 形态 (Round 14 staging 实证 5/5 全 REJECT).

```diff
// src/code-meta.ts SUFFIX_RULES (1 处)
{
  suffix: '_BROADCAST_SUCCESS',
  meta: {
    ...
-    level: 'INFO' as ReportLevel,
+    level: ['INFO', 'WARNING'] as readonly ReportLevel[],
    ...
  },
}
```

**收益**:
- 设备端 Q4-v2 partial-success wire 形态 Edge ACCEPT (不再 reject)
- Dashboard / 上游 consumer 可 `level === "WARNING"` + `data.failedSwitches[]` 区分 partial-success vs 全成功
- 老 consumer 仍按 INFO 解析视为成功 (向后兼容)
- 5 个 BARGRAPH BROADCAST 命令 `_WRITE_BROADCAST_SUCCESS` wire code 全受益

**Compressed cycle 5/5 判据**:
- ✅ additive (无 wire 变化 / 无新 field / 不删 code, 仅扩展 SUFFIX_RULES.level value 从 string 到 array)
- ✅ 设备端 0 emit 行为变化 (Q4-v2 已 emit 此形态, 协议改动只是让 Edge 不拒)
- ✅ Edge validator 行为放宽不收紧 (v1.10.3 已加 `Array.isArray + .includes` 兼容路径, v1.10.4 直接受益)
- ✅ 协议方工时 ≤ 30 min (~20 min: spec 1 处 + jest×2 + Edge tier rebuild)
- ✅ 设备端工时 = 0 (Q4-v2 已 ship 不需 redeploy)

**触发**: 设备端 Round 0 propose `device-team-proposal-broadcast-success-level-array-v1.10.4.md` (2026-05-21), 协议方 Round 1 ack (同款 v1.10.3 Bug #3 模式复用).

**测试**: jest 加 1 项 + 更新 1 项, 全 213 项 PASS.

**PROTOCOL_VERSION**: 1.10.3 → 1.10.4

---

## [1.10.3] - 2026-05-20

### Patch — EXPLICIT_META.level 支持 array (Option α, 修真 Self-reflection #12, compressed cycle 第 3 次实证)

**底层逻辑**: v1.10.2 加 EXPLICIT_META `PROGRAM_COMPLETE_FINAL` + `QUICK_DETECTION_COMPLETE_FINAL` 时 `level: 'INFO'` 单值锁死, Edge validator `report.level !== meta.level` 严格相等校验, 设备端 partial-failure 场景 emit `level=WARNING + error block` 仍 tolerated (v1.10.1 staging 14 TOL 残留)。

```diff
// src/code-meta.ts CodeMeta interface (向后兼容 type union)
export interface CodeMeta {
  ...
-  level: ReportLevel;
+  level: ReportLevel | readonly ReportLevel[];  // v1.10.3: 支持 array
  ...
}

// src/code-meta.ts EXPLICIT_META 双 entry
PROGRAM_COMPLETE_FINAL: {
  ...
-  level: 'INFO' as ReportLevel,
+  level: ['INFO', 'WARNING', 'ERROR'] as readonly ReportLevel[],
  ...
},
QUICK_DETECTION_COMPLETE_FINAL: { ...同上... },

// services/jrsoft-subway-edge/src/websocket/error-schema-validator.ts:232-237
- if (report && report.level !== meta.level) {
+ const metaLevel = meta.level as string | readonly string[];
+ const levelMismatch = Array.isArray(metaLevel)
+   ? !metaLevel.includes(report.level)
+   : report.level !== metaLevel;
+ if (levelMismatch) { ... }
```

**关键语义升级**:
- **EXPLICIT_META.level 支持 array** (向后兼容: string entry 仍走严格相等; array entry 走 includes)
- **Edge validator 改判逻辑** (但*放宽*, 不收紧 — 设备端 emit 更多场景 ACCEPT)
- 修复 v1.10.1 staging 14 TOL 残留 (M0:4 + M1:5 + M2:5 都是 META 期望 INFO mismatch)

**⚠️ 设备端 0 工时** (compressed cycle 顶层设计):
- 设备端 emit helper 不读 META.level, 不需任何代码改动
- 工控机不需 redeploy
- Edge tier upgrade 由协议方独立执行 (subway-edge package 协议方维护)

**Compressed cycle 节奏** (MEMORY rule #11 第 3 次实证):
- Round 0 协议方 ship proposal (2026-05-20 `discussions/protocol-team-proposal-explicit-meta-level-array-v1.10.3.md`)
- Round 1 设备端 final ack (隐式, 双方共识)
- skip Round 2-3
- Round 4 协议方 publish + Edge tier upgrade
- Round 7 simplified release notice
- Round 8' 168 复测 verify TOL → 0

#### v1.10.3 cycle 沉淀

- **MEMORY rule #2 第 17 次落地**: Self-reflection #12 修真 — v1.10.2 EXPLICIT_META vibe 答题 (只看 requiresErrorBlock 没看 level strict check)
- **MEMORY rule #11 第 3 次工程化实证**: compressed cycle ~30 min 节奏稳定可复现
- 新工程化 invariant: Edge validator 任何字段强校验改动必先 grep `meta.<field>` 看 validator 是否真 read

---

## [1.10.2] - 2026-05-19

### Patch — `_COMPLETE_FINAL` EXPLICIT_META 双 entry (compressed patch cycle, 2 次工程化实证)

**底层逻辑**: v1.10.1 staging 168 复测发现设备端 emit `PROGRAM_COMPLETE_FINAL` 在 partial-failure 场景 (50 子任务部分失败) 携带 `level=WARNING` + `data.error` 块。原 SUFFIX_RULES `_FINAL` (v1.10.1) 派生 `level=INFO`, 与 emit 不匹配。设备端反向 propose: 这是 by-design 的语义表达 (partial-failure 不应硬归类 INFO/ERROR 二选一)。协议方 self-reflection #8: 初版方案 A (设备端单边改 helper 强制 INFO) 是 vibe 答题, 未 grep 自己最近 cycle 加的镜像 invariant。ACCEPT 设备端 propose, 加显式 EXPLICIT_META 双 entry 描述 partial-failure 语义。

```diff
// src/code-meta.ts EXPLICIT_META — 在 BARGRAPH_SYNC_FRAME_PLAYED_COUNTER 后加
+ // v1.10.2 §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
+ // 镜像 invariant: 双 entry 必须完全相同
+ PROGRAM_COMPLETE_FINAL: {
+   isTerminal: false,
+   status: MessageStatus.IN_PROGRESS,
+   level: 'INFO' as ReportLevel,
+   semantic: 'phase_end',
+   requiresErrorBlock: false,
+ },
+ QUICK_DETECTION_COMPLETE_FINAL: {
+   isTerminal: false,
+   status: MessageStatus.IN_PROGRESS,
+   level: 'INFO' as ReportLevel,
+   semantic: 'phase_end',
+   requiresErrorBlock: false,
+ },
```

**关键语义升级** (vs v1.10.1 SUFFIX_RULES 派生):
- **EXPLICIT_META 优先级 > SUFFIX_RULES**: 双 entry 不再走 `_FINAL` SUFFIX 派生路径
- **level 默认 INFO**: 全成功场景保持兼容 (与 v1.10.1 SUFFIX 行为一致)
- **`requiresErrorBlock=false`**: 允许 emit 携带 optional error block (partial-failure 用)
- **设备端语义合法化**: 设备端可 emit `level=WARNING/ERROR + data.error 块` 表达 partial-failure, Edge validator 不再误判

**⚠️ 不变更设备端 emit 行为 / 工控机不 redeploy** (compressed cycle 顶层设计):
- 设备端 emit helper 早已 by-design 支持 partial-failure
- 本 cycle 仅协议方加 EXPLICIT_META entry, 设备端不改任何代码
- v1.10.1 staging 部署的工控机继续运行, 不需 redeploy

**Compressed cycle 节奏** (~30 min 总周期, MEMORY rule #11 第 2 次工程化实证):
- Round 0 协议方主动 proposal (2026-05-19 ship `protocol-team-proposal-explicit-meta-complete-final-v1.10.2.md`)
- Round 1 设备端 final ack (ACCEPT 5 项, 设备端附 Bug #1/#2 C# 根因定位)
- skip Round 2-3 (additive 双方共识)
- Round 4 协议方 publish
- skip Phase 6 / Round 8' staging (设备端 0 影响)

#### v1.10.2 cycle 协作记录

- Round 0 proposal: `discussions/protocol-team-proposal-explicit-meta-complete-final-v1.10.2.md`
- Round 1 final ack: `discussions/device-team-final-ack-v1.10.2.md` (设备端 ACCEPT + Bug #1/#2 ProtocolClientCompositeExtensions.cs:154-156/210-216 根因)
- Self-reflection #8: `discussions/protocol-team-reply-to-emit-bug-clarification-2026-05-19.md` (撤销 vibe 方案 A)
- 设备端 raw wire trace evidence: `discussions/device-team-bug-report-clarification-2026-05-19.md`

#### v1.10.2 cycle 沉淀的工程化教训

- **MEMORY rule #2 第 13 次落地**: vibe 答题再次被设备端 grep 反扑, 协议方诚信 self-reflection #8
- **MEMORY rule #11 第 2 次工程化实证**: compressed cycle ~30 min 节奏可复现
- **MEMORY rule sub-rule #12 (新)**: bug 报告必须含 raw wire trace JSON evidence, 不靠 vibe 推荐方案

---

## [1.10.1] - 2026-05-19

### Patch — SUFFIX_RULES `_FINAL` 显式派生规则 (compressed cycle)

**底层逻辑**: v1.9.0 §B + v1.10.0 §1 加了 2 个 `_FINAL` 后缀 orchestrator wire (`QUICK_DETECTION_COMPLETE_FINAL` + `PROGRAM_COMPLETE_FINAL`), 但 SUFFIX_RULES 无对应 rule, fallback `step_progress`. 本 patch 加显式规则, semantic 对齐 `phase_end`.

```diff
// src/code-meta.ts SUFFIX_RULES — 在 _COMPLETED 后加
+ {
+   suffix: '_FINAL',
+   meta: {
+     isTerminal: false,
+     status: MessageStatus.IN_PROGRESS,
+     level: 'INFO' as ReportLevel,
+     semantic: 'phase_end',  // ← v1.10.1 显式 (vs fallback step_progress)
+     requiresErrorBlock: false,
+   },
+ },
```

**⚠️ 不变更设备端 emit 行为** (compressed cycle 顶层设计):
- 设备端 emit helper (`EmitProgramCompletePhase` / `EmitDetectCompletePhase`) **不读** `semantic` 字段, 仅写 `status` / `level` / `data.error` 4 字段
- Edge validator 用 `isOrchestratorCodedWire` 判 4 字段强制, **不依赖** `resolveCodeMeta.semantic`
- 设备端 1583 C# tests 不需重跑, 工控机不需 redeploy

**Compressed cycle 节奏** (~30 min 总周期):
- Round 0 协议方主动 proposal (2026-05-19 ship `protocol-team-proposal-suffix-rules-final-v1.10.1.md`)
- Round 1 设备端 final ack (ACCEPT 5 项, 0 工时确认)
- skip Round 2-3 (additive 双方共识)
- Round 4 协议方 publish
- skip Phase 6 / Round 8' staging (设备端 0 影响)

#### v1.10.1 cycle 协作记录

- Round 0 proposal: `discussions/protocol-team-proposal-suffix-rules-final-v1.10.1.md`
- Round 1 final ack: `discussions/device-team-final-ack-v1.10.1.md` (设备端 ACCEPT)

#### MEMORY rule sub-rule #11 (新, 本 cycle 实证)

**compressed patch cycle 节奏** — 5 项判据全满足 → patch cycle ~30 min (vs full minor cycle 3-4 工日):
1. additive
2. 设备端 0 emit 行为变化
3. Edge validator 行为不变
4. 协议方工时 ≤ 30 min
5. 设备端工时 = 0

---

## [1.10.0] - 2026-05-19

### Spec — §1 PROGRAM 收尾对称化 + §2 BARGRAPH 命名 spec drift 清理 (双议题打包)

**底层逻辑**: v1.9.0 cycle 暴露的对称性 gap + BARGRAPH 命名 dead spec, v1.10.0 cycle 双议题一并 propose, 协议方 ~20 min + 设备端 ~1.5 工日.

#### §1 PROGRAM 收尾对称化 (镜像 v1.9.0 §B QUICK_DETECTION_COMPLETE)

```diff
// src/index.ts ErrorStep type
+ | 'ProgramWrapup'         // PROGRAM_COMPLETE (v1.10.0 §1 新增, 配 PROGRAM_COMPLETE_FINAL wire)

// src/index.ts ERROR_STEP_BY_PHASE
- PROGRAM_COMPLETE: [] as const,
+ PROGRAM_COMPLETE: ['ProgramWrapup'] as const,
```

**镜像 invariant**:
- `PROGRAM_COMPLETE_FINAL` 经 `isOrchestratorCodedWire` PROGRAM_ prefix #2 自动匹配 → orchestrator 4 字段
- 设备端 `ProgramUploadOrchestrator.EmitProgramCompletePhase` 4 caller emit (镜像 v1.9.0 EmitDetectCompletePhase)
- 失败路径 `data.error.{phase: 'PROGRAM_COMPLETE', step: 'ProgramWrapup', category, detail}` 4 字段

#### §2 BARGRAPH 命名 spec drift 清理 — 路径 b (删 dead spec)

```diff
// src/code-meta.ts:299 SUFFIX_RULES 注释
- 5 个光柱命令：BARGRAPH_LED_SWITCH / ... / BARGRAPH_PLAY_FRAMES_NUMBER / ...
+ 5 个光柱命令：BARGRAPH_LED_SWITCH / ... / BARGRAPH_TRAIN_LENGTH / ...
+ // v1.10.0 §2: 删 raw BARGRAPH_PLAY_FRAMES_NUMBER (dead spec, 设备端从未 emit)
```

**真实根因** (设备端 evidence 2026-05-19):
- `BARGRAPH_TRAIN_LENGTH` 才是设备端实际 emit 的真实 wire (业务别名)
- `BARGRAPH_PLAY_FRAMES_NUMBER` 在设备端 case body **不读 pictureNumber 入参** = dead spec
- 协议方 grep 实测验证: src/ 0 处 hardcoded wire config (仅 1 注释 + 1 jest test)

**⚠️ 不引入新 wire 行为** (MEMORY rule #9 sub-rule):
- 设备端 emit `BARGRAPH_TRAIN_LENGTH` 已存在 (协议方 jest tests 已用)
- 路径 b 仅协议 spec/doc/test 同步对齐设备端实际命名
- 不变更设备端任何 emit 行为, 消费者无需重新适配

#### §3 BARGRAPH_SYNC_FRAME_PLAYED_COUNTER 加显式 EXPLICIT_META entry

```typescript
BARGRAPH_SYNC_FRAME_PLAYED_COUNTER: {
  isTerminal: true,
  status: MessageStatus.COMPLETED,
  level: 'INFO' as ReportLevel,
  semantic: 'success',
  requiresErrorBlock: false,
  // ⚠️ 与 BARGRAPH_TRAIN_LENGTH 命名相近但语义截然不同 (READ-only 计数器 vs WRITE 配置)
}
```

#### 协议方 jest tests 加强 (镜像 invariant)

```typescript
// 1. PROGRAM_COMPLETE_FINAL orchestrator
test('PROGRAM_COMPLETE_FINAL → orchestrator (镜像 QUICK_DETECTION_COMPLETE_FINAL)', () => {
  expect(isOrchestratorCodedWire('PROGRAM_COMPLETE_FINAL')).toBe(true);
});

// 2. ERROR_STEP_BY_PHASE 镜像断言
test('PROGRAM_COMPLETE ↔ QUICK_DETECTION_COMPLETE 对称', () => {
  expect(ERROR_STEP_BY_PHASE.PROGRAM_COMPLETE).toEqual(['ProgramWrapup']);
  expect(ERROR_STEP_BY_PHASE.QUICK_DETECTION_COMPLETE).toEqual(['DetectionWrapup']);
});
```

#### Round 4 publish 6 处 grep 全清

| # | 位置 | 改动 |
|---|---|---|
| 1 | `src/code-meta.ts:299` | 注释 PLAY_FRAMES_NUMBER → TRAIN_LENGTH |
| 2 | `src/__tests__/code-meta-broadcast-terminal.test.ts:37-38` | jest wire 名改 |
| 3 | `docs/wire-emit-codes.md:115` | doc 改 (Round 1 review §2 grep 发现 proposal 漏列) |
| 4 | `CHANGELOG.md:592` | 历史 changelog 不改 |
| 5 | `test-tools/lib/commands-spec.js` | BARGRAPH_TRAIN_LENGTH broadcast=true + 删单独 PLAY_FRAMES_NUMBER 用例 |
| 6 | `claude-docs/device-command-reference{,-en}.md` | 设备端 Round 6 ownership |

#### v1.10.0 cycle 协作记录

- Round 0 proposal: `discussions/device-team-proposal-program-wrapup-symmetry-and-bargraph-naming-v1.10.0.md`
- Round 1 review (协议方): `discussions/protocol-team-round-1-review-of-v1.10.0-proposal.md` (ACCEPT + 3 加强建议 + grep 发现漏 1 处)
- Round 2 final ack (设备端): `discussions/device-team-final-ack-v1.10.0-round-2.md` (ACCEPT 全部)

#### MEMORY rule 累计强化 (v1.10.0 cycle 期间)

| Rule | 强化 |
|---|---|
| #2 grep 全项目同型模式 | 第 11 次落地 (协议方反扑 proposal 漏 1 处) |
| #3 反向 invariant | 第 4 次强化 (PROGRAM ↔ QUICK_DETECTION 镜像 jest test) |
| #5 wire emit PR checklist | 设备端 Round 6 6 项 (累计 9 项 PR checklist) |
| #9 (源 v1.9.0 self-reflection #5) | 第 2 次落地 (release notice 显式标注 "不引入新 wire") |

---

## [1.9.0] - 2026-05-18

### Spec — ProgressPhase detection 族命名空间统一 (D 选项: 7 phase 加 `QUICK_DETECTION_` 前缀)

**底层逻辑**: 闭合 v1.4.11 设计空洞 — `PROGRAM_*` / `EDGE_CACHE_*` / `SYNC_*` / `BATCH_*` 族都带命名空间前缀, 唯独 detection 族 7 phase 裸露无前缀. 协议方主动升 minor 治本, 不扩 prefix 打补丁 (MEMORY rule #1 治标 vs 治本).

#### Schema 改动 — ProgressPhase enum (7 值改名 + 镜像 ErrorPhase type)

```diff
- DETECT_INIT = 'DETECT_INIT'
- SWITCH_DETECT = 'SWITCH_DETECT'
- SWITCH_CONFIG_READ = 'SWITCH_CONFIG_READ'
- SYNC_DETECT = 'SYNC_DETECT'
- BARGRAPH_DETECT = 'BARGRAPH_DETECT'
- SYNC_RECOVER = 'SYNC_RECOVER'
- DETECT_COMPLETE = 'DETECT_COMPLETE'
+ QUICK_DETECTION_INIT = 'QUICK_DETECTION_INIT'
+ QUICK_DETECTION_SWITCH_DETECT = 'QUICK_DETECTION_SWITCH_DETECT'
+ QUICK_DETECTION_SWITCH_CONFIG_READ = 'QUICK_DETECTION_SWITCH_CONFIG_READ'
+ QUICK_DETECTION_SYNC_DETECT = 'QUICK_DETECTION_SYNC_DETECT'
+ QUICK_DETECTION_BARGRAPH_DETECT = 'QUICK_DETECTION_BARGRAPH_DETECT'
+ QUICK_DETECTION_SYNC_RECOVER = 'QUICK_DETECTION_SYNC_RECOVER'
+ QUICK_DETECTION_COMPLETE = 'QUICK_DETECTION_COMPLETE'
```

#### ErrorStep type — 加 2 新值

| 新值 | 归属 phase | 用途 (设备端 commit M+N 回滚到 4 字段 orchestrator) |
|---|---|---|
| `DetectionWrapup` | `QUICK_DETECTION_COMPLETE` | §B: wire `QUICK_DETECTION_COMPLETE_FINAL` 终态 error |
| `CommBoardInfoRead` | `QUICK_DETECTION_SWITCH_CONFIG_READ` | §C: wire `QUICK_DETECTION_SWITCH_CONFIG_READ_COMM_BOARD_FAILED` 通讯板读取失败 |

#### ERROR_STEP_BY_PHASE matrix — 7 key 改名 + §B/§C 新 step

```diff
- DETECT_INIT: ['DetectionInit']
- SWITCH_DETECT: ['NetworkScan']
- SWITCH_CONFIG_READ: ['SwitchConfigRead']
- SYNC_DETECT: ['SyncDeviceCheck']
- BARGRAPH_DETECT: ['BargraphNodeCheck']
- SYNC_RECOVER: ['SyncStatusRecover']
- DETECT_COMPLETE: []
+ QUICK_DETECTION_INIT: ['DetectionInit']
+ QUICK_DETECTION_SWITCH_DETECT: ['NetworkScan']
+ QUICK_DETECTION_SWITCH_CONFIG_READ: ['SwitchConfigRead', 'CommBoardInfoRead']  // §C 新增 CommBoardInfoRead
+ QUICK_DETECTION_SYNC_DETECT: ['SyncDeviceCheck']
+ QUICK_DETECTION_BARGRAPH_DETECT: ['BargraphNodeCheck']
+ QUICK_DETECTION_SYNC_RECOVER: ['SyncStatusRecover']
+ QUICK_DETECTION_COMPLETE: ['DetectionWrapup']  // §B 新增 DetectionWrapup (v1.8.5 是空 [])
```

#### isOrchestratorCodedWire — 由 7 prefix 降到 6 prefix

```diff
- // 3. QuickDetection 7 阶段 (7 phase 名 OR regex)
- if (/^(DETECT_INIT|SWITCH_DETECT|...)/.test(wireCode)) return true;
+ // 3. QuickDetection 7 阶段 (v1.9.0 单 prefix)
+ if (wireCode.startsWith('QUICK_DETECTION_')) return true;

- // 5. BARGRAPH_CHECK_ONLINE_STATUS → BARGRAPH_DETECT phase
- if (wireCode.startsWith('BARGRAPH_CHECK_ONLINE_STATUS')) return true;
  // 5. Edge 自合成 → EDGE_PROXY (v1.9.0 由 #6 升 #5)
```

**§D BARGRAPH_CHECK_ONLINE_STATUS prefix 删除底层逻辑**:
- 设备端 sg7-31-win 实测 0 emit (`CuredActionCode.BargraphCheckOnlineStatus=30` 仅作内部 routing)
- C# `CuredActionCode.BargraphCheckOnlineStatus=30` enum 保留 (设备端 3 处 caller 引用)
- 协议方 spec 删 wire prefix #5 = dead spec 清理 (MEMORY rule #6 新增: spec vs 内部 routing 解耦)

#### v1.9.0 cycle 协作记录

- Round 0 proposal: `claude-docs/discussions/protocol-team-proposal-progressphase-detection-namespace-v1.9.0.md`
- Round 1 review (设备端): `claude-docs/discussions/device-team-round-1-review-progressphase-detection-namespace-v1.9.0.md` (Cured 14 命令清单黄金 evidence)
- Round 2 reply (协议方): `claude-docs/discussions/protocol-team-reply-2-to-device-review-progressphase-detection-namespace-v1.9.0.md` (§A-§F 决议 + §B/§C 选 B 激进)
- Round 3 final ack (设备端): `claude-docs/discussions/device-team-final-ack-progressphase-detection-namespace-v1.9.0.md` (ACCEPT 全部 + §1 文字 typo 澄清 NetworkScan/SyncDeviceCheck)

#### MEMORY rule 升级 5→7 项

| Rule | v1.9.0 cycle 贡献 |
|---|---|
| #1 治标 vs 治本 | 协议方主动升 enum 治本, 不扩 prefix 打补丁 |
| #2 grep 全项目同型模式 | 设备端 Round 1 §3.2 实测 103 hits + §E PR checklist 4 grep 集成 |
| #3 反向 invariant | 命名空间统一 — 所有 detection wire 必带 QUICK_DETECTION_ 前缀 |
| #4 spec 改动 vs 实施改动 | v1.9.0 是 spec gap, 协议方升 minor 版本合理 |
| #5 wire emit PR checklist 3 项 | 累计 7 项 (本 cycle §E 新加 4 grep) |
| **#6 (新)** | **spec vs 内部 routing 解耦** — BARGRAPH_CHECK_ONLINE_STATUS 典型 (wire 删 / C# enum 保留) |
| **#7 (新)** | **Cured 命令清单 spec 固化** — 14 enum 值落 canonical doc, 防未来盲区 |

#### 自我反省 — Round 2 reply §1/§7.1 step 例子值文字 typo

| 项 | reply 文档 vibe 值 | 实际 spec 源码值 (src/index.ts:826-832) |
|---|---|---|
| SWITCH_DETECT 例子 step | `SwitchPing` (typo) | `NetworkScan` ✅ |
| SYNC_DETECT 例子 step | `CommBoardSync` (typo) | `SyncDeviceCheck` ✅ |

**owner 担责**: 设备端 Round 3 §1.2 主动 grep 协议方源码对照才发现. 不影响 publish (publish 走源码), 但 MEMORY rule #2 第 7 次落地不彻底. v1.9.0 spec 矩阵实际值用源码值 (NetworkScan/SyncDeviceCheck).

---

## [1.8.5] - 2026-05-18

### Spec invariant 显式声明 — PROGRESS_UPDATE / COMMAND_RESPONSE / PROGRAM_RESPONSE status 字段值域互斥

**核心变更** (3 个 interface TypeScript literal type 收紧 + 4 个新 Edge validator fail code):

#### Schema 改动 (TypeScript literal type 收紧)

```diff
- export interface ProgressUpdateMessage extends BaseMessage {
-   status: MessageStatus;       // v1.6.0 统一 status enum
- }
+ export interface ProgressUpdateMessage extends BaseMessage {
+   status: 'IN_PROGRESS';       // v1.8.5: literal type 收紧
+ }

- export interface CommandResponseMessage extends BaseMessage {
-   status: MessageStatus;
- }
+ export interface CommandResponseMessage extends BaseMessage {
+   // v1.8.5 反向约束: 不可 'IN_PROGRESS' (那是 PROGRESS_UPDATE 专属)
+   status: 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'TIMEOUT';
+ }

- export interface ProgramResponseMessage extends BaseMessage {
-   status: MessageStatus;
- }
+ export interface ProgramResponseMessage extends BaseMessage {
+   status: 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'TIMEOUT';
+ }
```

#### Edge validator 新增 4 fail code

| Code | 触发条件 |
|---|---|
| `ERROR_PROGRESS_UPDATE_STATUS_MISSING` | PROGRESS_UPDATE wire 缺 status 字段 |
| `ERROR_PROGRESS_UPDATE_TERMINAL_STATUS_NOT_ALLOWED` | PROGRESS_UPDATE wire status ≠ 'IN_PROGRESS' (违反 spec invariant) |
| `ERROR_RESPONSE_STATUS_MISSING` | COMMAND_RESPONSE / PROGRAM_RESPONSE wire 缺 status 字段 |
| `ERROR_RESPONSE_NON_TERMINAL_STATUS_NOT_ALLOWED` | COMMAND_RESPONSE / PROGRAM_RESPONSE wire status = 'IN_PROGRESS' (反向约束) |

#### 部署策略 — 单步 ship REJECT (0 grace mode)

**用户决策 2026-05-18**: v1.8.5 直接 REJECT, 不走 grace mode. 理由:
- 项目未正式上线 (jrsoft-windows 内网测试机 + aliyunlight 生产准备环境, 无真实工地用户流量)
- 100% 确定无未知下游 (只有 sg7-31-win 设备端 + 协议方 test-tools)
- 设备端 commit H+I 已合规, ship 当天 0 违规
- owner 担责, 立刻 enforce 优于 2 周观察

#### Schema 版本 (PROTOCOL_VERSION)

**升级 `'1.8.4'` → `'1.8.5'`** (minor — additive spec 约束 + 反向 invariant + Edge validator REJECT, **单步 ship**)

#### 历史教训引用 (v1.8.4 cycle)

本 spec invariant 来自 v1.8.4 cycle 设备端 sg7-31-win 报告 (`SYNC_MONITORING_EXPORT_DAY_PROGRESS`
status=COMPLETED 与 CodeMeta META 矛盾) 的反推:

- 设备端 commit H (`6d3ebae`) 修 `DeviceResponseExtensions.cs:815` SendCuredProgressUpdate helper (治本)
- 设备端 v1.8.5 Round 1 review 自检 grep 全项目, 发现 commit I 补 2 处漏修:
  - `UnifiedResponseManager.cs:646` (SendProtocolProgressAsync)
  - `MainForm.TunnelConnector.cs:675` (_tunnelV2Adapter callback)
- 设备端 1583/1583 C# tests pass, 全项目 0 处 progress-based COMPLETED 派生
- spec 收紧后这些隐藏漏点不再可能潜伏

参见:
- `claude-docs/discussions/device-team-ack-of-sync-monitoring-day-progress-status-mismatch-v1.8.4.md`
- `claude-docs/discussions/device-team-review-of-progress-update-status-invariant-v1.8.5.md`
- `claude-docs/discussions/protocol-team-reply-2-to-device-review-progress-update-status-invariant-v1.8.5.md`

#### v1.8.5 cycle 议价记录

| Round | 文档 | 立场 |
|---|---|---|
| Round 0 | protocol-team-proposal-progress-update-status-invariant-v1.8.5.md | canonical proposal |
| Round 1 | device-team-review-of-progress-update-status-invariant-v1.8.5.md | ACCEPT 全部 + 4 加强建议 + commit I 自检补漏 |
| Round 2 | protocol-team-reply-2-to-device-review-progress-update-status-invariant-v1.8.5.md | ACCEPT §4 4 项 + 自我反思 #2 + Phase B/C/D 整合 |
| Round 3 | (等设备端) | final ack |
| Round 4 | publish v1.8.5 | npm publish + Edge tier upgrade |

#### 协议方 MEMORY 升级 (cycle 长效价值)

v1.8.5 cycle 给协议方 MEMORY 升级 2 条规则 (`feedback_treat_symptom_vs_root_cause.md`):
- **grep 全项目同型模式** — 修 1 个 helper ≠ 全项目修对, 必须 grep 全项目 0 同型派生才算 close (来源: 设备端 Round 1 §1.1 grep 出 commit H 漏 2 处)
- **反向 invariant 设计原则** — 设计 spec 约束时不能只单向, 必须同时校验反向 (来源: 设备端 Round 1 §4.2)

---

## [1.8.4] - 2026-05-17

### npm 版本号注记 — v1.8.3 unpublished, 跳到 1.8.4

**v1.8.3** (shasum `8e982faf5540d6ffda11e1081048de4dd66d674c`) 发布 30 分钟后 unpublish,
原因: 设备端 follow-up CLI subcommand request 顺手并入. **npm policy 不允许重发同版本号**
(防 supply chain 攻击), 故跳到 1.8.4 累积 patch ship.

v1.8.4 = v1.8.3 + CLI follow-up:
- 全部 v1.8.3 clean break / wire-code-based / flag day 设计**不变** (议价 cycle Round 9'''/10'''/11''' 决议不变)
- 仅 additive 新增 CLI `subway-protocol resolve --batch` device-friendly output (Round 7'''' follow-up)

### Schema 版本 (PROTOCOL_VERSION)

**升级 `'1.8.2'` → `'1.8.4'`** (patch — 跳 1.8.3 / additive type relaxation + clean break Edge validator + CLI follow-up, **flag day deploy** required)

### Background — v1.8.x cycle 长尾 wart

设备端 sg7-31-win v1.8.x cycle audit 实证 (Round 8' + Round 8''):
- BATCH/COMPLEX 子项 wire (orchestrator-coded `BATCH_*` / `PROGRAM_*` / `*_DETECT_*` etc.) 已修, phase=BATCH_EXECUTE 等正确
- **leaf 码 wire** (SYNC/RATER/BARGRAPH SIMPLE 命令 + Cured-with-leaf-wire) 失败 wire 仍 stuck:
  - ErrorPhase 18 值无适用于独立 SIMPLE 命令的 phase
  - 设备端 `EnsureFailedReportHasErrorBlock` 兜底用 `phase=UNKNOWN/step=UNKNOWN` 占位
  - Edge `VALID_ERROR_PHASES` 白名单不含 UNKNOWN → wire 被严拒 → 合成假 `EDGE_COMMAND_REJECTED Command timeout`
  - dashboard ⚠️ TOLERATED / REJECTED 长尾

### 议价 cycle (Round 9'''/10'''/11''') 闭合

| Round | 文档 | 立场 |
|---|---|---|
| Round 9''' | protocol-team-reply-to-proposal-v1.8.3.md | ACCEPT 6 + open Sub-decision 3 (backward UNKNOWN) |
| Round 10''' | device-team-final-ack-v1.8.3.md | 6 ACCEPT + 1 **PUSH BACK** clean break |
| Round 11''' | protocol-team-reply-2-to-proposal-v1.8.3.md | ACCEPT clean break, 撤销 §3.3, cycle CLOSED |

### Changed — ErrorInfo phase/step 改 optional (additive)

```diff
  export interface ErrorInfo {
-   phase: ErrorPhase;       // 必填
-   step: ErrorStep;         // 必填
+   phase?: ErrorPhase;      // v1.8.3: optional (orchestrator-coded wire 必填; leaf wire 必须缺失)
+   step?: ErrorStep;        // v1.8.3: optional (同 phase)
    category: ErrorCategory; // 不变 — 必填
    detail: string;          // 不变 — 必填
  }
```

**SemVer 论据**: additive change (TypeScript 类型层 `T` → `T?` 对现有 consumer 0 break) + 0 第三方依赖 (grep 实证) + 延续 v1.8.x patch cycle 节奏 → patch (1.8.3) 而非 minor (1.9.0)。

### Added — `isOrchestratorCodedWire(wireCode)` export

新 helper 判定 wire `data.code` 是否携带 orchestrator phase 语义:

| wire code prefix | 判定 | phase 语义 |
|---|---|---|
| `BATCH_*` | orchestrator | BATCH_EXECUTE |
| `PROGRAM_(INIT/FETCH/EXTRACT/PREPROCESS/COMPILE/UPLOAD/STATS/COMPLETE)` | orchestrator | PROGRAM_* 8 阶段 |
| `(DETECT_INIT/SWITCH_DETECT/SWITCH_CONFIG_READ/SYNC_DETECT/BARGRAPH_DETECT/SYNC_RECOVER/DETECT_COMPLETE)` | orchestrator | DETECT_* 7 阶段 |
| `SYNC_MONITORING_TABLE_*` / `SYNC_EXPORT_*` | orchestrator | SYNC_EXPORT |
| `BARGRAPH_CHECK_ONLINE_STATUS_*` | orchestrator | BARGRAPH_DETECT |
| `EDGE_*` | orchestrator | EDGE_PROXY |
| **其它** (SYNC/RATER/BARGRAPH SIMPLE + Cured-with-leaf-wire) | **leaf** | (无, phase/step 必须缺失) |

**与设备端 sg7-31-win C# `IsOrchestratorCodedWire` (commit F) 完全同源**. 未来加新 orchestrator phase 时**双方必须同步**加 prefix 判。

### ⚠️ commandType 陷阱红线 — 设计哲学

`command.commandType` 反映 **caller frame**, `data.code` 反映 **wire 形态本身**. 两个维度独立, 不能互推:

- BATCH 子项 PROGRESS_UPDATE: `command.commandType="SIMPLE"` 但 wire `data.code="BATCH_*_FAILED"` → orchestrator-coded
- COMPLEX (e.g. QuickDetection) 子项: `command.commandType="SIMPLE"` 但 wire `data.code="SWITCH_DETECT_FAILED"` → orchestrator-coded
- Cured (e.g. SyncStartingSpeedLimitSet) 多步: caller 多步, wire `data.code="SYNC_STARTING_SPEED_LIMIT_SET_FAILED"` → leaf

**红线**: validator / 派生 / 路由 判据**必须**用 `isOrchestratorCodedWire(wireCode)` (基于 wire data.code), **禁止**用 `command.commandType`.

未来 PR 加 orchestrator phase 时 checklist:
- [ ] 已同步加 `isOrchestratorCodedWire` prefix?
- [ ] 已通知设备端 sg7-31-win 镜像加 C# helper?

### Changed — Edge validator clean break (无 backward UNKNOWN 通道)

```typescript
// services/jrsoft-subway-edge/src/websocket/error-schema-validator.ts

if (!isOrchestratorCodedWire(wireCode)) {
  // leaf 路径 clean break: phase/step 必须缺失, 任何值 (含 UNKNOWN) 即 REJECT
  if (errorBlock.phase !== undefined) return { fail: 'ERROR_BLOCK_LEAF_HAS_PHASE' };
  if (errorBlock.step  !== undefined) return { fail: 'ERROR_BLOCK_LEAF_HAS_STEP'  };
  return null;
}
// orchestrator 路径 clean break: UNKNOWN 占位 REJECT (与 leaf 对称)
if (errorBlock.phase === 'UNKNOWN') return { fail: 'ERROR_BLOCK_UNKNOWN_PHASE_NOT_ALLOWED' };
if (errorBlock.step  === 'UNKNOWN') return { fail: 'ERROR_BLOCK_UNKNOWN_STEP_NOT_ALLOWED'  };
```

**clean break 而非渐进迁移**: 设备端 §2.6 论据 5 条全部成立:
1. 未来无 zombie 代码
2. 设备端立场一致 (设备端不发 + Edge 不收, 对称)
3. flag day 痛苦有限 (部署面是受控工控机线路, 不是公网无数客户端)
4. 倒逼 ops 严肃 (留 backward = 可慢慢升, clean break = 必须严肃升)
5. 省一次未来 cycle (半年后还得开 v1.9.0 cycle 移除 backward, 不如一步到位)

### ⚠️ Deployment Mode — FLAG DAY (非滚动)

v1.8.3 必须 **协议方 Edge + 全部设备 sg7-31-win 同窗口期 deploy**, 不能滚动:

```
T-3 天  协议方 publish v1.8.3 npm (生产 Edge 仍 v1.8.2)
T-2 天  设备端 sg7-31-win 全部线路 rebuild + redeploy (临时 REJECTED 窗口开启)
T-1 天  双方 staging 联调验证 wire-code-based 判 + clean break
T+0 天  协议方 Edge v1.8.3 production deploy
```

**T-2 → T+0 ~2 天窗口期**: 设备端 SIMPLE 失败 wire 会被 v1.8.2 Edge REJECTED + 合成假 timeout. ops 应:
- 提前 ≥3 天通告业务侧
- T-2 选低业务流量窗口 (e.g. 周末凌晨)
- 应急回滚预案 (Docker tag pin)

### Test Coverage

- `services/jrsoft-subway-protocol/src/__tests__/wire-code-based-validation.test.ts` — 新增 5 describe / 17+ 用例 (orchestrator prefix / leaf / boundary / commandType 陷阱 / 镜像同源)
- `services/jrsoft-subway-edge/test/error-schema-validator-v1.8.3.test.ts` — 新增 6 describe / 15+ 用例 (clean break / orchestrator strict / commandType 陷阱)

### Cross-team co-evolution

- 设备端 sg7-31-win commit **A** (MainForm.MessageBus.cs, ~50 LOC) + **C** (DeviceResponseExtensions.cs + 5 callers, ~25 LOC) + **F** (ProtocolClient.cs EnsureFailedReportHasErrorBlock + C# `IsOrchestratorCodedWire` helper, ~40 LOC), 1583/1583 tests passed
- 协议方 `isOrchestratorCodedWire` npm export 与设备端 C# helper 同源, 加新 orchestrator phase 时双方同步约束 (设备端 MEMORY.md `v1-8-x-cycle-wire-code-judgment-mirror` 已记录)

### Added — CLI `subway-protocol resolve --batch` device-friendly output (Round 7'''' follow-up)

设备端 follow-up request (`device-team-followup-request-cli-tool-v1.8.3.md`) 顺手并入 v1.8.3:

```bash
npx @thejrsoft/subway-protocol resolve --batch < docs/wire-emit-codes-extended.jsonl
```

- 新增 `bin.subway-protocol` 别名 (旧 `subway-protocol-resolve` 保留)
- 第一个 positional "resolve" subcommand 风格被 shift, 兼容 `subway-protocol resolve --batch` 调用
- batch 输出新格式 (向后兼容): `{code, ctx, derivedMeta, matchedBy, expected?, match?, diff?}` JSONL
  - `code` / `ctx` 顶层平铺 (替代旧 `input` 嵌套)
  - `derivedMeta` 同 `derived` (设备端 docs/scripts/resolve-base-meta.js 同名)
  - `matchedBy` 新字段: `EXPLICIT` / `SUFFIX:<suffix>` / `FALLBACK`, 附 `+R1+R2...` 上下文规则标记
  - `expected` / `match` / `diff` 保留 (向后兼容, 仅 input 含 expected 时输出)
- 设备端可弃用本地 `docs/scripts/resolve-base-meta.js` 临时 mirror script
- jest: `src/__tests__/cli-batch.test.ts` 8 用例 (basic / FALLBACK / EXPLICIT / SUFFIX / R1-R7 / 多行 / expected 兼容 / subcommand shift)

### Discussion docs

- `claude-docs/discussions/device-team-proposal-error-block-optional-for-simple-v1.8.3.md` (~425 行)
- `claude-docs/discussions/protocol-team-reply-to-proposal-v1.8.3.md` (Round 9''')
- `claude-docs/discussions/device-team-final-ack-v1.8.3.md` (Round 10''' 6 ACCEPT + 1 PUSH BACK)
- `claude-docs/discussions/protocol-team-reply-2-to-proposal-v1.8.3.md` (Round 11''' 议价闭合)

---

## [1.8.2] - 2026-05-17

### Schema 版本 (PROTOCOL_VERSION)

**升级 `'1.8.1'` → `'1.8.2'`** (patch — additive map fix, 全向后兼容)

### Fix — ERROR_STEP_BY_PHASE map 补 v1.5.0 设计塌方残留

**触发**: 设备端 v1.8.1 staging Round 8' audit dashboard 实证:
```
req_1778991911621_gjqlbi BATCH BARGRAPH_MISALIGNMENT READ
  PROGRESS_UPDATE BARGRAPH_MISALIGNMENT_READ_FAILED ⚠️ TOLERATED
    宽容放行原因 (2) — Edge META 校验未过但已 forward:
      • data.error.phase "BATCH_EXECUTE" 不在白名单内
      • data.error.step "DataTransfer" 不属于 phase "BATCH_EXECUTE"
```

**根因 (grep 实证)**:
- `ErrorPhase` type (index.ts:506-524, 18 值) 含 `BATCH_EXECUTE` + `SYNC_EXPORT` ✓
- `ERROR_STEP_BY_PHASE` map (index.ts:770-789, 16 key) **缺这 2 key** ❌
- Edge `VALID_ERROR_PHASES = Object.keys(ERROR_STEP_BY_PHASE)` 派生自 map, 也缺
- 设备端合法 wire `phase=BATCH_EXECUTE` 被 Edge 误判 → TOLERATED

**v1.5.0 设计塌方残留** — 协议方加 ErrorPhase enum 时漏同步 map, 1 年后 v1.8.x cycle audit 终于触发。

### Added — ErrorStep type 补 4 候选 step (additive)

```diff
  export type ErrorStep =
    ... (现有 22 个 step) ...
+   // v1.8.2: BATCH_EXECUTE step (BATCH 子项 failure 路径)
+   | 'SubItemTimeout'
+   | 'SubItemDeviceQuery'
+   // v1.8.2: SYNC_EXPORT step (监播表导出 failure 路径)
+   | 'ReadDayData'
+   | 'AggregateExport';
```

`DataTransfer` step 已存在 (PROGRAM_UPLOAD step), v1.8.2 在 BATCH_EXECUTE 复用 — 设备端 commit c9bdc03 已用此 step。

### Added — ERROR_STEP_BY_PHASE map 补 2 key

```diff
  export const ERROR_STEP_BY_PHASE: Record<string, readonly ErrorStep[]> = {
    ...
    EDGE_PROXY: [...] as const,
+   BATCH_EXECUTE: ['DataTransfer', 'SubItemTimeout', 'SubItemDeviceQuery'] as const,
+   SYNC_EXPORT: ['ReadDayData', 'AggregateExport'] as const,
  } as const;
```

### Edge / Gateway / Backend / 设备端

- **设备端**: 0 改动 (wire 已 emit BATCH_EXECUTE/DataTransfer, 修后 Edge 直接 STRICT 通过)
- **Edge**: 0 code 改动 (VALID_ERROR_PHASES 自动从新 map 派生)
- **Gateway**: 0 code 改动
- **Backend**: 0 code 改动, 仅 `package.json` bump `@thejrsoft/subway-protocol@^1.8.2`

### backward-compat

- 纯 additive (新增 4 ErrorStep 值 + 2 map key, 0 删除 / 0 修改现有值)
- 0 第三方 import ErrorStep 常量 (grep 实证)
- npm `^1.8.1` → `1.8.2` 自动取, 0 break

### Test

- `__tests__/error-phase-batch-execute.test.ts` (新) +5 tests:
  - `isValidErrorStepForPhase('DataTransfer', 'BATCH_EXECUTE')` 应 true
  - `isValidErrorStepForPhase('SubItemTimeout', 'BATCH_EXECUTE')` 应 true
  - `isValidErrorStepForPhase('ReadDayData', 'SYNC_EXPORT')` 应 true
  - `Object.keys(ERROR_STEP_BY_PHASE)` 应 ⊇ `ErrorPhase` 全 18 值 (镜像一致性)
  - 已有 PROGRAM_UPLOAD step 兼容性回归

### 设计协商记录

- Round 9'' reply: `claude-docs/discussions/protocol-team-reply-to-batch-execute-gap-v1.8.1.md`
- Round 10'' final ack: `claude-docs/discussions/device-team-final-ack-v1.8.1.md`
- v1.8.1 Phase 6 deploy 取消 (transition mode 第 2 次)

### 设计 lesson learned (写入 v1.9.0 cycle Round 0 self-check)

设备端 Round 10'' §6 补充 — 协议方 CI 加 enum 镜像一致性 lint:

```bash
# v1.9.0 协议方 CI:
for type in ErrorPhase ProgressPhase CodeSemantic; do
  type_values=$(grep ...) ; map_keys=$(grep ...)
  diff <(echo "$type_values") <(echo "$map_keys") || { echo "❌ drift"; exit 1; }
done
```

---

## [1.8.1] - 2026-05-17

### Schema 版本（PROTOCOL_VERSION 常量）

**升级 `'1.8.0'` → `'1.8.1'`**（patch — envelope 重构 + 删 mutation-injected 字段, 全向后兼容）。

### Fix — envelope 重构 (设备端 Round 8 audit P1 finding)

**触发**: 设备端在 staging 看到 dashboard 展开 PROGRESS_UPDATE payload 含 `metaCompliance` 字段, 误判 "Edge/Gateway 私自加字段"。grep 证实是 Edge `device-websocket.server.ts:444/465` 用 `(message as any).metaCompliance = ...` mutate device 原始 wire payload。

**根因**: v1.8.0 把 Edge metadata (`metaCompliance`) 加进 `ProgressUpdateMessage` wire schema 是 separation of concerns 塌方 — 业务数据 (device payload) 不应被 Edge metadata 污染。

### Removed — `ProgressUpdateMessage.metaCompliance` 字段

```diff
  interface ProgressUpdateMessage {
    ...
-   metaCompliance?: 'STRICT' | 'TOLERATED';
  }
```

**backward-compat 实证**: 全 repo 仅协议方 own 4 文件 (Edge/Gateway/dashboard) 用此字段; 设备端 C# / Backend / 第三方 0 grep 命中。删字段对 npm consumer ^1.8.0 → 1.8.1 全兼容。

### Added — `EdgeForwardEnvelope` interface (Edge↔Gateway 内部协议)

```typescript
export interface EdgeForwardEnvelope {
  edgePayload: ProgressUpdateMessage | CommandResponseMessage | ProgramResponseMessage;
  edgeAnnotation: {
    metaCompliance: 'STRICT' | 'TOLERATED';
    validationFailures?: string[];
    edgeReceivedAt: string;
  };
  envelopeVersion: '1.8.1';
}

export function isEdgeForwardEnvelope(msg: unknown): msg is EdgeForwardEnvelope;
```

**协议层使用范围**: 仅 Edge↔Gateway WebSocket 内部协议, **不是 device-emit wire 协议**。设备端 wire 0 改动。

### Edge / Gateway 集成

- Edge `device-websocket.server.ts`: 删 `(message as any).metaCompliance` mutate
- Edge `protocol-websocket.client.ts` forward to Gateway: 包 EdgeForwardEnvelope, 不再 send raw message
- Gateway `message.handler.ts`: 解 envelope (含 forward-compat: 老 Edge raw message 仍兼容)
- Gateway `command-message.store.ts`: `meta_compliance` 列从 envelope.edgeAnnotation 读, 不再从 payload 读
- Gateway dashboard `messages.html`: `it.metaCompliance` (store 列) 替换 `it.payload?.metaCompliance`

### Backend / 设备端 / 第三方

- Backend: 0 code 改动, 仅 `package.json` bump `@thejrsoft/subway-protocol@^1.8.1`
- 设备端: **0 改动** (wire / C# / catalog / CodeMetaResolver 全不变)
- 第三方: 0 影响 (grep 实证)

### 设计协商记录

- Round 9 reply: `claude-docs/discussions/protocol-team-reply-to-consumer-audit-v1.8.0.md`
- Round 10 final ack: `claude-docs/discussions/device-team-final-ack-v1.8.0.md`
- v1.8.0 测试环境 / 生产 Phase 6 deploy 取消 (transition mode)

### Test

- `__tests__/edge-forward-envelope.test.ts` +10 测试 (envelope shape + 类型守卫 + 兼容老 raw message)
- 全量 161+10 = 171 tests passing (0 regression)

### 设计 lesson learned (写入 v1.9.0 cycle Round 0 checklist)

协议方 Round 0 写 upgrade-guide 时, 排查所有新加 interface 字段:
- 字段是 device 业务数据? 还是中间件 metadata?
- 若是 metadata (e.g. `processedBy`, `forwardedAt`, `validationStatus`) → 应放 envelope 层, **不**注入 device wire schema

---

## [1.8.0] - 2026-05-16

### Schema 版本（PROTOCOL_VERSION 常量）

**升级 `'1.7.7'` → `'1.8.0'`**（minor bump — 上下文派生 + Edge 宽容策略, 全向后兼容）。

### Major — resolveCodeMeta(code, ctx?) 升维

**触发**: v1.7.x cycle 多轮 META 命名空间冲突 + 真实测试环境 17 条 Edge rejected_messages 归因。BATCH 子项 PROGRESS_UPDATE wire 拷贝 SIMPLE 命名约定 (设备端历史架构), 协议层 1 维查表全部命中终态 → Edge 严格 reject → dashboard 失去实时反馈。

### Added — 5 维上下文派生 + R1-R7 规则

```typescript
export function resolveCodeMeta(code: string, ctx?: CodeMetaContext): CodeMeta;
export interface CodeMetaContext {
  messageType?, parentCommandType?, phase?, sourceType?, subCommandType?
}
```

- **R1**: PROGRESS_UPDATE 终态 code 降级 step META
- **R2**: BATCH/COMPLEX 父 + SIMPLE 子 终态降级 (R1 双保险, 不限 phase, Q13)
- **R3**: EDGE_CACHE phase + sourceType ≠ EDGE → invariant 破坏 (强制 reject, Q8)
- **R4/R5/R7**: PROGRAM_/QUICK_DETECTION_/SYNC_MONITORING_ 命名空间合规 WARN
- **R6**: EXPLICIT_META 终态 code 在 PROGRESS_UPDATE 中合法降级 (R1 覆盖)

### Added — SUFFIX_RULES +2 (Q9)

- `_STEP_OK` → IN_PROGRESS/INFO/step_success
- `_STEP_FAIL` → IN_PROGRESS/WARNING/phase_failed/无 errBlock

### Changed — Q5 fix

- `QUICK_DETECTION_ALL_OFFLINE.status` COMPLETED → FAILED (解决 COMPLETED+ERROR 语义打架)

### Added — ProgressUpdateMessage.metaCompliance / CLI tool / Edge 宽容策略 / Gateway UI ⚠️

详见 `claude-docs/discussions/upgrade-guide-v1.8.0.md` + Round 0-4 协商档案。

### Test

- `__tests__/code-meta-context.test.ts` +31 测试, 全量 **161/161 passed**, 17 历史拒收样本全过 0 reject

---

## [1.7.8] - 2026-05-15

### Schema 版本（PROTOCOL_VERSION 常量）

**升级 `'1.7.6'` → `'1.7.7'`**（SUFFIX_RULES 新增 2 条对称侧终态规则）。

### Fix — SUFFIX_RULES 第 3 轮对称补完（v1.7.7 留漏）

**触发**: v1.7.7 cycle wire 联调 `req_1778824943659_4gaxtv`（`BARGRAPH_MISALIGNMENT_WRITE_BROADCAST_SUCCESS`）

- 设备端 ECAN 广播 WRITE 路径 emit `<COMMAND>_WRITE_BROADCAST_SUCCESS` / `_FAILED`
- v1.7.7 SUFFIX_RULES 16 条全部 miss `_BROADCAST_*`，落 `_SUCCESS` / `_FAILED` 通用规则
- → 命中 IN_PROGRESS step_success / phase_failed，与 wire `status: COMPLETED|FAILED` 终态不符
- → Edge 等不到 terminal → 30s 假 timeout（完全镜像 v1.7.5/v1.7.6/v1.7.7 漏洞模式）

### 修复（方案 A — 扩展 SUFFIX_RULES）

新增 2 条规则（按 endsWith 长后缀优先命中，位置严格排序）：

```typescript
{ suffix: '_BROADCAST_FAILED',  meta: { isTerminal: true, status: FAILED,    level: 'ERROR', semantic: 'failure', requiresErrorBlock: true  } },
{ suffix: '_BROADCAST_SUCCESS', meta: { isTerminal: true, status: COMPLETED, level: 'INFO',  semantic: 'success', requiresErrorBlock: false } },
```

**v1.7.8 后 SUFFIX_RULES 18 条完整优先级**:
- `_READ_FAILED` / `_WRITE_FAILED` / `_ALL_FAILED` / **`_BROADCAST_FAILED`** > `_FAILED`
- `_RETRY_PENDING` / `_RETRY` / `_WARN` / `_SKIPPED` / `_COMPLETED` / `_START` / `_PROGRESS`
- `_READ_SUCCESS` / `_WRITE_SUCCESS` / `_ALL_SUCCESS` / `_PARTIAL_SUCCESS` / **`_BROADCAST_SUCCESS`** > `_SUCCESS`
- EXPLICIT_META 始终最优先（17 条 closed-enum）

### 影响范围

5 个光柱命令的广播 WRITE 终态（共 10 个 wire code）—— 见设备端 `wire-emit-codes.md` §2:
- `BARGRAPH_LED_SWITCH_WRITE_BROADCAST_{SUCCESS,FAILED}`
- `BARGRAPH_PLAYBACK_FORBID_WRITE_BROADCAST_{SUCCESS,FAILED}`
- `BARGRAPH_MISALIGNMENT_WRITE_BROADCAST_{SUCCESS,FAILED}` ← 本次直接暴露
- `BARGRAPH_PLAY_FRAMES_NUMBER_WRITE_BROADCAST_{SUCCESS,FAILED}`
- `BARGRAPH_PROGRAM_PLAY_IMMEDIATELY_WRITE_BROADCAST_{SUCCESS,FAILED}`

注：READ 不广播（CAN 总线多设备并发响应冲突），仅 WRITE 受影响。

### Process — 工具卡 `check:consumer-ack` 进 prepublishOnly

**根因**: v1.7.5/v1.7.6/v1.7.7 三轮"对称补完"漏洞模式 + v1.7.7 cycle 协议方直接 publish 跳过 Round 2 reply 的 Principle 5 违规。

**新增 prepublishOnly 第 5 stage**:
```json
"prepublishOnly": "npm run clean && npm run build && npm run check:protocol-version && npm run check:dead-spec && npm run check:consumer-ack"
```

`scripts/check-consumer-ack.sh` 验证 `claude-docs/discussions/` 内存在对应版本的 `device-team-(final-)?ack-(of-protocol-reply-<topic>-)?v<X.Y.Z>.md` 文档。
- PASS → publish 继续 + audit jsonl 写 PASS
- ESCAPE_HATCH_USED → 显示文本 banner + audit jsonl 写 ESCAPE_HATCH_USED + 要求 CHANGELOG 自首
- FAILED_NO_ACK → 阻塞 publish + audit jsonl 写 FAILED_NO_ACK

**v1.7.7 cycle 违规回溯**: `scripts/escape-hatch-audit.jsonl` 首行 baseline 条目（`ESCAPE_HATCH_USED_RETROACTIVE`）公开标注协议方 v1.7.7 cycle 跳 Round 2 reply 直接 publish 的违规历史，建立"自首文化"。

### 配套文档

- `CONTRIBUTING.md`（新增）—— Release Process 强制流程 + cross-check 守则 + escape hatch 自首要求 + 实施清单
- `docs/release-ops.md`（新增）—— 月度 escape hatch review SOP + 健康阈值（0/1/2/≥3 次 + 累计 ≥5 次 hard stop）
- `docs/release-ops/escape-hatch-spec.md`（新增）—— PARTIAL ACCEPT 决策 + 当前实施版本 + v1.7.9/v1.8.0/v2.x 后续路径
- `docs/wire-emit-codes.md`（mirror，更新版）—— 设备端 source of truth catalog

### 测试

新增 `src/__tests__/code-meta-broadcast-terminal.test.ts` 13 测试覆盖：
- 5 命令家族 BROADCAST_SUCCESS 终态（含真实联调 `BARGRAPH_MISALIGNMENT_WRITE_BROADCAST_SUCCESS`）
- BROADCAST_FAILED 终态 + requiresErrorBlock 验证
- 优先级回归 4 测试（`_BROADCAST_*` > `_SUCCESS/_FAILED` 通用 + 阶段事件仍 IN_PROGRESS + EXPLICIT_META 最优先）
- isKnownCode 含反向 case

测试总数 117 → 130 全过。

### Migration notes

**Wire 行为零变化** — 设备端 wire 不变。本版仅修协议方 META 派生表 + 加 publish gate hook。

升级前: 设备端 `BARGRAPH_MISALIGNMENT_WRITE_BROADCAST_SUCCESS` 响应被 Edge 拒收 + 30s 假 timeout
升级后: 同样 wire 响应被 Edge 正确识别为 COMPLETED 终态，1s 内回传 Gateway

**消费方升级路径**: `npm install @thejrsoft/subway-protocol@^1.7.8` + 重启 Edge / Backend / Gateway。无代码变更。

### Cycle Process 改进（per protocol-version-coevolution skill）

本 cycle 共 5 轮严守流程:
- Round 1: 设备端 broadcast-gap audit（在 v1.7.7 ack 文档 §2 中）
- Round 2: 协议方 reply（`protocol-team-reply-to-broadcast-gap-v1.7.8.md`）— **零 src 改动 / 零 publish**
- Round 3: 设备端 ack reply + handoff catalog + escape hatch suggestion
- Round 4a/4b: 协议方双 ack（handoff + suggestion）
- Round 5: 设备端 final ack → unblock implement
- Phase 4 implement → Phase 5 release notice → Phase 6 部署

对比 v1.7.7 cycle 直接 publish 跳过 Round 2 reply 的违规——v1.7.8 是 process 闭环典范。

---

## [1.7.7] - 2026-05-15

### Schema 版本（PROTOCOL_VERSION 常量）

**升级 `'1.7.5'` → `'1.7.6'`**（SUFFIX_RULES 新增 5 条对称侧终态规则）。

### Fix — SUFFIX_RULES 对称侧补完（v1.7.6 留漏）

**触发**: 设备端 v1.7.6 wire 联调 audit (commit `c80f09f` @ sg7-31-win)
- `req_1778824929909_jlvome` (BARGRAPH_MISALIGNMENT_READ_FAILED)
- `req_1778824891155_4rlb7b` (BATCH_*_PARTIAL_SUCCESS)
- `req_1778824875675_m9rqps` (BATCH_*_ALL_FAILED)

设备端发的 `status: FAILED` + `report.code: BARGRAPH_MISALIGNMENT_READ_FAILED` 终态响应：
- v1.7.6 META 派生路径：EXPLICIT_META miss → SUFFIX_RULES 命中 `_FAILED` → `{ isTerminal: false, status: IN_PROGRESS, semantic: 'phase_failed' }`
- **Mismatch**: wire FAILED 终态 vs META IN_PROGRESS → Edge 等不到 terminal → 30s 假 timeout
- 完全镜像 v1.7.5 时期的 `_READ_SUCCESS` bug，v1.7.6 只修 SUCCESS 侧留下 FAILED 对称漏

BATCH 三态（部分成功 / 全部失败）同理：`_PARTIAL_SUCCESS` 命中 `_SUCCESS` 规则 → step_success；`_ALL_FAILED` 命中 `_FAILED` 规则 → phase_failed。两者都被错判非终态。

### 修复（方案 A — 扩展 SUFFIX_RULES）

新增 5 条长后缀规则（按 endsWith 长后缀优先命中，放在原 `_FAILED` / `_SUCCESS` 之前）：

```typescript
{ suffix: '_READ_FAILED',  meta: { isTerminal: true, status: FAILED, level: 'ERROR', semantic: 'failure', requiresErrorBlock: true } },
{ suffix: '_WRITE_FAILED', meta: { isTerminal: true, status: FAILED, level: 'ERROR', semantic: 'failure', requiresErrorBlock: true } },
{ suffix: '_ALL_FAILED',   meta: { isTerminal: true, status: FAILED, level: 'ERROR', semantic: 'failure', requiresErrorBlock: true } },
{ suffix: '_ALL_SUCCESS',     meta: { isTerminal: true, status: COMPLETED, level: 'INFO',    semantic: 'success', requiresErrorBlock: false } },
{ suffix: '_PARTIAL_SUCCESS', meta: { isTerminal: true, status: COMPLETED, level: 'WARNING', semantic: 'partial', requiresErrorBlock: false } },
```

**优先级显式声明**（按规则数组顺序）:
- `_READ_FAILED` / `_WRITE_FAILED` / `_ALL_FAILED` > `_FAILED`
- `_ALL_SUCCESS` / `_PARTIAL_SUCCESS` > `_SUCCESS`
- `_READ_SUCCESS` / `_WRITE_SUCCESS` > `_SUCCESS`（v1.7.6 已加）
- EXPLICIT_META 始终最优先（PROGRAM_* / QUICK_DETECTION_* / SYNC_MONITORING_TABLE_* / EDGE_*）

### 影响范围（v1.7.0–v1.7.6 全部受影响）

**SIMPLE 失败终态家族**（已有命名约定的命令）：
- `DEVICE_DATETIME_INFORMATION_{READ,WRITE}_FAILED`
- `SYNC_FUNCTIONS_SWITCH_{READ,WRITE}_FAILED`
- `SYNC_PROGRAM_CONTROL_{READ,WRITE}_FAILED`
- `SYNC_PROGRAM_PUZZLE_{READ,WRITE}_FAILED`
- `SYNC_PROGRAM_SWITCH_{READ,WRITE}_FAILED`
- `SYNC_PIXEL_PITCH_SETTING_{READ,WRITE}_FAILED`
- `SYNC_BARGRAPH_SPACING_{READ,WRITE}_FAILED`
- `SYNC_STOP_AT_LOW_SPEED_SETTING_{READ,WRITE}_FAILED`
- `SYNC_STARTING_SPEED_LIMIT_{READ,WRITE}_FAILED`
- `RATER_LASER_SPACING_SETTING_{READ,WRITE}_FAILED`
- `BARGRAPH_MISALIGNMENT_{READ,WRITE}_FAILED` ← 本次直接暴露

**BATCH 终态家族**（每个 BATCH 命令派生 6 个）:
- `BATCH_<COMMAND>_{READ,WRITE}_ALL_SUCCESS`
- `BATCH_<COMMAND>_{READ,WRITE}_PARTIAL_SUCCESS`
- `BATCH_<COMMAND>_{READ,WRITE}_ALL_FAILED`

按 canonical doc BATCH 命令一览 15+ 命令 × 6 = 90+ 派生码。

### 测试

新增 `src/__tests__/code-meta-failure-batch.test.ts` 19 测试覆盖：
- SIMPLE FAILED 5 测试（含 BARGRAPH_MISALIGNMENT_READ_FAILED 真实联调失败 code）
- BATCH 三态 4 测试
- 优先级回归 6 测试（READ/WRITE/ALL/PARTIAL 长后缀 > 短后缀，EXPLICIT_META > SUFFIX_RULES）
- isKnownCode 4 测试

### Migration notes

**Wire 行为零变化** — 设备端 wire 不变。本版仅修协议方 META 派生表。

升级前: 设备端 `BARGRAPH_MISALIGNMENT_READ_FAILED` 响应被 Edge 拒收 + 30s 假 timeout
升级后: 同样 wire 响应被 Edge 正确识别为 FAILED 终态，0.7s 内回传 Gateway

**消费方升级路径**: `npm install @thejrsoft/subway-protocol@^1.7.7` + 重启 Edge / Backend / Gateway。无代码变更。

---

## [1.7.6] - 2026-05-15

### Schema 版本（PROTOCOL_VERSION 常量）

**升级 `'1.7.4'` → `'1.7.5'`**（SUFFIX_RULES 新增 2 条终态规则，是 schema 行为变化）。

### Fix — SUFFIX_RULES 新增 `_READ_SUCCESS` / `_WRITE_SUCCESS` 终态后缀

**触发**: v1.7.5 wire 联调 `req_1778811285514_oan6qa`（SYNC_FUNCTIONS_SWITCH_READ_SUCCESS）
- 设备端 0.7s 内返回 `status: COMPLETED` + `report.code: SYNC_FUNCTIONS_SWITCH_READ_SUCCESS`
- Edge 协议校验拒收：META 期望 `IN_PROGRESS` 但 wire 是 `COMPLETED`
- 30s 后 Edge 合成假 timeout 上报 Gateway，设备真实响应被丢弃

**根因**:
1. EXPLICIT_META 只显式列了 3 类终态系列（PROGRAM_* / QUICK_DETECTION_* / SYNC_MONITORING_TABLE_READ_*），**没覆盖** SIMPLE 命令的 `<COMMAND>_<OPERATION>_SUCCESS` 终态
2. 落到 SUFFIX_RULES 时命中 `_SUCCESS` 规则（设计为"阶段成功" `step_success` / `IN_PROGRESS`）
3. 与设备端 `device-command-reference.md §3.x` 已明确的"SIMPLE 命令终态命名约定 `<COMMAND_CODE>_<OPERATION>_SUCCESS`"冲突

**影响面（v1.7.0–v1.7.5 全部受影响）**:
设备端 canonical doc 列出的 SIMPLE 命令终态码包括（不完全列）：
- `DEVICE_DATETIME_INFORMATION_{READ,WRITE}_SUCCESS`
- `SYNC_FUNCTIONS_SWITCH_{READ,WRITE}_SUCCESS`
- `SYNC_PROGRAM_CONTROL_{READ,WRITE}_SUCCESS`
- `SYNC_PROGRAM_PUZZLE_{READ,WRITE}_SUCCESS`
- `SYNC_PROGRAM_SWITCH_{READ,WRITE}_SUCCESS`
- `SYNC_PIXEL_PITCH_SETTING_{READ,WRITE}_SUCCESS`
- `SYNC_BARGRAPH_SPACING_{READ,WRITE}_SUCCESS`
- `SYNC_STOP_AT_LOW_SPEED_SETTING_{READ,WRITE}_SUCCESS`
- `SYNC_STARTING_SPEED_LIMIT_{READ,WRITE}_SUCCESS`
- `DEVICE_SELF_TEST_RESULTS_READ_SUCCESS`
- `SYNC_LAST_SPEED_RECORDS_READ_SUCCESS`
- `SYNC_PROGRAM_TODAY_PLAY_NUMBER_READ_SUCCESS`

**修复**: SUFFIX_RULES 新增 2 条规则（优先级 > `_SUCCESS`，因 endsWith 长后缀优先命中）：

```typescript
{
  suffix: '_READ_SUCCESS',
  meta: { isTerminal: true, status: COMPLETED, level: 'INFO',
          semantic: 'success', requiresErrorBlock: false }
},
{
  suffix: '_WRITE_SUCCESS',
  meta: { isTerminal: true, status: COMPLETED, level: 'INFO',
          semantic: 'success', requiresErrorBlock: false }
}
```

### 结构性重构

- `SuffixRule.meta` 类型从 `Omit<CodeMeta, 'isTerminal'>` 改为完整 `CodeMeta`（含 `isTerminal`）
- `resolveCodeMeta` 不再硬编码 `isTerminal: false`，从 `rule.meta.isTerminal` 派生
- 9 条现有 SUFFIX_RULES 显式标注 `isTerminal: false`（保持原行为）

### 测试

- 新增 `src/__tests__/code-meta-simple-terminal.test.ts` 18 测试覆盖：
  - SIMPLE READ 终态（含真实联调失败的 SYNC_FUNCTIONS_SWITCH_READ_SUCCESS）
  - SIMPLE WRITE 终态
  - 优先级回归：EXPLICIT_META > SUFFIX_RULES（`SYNC_MONITORING_TABLE_READ_SUCCESS` 仍走 explicit）
  - 优先级回归：长后缀 > 短后缀（`_READ_SUCCESS` > `_SUCCESS`）
  - 回归：阶段成功 `PROGRAM_DOWNLOAD_HEADER_SUCCESS` 仍 IN_PROGRESS（不含 READ/WRITE 后缀）
  - 回归：其他 9 条 SUFFIX_RULES 保持非终态行为
  - `isKnownCode` 计入新后缀

### Migration notes

**Wire 行为零变化** — 设备端发送的 wire 内容不变。本版仅修协议方的 META 派生表，让 Edge / Backend 正确识别 SIMPLE 命令终态响应。

升级前: 设备端 `SYNC_FUNCTIONS_SWITCH_READ_SUCCESS` 响应被 Edge 拒收 + 30s 假 timeout
升级后: 同样 wire 响应被 Edge 正确识别为终态，0.7s 内回传 Gateway

**消费方升级路径**: 仅需 `npm install @thejrsoft/subway-protocol@^1.7.6` + 重启服务。无代码变更。

---

## [1.7.5] - 2026-05-14

### Schema 版本（PROTOCOL_VERSION 常量）

**升级 `'1.7.3'` → `'1.7.4'`**（本版 wire BREAKING — interface 字段名 rename）。

### BREAKING — wire schema 变化

#### `programNumber` → `programNo`

```
✂️ RENAME: ProgramParameters.programNumber → programNo  (1-10)
✂️ RENAME: ProgramContext.programNumber    → programNo  (1-10)
```

设备端 wire 自 v1.4.x 时代起就 emit `programNo`（`[JsonProperty("programNo")]`），
protocol pkg interfaces 自 v1.5.0 rename 后未同步是 stale 残留。本版对齐 deployed reality：

| 维度 | v1.7.4 | v1.7.5 |
|------|--------|--------|
| Protocol pkg interface | `programNumber` | `programNo` ✅ |
| 设备 wire emit (C# `JsonProperty`) | `programNo` | `programNo` (不变) |
| Doc canonical (device-command-reference.md) | `programNumber`/`programNo` 混用 | `programNo` 统一 |
| Dashboard i18n (translations.js) | `programNumber` 标签键 | `programNo` |

### Why now（Phase 3.5 Category C escalation）

- 设备端 v1.7.4 cycle final audit 实证（commit `d61914d`，5-agent 三源比对）发现 protocol pkg interfaces 与 device wire / canonical doc 双向不一致
- 设备端无单方面修复权（不能改 protocol pkg；反向 rename device wire 会破坏 1+ year 的 deployed wire）
- 经 escalation reply 评估 4 个 Option（A: 此次 rename / B: 设备回退 / C: alias / D: 暂不动），选 **Option A** —— 与 deployed reality + doc + device 100% 对齐

### Migration notes

**Wire 层零行为变化** —— deployed device wire 早已 emit `programNo`。本版仅修 protocol pkg TS interface 字段名签名，所有跑通的第三方升级 npm 包后**运行时无差异**。

**消费方升级路径**（强类型 TS 客户端）:
```diff
- const params: ProgramParameters = { ...rest, programNumber: 1 };
+ const params: ProgramParameters = { ...rest, programNo: 1 };
```

弱类型客户端（直接构 JSON dict）**无需修改** —— wire 字段 `programNo` 早已是约定值。

### Backend 同步（三层端到端统一）

按 user 拉范围"API/业务/dashboard/持久全部统一 programNo 风格，DB 列也动"：

**实体 + DB 列**:
- `task.entity.ts`: TS `programNo` + `@Column({name: 'program_no'})` 双改
- `task-progress-update.entity.ts`: TS `contextProgramNo?: number` + `@Column({name: 'context_program_no'})`
- `task-program-response.entity.ts`: 同上

**DB Migration**: `1747000000000-RenameProgramNumberToProgramNo.ts`（新增）
- `ALTER TABLE tasks RENAME COLUMN program_number TO program_no`
- `ALTER TABLE task_progress_updates RENAME COLUMN context_program_number TO context_program_no`
- `ALTER TABLE task_program_responses RENAME COLUMN context_program_number TO context_program_no`
- up/down 双向幂等（用 information_schema 探测旧列存在才动），可重跑

**业务层**:
- `eudi.controller.ts`: HTTP body 字段 `programNo`（createProgram + updateProgram + createTask 4 处）
- `task.service.ts`: createTask 入参 + 校验消息（`programNo is required` / `PROGRAM_NO_OUT_OF_RANGE`） + `progressUpdate.contextProgramNo` / `programResponse.contextProgramNo` 赋值 sync
- `program.service.ts` / `command.dispatcher.ts` / `validator.ts`: TS 字段同步

**Backend Dashboard (EJS + i18n)**:
- `views/tasks.ejs`: form id `program_no` + camelCase body 字段 `programNo` + JS 校验
- `views/programs.ejs`: 注释 sync
- `public/js/i18n.js`: i18n key `program_no_*` + 错误消息字符串 `programNo` 标识

**测试**:
- `tests/unit/entities/task-program-response.test.ts` / `task-progress-update.test.ts` / `tests/api/task-details.test.ts`: fixture sync
- `tests/integration/task-execution-flow.test.ts` / `tests/protocol/message-validation.test.ts` / `tests/utils/test-helpers.ts`: fixture sync

### Gateway 同步

- `swagger.config.ts` / `swagger.config.en.ts`: `ProgramContext.programNumber` schema → `programNo`
- `asyncapi.config.ts`: 6 处 example + schema 同步
- `callback-guide.routes.ts` / `callback-guide-en.routes.ts`: 4 处 JSON example 同步
- `e2e-message-flow.test.ts`: 1 处 fixture 同步
- Dashboard `index.html`: form 字段 `id="programNo"` + `data-i18n="programUpload.programNo"`
- Dashboard `js/translations.js` / `commands-metadata.js` / `ui.js`: i18n 键 + 模板插值 + DOM 读取键 sweep
- Public docs (`device-command-reference.md` / `-en.md`): SYNC_PROGRAM_CONTROL / SYNC_PROGRAM_PUZZLE 14 处 sweep

### Test infra

- `__tests__/message-validator-types.test.ts`: PROGRAM.parameters numeric fields 测试组 10 处 sweep + describe 标签 `programNumber` → `programNo`
- `test-tools/test-all-commands.cjs`: SYNC_PROGRAM_CONTROL / SYNC_PROGRAM_PUZZLE 6 处 fixture
- `test-tools/e2e-program-deploy/lib/helpers.js` + `tests/01-basic-deploy.js` + `tests/12-device-disconnect.js`: e2e 测试 wire fixture sync
- `test-tools/load-tests/progress-flood.js`: 压测 fixture sync

### csharp-model sync（独立 C# 项目）

- `Commands/Parameters/ProgramBrightnessParameters.cs` / `ProgramJigsawModeParameters.cs`: 属性 `ProgramNo` + `[JsonProperty("programNo")]`
- `Responses/ProgramBrightnessResponse.cs` / `ProgramJigsawModeResponse.cs`: 同上
- `Commands/Simple/ProgramJigsawModeCommandHandler.cs`: 引用 sync
- `typescript-schemas/ProgramBrightness{Parameters,Response}.{generated.ts,schema.json}` + `ProgramJigsawMode{Parameters,Response}.{generated.ts,schema.json}`: 生成的 TS 类型 + JSON Schema sync

### Protocol pkg docs sync

- `docs/01-protocol/message-types.md` / `specification.md`: 协议规范 wire 示例
- `docs/05-examples/progress-update-examples.md` + `docs/06-reference/api.md`: 示例 + API reference

### prepublishOnly hook

3 stage 校验保持启用（check-protocol-version + check-dead-spec + Stage 3 doc↔protocol coverage）。

---

## [1.7.4] - 2026-05-14

### Schema 版本（PROTOCOL_VERSION 常量）

**升级 `'1.7.2'` → `'1.7.3'`**（本版 schema 有真实 wire 变化 — EXPLICIT_META 新增 + rename + OperationType 缩值 + messageEn 删）。

详见 [README.md > Versioning Policy](./README.md#versioning-policy)。

### BREAKING — wire schema 变化

#### EXPLICIT_META rename + add

```
✂️ RENAME: PROGRAM_PARTIALLY_SUCCEEDED → PROGRAM_PARTIAL_SUCCESS
   (副词 PARTIALLY 是 PROGRAM 域单独历史语法异类，sweep 为形容词+SUCCESS noun)
   (与 SYNC_MONITORING_TABLE_READ_PARTIAL_SUCCESS 语法对齐)
   (QUICKLY_DETECTION_PARTIAL_ONLINE 保留 — outcome axis 是在线度，不同维度不一致是有意的)

🆕 ADD: SYNC_MONITORING_TABLE_READ_PARTIAL_SUCCESS
   {isTerminal: true, status: COMPLETED, level: WARNING, semantic: 'partial', requiresErrorBlock: false}
   (设备端 "部分天读取成功部分天 timeout" 场景已 emit，协议方 v1.7.4 补 EXPLICIT_META 对齐)
```

#### OperationType 缩 READ/WRITE 二值（Scenario B 全删 dead spec）

```
✂️ DELETE: QUERY     (3 端 0 真实使用)
✂️ DELETE: UPDATE    (历史用途配置文件更新已下线；backend gateway.client.ts:556 唯一 1 处 `as any` cast 是 hack)
✂️ DELETE: CONTROL   (3 端 0 真实使用)

最终 enum: { READ, WRITE }
backend 同步修: gateway.client.ts:556 'UPDATE' as any → 'WRITE'
```

#### ReportMessage 删 messageEn 字段

```
✂️ DELETE: ReportMessage.messageEn?: string
   v1.5.0 引入，v1.7.4 移除 — 3 端 0 真实使用 (grep cross-check)
   设备端 i18n 实践 = 全英文 message (事实标准)
```

### 工程化（防回归 — Stage 3 hook 扩展）

per `device-team-audit-of-v1.7.3-round2.md §5` 建议:

```
scripts/check-dead-spec.js Stage 3 新增:
  3a. Enum value coverage — 每个 enum 值必须在 doc 至少出现 1 次
       (catches: protocol delete value 但 doc 仍提及；或 doc 提及 value 但 protocol 无对应)
  3b. EXPLICIT_META code coverage — 每个 code 必须在 doc 中出现
       (catches: SYNC_MONITORING_TABLE_PARTIAL_SUCCESS-style 设备端 emit 但 EXPLICIT_META 不识别)
  
触发: 仅当 ../../claude-docs/device-command-reference.md 存在时（main repo 环境）
隔离 / consumer 端 npm install 后跑 hook 自动 skip Stage 3，不影响安装
```

### 终态数字

| 维度 | v1.7.3 | v1.7.4 |
|---|---|---|
| EXPLICIT_META 总条数 | 16 | **17** (+SYNC_MONITORING_TABLE_READ_PARTIAL_SUCCESS) |
| PROGRAM 终态 partial code | `PROGRAM_PARTIALLY_SUCCEEDED` | `PROGRAM_PARTIAL_SUCCESS` (rename) |
| OperationType 值数 | 5 (含 3 dead) | **2** (READ/WRITE) |
| ReportMessage 字段数 | 5 (含 messageEn) | **4** (无 messageEn) |
| PROTOCOL_VERSION schema | '1.7.2' | **'1.7.3'** |
| package.json version | 1.7.3 | **1.7.4** |
| prepublishOnly hook 阶段数 | 2 (protocol-version + dead-spec stage 1-2) | **3** (+Stage 3 doc↔protocol coverage) |

### Migration

#### 消费方 (设备端 / backend / gateway / edge)

```typescript
// v1.7.3
import { ReportMessage, OperationType } from '@thejrsoft/subway-protocol';
const msg: ReportMessage = { level, message, code, messageEn: 'optional english', data };
const op: OperationType = OperationType.UPDATE;  // ← 编译过

// v1.7.4
const msg: ReportMessage = { level, message, code, data };  // ← 删 messageEn
const op: OperationType = OperationType.UPDATE;  // ← 编译错 (字段不存在)
// 改用: OperationType.WRITE / READ
```

#### wire 兼容性

| 旧 wire 字符串 | v1.7.4 接收行为 |
|---|---|
| `"PROGRAM_PARTIALLY_SUCCEEDED"` | EXPLICIT_META 0 命中 → 走 SUFFIX_RULES `_SUCCEEDED` 派生 (无该规则，落 FALLBACK_META) |
| `"PROGRAM_PARTIAL_SUCCESS"` | EXPLICIT_META 命中 → terminal/WARNING/partial ✓ |
| `"operationType": "UPDATE"` | TypeScript 类型层 reject；运行时 wire 解析为 unknown enum (consumer 可选保留 [Obsolete] 别名缓冲) |

#### 三端同步修

- `services/jrsoft-subway-backend/src/gateway/gateway.client.ts:556` `'UPDATE' as any` → `'WRITE'`
- `test-tools/test-all-commands.cjs` 2 处 `'PROGRAM_PARTIALLY_SUCCEEDED'` → `'PROGRAM_PARTIAL_SUCCESS'`

#### 设备端 sg7-31-win 影响（per Round 10' device team ack）

```
16 code 引用 + 8 doc 引用 = 24 处 sweep (设备端子仓内)
  · CodeMetaResolver.cs EXPLICIT_META rename (1)
  · ProgramUploadOrchestrator.cs CODE_PARTIAL_SUCCESS const rename (1)
  · MainForm.CompositeAction.cs if branch (1)
  · 5 个 test 文件 (~13)
  · doc 8 处
设备端预计 ~1-2h sweep。可选保留 [Obsolete] 别名作接收兼容缓冲。
```

### 协作纪律沉淀（v1.7.3 → v1.7.4 反思）

通过 sg7-31-win 设备端 Round 2 deep audit (含 L5 SSoT 维度) + Round 10' push back，协议方沉淀:

- **规则 9 (新)**: BREAKING 影响面评估必须 **grep 设备端 cross-repo 实际 commit 状态**，不能默认"还没启动"
  - 协议方在 Round 9'-2 reply 写 "Sprint 1 还没 emit → 0 break" — 错估
  - 真相: 设备端 HEAD d6f7844 已 4 commit emit PROGRAM_PARTIALLY_SUCCEEDED 24 处
  - 设备端 Round 10' §2 catch — 这是 Principle 1 (Grep before opining) 跨 repo 边界的扩展应用

- **规则 10 (新)**: Round 9 reply 后**不**提供 "implement immediately" 选项给用户
  - 协议方 Round 9'-2 reply 后给出 3 选项菜单含 "直接实施 v1.7.4"
  - 这违反 round-based 纪律 — Round 10 ack 是必经的 gate
  - 用户 catch 后协议方 patch skill: 加 Principle 5 "Decouple reply from implementation"

### Migration 工具

设备端可选地保留 [Obsolete] OperationType 别名作接收向后兼容缓冲:

```csharp
public enum OperationType {
    [EnumMember(Value = "READ")]    READ,
    [EnumMember(Value = "WRITE")]   WRITE,
    // v1.7.4 删，可选保留为接收兼容（不可 emit）:
    [Obsolete("v1.7.4+ 协议已删，保留作接收兼容")] QUERY,
    [Obsolete("v1.7.4+ 协议已删，保留作接收兼容")] UPDATE,
    [Obsolete("v1.7.4+ 协议已删，保留作接收兼容")] CONTROL,
}
```

### 完整设计依据

- `claude-docs/discussions/device-team-audit-of-v1.7.3-round2.md` (Round 2 deep audit 5-agent + L5 SSoT)
- `claude-docs/discussions/protocol-team-reply-to-device-audit-v1.7.3-round2.md` (协议方 4 议题处置 + Round 10' 后 §7.6 订正)
- `claude-docs/discussions/device-team-ack-of-protocol-reply-v1.7.3-round2.md` (Round 10' ack + "0 break" push back)

---

## [1.7.3] - 2026-05-13

### Schema 版本（PROTOCOL_VERSION 常量）

**保持 `'1.7.2'`**（v1.7.3 patch 仅修复 v1.7.2 publish-time 漏改 + 加工程化 hook，**无 wire 行为变化**）。

详见 [README.md > Versioning Policy](./README.md#versioning-policy)：本包采用 schema 版本与 package 版本解耦演进的策略。

### 修复（patch — v1.7.2 漏改善后）

- **src/index.ts:1240** `PROTOCOL_VERSION` 常量从误植的 `'1.7.1'` 修正为 `'1.7.2'`（v1.7.2 发版时漏改，package.json 已正但 src 常量未跟）
- **src/index.ts:1240 附近** 注释扩为多行 JSDoc，明示 schema 版本与 package 版本解耦的设计意图 + v1.7.3 patch 的历史背景，防止下次 archeology 又 catch 一遍

### 工程化（防再犯 — markdown 承诺 → CI hook）

通过 v1.7.x 系列 consumer audit 累积发现 3 次同款 publish-time 漏改（RATER_DETECT / alias 撒谎注释 / PROTOCOL_VERSION），承诺已纸面化但未工程化。本版**写进 prepublishOnly hook**：

- **`scripts/check-protocol-version.js`** — 校验 PROTOCOL_VERSION ↔ package.json.version ↔ CHANGELOG 的三方一致性
  - 约束：`pkg >= schema` + schema 在 CHANGELOG 已记录中 + 若 schema 滞后 pkg 则当前 CHANGELOG entry 必须显式声明
  - 三种场景覆盖：v1.7.2-style 漏改 REJECT / v1.7.3-style 故意 lag PASS / 正常同步 PASS
  - 采用 sg7-31-win 设备端 final-ack §1.3 修正版逻辑（修正前一版强化逻辑的 chicken-and-egg bug）
- **`scripts/check-dead-spec.js`** — 拦 v1.7.1 同款 sibling enum drift + alias 撒谎注释
  - ProgressPhase enum ↔ ErrorPhase type 共享业务子集（17 个 phase）算术对齐校验
  - 禁止已知撒谎措辞（`alias of ProgressPhase`, `完全对齐` 等），除非显式标 `(historical)`
- **`package.json` scripts.prepublishOnly** 改为 `clean && build && check:protocol-version && check:dead-spec`——`npm publish` 命令本身拒绝不一致

### 文档（versioning policy 显式化）

- **README.md** 新增 § Versioning Policy（schema 版本与 package 版本解耦的设计意图 + 历史背景）
- **本 CHANGELOG entry** 显式声明 `PROTOCOL_VERSION` 保持 `'1.7.2'`（防 hook 拒绝 v1.7.3 自身发版 + 解释设计意图）

### 三方协作模式工程化拐点（v1.7.x → v1.8.0 起）

本版是 protocol-consumer co-evolution 从「人脑 review + 事后 audit」转向「工程化阻断 + 战略 audit」的拐点：

```
v1.7.0–v1.7.1: consumer audit 教 protocol team 自审
v1.7.2:        protocol team 写下"自审承诺"在 markdown
v1.7.3:        承诺写成 prepublishOnly hook  ← 本版
v1.8.0 起:     consumer 不再做 publish-time 漏改的机械检查，
              注意力 100% 聚焦于真正的协议设计问题
```

### Migration

- **三端**：`npm install @thejrsoft/subway-protocol@^1.7.3` + 重新 build。无 wire 变化，无代码改造。
- **Consumer audit**：v1.7.3 之后协议方自己 prepublishOnly 跑过；consumer audit 仍可保留但战略转移。
- **Schema 版本读取者**（如 health endpoint 用 `PROTOCOL_VERSION`）：返回值仍是 `'1.7.2'`（v1.7.3 patch 不动 schema）；详见 README Versioning Policy。

### 设计依据

- `claude-docs/discussions/device-team-audit-of-v1.7.2.md`（设备端 Round 12 audit 发现 P0）
- `claude-docs/discussions/protocol-team-reply-to-device-audit-v1.7.2.md`（协议方 Round 9 reply with v1.7.3 plan）
- `claude-docs/discussions/device-team-final-ack-v1.7.3.md`（设备端 Round 10 transition mode ack + check 逻辑订正 push back）

### 沉淀的工程规则（v1.7.2 → v1.7.3 cycle 新增）

- **N+1**：Markdown 上的承诺 ≠ 工程修复。任何「以后会注意」必须配套 CI hook / pre-commit 等机械化阻断
- **N+2**：Publish-time 至少 3 项强制 check：版本号一致性 / 已知撒谎注释模式 / sibling enum 对齐
- **N+3**：Consumer 自反（如设备端订正自己 audit 的 false positive）是稀缺信号，双向都肯翻盘自己的协作是 v1.7.x 系列最大资产
- **N+4**：Audit Agent 之间数字算术 cross-check（一个 Agent 说「19 值 + 含 RATER_DETECT」自身矛盾应被合并阶段 catch）

---

## [1.7.2] - 2026-05-13

### Schema 变更（patch — 4 项 internal drift 一次扫光）

通过 sg7-31-win 设备端 `spec-conformance-audit` 反查 v1.7.1，识别协议方 4 项遗留 drift：

- **`ProgressPhase` enum 删 `RATER_DETECT`**（v1.7.1 仅改 ErrorPhase 漏改 ProgressPhase，本版同步对齐）
- **`ReportLevel` enum 删 `DEBUG` / `CRITICAL`**（dead code 清理：EXPLICIT_META / SUFFIX_RULES / 三端代码 0 处使用，与 v1.7.0 删 PENDING/PAUSED 节奏一致）
- **`ErrorPhase` 注释订正**：撤回 "ProgressPhase 的 alias"（v1.5.0–v1.7.1 撒谎措辞），改 "两个独立 closed set，共享业务阶段子集，各自含一组对方没有的 EDGE_* 值"
- **`EDGE_CACHE_FETCH/READY` 注释加注**："Edge 服务端专用，设备端不发"（与 ErrorPhase 的 EDGE_PROXY 注释对称）

### 终态数字

| 维度 | v1.7.1 | v1.7.2 |
|------|--------|--------|
| `ProgressPhase` enum 值数 | 20（漏删 RATER_DETECT）| **19**（同步删 RATER_DETECT）|
| `ReportLevel` enum 值数 | 5（含死代码 DEBUG/CRITICAL）| **3**（INFO/WARNING/ERROR）|
| `ErrorPhase` type 值数 | 18 | 18（不变）|
| `Priority` enum CRITICAL | 在 | **在**（独立 enum，命令优先级，不要误删）|
| 注释 "alias" 措辞 | 撒谎 5 个版本 | **撤销** |

### Migration

```typescript
// v1.7.1（漏删）
import { ProgressPhase } from '@thejrsoft/subway-protocol';
ProgressPhase.RATER_DETECT  // ← 仍可用，但与 ErrorPhase 不对应

// v1.7.2（同步对齐）
ProgressPhase.RATER_DETECT  // ← 编译错（删了）
// 改：设备端不发 RATER_DETECT；Edge / Gateway / Backend 解析时如收到走 fallback 处理
```

```typescript
// v1.7.1
ReportLevel.DEBUG / ReportLevel.CRITICAL  // ← 仍可用，但 0 处使用

// v1.7.2
ReportLevel.DEBUG / ReportLevel.CRITICAL  // ← 编译错（删了）
// 三端实际只发 INFO / WARNING / ERROR，无消费方 wire 影响
```

### 协议契约保证

| 保证 | v1.7.1 | v1.7.2 |
|------|:------:|:------:|
| 一码一义 | ✓ | ✓ |
| SUFFIX_RULES 9 条 suffix-only | ✓ | ✓ |
| MessageStatus 单一 enum 4 值 | ✓ | ✓ |
| ProgressPhase / ErrorPhase 对账 | ❌ 漏删 RATER_DETECT | ✅ 一致 |
| ReportLevel 实际值数与定义对齐 | ❌ 5 vs 实际 3 | ✅ 3 值 closed enum |
| 注释与代码行为对齐 | ❌ "alias" 撒谎 | ✅ 明示独立 closed set |

### 设计依据

通过 sg7-31-win 设备端 10 轮协作档案的 final 闭环：

- Round 8 `device-team-audit-of-v1.7.1.md`（6 agent 并行 spec-conformance-audit）
- Round 9 `protocol-team-reply-to-device-audit-v1.7.1.md`（协议方逐项 closed + 选 (c) 独立 closed set）
- Round 10 `device-team-final-ack-v1.7.2.md`（设备端 4 项全 ACCEPT + 2 bonus 提醒）

### 设备端 Bonus 提醒（已落地）

1. **`Priority.CRITICAL` 不要误删** — 命令优先级 enum，与 ReportLevel.CRITICAL 名相同但是独立 enum（line 74-80），v1.7.2 保持不动
2. **`device-upgrade-guide-v1.7.2.md` 新建不重命名** — 保留 v1.7.0/v1.7.1 历史档案可追溯

### 工程规则沉淀（v1.7.1 → v1.7.2 反思）

- 协议层改 enum / type 时，必须 cross-check **所有共享同概念的 enum/type**（不能只改一边）
- 注释与代码 drift 是 v1.5.0 写入并扩散到 v1.7.1 的"长期 silent bug"——v1.7.2 起协议改动**注释必须与代码同 PR 校对**
- "vibes 答题"（不拉 grep 凭直觉给建议）是协议方 v1.7.1→v1.7.2 这一轮的真实负向，已主动撤销 + 自反
- consumer 反查（设备端 audit）是发现协议 internal drift 的有效抓手，应当作发版前的标准 review

### 完整设计依据

- `claude-docs/discussions/device-team-audit-of-v1.7.1.md`
- `claude-docs/discussions/protocol-team-reply-to-device-audit-v1.7.1.md`
- `claude-docs/discussions/device-team-final-ack-v1.7.2.md`

---

## [1.7.1] - 2026-05-13

### Schema 变更

- **删除 `RATER_DETECT`** from `ErrorPhase` type（sg7-31-win 设备端 v1.5.0 起架构性移除 RATER 检测路径，dead spec 清理）
- **删除 `CloudReport`** from `ErrorStep` type（云端 RabbitMQ 通道下线，PROGRAM_STATS 阶段无失败 step）
- **新增 6 个 DETECT 族 step**（按 sg7-31-win 设备端 final 单 step 命名）：
  - `DetectionInit` (DETECT_INIT)
  - `NetworkScan` (SWITCH_DETECT)
  - `SwitchConfigRead` (SWITCH_CONFIG_READ)
  - `SyncDeviceCheck` (SYNC_DETECT)
  - `BargraphNodeCheck` (BARGRAPH_DETECT)
  - `SyncStatusRecover` (SYNC_RECOVER)

### ERROR_STEP_BY_PHASE 映射更新

```typescript
PROGRAM_STATS: [] as const,         // 删 CloudReport
DETECT_INIT: ['DetectionInit'] as const,
SWITCH_DETECT: ['NetworkScan'] as const,
SWITCH_CONFIG_READ: ['SwitchConfigRead'] as const,
SYNC_DETECT: ['SyncDeviceCheck'] as const,
BARGRAPH_DETECT: ['BargraphNodeCheck'] as const,
SYNC_RECOVER: ['SyncStatusRecover'] as const,
DETECT_COMPLETE: [] as const,
```

### SUFFIX_RULES 设计修正（_WARN infix → suffix）

- **删除 `SuffixRule.infix` 字段**，9 条规则统一为 suffix-only
- `_WARN` 从 `infix '_WARN'` 改为 `suffix '_WARN'`
- `resolveCodeMeta()` / `isKnownCode()` 删除 infix 分支

**修复的真实 bug**：

```
v1.7.0: PROGRAM_UPLOAD_WARN_METADATA_FAILED 同时含 _WARN 和 _FAILED
        → resolveCodeMeta 按 _FAILED 优先命中 → ERROR + requiresErrorBlock=true
        → 调用方意图是 warning（无 error block）
        → Edge MessageValidator 拒收（双关键字双命中冲突）

v1.7.1: 规则全部 suffix-only，命名规范要求 *_WARN 在末尾
        命中确定性 100%，一码一义在 SUFFIX_RULES 层面强制保证
```

### 命名规范要求（v1.7.1 起）

- ✅ 正确：`PROGRAM_UPLOAD_CAN_BUS_ALARM_WARN`、`PROGRAM_UPLOAD_METADATA_WARN`
- ❌ 错误：`PROGRAM_UPLOAD_WARN_CAN_BUS_ALARM`（中缀不再命中）

### v1.7.1 final 数字

| 维度 | v1.7.0 | v1.7.1 |
|------|--------|--------|
| ErrorPhase 总数 | 19 | **18** (− RATER_DETECT) |
| ErrorStep 总数 | 25 | **30** (− CloudReport + 6 DETECT) |
| QUICKLY_DETECTION phase 数 | 8 | **7** |
| PROGRAM step 总数 | 21 | **20** (− CloudReport) |
| DETECT step 总数 | 0 | **6** |
| EDGE_PROXY step | 4 | **4** (不变) |
| 设备端 ProgressPhase enum | — | **17** (18 − EDGE_PROXY) |
| 设备端 ErrorStep enum | — | **26** (30 − EDGE_PROXY 4) |

### 设计原则升级（v1.7.0 → v1.7.1 协作沉淀）

通过 sg7-31-win 设备端三轮 review，协议团队识别并修正 5 个设计盲点：

1. `_WARN` 中缀设计（识别为 SUFFIX_RULES 设计噪点 + 真实 bug）
2. B1 `_SKIPPED / _WARN / _FAILED` 三档语义错位
3. DETECT 2-step 拆分凭想象（YAGNI 违反）
4. §4.2 _WARN 重命名清单漏列 2 个 code
5. CloudReport drift（协议契约脱节系统真相）

**沉淀的工程规则**：

- 协议层 enum / 映射 / 命名提议必须 cross-check 主消费方代码现状
- 文档数字 / 列表必须有 code 引用 / 行号支撑
- 每次升级 publish 前做 dead spec 扫描
- 重大破坏性升级 review 优先听消费方代码验证派的声音

### Migration 影响

- **协议层**：删 RATER_DETECT / CloudReport + 加 6 DETECT step + SUFFIX_RULES suffix-only
- **三端代码**：仅 import 重命名（无函数签名变化）
- **device-command-reference**：7 个 _WARN code 重命名（5 standard rename + 1 SKIPPED 拆分 + 1 双关键字 bug 修）
- **设备端 sg7-31-win**：Sprint 1 按 17/26 enum 基线落地

### 完整设计依据

- `claude-docs/discussions/protocol-team-reply-3-to-device-v1.7.0.md`
- `claude-docs/discussions/device-team-review-2-of-protocol-reply-v1.7.0.md`
- `claude-docs/discussions/protocol-proposal-warn-suffix-2026-05-13.md`

---

## [1.7.0] - 2026-05-13

### ⚠️ BREAKING — 删除 3 个旧 Factory 方法

完全删除以下三个旧 helper：

- ✂️ `MessageFactory.createCommandResponseMessage`
- ✂️ `MessageFactory.createProgramResponseMessage`
- ✂️ `MessageFactory.createProgressUpdateMessage`

唯一终态/进度消息构造入口：`MessageFactory.dispatchMessage(params)`。

### Migration

```typescript
// v1.6.x (已删)
MessageFactory.createCommandResponseMessage(
  clientId, requestRef, MessageStatus.COMPLETED, result, 'message',
);

// v1.7.0 (唯一可用)
MessageFactory.dispatchMessage({
  clientId, requestRef,
  code: 'SYNC_FUNCTIONS_SWITCH_WRITE_SUCCESS',  // 单一信息源
  message: 'message',
  result,
  // status / level 由 code → CodeMeta 自动派生
});
```

### 设计依据

完整设计原则见 `claude-docs/protocol-upgrade-proposal-v1.6.0.md`。

---

## [1.6.1] - 2026-05-13

### Added (零破坏，向后兼容)

- `EXPLICIT_META` 新增 2 个终态 code：`EDGE_COMMAND_REJECTED` / `EDGE_PROGRAM_REJECTED`
- 用于 Edge 代理层合成终态消息（设备未响应时合成 "as-if device error response"）
- 失败具体原因通过 `data.error.{phase, step, category, detail}` 表达

### Migration

Edge `edge-proxy.manager.ts` 中 9 处错误响应合成调用迁移到 `dispatchMessage`：

```typescript
// Before (v1.6.0 旧 Factory 调用)
MessageFactory.createCommandResponseMessage(
  edgeId, requestRef, MessageStatus.FAILED, { error: '...' }
);

// After (v1.6.1 推荐)
MessageFactory.dispatchMessage({
  clientId: edgeId, requestRef,
  code: 'EDGE_COMMAND_REJECTED',
  message: '...',
  error: { phase: 'BATCH_EXECUTE', step: '...', category: 'TRANSPORT', detail: '...' },
});
```

---

## [1.6.0] - 2026-05-13

### ⚠️ BREAKING CHANGES — 硬切，零兼容（系统未上线）

#### 1. CodeMeta 单一信息源 — status / level 由 report.code 派生

新设计：所有协议消息的 `status` 和 `level` 字段由 `report.code` 通过 `CodeMeta` 表自动派生。
集成方收到消息后调用 `resolveCodeMeta(code)` 查表得到 `semantic` / `requiresErrorBlock` 等元数据。

```typescript
import { resolveCodeMeta } from '@thejrsoft/subway-protocol';

const meta = resolveCodeMeta(msg.report.code);
// meta: { isTerminal, status, level, semantic, requiresErrorBlock }
switch (meta.semantic) {
  case 'success': /* ALL_SUCCESS / ALL_ONLINE 等 */ break;
  case 'partial': /* PARTIAL_SUCCESS / PARTIAL_ONLINE */ break;
  case 'failure': /* ALL_FAILED / UPLOAD_FAILED */    break;
  case 'busy':    /* BUSY 并发拒绝 */                break;
  case 'cancel':  /* CANCELLED */                    break;
  case 'no-op':   /* NO_PROGRAMS */                  break;
  // 进度语义: phase_start / phase_end / phase_failed / step_success ...
}
```

#### 2. MessageStatus 单一 enum 取代 CommandStatus / ProgressStatus

```typescript
// v1.5.0 (已删)
enum CommandStatus  { COMPLETED, FAILED, TIMEOUT, CANCELLED, IN_PROGRESS }  ← 删
enum ProgressStatus { PENDING, IN_PROGRESS, PAUSED, COMPLETED, FAILED, CANCELLED } ← 删

// v1.6.0 (新)
enum MessageStatus { IN_PROGRESS, COMPLETED, FAILED, CANCELLED }
```

**删除的死代码值**:
- `ProgressStatus.PENDING` / `PAUSED`（v1.5.0 内 0 引用）
- `CommandStatus.TIMEOUT` 语义迁移到 `status=FAILED + data.error.category='TIMEOUT'`

#### 3. dispatchMessage 统一发消息接口

替代旧 `MessageFactory.createCommandResponseMessage` / `createProgramResponseMessage` /
`createProgressUpdateMessage`：

```typescript
MessageFactory.dispatchMessage({
  clientId, requestRef,
  code: 'PROGRAM_COMPLETED',  // 单一信息源
  message: 'Program upload completed',
  data: { totalPrograms: 5, successCount: 5 },
  // status / level / 消息类型由 CodeMeta 自动推导
});
```

设备端只填 `code` 和业务字段，违反 META 决策矩阵的风险归零。

#### 4. 拆分歧义 code — 一码一义原则

`PROGRAM_UPLOAD_FAILED` v1.5.0 既表 FAILED 又表 BUSY → v1.6.0 拆分：
- `PROGRAM_UPLOAD_FAILED`：仅表异常中断（status=FAILED + level=ERROR）
- `PROGRAM_UPLOAD_BUSY`：独立 code，表并发拒绝（status=FAILED + level=WARNING）

### 新增

- `MessageStatus` enum（4 值）
- `CodeMeta` interface（5 字段）
- `CodeSemantic` 联合类型（15 值，覆盖终态 + 进度）
- `EXPLICIT_META` 终态显式表（~14 entries）
- `SUFFIX_RULES` 进度后缀规则（9 条）
- `resolveCodeMeta(code)` / `isKnownCode(code)` 工具函数
- `MessageFactory.dispatchMessage(params)` 统一发消息接口

### 删除

- ✂️ `enum CommandStatus`（v1.5.0 5 值）
- ✂️ `enum ProgressStatus`（v1.5.0 6 值）
- ✂️ `CommandStatus.TIMEOUT`（合并到 data.error.category）
- ✂️ `ProgressStatus.PENDING` / `PAUSED`（0 引用死代码）

### Migration

```typescript
// v1.5.0
import { CommandStatus, ProgressStatus, MessageFactory } from '@thejrsoft/subway-protocol';
const msg = MessageFactory.createCommandResponseMessage(
  clientId, requestRef, CommandStatus.COMPLETED, result, message,
);

// v1.6.0
import { MessageStatus, MessageFactory } from '@thejrsoft/subway-protocol';
const msg = MessageFactory.dispatchMessage({
  clientId, requestRef,
  code: 'SYNC_FUNCTIONS_SWITCH_WRITE_SUCCESS',  // 单一信息源
  message: 'Switch set to On',
  result,
});
// status (COMPLETED) + level (INFO) 自动从 code 派生
```

### 协议设计依据

完整设计原则、决策回放（CodeMeta 设计奇点、status 合并理由、为什么 semantic 不进消息等）
见 `claude-docs/protocol-upgrade-proposal-v1.6.0.md`。

---

## [1.5.0] - 2026-05-12

### ⚠️ BREAKING CHANGES — 无兼容、无 fallback（系统未上线）

#### 1. 统一失败信号 schema（`data.error` 嵌套对象）

所有失败上报（`level=ERROR` / `status=FAILED`）必须使用新 schema：

```json
{
  "report": {
    "code": "PROGRAM_UPLOAD_FAILED",
    "level": "ERROR",
    "message": "Program download timed out",
    "messageEn": "Program download timed out",
    "data": {
      "error": {
        "phase": "PROGRAM_FETCH",
        "step": "Download",
        "category": "TIMEOUT",
        "detail": "HTTP 504 from CDN after 30s, 3/3 attempts"
      }
    }
  }
}
```

#### 2. 新增 / 移除字段

**新增**:
- `ReportMessage.messageEn?: string` — 英文文本（与 message 同义，便于 i18n 兼容）
- `ReportData` 接口（含 `error?: ErrorInfo` + 业务字段 index signature）
- `ErrorInfo` 接口（`phase` / `step` / `category` / `detail` 4 字段全必填）
- `ErrorPhase` 类型（15 个值，与 ProgressPhase 对齐）
- `ErrorStep` 类型（21 个值的 closed enum）
- `ErrorCategory` 类型（**7 类**：在 v1.4.14 的 4 类基础上加 `CONFIGURATION` / `PROTOCOL` / `AUTHORIZATION`）
- `ERROR_STEP_BY_PHASE` 映射常量（强校验 step ↔ phase 归属）
- 工具函数：`VALID_ERROR_CATEGORIES` / `isValidErrorCategory()` / `normalizeErrorCategory()` / `isValidErrorStepForPhase()`
- `ProgressPhase` 枚举新增 2 个值：`PROGRAM_INIT`（任务初始化）/ `PROGRAM_COMPLETE`（终态边界）

**删除**:
- `ReportMessage.category` 顶层字段（下沉到 `data.error.category`）
- `ReportCategory` 类型（重命名为 `ErrorCategory`，并扩到 7 类）
- `VALID_REPORT_CATEGORIES` / `isValidReportCategory()` / `normalizeReportCategory()`（重命名为 `VALID_ERROR_CATEGORIES` 等）
- `data.failedPhase` / `data.failedSubStage` / `data.failedReason` 平铺字段（合并到 `data.error.*`）
- PROGRAM 命令 6 个独立失败 code（统一为 `PROGRAM_UPLOAD_FAILED` + `data.error.*`）：
  - `PROGRAM_DOWNLOAD_FAILED`
  - `PROGRAM_CHECKSUM_MISMATCH`
  - `PROGRAM_COMPILE_FAILED`
  - `PROGRAM_COMPILE_ERROR`
  - `PROGRAM_DEPLOY_NOT_INITIALIZED`
  - `PROGRAM_DEPLOY_ERROR`
- `failedReason` 6 值枚举（合并到 `category` + `step` + `detail`）

#### 3. COMPLEX 命令统一生命周期

所有 COMPLEX 命令统一为 `{CMD}_INIT (progress=0%) → 业务 phase → {CMD}_COMPLETE (progress=100%)`：

- PROGRAM 命令新增 `PROGRAM_INIT` / `PROGRAM_COMPLETE` phase（8 phase 序列）
- 终态决策矩阵在 `PROGRAM_COMPLETE` 触发（唯一收尾点）
- BUSY 并发拒绝场景从"特例"变成 `PROGRAM_INIT.CheckConcurrency` 阶段正常失败

#### 4. PROGRAM phase 序列重排（6 → 8）

| 序号 | phase | progress |
|---|---|---|
| 0 | `PROGRAM_INIT` | 0% |
| 1 | `PROGRAM_FETCH` | 1~15% |
| 2 | `PROGRAM_EXTRACT` | 16~25% |
| 3 | `PROGRAM_PREPROCESS` | 26~50% |
| 4 | `PROGRAM_COMPILE` | 51~70% |
| 5 | `PROGRAM_UPLOAD` | 71~95% |
| 6 | `PROGRAM_STATS` | 96~99% |
| 7 | `PROGRAM_COMPLETE` | 100% |

### Migration

设备端 (C#):
1. 新增 `ErrorPhase` / `ErrorStep` / `ErrorCategory` 强类型 enum
2. `ReportMessage` 移除 `Category` 顶层，新增 `ReportData.Error`
3. 新建 `ErrorClassifier` 集中归类异常 → (phase, step, category) 三元组
4. PROGRAM 编排器 6 处独立失败 code 调用改用 `ErrorClassifier.Classify(...)` 生成 `ErrorInfo`
5. 新增 `PROGRAM_INIT` / `PROGRAM_COMPLETE` phase 上报

第三方 (TypeScript/JS):
```javascript
function parseFailureMessage(msg) {
  if (msg.report.level !== 'ERROR' && msg.status !== 'FAILED') return null;
  const err = msg.report.data?.error;
  if (!err) return null;
  return {
    category: err.category,                              // 粗分类（告警/聚合）
    location: `${err.phase}.${err.step}`,                // 细位置（诊断/UI）
    i18nKey: `${err.phase}.${err.step}.${err.category}`, // i18n key
    diagnostic: err.detail,                              // 日志/调试
  };
}
```

### 协议设计依据

完整设计原则、决策回放（11 个关键技术选择记录）、归类决策树、错误映射表见 `claude-docs/protocol-upgrade-proposal-v1.5.0.md`。

---

## [1.4.14] - 2026-05-11

### Added (零破坏 / minor bump)
- **`ReportCategory` enum 扩展为 4 档**：在原 `TRANSPORT | TIMEOUT | BUSINESS` 基础上加 `RESOURCE`，表达设备端本地资源耗尽故障（磁盘满 / 内存不足 / 文件句柄耗尽 / cache 写入失败）。区别于网络层 `TRANSPORT` / 超时 `TIMEOUT` / 业务结果 `BUSINESS`
- 抽出独立 `export type ReportCategory` 公开类型（之前是 `ReportMessage.category` inline literal union）
- 新增 helper：`VALID_REPORT_CATEGORIES`（只读数组）、`isValidReportCategory()` 类型守卫、`normalizeReportCategory()` 降级函数（未知值 → BUSINESS）

### Changed (文档约束放开，schema 零变更)
- **解除 v1.4.13 "仅 SIMPLE/BATCH 使用 category" 限制**：现允许 COMPLEX 命令（PROGRAM / QUICKLY_DETECTION / SYNC_MONITORING_TABLE）所有 PROGRESS_UPDATE 与终态响应使用 category。BATCH 聚合 COMMAND_RESPONSE 仍是唯一例外
- `ReportMessage.category` 字段类型从 inline literal union → `ReportCategory`（语义不变，类型抽离更清晰）

### Compatibility
- 完全向后兼容
  - 旧版客户端（v1.4.13）发 v1.4.14 服务端：仍发 3 档值，type 兼容
  - 新版服务端（v1.4.14）发 `RESOURCE` 给旧版客户端：旧客户端 enum 不识别 → 调用 `normalizeReportCategory()` 自动降级为 `BUSINESS`
- 字段始终 optional，缺省 = `BUSINESS`，老消息天然合法
- 文档约束放开是软约束，schema 零变更

### Migration（消费方建议，不强制）
```typescript
// 升级前
switch (msg.report?.category) {
  case 'TRANSPORT': pageNetworkOps(); break;
  case 'TIMEOUT':   pageFieldEngineer(); break;
  case 'BUSINESS':
  default:          ticketToProduct(); break;
}

// 升级后（接收 RESOURCE 路由到运维清磁盘）
switch (msg.report?.category) {
  case 'TRANSPORT': pageNetworkOps(); break;
  case 'TIMEOUT':   pageFieldEngineer(); break;
  case 'RESOURCE':  pageDevOpsToCleanDisk(); break;  // ← 新增
  case 'BUSINESS':
  default:          ticketToProduct(); break;
}
```

## [1.4.13] - 2026-05-09

### Added (Breaking)
- **`DevicePhysicalParams` 接口** + **`ClientInfo.physicalParams` 字段**：将屏幕尺寸/滚动方向/节目槽位上限从「节目实体」迁出，改由设备在 REGISTER 时上报，由 Backend 持久化。字段语义 1:1 对齐 `ProgramParameters`（同名 `width / height / direction`），不引入新概念
- **`MessageValidator.validateRegisterMessage` 强校验**：当 `clientType === DEVICE` 时，必须携带完整 `physicalParams`（width 正整数、height 正整数、direction ∈ {LEFT_TO_RIGHT, RIGHT_TO_LEFT}、maxProgramSlots ∈ [1, 10] 整数），否则注册被拒收

### Changed
- 节目实体瘦身：`width / height / direction / programNumber` 不再属于节目；`programNumber` 由任务在创建时按设备 `maxProgramSlots` 范围指定；`width / height / direction` 由 Backend 在下发 PROGRAM 时从设备实体读取拼装
- 老设备固件需要 1 次升级 REGISTER payload 增加 `clientInfo.physicalParams`；未升级的设备会被 Gateway 拒绝注册

### Migration
- Backend：`programs` 表删除 `width / height / direction / program_number` 列；`devices` 表新增 `width / height / direction / max_program_slots` 列（DEVICE 注册必入）；`tasks` 表新增 `program_number INT NOT NULL` 列
- Edge：在收到 DEVICE 的 REGISTER 时透传 `physicalParams` 给上游
- Backend：任务创建 API 校验 `1 ≤ program_number ≤ device.max_program_slots`，超界 400 `PROGRAM_NUMBER_OUT_OF_RANGE`
- Backend：PROGRAM 下发时若设备未上报 physicalParams，任务直接 FAILED，错误码 `DEVICE_PHYSICAL_PARAMS_MISSING`
- 历史数据策略：不做兼容、不做 fallback，**所有设备重连一次重新上报**

## [1.4.11] - 2026-04-29

### Changed (Breaking)
- **`ProgressPhase` 枚举重写**（不保留 deprecated 旧值）：
  - 节目处理 pipeline 改用 `PROGRAM_` 前缀，与 `SYNC_DETECT`/`SWITCH_DETECT` 同风格
    - `DOWNLOAD` → `PROGRAM_FETCH`（从 OSS/Edge 拉源文件，FETCH 系列与 EDGE_CACHE_FETCH 统一）
    - `DECOMPRESS` → `PROGRAM_EXTRACT`（解压归档）
    - `PREPROCESS` → `PROGRAM_PREPROCESS`（图片预处理）
    - `FRAMES` → `PROGRAM_COMPILE`（编译为显示帧）
    - `UPLOAD` → `PROGRAM_UPLOAD`（上传到底层显示设备）
    - `STATS` → `PROGRAM_STATS`（状态统计）
  - 一键检测首尾边界标记加 `DETECT_` 前缀：
    - `INITIALIZATION`（旧版未入 enum 的 ad-hoc 字符串）→ `DETECT_INIT`
    - `COMPLETE` → `DETECT_COMPLETE`
  - `EXPORT` → `SYNC_EXPORT`（与 SYNC_DETECT/SYNC_RECOVER 同主语，明确为同步器监播表导出）
  - 新增 `BATCH_EXECUTE`（替代设备端 BATCH 命令场景的 `'executing'` 字符串，消除与 Backend 状态机内部状态命名撞名）
- **`MessageValidator.validateProgressUpdate` 启用 phase 强校验**：
  - phase 必须在 `ProgressPhase` enum 中，否则消息被拒收
  - 此前只检查 phase 字段是否存在，导致 `'executing'`/`'Initialization'` 等非协议字符串能写入数据库

### Added
- `ProgressPhase.EDGE_CACHE_FETCH` / `ProgressPhase.EDGE_CACHE_READY`：Edge 缓存活动可见性（v1.4.11 新增 phase，由 Edge 通过 PROGRESS_UPDATE 上报）
- `ProgressSourceType` 类型导出（`'COMMAND' | 'SYSTEM' | 'EDGE'`），sourceType 字段新增 `EDGE` 值
- `MessageValidator` 校验 sourceType 时接受 `EDGE`

### Removed
- 旧 `ProgressPhase` 枚举值（`DOWNLOAD` / `DECOMPRESS` / `PREPROCESS` / `FRAMES` / `UPLOAD` / `STATS` / `EXPORT` / `INITIALIZATION` / `COMPLETE`）— 全部直接清理，不保留 deprecated 兼容
- 调用方需同步升级（设备端节目处理 pipeline / 一键检测命令实现）

### Changed
- `PROTOCOL_VERSION` 更新至 `1.4.11`

## [1.4.10] - 2026-04-27

### Fixed
- `MessageValidator` 数值字段类型校验漏洞修复
  - 此前 `progress < 0 || progress > 100` 等数值范围比较在收到 `progress: "abc"` 这类非数字输入时，`NaN` 比较恒为 `false`，导致非法值意外通过校验
  - 现在所有数值字段先做 `typeof !== 'number' || isNaN()` 强类型检查再做范围检查
- 修复以下字段：
  - `validateProgressUpdate`: `progress`
  - `validateCommandMessage`: `timeout`（补 `isNaN` 防御）
  - `validateProgramMessage`: `timeout`、`programNumber`，新增 `width` / `height` / `fileSize` 类型 + 非负检查
  - `validateHeartbeatMessage`: `sequence`
- 新增 `src/__tests__/message-validator-types.test.ts`，覆盖 28 个边界用例

### Changed
- `PROTOCOL_VERSION` 更新至 `1.4.10`

## [1.4.7] - 2026-04-02

### Added
- `MessageType` 新增三个接入授权消息类型（RFC 8628 适配）：
  - `REGISTER_PENDING`：Gateway 收到无 token 的注册请求后，通知客户端等待管理员审批
  - `AUTHORIZATION_GRANTED`：管理员审批通过，颁发 JWT licenseToken
  - `AUTHORIZATION_REJECTED`：管理员审批拒绝
- 对应三个消息接口：`RegisterPendingMessage`、`AuthorizationGrantedMessage`、`AuthorizationRejectedMessage`
- `MessageFactory` 新增三个工厂方法
- 新增三个类型守卫函数

### Changed
- `RegisterMessage` 新增 `licenseToken?: string` 可选字段
  - 首次连接时缺省（触发审批流程）
  - 后续连接携带 JWT（直接验签通过）
- `PROTOCOL_VERSION` 更新至 `1.4.7`

## [1.4.6] - 2026-04-01

### Added
- `ProgressPhase.EXPORT = 'EXPORT'`：新增数据导出阶段
  - 适用于监播表导出、测速值导出等长时间数据回收操作
  - 命令方向与部署流程相反（设备 → Edge/云端），用于数据采集类指令的进度跟踪

### Fixed
- `PROTOCOL_VERSION` 常量同步更新为 `'1.4.6'`（1.4.5 因重复发布限制未能同步）

## [1.4.4] - 2026-03-25

### Changed
- `ProgramParameters.fileSize` 改为可选字段（`fileSize?: number`）
  - 设备下载完成后可自行获取文件大小，客户端无需预先传入
- `ProgramParameters.publishTime` 改为可选字段（`publishTime?: string`）
  - 不填则立即生效，无需强制传入当前时间作为默认值
- `ProgramParameters.unpublishTime` 改为可选字段（`unpublishTime?: string`）
  - 不填则无限期播放，无需强制传入默认截止时间
- `MessageValidator` 同步移除对上述三个字段的必填校验

## [1.4.3] - 2026-03-24

### Changed
- `MessageFactory` 所有方法的 `version` 字段统一固定为 `"1.0"`（消息格式版本），不再使用包版本号
- 移除 `MessageValidator` 中 `version !== PROTOCOL_VERSION` 的误报 warning
- `PROTOCOL_VERSION` 常量仅用于包版本管理和文档，不写入消息体
- 澄清 `BaseMessage.version` 注释：固定为 `"1.0"`，与 `PROTOCOL_VERSION` 无关

## [1.4.2] - 2026-03-24

### Changed
- `ProgramParameters.hashAlgorithm` 类型从 `'SHA256'` 扩展为 `'SHA256' | 'MD5'`
  - MD5 适用于嵌入式设备算力有限或历史遗留系统场景
  - SHA256 仍为首选推荐算法
- `MessageValidator.validateProgramMessage()` 更新验证逻辑，同时接受 `SHA256` 和 `MD5`
- AsyncAPI 文档同步更新 `hashAlgorithm` 字段描述

## [1.4.1] - 2025-08-19

### Added
- 为所有响应消息添加 `clientId` 字段以标识响应设备
  - `CommandResponseMessage` 新增 `clientId: string` 字段
  - `ProgramResponseMessage` 新增 `clientId: string` 字段  
  - `ProgressUpdateMessage` 新增 `clientId: string` 字段
- `UpdateRoutesMessage` 和 `UpdateRoutesAckMessage` 统一使用 `clientId` 替代 `edgeId`

### Changed
- `MessageFactory.createProgressUpdateMessage()` 第一个参数改为 `clientId`
- `MessageFactory.createCommandResponseMessage()` 第一个参数改为 `clientId`
- `MessageFactory.createProgramResponseMessage()` 第一个参数改为 `clientId`
- `MessageFactory.createUpdateRoutesMessage()` 第一个参数改为 `clientId`
- `MessageFactory.createUpdateRoutesAckMessage()` 第一个参数改为 `clientId`
- 协议版本号更新到 `1.4.1`

### Why These Changes
- 统一所有消息使用 `clientId` 标识发送方，提高协议一致性
- 响应消息能够明确标识来源设备，便于路由和跟踪
- 支持更好的消息追踪和调试能力

## [1.4.0] - 2025-08-18

### Added
- 完整的 UPDATE_ROUTES 消息支持，用于 Edge 向 Gateway 同步设备路由信息

## [1.0.1] - 2024-01-XX

### Added
- 为 `CommandMessage` 的 `retryCount` 字段添加默认值 0
- 为 `createCommandResponseMessage` 添加自动计算 `executionTime` 功能
  - 新增 `commandStartTime` 选项，可自动计算命令执行时间
- 为 `createProgramResponseMessage` 添加自动计算 `executionTime` 功能
  - 新增 `programStartTime` 选项，可自动计算程序执行时间
- 为 `createHeartbeatAckMessage` 添加自动计算 `latency` 功能
  - 新增 `heartbeatReceivedTime` 选项，可自动计算处理延迟

### 使用示例

```typescript
// 1. retryCount 现在有默认值 0
const command = MessageFactory.createCommandMessage(
  requestRef,
  targetClientId,
  commandObj,
  callbackUrl
  // retryCount 将默认为 0
);

// 2. 自动计算 executionTime
const startTime = Date.now();
// ... 执行命令 ...
const response = MessageFactory.createCommandResponseMessage(
  requestRef,
  CommandStatus.COMPLETED,
  result,
  'Success',
  { commandStartTime: startTime }  // executionTime 将自动计算
);

// 3. 自动计算 latency
const receivedTime = Date.now();
// ... 处理心跳 ...
const ack = MessageFactory.createHeartbeatAckMessage(
  sequence,
  clientId,
  clientTime,
  { heartbeatReceivedTime: receivedTime }  // latency 将自动计算
);
```

### Changed
- 无破坏性更改，所有改动向后兼容

## [1.0.0] - 2024-01-XX

### Initial Release
- 统一的 WebSocket 协议定义
- 完整的消息类型系统
- MessageFactory 工具类
- MessageValidator 验证器
- ProtocolUtils 工具类