diff --git a/.planning/phases/07-data-fixes/07-12-PLAN.md b/.planning/phases/07-data-fixes/07-12-PLAN.md index 73a849d..68ac68f 100644 --- a/.planning/phases/07-data-fixes/07-12-PLAN.md +++ b/.planning/phases/07-data-fixes/07-12-PLAN.md @@ -17,9 +17,9 @@ autonomous: false requirements: - FIX-01 estimate: - tokens: 58000 - raw_tokens: 58000 - tasks: 5 + tokens: 64000 + raw_tokens: 64000 + tasks: 6 confidence: low must_haves: truths: @@ -27,6 +27,7 @@ must_haves: - "`HeroAttribute.deleteModifierByIndex` 与 `deleteModifier` 的簿记一致:`bindAttribute(null)`、清理 `modifierName`/`modifierNosave`、`markDirty(name)`;被删修饰器可被再次 `addModifier`(不再因残留 `owner` 被码 108 拒绝)(WR-05 关闭)" - "`HeroAttribute.iterateModifiers()` / `getModifierIndex()` 在 `deleteModifierByIndex` 之后不再产出/解析已删除的修饰器;`finalAttribute` 不再停留在陈旧值" - "`HeroEquipment.loadState` 不再经 `equip()` 追加 `Equip` 录像指令:读档期间 `replaySystem.route.add` 不被调用(WR-06 关闭,按 Task 0 Q3 裁决口径)" + - "空存档读档(`{ equipments: [] }`)把 `HeroEquipsStore` 的 uid 计数器重置为 0 且不产生码 58,且该重置可观测(随后的 `add(...)` 返回 0)——由测试锚定(IN-02 测试契约对齐 `ba60ef9`;本计划对 IN-02 **只改测试**,不改其生产逻辑)" - "`pnpm test:ci` 全绿,不新增 `it.skip`;`equipment.test.ts:306`(码 147,D-06)保持原样" artifacts: - path: packages-user/data-base/src/hero/equipStore.ts @@ -38,7 +39,7 @@ must_haves: - path: packages-user/data-base/src/hero/equipment.ts provides: "`HeroEquipment.loadState` 的无录像副作用还原(按 Q3 裁决:disable/revert 包裹或内部无记录装备助手);按 Q4(ii) 裁决处理 `equipment.test.ts` 相邻缺陷" - path: packages-user/data-base/src/hero/saveLoad.test.ts - provides: "WR-04 三档回归(活值往返或契约说明)、WR-06 读档无录像副作用回归、WR-05 相关容器级断言" + provides: "WR-04 三档回归(活值往返或契约说明)、WR-06 读档无录像副作用回归、WR-05 相关容器级断言、IN-02 空存档重置 `nextUid` 断言(改写 `:447-457`,把当前 RED 用例按 `ba60ef9` 的新契约转绿)" - path: packages-user/data-base/src/hero/attribute.test.ts provides: "WR-05 回归:`deleteModifierByIndex` 后 iterate/getModifierIndex/final 值/可重新挂载" - path: packages-user/data-base/src/hero/equipment.test.ts @@ -50,41 +51,45 @@ must_haves: - "`HeroEquipment.loadState`(`equipment.ts:337-347`)→ `equip()`(`:153-198`)→ `replay.route.add(ReplayCommandCode.Equip, [uid])`(`:195`)——读档经公开入口产生了真实录像副作用" - "`HeroEquipment.equip/unequip` 的 `replay.disable()`/`revert()`(`:169-170`/`:194`/`:211-212`)↔ `ReplaySystem.route`(`data-common/src/replay/system.ts:89-95`,`disable`/`revert` 直接转发到 `ReplayArray`)——Q3 选项 A 依赖该既有通道" - "`compareEquip` 的 `this.attribute.getModifierIndex(modifier)` + `clone.deleteModifierByIndex(name, index)`(`equipment.ts:281-284`)——索引取自原属性却作用于克隆属性(相邻缺陷,按 Q4(ii) 裁决处置)" + - "`HeroEquipsStore.loadState`(`equipStore.ts:300-309`,生产修复 `ba60ef9`)↔ `HeroEquipsStore.add`(`:184-192`,`const uid = this.nextUid++`)——空存档 → `nextUid = 0` 的唯一可观测后果是「下一次 `add` 发号 0」,测试必须用 `add` 返回值锚定,而不是只断言「没有码 58」(IN-02)" assumptions: - - "A1(D-10 例外,本计划授权):本计划为修复计划。Q1 若选「活修饰器值为存档源」,则 `saveLoad.test.ts:343-380` 中「读档后回到原始定义值」的既有断言必须改为「回到存档点的活值」——该改写是本计划唯一被授权的既有断言变更,必须在 SUMMARY 逐条说明;其余既有断言一字不改" + - "A1(D-10 例外,本计划授权):本计划为修复计划。Q1 若选「活修饰器值为存档源」,则 `saveLoad.test.ts:343-380` 中「读档后回到原始定义值」的既有断言必须改为「回到存档点的活值」——该改写是本计划两处被授权的既有断言变更之一(另一处为 A5 的 IN-02 用例改写),必须在 SUMMARY 逐条说明;其余既有断言一字不改" - "A2(项目未发布,与 07-09 的 A2 同口径):`IEquipmentStateSave` 形状可变更,不保留旧形状兼容分支;本计划优先保持形状、只改语义(Q1 选项 A 可在现有 `value`/`percentage` 两个 Map 上表达活值与定义的差异)" - "A3(不新增修饰器 API):`BaseHeroModifier` 已有 `saveState`/`loadState`/`getValue`/`setValue`,本计划**不**修改 `modifier.ts`;若实现中发现必须改修饰器基类,暂停并回到 Task 0 关卡追加裁决,不得自行扩大改动面" - - "A4(范围守卫):仅修 WR-04 / WR-05 / WR-06 三条;`IN-02`(空存档不重置 `nextUid`)与 `IN-04`/`IN-05` 不在本轮清单,只登记不修;`ReplayArray.setReplayArray` 的 `disabled = 0` 重置(`array.ts:807`)属 07-10 的文件,本计划不改" + - "A4(范围守卫):代码修复面仅 WR-04 / WR-05 / WR-06 三条;`IN-04`/`IN-05` 不在本轮清单,只登记不修;`IN-02`(空存档不重置 `nextUid`)的生产代码已由用户在 `ba60ef9` 修复,本计划以**测试专用**方式纳入(Task 1),不改其生产逻辑;`ReplayArray.setReplayArray` 的 `disabled = 0` 重置(`array.ts:807`)属 07-10 的文件,本计划不改" + - "A5(IN-02 测试专用,本计划授权;与 A1 并列的第二处授权断言变更):`saveLoad.test.ts:447-457`(`warns code 58 when the max equipment uid cannot be found`)在 `ba60ef9` 之后**当前为 RED**(`vitest run …/hero/saveLoad.test.ts` → 1 failed / 26 passed),因为空存档已不再发码 58 且会重置 `nextUid`。本计划把该用例改写为新契约(空存档 → `nextUid` 归零、随后的 `add` 返回 0、不发码 58),并在 SUMMARY 登记一条**契约/覆盖变化**:`logger.error(58)` 分支(`equipStore.ts:304-307`)现在只剩「非空但 `maxBy` 仍返回 `undefined`」的畸形数组防御路径可达,码 58 不再由空存档用例覆盖。除该用例与该变化外,其余既有断言一字不改" prohibitions: - "不改 `ReplayArray`/`ReplaySandbox`/`ReplaySystem`(`packages-user/data-common/src/replay/*`)——Q3 的修法只能在 `equipment.ts` 侧实现" - "不改 `modifier.ts` 的 `BaseHeroModifier`(A3);不改 `attribute.ts` 的 `addModifier`/`deleteModifier`(只动 `deleteModifierByIndex` 与其余按 Q2 裁决点名的部分)" - "不改 `HeroState`/`equipStore` 的存读档**实例身份**语义(07-11 已定);本计划不重新引入 `clear()` + 重建" - "不改 `data-state`(`core.ts` / saveables 顺序)、不改渲染端 `@user/client-*` 与 legacy 接线" - "不引入新依赖、不新建源码文件、不新增 `import type`、不使用连续 `as` 断言" - - "不新增 `it.skip`;不为了转绿而弱化断言(A1 授权的两处除外)" + - "不新增 `it.skip`;不为了转绿而弱化断言(A1、A5 授权的两处除外)" + - "IN-02 属**测试专用**:不得为 IN-02 改动 `HeroEquipsStore.loadState` 的 `nextUid` 分支(`equipStore.ts:300-309`)——`ba60ef9` 是用户已完成的生产修复基线,本计划只把测试对齐它;WR-04 对 `equipStore.ts` 存档源的改动不受此禁令限制" --- -闭合装备/属性子系统的三条存档正确性缺陷:装备数值加成的**存档源**(WR-04)、修饰器删除的**簿记**(WR-05)、读档期的**录像副作用**(WR-06)。 +闭合装备/属性子系统的三条存档正确性缺陷:装备数值加成的**存档源**(WR-04)、修饰器删除的**簿记**(WR-05)、读档期的**录像副作用**(WR-06);并附一条**测试专用**的契约对齐(IN-02:空存档读档的 uid 计数器契约,生产代码已由用户在 `ba60ef9` 修复,本计划只把测试改写对齐,不重改生产逻辑)。 现状(逐条锚点): 1. **WR-04(warning,`equipStore.ts:34-36, 67-102, 259-288`)** `this.value`/`this.percentage` 只在构造期从 `item.equip` 拷贝、此后只被另一次读档覆盖;活修饰器值(`BaseHeroModifier.setValue`,`modifier.ts:25-28`)从不回流。后果:`saveDiff()`(`:80-102`)与 `item.equip` 比较恒相等 → 低/高压缩档「无差异」→ 装备状态**一个字节都不存**;`saveNoCompression()`(`:67-74`)存的也是 `this.value` 而非修饰器值 → 无压缩档同样丢弃运行时变更。`07-REVIEW.md` 指出 `saveLoad.test.ts:343-380` 恰好把这个错误行为写成了断言(改修饰器到 0.9/99,断言读回 0.5/5)。 2. **WR-05(warning,`attribute.ts:230-239`)** `deleteModifierByIndex()` 只 `splice`:不 `bindAttribute(null)`、不清理 `modifierName`/`modifierNosave`、不 `markDirty`。后果:`iterateModifiers()`/`getModifierIndex()` 仍能看到已删除的修饰器;被删修饰器 `owner` 残留 → 再次 `addModifier` 被码 108 拒绝(永久不可用);`finalAttribute[name]` 陈旧。唯一调用点 `compareEquip`(`equipment.ts:281-284`)。 3. **WR-06(warning,`equipment.ts:336-346`)** `loadState()` 通过公开 `equip(uid, index)`(`:168-196`,末尾 `:195` 有 `replay.route.add(ReplayCommandCode.Equip, [uid])`)恢复槽位,读档因此向活录像追加 `Equip` 指令。今日只被 `CoreState.loadState` 的 saveable 顺序掩盖(hero 在 replay 之前,随后被覆盖),英雄单独读档或存档缺 `@system/replay` 时污染录像。 +4. **IN-02(info,`equipStore.ts:282-287` 原文 → 现 `:300-309`)** 原缺陷:空存档时 `maxBy` 返回 `undefined`、发码 58 并提前 `return`,`nextUid` 停在陈旧值。**生产代码已由用户在 `ba60ef9` 修复**:`state.equipments.length === 0` → `this.nextUid = 0`;否则 `maxBy` 取 `maxUid.uid + 1`,取不到才 `logger.error(58)`。但 `saveLoad.test.ts:447-457` 仍按旧契约断言(空存档 → `toContain(58)`),因此该文件**当前为 RED**(`vitest run …/hero/saveLoad.test.ts` → 1 failed / 26 passed);且该用例必须先转绿,否则后续 Task 的 `saveLoad.test.ts` 级 `` 会被这条**既存失败**阻塞。本计划把该用例改写为新契约(空存档 → `nextUid` 归零、随后的 `add` 返回 0、不发码 58),并登记随之而来的契约/覆盖变化。 -Purpose: 装备是属性加成的来源,装备存档一旦不落地,「读档后数值正确」就只在无压缩档的部分路径上成立;删除簿记不全则修饰器图与访问器视图分叉;读档写录像会让「读档」这个只读语义产生可回放的历史。三者都在存档边界上,属同一主题。 +Purpose: 装备是属性加成的来源,装备存档一旦不落地,「读档后数值正确」就只在无压缩档的部分路径上成立;删除簿记不全则修饰器图与访问器视图分叉;读档写录像会让「读档」这个只读语义产生可回放的历史。三者都在存档边界上,属同一主题。IN-02 则不同:生产行为已正确,缺的是**测试仍锚定旧契约**——不修则套件持续为红、也失去对 `ba60ef9` 的回归保护。 -Output: `equipStore.ts`(存档源)+ `attribute.ts`(簿记)+ `equipment.ts`(读档无副作用)+ `types.ts`(契约 jsDoc)+ 三份测试文件的回归。 +Output: `equipStore.ts`(存档源)+ `attribute.ts`(簿记)+ `equipment.ts`(读档无副作用)+ `types.ts`(契约 jsDoc)+ 三份测试文件的回归 + IN-02 测试契约对齐(`saveLoad.test.ts:447-457` 改写,把当前 RED 转绿)。 -**Artifacts this phase produces(本计划产出物)**:修改文件见 frontmatter `files_modified`;回归用例见 `must_haves.artifacts`;无新建源码文件、无新增依赖。 +**Artifacts this phase produces(本计划产出物)**:修改文件见 frontmatter `files_modified`;回归用例见 `must_haves.artifacts`;IN-02 为**测试专用**条目(不新增文件、不改其生产逻辑);无新建源码文件、无新增依赖。 **Phase 7 验证已失效**:`07-VERIFICATION.md`(2026-09-16T06:04:11Z)已因 07-09 与本轮 07-10..07-14 失效。本计划执行完毕后**必须重跑 `/gsd-verify-work`**(或 `gsd-verify-work 7`)重新出具验证结论。 -Phase 7 裁决对应(逐条引用):D-01: 本计划是 Phase 7 第 12 个计划,承接 `07-REVIEW.md` 的 WR-04/WR-05/WR-06。D-02: 属 hero 系统(`packages-user/data-base/src/hero`),与 07-04(hero 存读档与属性/槽位)、07-09(属性同引用)、07-11(容器同引用)同系统、按严重度递减排序。D-03/D-04/D-05/D-06: 其它系统契约裁决,不涉及(`HeroEquipment` 的码 146/147 语义不变,147 保持 D-06 的保留码状态)。D-07: path 接线,不涉及。D-08: enemy,不涉及。D-09: 由 Task 0 关卡承载。D-10: 本计划为修复计划,回归用例为必需项(A1)。D-11: 存档源语义变更必须同步 `types.ts` 的 jsDoc。D-12: 沿用 06-CONTEXT D-44 文件级三步门禁。D-13: 按根因原子提交(Task 1 / 2 / 3)。D-14: 三条均无 `WINDOWS.md` 条目,只登记不新建。 +Phase 7 裁决对应(逐条引用):D-01: 本计划是 Phase 7 第 12 个计划,承接 `07-REVIEW.md` 的 WR-04/WR-05/WR-06。D-02: 属 hero 系统(`packages-user/data-base/src/hero`),与 07-04(hero 存读档与属性/槽位)、07-09(属性同引用)、07-11(容器同引用)同系统、按严重度递减排序。D-03/D-04/D-05/D-06: 其它系统契约裁决,不涉及(`HeroEquipment` 的码 146/147 语义不变,147 保持 D-06 的保留码状态)。D-07: path 接线,不涉及。D-08: enemy,不涉及。D-09: 由 Task 0 关卡承载。D-10: 本计划为修复计划,回归用例为必需项(A1;IN-02 测试对齐见 A5)。D-11: 存档源语义变更必须同步 `types.ts` 的 jsDoc。D-12: 沿用 06-CONTEXT D-44 文件级三步门禁。D-13: 按根因原子提交(Task 2 / 3 / 4 分别对应 WR-04 / WR-05 / WR-06;Task 1 的 IN-02 测试对齐单独一条 `test(07-12): …` 提交,不与其根因混提)。D-14: 三条均无 `WINDOWS.md` 条目,只登记不新建。 执行序:`depends_on: 07-11`(Wave 12)。除门禁型依赖(D-09 逐计划裁决关卡 + D-12 全量测试基线逐计划重录)外,还有**文件重叠的隐式依赖**:`hero/equipStore.ts`、`hero/types.ts`、`hero/saveLoad.test.ts` 与 07-11 的 `files_modified` 重叠,同波并行会互相覆盖,故必须排在 07-11 之后。 -**基线(2026-09-16 实测)**:`pnpm test:ci` = 66 文件 / 690 通过 / 1 跳过(`equipment.test.ts:306` 码 147,D-06)。执行前重录当日基线并与本行对比。 +**基线(2026-09-16 实测)**:`pnpm test:ci` = 66 文件 / 690 通过 / 1 跳过(`equipment.test.ts:306` 码 147,D-06)。**自 `ba60ef9` 起**:`saveLoad.test.ts` 的 IN-02 用例转红(单文件 1 failed / 26 passed),故当日全量基线实为「689 通过 / 1 失败(IN-02,`saveLoad.test.ts:447-457`)/ 1 跳过」。执行前重录当日基线并与本行对比;**该 1 条失败是本计划 Task 1 的预期输入**(Task 1 把它转绿),而 Task 5 的全量门禁要求 0 失败、跳过项仍只有 `equipment.test.ts:306` 一条。 @@ -107,6 +112,9 @@ Phase 7 裁决对应(逐条引用):D-01: 本计划是 Phase 7 第 12 个 @packages-user/data-base/src/hero/attribute.ts @packages-user/data-base/src/hero/equipment.ts @packages-user/data-base/src/hero/types.ts +@packages-user/data-base/src/hero/saveLoad.test.ts + +**执行起点提示(IN-02,测试专用)**:`packages-user/data-base/src/hero/saveLoad.test.ts` **当前为 RED**——`warns code 58 when the max equipment uid cannot be found`(`:447-457`)仍按旧契约断言「空存档 → 出现码 58」,而生产代码已在用户提交 `ba60ef9`(`equipStore.ts:300-309`:空存档 → `nextUid = 0`,不再发码 58)中修复,故 `pnpm exec vitest run …/hero/saveLoad.test.ts` = 1 failed / 26 passed。`ba60ef9` 是 Task 1 测试调整的**基线**:Task 1 改写该用例使其对齐新契约,从而把该文件转绿(并解除对 Task 2 / Task 4 的 `saveLoad.test.ts` 级 `` 的既存失败阻塞);本计划**不**再改动 IN-02 的生产逻辑。 @@ -203,7 +211,7 @@ Phase 7 裁决对应(逐条引用):D-01: 本计划是 Phase 7 第 12 个 待用户在关卡处逐条填写(Q1..Q4:勾选选项 id 或给出裁决文字;Q4 若选 B/C 请写清具体哪一处修、哪一处登记)。 **空的 `` 表示尚未裁决**:执行者必须停在本任务,不得按「建议默认」推进。 - 填写后 Task 1–3 一律按本记录执行;若裁决与 Task 1–3 的分支描述冲突,暂停并请用户澄清。 + 填写后 Task 2–4 一律按本记录执行;若裁决与 Task 2–4 的分支描述冲突,暂停并请用户澄清。 逐条给出 Q1..Q4 的裁决(例如「Q1=A, Q2=A, Q3=A, Q4=B」);若要修订方案,回复修订点,执行者退出并按 D-09 修订本 PLAN.md 向用户汇报上述 4 个契约点与各自的直接后果(尤其 Q1 对 `saveLoad.test.ts:341-380` 断言的影响、Q3 对录像语义的影响)。不得在裁决前修改任何文件。 @@ -220,7 +228,35 @@ Phase 7 裁决对应(逐条引用):D-01: 本计划是 Phase 7 第 12 个 - Task 1: WR-04 —— 装备存档源改为活修饰器值(按 Q1 裁决),三档压缩下运行时变更可往返或契约明写 + Task 1: IN-02(测试专用)—— 空存档读档重置 `nextUid` 为 0 且不发码 58:改写 `saveLoad.test.ts:447-457` 到 `ba60ef9` 的新契约,把当前 RED 转绿 + packages-user/data-base/src/hero/saveLoad.test.ts + + - packages-user/data-base/src/hero/equipStore.ts(:267-310 `HeroEquipsStore.loadState`,尤其 :300-309 的 `state.equipments.length === 0 → this.nextUid = 0`、`maxBy(state.equipments, 'uid')` 与 `logger.error(58)` 分支;:184-192 `add` 用 `const uid = this.nextUid++` 发号) + - packages-user/data-base/src/hero/saveLoad.test.ts(:406-460 `HeroEquipsStore save and load round trips`::408-423 的夹具与「续接自增 uid」断言风格、:426-444 的码 59 用例(`logger.catch` 用法参考)、:447-457 **当前 RED** 的 `warns code 58 when the max equipment uid cannot be found`) + - packages-user/data-base/src/hero/equipStore.test.ts(:140-200 `add` 返回值与 `delete` 用例的断言风格) + - .planning/phases/07-data-fixes/07-REVIEW.md(IN-02 原文:`:247-251`;`Fix: set this.nextUid = 0 in the empty branch`) + + + **背景与边界**:IN-02 的**生产修复已由用户在 `ba60ef9` 完成**(`equipStore.ts:300-309`:空存档 → `nextUid = 0`;否则 `maxUid.uid + 1`,取不到才 `logger.error(58)`)。本任务**只改测试**:不得为 IN-02 修改 `equipStore.ts` 的任何生产逻辑,也不得顺手改动 `nextUid` 分支的写法。本任务与 Task 0 的 Q1..Q4 裁决无关(生产行为已定),但按文档顺序在 Task 0 关卡之后执行。**先做本任务**:`saveLoad.test.ts` 现有 1 条 RED,若不先转绿,Task 2 / Task 4 对该文件的 `` 会被这条既存失败阻塞。 + (1) 改写 `saveLoad.test.ts:447-457` 的用例:夹具改为与 `:408-423` 同口径(先 `registerItem(env, createEquipItem(10, 'sword', [0], [['atk', 5]]))`,让 `add(10)` 可发号),用例名改为反映新契约(如 `resets the uid counter when the save has no equipment`); + (2) 用 `logger.catch(() => env.store.loadState({ equipments: [] }, SaveCompression.NoCompression))` 读入空存档后,断言两件事:(a) 计数器重置**可观测**——`expect(env.store.add(10)).toBe(0)`(唯一可观测后果,见 frontmatter `must_haves.key_links`);(b) 该路径**不产生码 58**——`expect(result.info.map(info => info.code)).not.toContain(58)`。只断言 (b) 不足以证明重置发生,两条都必须有; + (3) 在该用例内用一行注释登记**契约/覆盖变化**:`logger.error(58)` 现在只剩「非空数组但 `maxBy` 仍返回 `undefined`(畸形/含无效条目)」的防御路径可达,不再是空存档的行为;注释须写明该分支属 `equipStore.ts` 的用户实现、保持原样(防止后续被误当作死代码删除),并在 Task 5 的 SUMMARY 登记段落复述同一结论; + (4) 不改动同文件其它用例(尤其 `:408-423` 的 uid 连续性用例、`:426-444` 的码 59 用例);不新增跳过用例;不弱化断言——本用例是新契约的等值/真值断言,不是放宽。 + + + pnpm exec vitest run packages-user/data-base/src/hero/saveLoad.test.ts + 该文件仍有失败用例(含 `:447-457` 改写后的用例);或改写后的用例仍断言出现码 58;或空存档读档后 `env.store.add(10)` 未返回 0(重置不可观测/未发生);或为让该用例通过而改动了 `equipStore.ts` 的生产逻辑 —— 按 D-09 退出并修订计划 + + + - `pnpm exec vitest run packages-user/data-base/src/hero/saveLoad.test.ts` 全绿:改写后的用例断言「空存档 → 随后 `add(10)` 返回 0」且「本次读档不产生码 58」,此前的 RED 已消除 + - 用例内注释登记了码 58 分支的剩余可达条件(非空畸形数组)与「该分支保持原样」的结论 + - `equipStore.ts` 的 IN-02 生产逻辑零改动(`ba60ef9` 为基线);未新增跳过用例;同文件其它用例逐字未动 + + IN-02 的测试契约与 `ba60ef9` 的生产行为一致,`saveLoad.test.ts` 全绿(本计划只改测试);码 58 的覆盖变化已在使用例内注释登记,待 Task 5 汇总进 SUMMARY + + + + Task 2: WR-04 —— 装备存档源改为活修饰器值(按 Q1 裁决),三档压缩下运行时变更可往返或契约明写 packages-user/data-base/src/hero/equipStore.ts, packages-user/data-base/src/hero/types.ts, packages-user/data-base/src/hero/saveLoad.test.ts - packages-user/data-base/src/hero/equipStore.ts(:15-165 `EquipmentState` 全量:`value`/`percentage` 私有表、`rebuildModifiers`、三档 save/load) @@ -254,7 +290,7 @@ Phase 7 裁决对应(逐条引用):D-01: 本计划是 Phase 7 第 12 个 - Task 2: WR-05 —— `HeroAttribute.deleteModifierByIndex` 簿记与 `deleteModifier` 对齐(含迭代器/索引/final/可重新挂载回归),按 Q4(i) 裁决处理 `compareEquip` + Task 3: WR-05 —— `HeroAttribute.deleteModifierByIndex` 簿记与 `deleteModifier` 对齐(含迭代器/索引/final/可重新挂载回归),按 Q4(i) 裁决处理 `compareEquip` packages-user/data-base/src/hero/attribute.ts, packages-user/data-base/src/hero/attribute.test.ts, packages-user/data-base/src/hero/equipment.ts, packages-user/data-base/src/hero/equipment.test.ts - packages-user/data-base/src/hero/attribute.ts(:169-188 `iterateModifiers`/`getModifiers`/`getModifierIndex`、:190-211 `addModifier`(:195-199 码 108 拒绝)、:213-228 `deleteModifier`、:230-239 `deleteModifierByIndex`、:241-249 `markDirty`/`markModifierDirty`) @@ -285,7 +321,7 @@ Phase 7 裁决对应(逐条引用):D-01: 本计划是 Phase 7 第 12 个 - Task 3: WR-06 —— `HeroEquipment.loadState` 读档不再追加录像指令(按 Q3 裁决) + Task 4: WR-06 —— `HeroEquipment.loadState` 读档不再追加录像指令(按 Q3 裁决) packages-user/data-base/src/hero/equipment.ts, packages-user/data-base/src/hero/equipment.test.ts, packages-user/data-base/src/hero/saveLoad.test.ts - packages-user/data-base/src/hero/equipment.ts(:153-198 `equip`(:168-170 注释与 disable、:194 revert、:195 route.add)、:200-218 `unequip`(:210-212 route.add)、:330-347 `saveState`/`loadState`) @@ -316,18 +352,19 @@ Phase 7 裁决对应(逐条引用):D-01: 本计划是 Phase 7 第 12 个 - Task 4: D-12/D-44 门禁 + 全量套件 + 缺口登记(WR-04/05/06 + Q4 处置 + IN-02/IN-04/IN-05)与 Phase 7 验证失效说明 + Task 5: D-12/D-44 门禁 + 全量套件 + 缺口登记(WR-04/05/06 + Q4 处置 + IN-02 已闭合(测试专用) + IN-04/IN-05 保留在册)与 Phase 7 验证失效说明 .planning/phases/07-data-fixes/07-12-SUMMARY.md - - .planning/phases/07-data-fixes/07-REVIEW.md(WR-04/WR-05/WR-06 与 IN-02/IN-04/IN-05 的编号,便于逐条对照闭合/登记) + - .planning/phases/07-data-fixes/07-REVIEW.md(WR-04/WR-05/WR-06 与 IN-02/IN-04/IN-05 的编号,便于逐条对照闭合/登记;IN-02 原文在 `:247-251`) + - packages-user/data-base/src/hero/saveLoad.test.ts(Task 1 改写后的 IN-02 用例与其中的契约注释 —— SUMMARY 的覆盖变化登记须与之一致) - .planning/phases/07-data-fixes/07-VERIFICATION.md(头部 verified/status) - .planning/ROADMAP.md(Phase 7 段 Plans 与 Wave 列表) - .planning/WINDOWS.md(确认三条无入账 id) - (1) **门禁(D-12 / D-44 文件级)**:对 `files_modified` 中每个改动的文件执行 `pnpm exec eslint --fix <文件>` 后 `pnpm exec eslint <文件>` 0 错误;**文件级类型门禁按 06-RESEARCH Pitfall 4 的判定方式**:`pnpm exec vue-tsc --noEmit 2>&1 | Select-String -Pattern "<改动文件相对路径>"` 无命中(**不得**用「退出码非 0」判定失败);`pnpm test:ci` 全绿(对比基线 66 文件 / 690 通过 / 1 跳过,跳过项仍只能是 `equipment.test.ts:306` 码 147)。 - (2) **原子提交(D-13)**:Task 1 / 2 / 3 各自独立提交(`fix(07-12): …`,携带 `WR-04` / `WR-05` / `WR-06` 与必要的 `#06-09-1` 锚点)。 - (3) **缺口登记**:SUMMARY 的 findings/ledger 段落逐条标注 WR-04/WR-05/WR-06 已闭合(附 commit)、Q4 两处相邻缺陷的处置(已修/已登记)、`IN-02`/`IN-04`/`IN-05` 保留在册;若 A1 授权的断言翻转发生,在此逐条列出「旧断言 → 新断言 → 理由」。`WINDOWS.md` 不新建、不 `fixed`/`waive`。 + (1) **门禁(D-12 / D-44 文件级)**:对 `files_modified` 中每个改动的文件执行 `pnpm exec eslint --fix <文件>` 后 `pnpm exec eslint <文件>` 0 错误;**文件级类型门禁按 06-RESEARCH Pitfall 4 的判定方式**:`pnpm exec vue-tsc --noEmit 2>&1 | Select-String -Pattern "<改动文件相对路径>"` 无命中(**不得**用「退出码非 0」判定失败);`pnpm test:ci` 全绿(对比基线 66 文件 / 690 通过 / 1 跳过,跳过项仍只能是 `equipment.test.ts:306` 码 147;**执行起点含 `ba60ef9` 引入的 1 条 IN-02 失败,须由 Task 1 消除**——门禁要求 0 失败)。 + (2) **原子提交(D-13)**:Task 1..4 各自独立提交——Task 1 为 `test(07-12): …`(IN-02 测试契约对齐 `ba60ef9`),Task 2 / 3 / 4 为 `fix(07-12): …`(分别携带 `WR-04` / `WR-05` / `WR-06` 与必要的 `#06-09-1` 锚点);IN-02 的测试调整不得与其根因混入同一条提交。 + (3) **缺口登记**:SUMMARY 的 findings/ledger 段落逐条标注 WR-04/WR-05/WR-06 已闭合(附 commit)、Q4 两处相邻缺陷的处置(已修/已登记)、**IN-02 已闭合(测试专用:测试契约对齐 `ba60ef9`,附 commit;生产逻辑零改动)并附其契约/覆盖变化——`logger.error(58)` 只剩「非空但 `maxBy` 返回 `undefined`」的畸形数组防御路径可达,码 58 不再由空存档用例覆盖**、`IN-04`/`IN-05` 保留在册;若 A1 授权的断言翻转或 A5 授权的 IN-02 用例改写发生,在此逐条列出「旧断言 → 新断言 → 理由」。`WINDOWS.md` 不新建、不 `fixed`/`waive`。 (4) **Phase 7 验证失效**:SUMMARY 明写 `07-VERIFICATION.md` 已因 07-09 + 07-10..07-14 失效,结束后必须重跑 `/gsd-verify-work`(或 `gsd-verify-work 7`);`ROADMAP.md` 的 Phase 7 计划数与 Wave 列表同步。 @@ -336,8 +373,8 @@ Phase 7 裁决对应(逐条引用):D-01: 本计划是 Phase 7 第 12 个 - 改动文件 `eslint` 0 错误、`vue-tsc --noEmit` 该文件 0 类型错误、`pnpm test:ci` 全绿且跳过项仍仅 1 条 - - Task 1–3 各有独立提交并含对应锚点 - - SUMMARY 完成缺口登记(WR-04/05/06、Q4 处置、IN-02/IN-04/IN-05、A1 断言翻转清单)与 Phase 7 验证失效说明 + - Task 1–4 各有独立提交并含对应锚点(Task 1 = IN-02 `test(07-12)`;Task 2–4 = WR-04/05/06 `fix(07-12)`) + - SUMMARY 完成缺口登记(WR-04/05/06、Q4 处置、IN-02 已闭合 + 码 58 覆盖变化、IN-04/IN-05 保留在册、A1 断言翻转 / A5 IN-02 用例改写清单)与 Phase 7 验证失效说明 - `WINDOWS.md` 未被本计划改动 D-44 三步门禁全绿、原子提交完成、缺口与断言变更逐条登记、Phase 7 验证失效说明写入 SUMMARY @@ -353,35 +390,39 @@ Phase 7 裁决对应(逐条引用):D-01: 本计划是 Phase 7 第 12 个 | 存档数据 → 装备/属性状态 | 读档决定修饰器的值与成员集合;错误的事实源会让「读档后数值」静默偏离 | | 公开 API 调用方 → 修饰器图 | `deleteModifierByIndex` / `getModifiers()` / `compareEquip` 允许外部改动或误删修饰器,簿记不全即产生与访问器视图分叉的状态 | | 读档 → 录像路由 | `loadState` 经 `equip()` 触发录像记录,把只读语义变成可回放历史 | +| 存档快照 → uid 计数器 | 空存档读档决定 `HeroEquipsStore.nextUid` 的复位;测试若锚定旧契约,即失去对已修行为的回归保护(且套件持续为红) | ## STRIDE Threat Register | Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | |-----------|----------|-----------|----------|-------------|-----------------| -| T-7-31 | Tampering | `EquipmentState.saveDiff` / `saveNoCompression`(`equipStore.ts:67-102`) | medium | mitigate | Task 1:按 Q1 裁决以活修饰器值为存档源(或明写契约),消除「压缩档零持久化」与「运行时变更丢失」 | -| T-7-32 | Tampering | `HeroAttribute.deleteModifierByIndex`(`attribute.ts:230-239`) | medium | mitigate | Task 2:与 `deleteModifier` 同构的簿记(解绑 + 清理两表 + `markDirty`),并有四条后果的回归断言 | -| T-7-33 | Tampering | `compareEquip` 的索引错配(`equipment.ts:281-284`) | medium | mitigate | Task 2 按 Q4(i) 裁决:在克隆属性上定位修饰器(修)或不修并登记为已知缺陷 | -| T-7-34 | Repudiation | `HeroEquipment.loadState` 追加 `Equip` 指令(`equipment.ts:195` 经 `:345`) | medium | mitigate | Task 3:按 Q3 裁决禁用记录或无记录助手;断言 `route.add` 在读档期未被调用,且正常 `equip`/`unequip` 仍记录 | +| T-7-31 | Tampering | `EquipmentState.saveDiff` / `saveNoCompression`(`equipStore.ts:67-102`) | medium | mitigate | Task 2:按 Q1 裁决以活修饰器值为存档源(或明写契约),消除「压缩档零持久化」与「运行时变更丢失」 | +| T-7-32 | Tampering | `HeroAttribute.deleteModifierByIndex`(`attribute.ts:230-239`) | medium | mitigate | Task 3:与 `deleteModifier` 同构的簿记(解绑 + 清理两表 + `markDirty`),并有四条后果的回归断言 | +| T-7-33 | Tampering | `compareEquip` 的索引错配(`equipment.ts:281-284`) | medium | mitigate | Task 3 按 Q4(i) 裁决:在克隆属性上定位修饰器(修)或不修并登记为已知缺陷 | +| T-7-34 | Repudiation | `HeroEquipment.loadState` 追加 `Equip` 指令(`equipment.ts:195` 经 `:345`) | medium | mitigate | Task 4:按 Q3 裁决禁用记录或无记录助手;断言 `route.add` 在读档期未被调用,且正常 `equip`/`unequip` 仍记录 | | T-7-35 | Tampering | `EquipmentState.getModifiers()` 返回内部数组(`equipStore.ts:57-61`) | low | mitigate | 按 Q4(ii) 裁决:纳入则返回副本/只读视图,否则登记在册 | -| T-7-36 | Denial of Service | `replay.disable()`/`revert()` 配对失衡(Q3 选项 A) | low | mitigate | Task 3:用 `try/finally` 或与 `equip()` 一致的配对路径保证 `revert` 必达;`` 拦截「正常记录被永久关闭」 | +| T-7-36 | Denial of Service | `replay.disable()`/`revert()` 配对失衡(Q3 选项 A) | low | mitigate | Task 4:用 `try/finally` 或与 `equip()` 一致的配对路径保证 `revert` 必达;`` 拦截「正常记录被永久关闭」 | +| T-7-37 | Repudiation | `HeroEquipsStore.loadState` 空存档的 uid 计数器契约(`equipStore.ts:300-309`,生产修复 `ba60ef9`) | low | mitigate | Task 1:测试锚定「空存档 → 随后 `add` 发号 0」且该路径不产生码 58;并在用例注释与 SUMMARY 登记「码 58 只剩非空畸形数组可达」的覆盖/契约变化(测试专用,生产逻辑零改动) | | T-7-SC | Tampering | npm/pnpm 依赖安装 | high | mitigate | 本计划不新增依赖、不改 `package.json`;出现安装需求即暂停并要求用户确认包合法性 | -1. `pnpm exec vitest run packages-user/data-base/src/hero/saveLoad.test.ts packages-user/data-base/src/hero/attribute.test.ts packages-user/data-base/src/hero/equipment.test.ts packages-user/data-base/src/hero/equipStore.test.ts` 全绿。 -2. `pnpm test:ci` 全绿:66 文件 / 690 通过 / 1 跳过(仅 `equipment.test.ts:306` 码 147)。 +1. `pnpm exec vitest run packages-user/data-base/src/hero/saveLoad.test.ts packages-user/data-base/src/hero/attribute.test.ts packages-user/data-base/src/hero/equipment.test.ts packages-user/data-base/src/hero/equipStore.test.ts` 全绿(`saveLoad.test.ts` 的 IN-02 用例已按 `ba60ef9` 的新契约改写,不再是 1 failed / 26 passed)。 +2. `pnpm test:ci` 全绿:66 文件 / 690 通过 / 1 跳过(仅 `equipment.test.ts:306` 码 147);执行起点的 IN-02 失败已由 Task 1 消除。 3. `pnpm exec eslint ` 0 错误;`pnpm exec vue-tsc --noEmit` 输出不含这些文件的类型错误。 -4. Task 0 的 `` 中 Q1..Q4 均有裁决,代码与裁决逐条一致(SUMMARY 对照表);A1 授权的断言翻转已逐条列出。 -5. 无新增依赖、无新建源码文件、`packages-user/data-common` 下零改动、`WINDOWS.md` 未改动。 -6. Phase 7 验证失效与重跑要求已在 `07-12-SUMMARY.md` 明写,`ROADMAP.md` 已更新。 +4. Task 0 的 `` 中 Q1..Q4 均有裁决,代码(Task 2 / 3 / 4)与裁决逐条一致(SUMMARY 对照表);A1 授权的断言翻转与 A5 授权的 IN-02 用例改写均已逐条列出「旧断言 → 新断言 → 理由」。 +5. IN-02 为测试专用:`equipStore.ts` 的 `nextUid` 分支(`:300-309`)零改动(与 `ba60ef9` 逐字一致);改写后的用例断言「`saved: { equipments: [] }` → 随后 `add(10)` 返回 0」且该路径不产生码 58;码 58 的剩余可达条件(非空但 `maxBy` 返回 `undefined`)已在用例注释与 SUMMARY 登记。 +6. 无新增依赖、无新建源码文件、`packages-user/data-common` 下零改动、`WINDOWS.md` 未改动。 +7. Phase 7 验证失效与重跑要求已在 `07-12-SUMMARY.md` 明写,`ROADMAP.md` 已更新。 +- IN-02(测试专用)闭合:`saveLoad.test.ts:447-457` 改写为「空存档 → `nextUid` 归零、`add` 发号 0、不产生码 58」,该文件由 RED(1 failed / 26 passed)转绿;`equipStore.ts` 的 IN-02 生产逻辑零改动(`ba60ef9` 为基线);码 58 的契约/覆盖变化已登记。 - WR-04 按 Q1 裁决闭合:装备存档的事实源与裁决一致,三档压缩下有对应断言(或契约明写并有测试锚定)。 - WR-05 闭合:`deleteModifierByIndex` 后迭代器/索引/final/重新挂载四条后果全部自洽。 - WR-06 按 Q3 裁决闭合:读档不产生录像命令,正常装备/卸下记录能力不变。 - Q4 两处相邻缺陷按裁决修或登记,并在 SUMMARY 逐条记录。 -- `pnpm test:ci` 全绿、无新增 `it.skip`;D-44 三步门禁全绿;A1 授权的断言变更已逐条登记。 +- `pnpm test:ci` 全绿、无新增跳过用例;D-44 三步门禁全绿;A1 / A5 授权的断言变更已逐条登记。 - Phase 7 验证失效与重跑要求已记录。