diff --git a/.planning/phases/07-data-fixes/07-03-PLAN.md b/.planning/phases/07-data-fixes/07-03-PLAN.md
index 2ae93b7..6aa2d1e 100644
--- a/.planning/phases/07-data-fixes/07-03-PLAN.md
+++ b/.planning/phases/07-data-fixes/07-03-PLAN.md
@@ -12,45 +12,55 @@ autonomous: false
requirements:
- FIX-01
estimate:
- tokens: 48000
- raw_tokens: 48000
+ tokens: 60000
+ raw_tokens: 60000
tasks: 6
confidence: low
must_haves:
truths:
- "取消 skip 后 replay/array.test.ts 的 int64 往返用例转绿"
- "取消 skip 后多字节 bigint 往返用例与 bigint/int64 异质混排用例转绿"
+ - "负 int64(-1、-2147483649、-4294967297)经同一 8 字节 int64 槽位精确往返,非负值(含 2^53-1)行为逐位不变"
+ - "负 bigint(-1n、-128n、-129n、-256n、-65537n)经长度前缀最高位符号位精确往返,非负 bigint 编解码逐字节不变且既有 255 字节上限与 warn-152 墙行为不变"
- "取消 skip 后 delete 中间步与异质路线删除用例转绿"
- "取消 skip 后 insert 与异质路线插入用例转绿,且插入后 array.get(i) 读到正确参数"
- "delete(0)、insert 零参数、set、位宽切换与扩容等既有绿用例不回归"
- "pnpm test:ci 全绿,且本计划不新增任何 it.skip"
artifacts:
- path: packages-user/data-common/src/replay/array.ts
- provides: "编解码精确往返(int64/bigint)与命令索引/参数字节偏移严格区分"
+ provides: "编解码精确往返(int64 含负数 / bigint 含负数)与命令索引/参数字节偏移严格区分"
key_links:
- "array.ts:621 解码乘数 ↔ array.ts:369-370 编码侧的 2147483648"
+ - "负 int64 的 low ∈ [0, 2^31) ↔ array.ts:369-370 编码式 num - high * 2147483648 与 :621 解码式 high * 2147483648 + low"
- "array.ts:628 长度前缀读取 ↔ array.ts:378 以无符号写入的 arr.length"
+ - "负 bigint 长度前缀最高位符号位 ↔ array.ts:378 写入 arr.length | 0x80 与 :628 以 getUint8 读出后按位还原(asIntN)"
- "indexArray[i] = 第 i 条命令的参数起始字节(array.ts:405 写入、:749-767 rebuildIndexArray 重建)↔ delete/insert 的位移与回退起点"
- "末条命令的终点哨兵 ↔ paramUsed(array.ts:63/406/456/495/766 维护)"
assumptions:
- - "FIX-01 探针未分类:本计划以显式假设承接,即 FIX-01 = 「登记缺陷的正确预期 skip 用例转绿且不新增跳过」;若用户对 replay 四条的期望不同,在 D-09 预执行汇报时裁决"
- - "A6:`#06-04-4` 同时修 `:424`(paramArray 方向)与 `:425`(indexArray 方向)属同一根因的完整修复;只修 `:424` 也能让两条 skip 转绿,但 array.get(i) 的插入语义仍错——该取舍须在 D-09 汇报中点名"
+ - "FIX-01 判据(D-09 已裁决):保留既有正确预期 skip 的取消(含其断言),并为本计划新增的负值路径补充聚焦用例;除本条外不改变其他计划的 FIX-01 口径"
+ - "A6(D-09 已裁决为 approve-both):`#06-04-4` 同时修 `:424`(paramArray 方向)与 `:425`(indexArray 方向)属同一根因的完整修复;只修 `:424` 也能让两条 skip 转绿但 array.get(i) 的插入语义仍错,故两处同修为最终选择"
+ - "A7(D-10 例外,用户显式授权且仅限本计划):允许为负 int64 / 负 bigint 往返路径新增聚焦 it 用例(负值用例 + 非负对照);既有断言一律一字不改,且不得为四条已登记缺陷新增用例"
+ - "A8(负 int64 编码约定,用户裁决):编码侧 low = num - high * 2147483648 使 low ∈ [0, 2^31),解码侧 value = high * 2147483648 + low;对非负值与既有 num % 2147483648 结果逐位相同,沿用现有 8 字节 int64 槽位与字节布局,无格式变更"
+ - "A9(负 bigint 方法 α,用户裁决):复用 bigint 长度前缀字节的最高位作符号位——负值写 lengthByte = length | 0x80 并以 BigInt.asUintN(8 * length, param) 取补码载荷;解码读 lengthByte & 0x7f 为长度,符号位为 1 时以 BigInt.asIntN(8 * (lengthByte & 0x7f), 累积值) 还原;非负 bigint 编解码逐字节不变;已知取舍:负值载荷上限 127 字节(约 2^1016),窄于非负的 255 字节"
prohibitions:
- - "不新增测试用例(D-10);只取消 it.skip 与 D-09 点名的既有断言纠偏"
+ - "不新增测试用例(D-10)——唯一例外:本计划经用户显式授权(A7),仅可为负 int64 / 负 bigint 往返路径及其非负对照新增聚焦 it 用例;既有断言一律一字不改,且不得为四条已登记缺陷新增用例"
- "不弱化断言,不改写为可跑绿假象"
- "不修改渲染端 @user/client-* 与 legacy 渲染接线"
- "不引入新依赖、不新建文件"
- - "不修改本计划 files_modified 之外的源码;相邻未登记的同族缺口(ReplayArray.set 的末步 nextParam 与回退起点、insert(index === length) 的 paramStart、负 int64/负 bigint 不可逆)一律只登记不修"
+ - "不改动非负 bigint 的编码字节布局与 255 字节长度上限,也不改变 warn-152 墙行为;负值载荷 127 字节上限为方法 α 的已知取舍(A9),不得以牺牲非负兼容性换取负值范围"
+ - "不修改本计划 files_modified 之外的源码;相邻未登记的同族缺口(ReplayArray.set 的末步 nextParam 与回退起点 paramStart + 1、insert(index === length) 的 paramStart)一律只登记不修"
---
修复录像数组(`@user/data-common` L0)的 4 条编解码 / 索引编辑缺陷:`#06-04-1` int64 解码乘数写成 `2^31 - 1`、`#06-04-2` 多字节 bigint 编解码错误(编码循环体 + 有符号读取长度前缀与字节)、`#06-04-3` `delete` 以参数字节偏移当作命令索引回退起点且末步缺终点哨兵、`#06-04-4` `insert` 的两处 `copyWithin` 位移方向相反。
-Purpose: 录像数组是回放一致性的唯一数据载体;编解码不可逆或索引错位会让回放在首个分歧处失败,且诊断码(151/152/154/155/2001–2008)无法反映真实原因。
+在 D-09 裁决(`approve-both`)基础上扩大范围:同时补齐**负值路径**——负 int64 沿用现有 8 字节 int64 槽位,编码 `low = num - high * 2147483648`(`low ∈ [0, 2^31)`)、解码 `value = high * 2147483648 + low`;负 bigint 用长度前缀最高位作符号位(方法 α,`BigInt.asUintN`/`asIntN` 补码)。两者对非负值均字节级向后兼容,使整数在 `|n| ≤ 2^53-1` 含负数全可逆、bigint 含负数可逆;并为负值路径新增经用户显式授权的聚焦用例。
-Output: `array.ts` 四处最小修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04-3`/`#06-04-4` 各成一族)+ 7 条 skip 取消 + WINDOWS.md id 23 结清 + 同族未登记缺口登记。
+Purpose: 录像数组是回放一致性的唯一数据载体;编解码不可逆或索引错位会让回放在首个分歧处失败,且诊断码(151/152/154/155/2001–2008)无法反映真实原因。负值(坐标 / 伤害中间值等)此前在同一编解码家族内静默失真,属完整性缺口。
-执行序:本计划在 07-02(enemy)之后串行执行;`depends_on` 表示 D-09 逐步用户确认与 D-12c 全量门禁的串行约束,源码层面与本系统无耦合。
+Output: `array.ts` 四处登记修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04-3`/`#06-04-4` 各成一族)+ 两处负值支持(负 int64、负 bigint 方法 α)+ 7 条既有 skip 取消 + 经授权的负值聚焦用例(含非负对照)+ WINDOWS.md id 23 结清 + 同族未修复缺口登记。
+
+执行序:本计划在 07-02(enemy)之后串行执行;`depends_on` 表示 D-09 逐步用户确认与 D-12c 全量门禁的串行约束,源码层面与本系统无耦合。D-09 预执行检查点已由用户裁决(`approve-both` + 负值支持 + 授权新增负值用例),本修订记录该裁决(见 Task 0 的 ``)。
@@ -75,7 +85,7 @@ Output: `array.ts` 四处最小修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04
- Task 0: D-09 预执行汇报 —— 汇报 replay 四条 + 共同根因家族与越界清单
+ Task 0: D-09 裁决记录(用户已裁决 approve-both + 负值支持)—— replay 四条修复范围与负值纳入
(只读汇报,不修改任何文件)
- packages-user/data-common/src/replay/array.ts(:240-300 编码、:347-513 add/insert/delete/set、:586-656 解码、:708-794 读流与 rebuildIndexArray)
@@ -83,7 +93,7 @@ Output: `array.ts` 四处最小修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04
- .planning/phases/07-data-fixes/07-RESEARCH.md(§Per-Finding replay 四小节、§跨条目共享模式、§Open Questions A/B/C、§Assumptions Log A6)
-向用户逐条汇报以下内容,等待明确确认后才进入 Task 1。
+以下为 D-09 预执行汇报稿(**历史记录**:该检查点已由用户裁决,锁定结果见本任务 `` 与 ``;下述 ⑥ 的越界项已被用户裁决部分纳入——负 int64、负 bigint 方法 α 本次修复,`set` 与末位 `insert` 仍只登记不修)。
① `#06-04-1`:`array.ts:621` 解码乘数 `2147483647` 改为 `2147483648`(与 `:369-370` 编码侧一致)。
@@ -95,107 +105,153 @@ Output: `array.ts` 四处最小修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04
⑤ **必须点名的覆盖真相(A6)**:两个 insert 目标用例只用 `createReadStream` 顺序读,读流的 `currParam` 顺序累加、不依赖 `indexArray` 绝对值,因此只修 `:424` 也能让它们转绿;但 `array.get(i)`(`:697-706`)依赖 `indexArray[i]`,只修一处时插入后的 `get` 语义仍错。建议两处同修(完整修复)。
-⑥ **越界清单(默认不改,请用户确认是否纳入)**:负 int64 不可逆(`-2147483649` → `-4294967297`);负 bigint 不可往返;`ReplayArray.set` 的末步 `nextParam` 与回退起点 `paramStart + 1`;`insert(index === length)` 取 `paramStart = indexArray[length] = 0`。研究建议一律不纳入本阶段(未登记、无既有测试、会扩大变更面)。
+⑥ **越界清单(请用户确认是否纳入)**:负 int64 不可逆(`-2147483649` → `-4294967297`);负 bigint 不可往返;`ReplayArray.set` 的末步 `nextParam` 与回退起点 `paramStart + 1`;`insert(index === length)` 取 `paramStart = indexArray[length] = 0`。研究原建议一律不纳入(未登记、无既有测试、会扩大变更面);**用户最终裁决:负 int64 与负 bigint 纳入本次修复(A8/A9),`set` 与末位 `insert` 仍只登记不修。**
⑦ **账本事实(D-14 的执行障碍)**:WINDOWS.md 中只有 id 23 对应 `#06-04-1`/`#06-04-2`;`#06-04-3`/`#06-04-4` **没有对应条目**,修复后无 `fixed` 可打。研究建议不为 Phase 6 的 finding 新建账本条目(新增会抬高 `open_count` 门禁),改为在本计划 SUMMARY 登记完整对照表。请用户确认该处置。
⑧ 回滚与失败纪律:按缺陷/根因原子提交;任一任务方案失败必须退出本次修改、修订本 PLAN.md 后重新执行(D-09)。
+
+**本检查点已裁决**(用户回复 `approve-both` 并扩大范围:负 int64、负 bigint 方法 α、授权新增负值聚焦用例)。以下为锁定结果,执行者直接进入 Task 1,不再重新询问;裁决全文见本任务 ``。
- replay 四条修复方案的执行范围:`#06-04-4` 是两处同修还是只修 `:424`,以及同族未登记缺口(set、末位 insert、负 int64/负 bigint)是否纳入
+ replay 四条修复方案的执行范围与负值纳入(D-09 已裁决):`#06-04-4` 两处同修;负 int64 与负 bigint(方法 α)纳入;负值路径新增聚焦用例经用户授权;账本仅结清 id 23、不新建条目
- replay 四条共享「命令索引与参数字节偏移混用 + 末步缺终点哨兵」根因。`#06-04-4` 的两条 skip 只经读流顺序读,因此只修 `:424` 也能转绿,但 array.get(i) 的插入语义仍错(Assumption A6)。同族的 set/末位 insert/负值路径均未登记、无既有测试,纳入会显著扩大变更面。
+ replay 四条共享「命令索引与参数字节偏移混用 + 末步缺终点哨兵」根因。`#06-04-4` 的两条 skip 只经读流顺序读,因此只修 `:424` 也能转绿,但 array.get(i) 的插入语义仍错(Assumption A6),用户据此选择两处同修。`ReplayArray.set` 与末位 `insert` 仍属同族未登记缺口(只登记不修);负 int64 与负 bigint 原为越界项,经用户裁决**本次一并修复**(A8/A9),并授权为负值路径新增聚焦用例(A7)。
+
- 回复 approve-both / approve-min-insert / include-set,或给出修订意见
+
+ 用户最终裁决(`approve-both` 扩展版,以下为**锁定结果**,执行者不得再询问):
+ 1. 四条已登记修复全部执行:`#06-04-1` int64 解码乘数、`#06-04-2` 多字节 bigint 编解码、`#06-04-3` delete 索引回退与末步哨兵、`#06-04-4` insert 位移方向。
+ 2. `#06-04-4` 采 `approve-both`:**两处同修**(`:424` paramArray 与 `:425` indexArray),不采用严格最小分支。
+ 3. **新增**负 int64 支持(用户选择):编码 `low = num - high * 2147483648`(`low ∈ [0, 2^31)`),解码 `value = high * 2147483648 + low`;沿用现有 8 字节 int64 槽位与字节布局,非负值逐位向后兼容(A8)。
+ 4. **新增**负 bigint 支持(用户选择方法 α):长度前缀最高位作符号位(负值 `lengthByte = length | 0x80`、载荷 `BigInt.asUintN(8 * length, param)`;解码 `BigInt.asIntN(8 * (lengthByte & 0x7f), 累积值)`);非负路径字节级不变;接受负值载荷 127 字节上限的相对收窄(A9)。
+ 5. **新增聚焦用例获授权**(D-10 的本计划唯一例外 A7):负 int64 往返(`-1`、`-2147483649`、`-4294967297`)+ 非负对照;负 bigint 往返(`-1n`、`-128n`、`-129n`、`-256n`、`-65537n`)+ 非负对照;既有断言一字不改。
+ 6. 越界清单处置:`ReplayArray.set` 与 `insert(index === length)` 仍为**只登记不修**;负 int64 / 负 bigint **不再登记为缺口**(本次已修)。
+ 7. 账本处置:仅结清 id 23;`#06-04-3`/`#06-04-4` 无条目,**不新建条目**,改在 SUMMARY 记录完整 findings↔ledger 对照表。
+
+ 裁决已完成并锁入本计划(见 ``):直接进入 Task 1。若负值编码约定执行失败,按 D-09 退出并修订本 PLAN.md,不得自行另辟他法;若发现本记录与用户实际意图不符,暂停等待用户澄清。
- 用户明确回复 approve-both、approve-min-insert 或 include-set
- 用户回复 revise,或未回复即继续 —— 必须停止执行
+ 裁决记录(``)与用户最终选择一致:approve-both + 负 int64 + 负 bigint 方法 α + 授权负值聚焦用例;已由用户确认,无需再次询问
+ 记录与用户实际意图不符 —— 暂停并请用户澄清,不得自行改写范围
- - 用户明确 `#06-04-4` 的修法范围(两处同修 / 只修 `:424`)
- - 用户明确越界清单的处置(默认不纳入)与账本条目处置(默认不新建)
+ - D-09 裁决已记录在案:`#06-04-4` 两处同修(`approve-both`)
+ - 负 int64 与负 bigint(方法 α)已纳入本计划范围,编码约定与范围取舍已记录(A8/A9)
+ - 负值聚焦用例的新增授权已记录(D-10 本计划唯一例外 A7),且既有断言要求一字不改
+ - 越界清单与账本条目处置已明确(`set` / 末位 `insert` 只登记;不新建账本条目)
+ - 执行者据本记录直接进入 Task 1,不再重新询问
- 用户给出明确选项;本计划后续任务按该选项执行
+ D-09 裁决结果已完整记录(approve-both + 负 int64 + 负 bigint α + 授权负值用例 + 账本/越界处置);执行者据此直接进入 Task 1,不再重新索取确认
- Task 1: 修复 #06-04-1 —— int64 解码乘数改为 2147483648
+ Task 1: 修复 #06-04-1 —— int64 解码乘数改为 2147483648 并补齐负 int64 往返
packages-user/data-common/src/replay/array.ts, packages-user/data-common/src/replay/array.test.ts
- - packages-user/data-common/src/replay/array.ts(:586-656 decodeParam 全路径、:347-380 编码侧 int64 分支)
- - packages-user/data-common/src/replay/array.test.ts(:82-96 位宽用例注释、:259-266、:287-291 目标 skip)
+ - packages-user/data-common/src/replay/array.ts(:586-656 decodeParam 全路径、:347-380 编码侧 int64 分支、:218-249 位宽选择)
+ - packages-user/data-common/src/replay/array.test.ts(:82-96 位宽用例注释、:259-266、:287-291 目标 skip、:51-63 既有 typed 断言助手)
-把 `array.ts:621` 的解码乘数由 `2147483647` 改为 `2147483648`,与 `:369-370` 编码侧(`Math.floor(num / 2147483648)` / `num % 2147483648`)严格一致。不改 `getInt32` 的读取偏移(`:618`/`:619`)与 `byte = 9`(`:620`)。
+修 `#06-04-1` 的解码乘数,并补齐负 int64 往返(D-09 裁决新增,见 Task 0 ``)。
+
+(1) 解码乘数:把 `array.ts:621` 的解码乘数由 `2147483647` 改为 `2147483648`。不改 `getInt32` 的读取偏移(`:618`/`:619`)与 `byte = 9`(`:620`)。
+
+(2) 负 int64(Task 0 裁决 A8):在编码侧 `array.ts:369-370` 把 `low = num % 2147483648` 改为 `low = num - high * 2147483648`(`high = Math.floor(num / 2147483648)` 不变),使 `low ∈ [0, 2^31)`;解码侧保持 `value = high * 2147483648 + low` 即可与之上位。非负值下 `num - high * 2^31` 与 `num % 2^31` 逐位相同(向后兼容),沿用现有 8 字节 int64 槽位与 `setInt32(index + 1, low)` / `setInt32(index + 5, high)` 布局,无格式变更。不改类型 token 表,也不改 `normalizeParam` 对 int64 的位宽选择判定(`:218-249`)。
按 D-10 取消 `array.test.ts:287-291`(`round-trips int64 values above the int32 range`)的 skip,断言一字不改,同步 it 前的中文注释。
-不做的事:不处理负 int64 路径(`Math.floor` + JS `%` 符号语义,属未登记缺口,Task 0 已确认不纳入);不改编码侧;不新增用例。
+按用户显式授权(A7,D-10 本计划唯一例外)在同一 `describe('ReplayArray param codec')` 内新增聚焦用例,不改动任何既有断言:
+- 负 int64 往返:对 `-1`、`-2147483649`、`-4294967297` 各自 `array.add(0, [v])` 后断言 `array.get(0).params` 读回原值(优先复用既有 `expectParamTyped` 做 typeof + 严格相等,风格与相邻用例一致);
+- 非负对照:`2147483648`(既有 skip 已覆盖)之外补一条 `9007199254740991`(2^53-1)往返,确认上限仍精确;
+- 用例前写中文单行注释说明「负 int64 依赖 `low = num - high * 2^31`(A8)」,命名反映意图(如 `round-trips negative int64 values below the int32 range`)。
-验证:`pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿(重点确认 `:259-266` 的位宽切换用例与告警码用例不回归)→ D-44 门禁三步。提交信息:`fix(07-03): #06-04-1 fix the int64 decode multiplier`。
+不做的事:不改类型 token 与字节布局;不碰其他参数类型分支;不新增除上述负值 / 非负对照以外的用例。
+
+验证:`pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿(重点确认 `:259-266` 位宽切换用例、告警码用例与既有 int64 skip 转绿后不回归,新增负值与 2^53-1 对照转绿)→ D-44 门禁三步。提交信息:`fix(07-03): #06-04-1 round-trip negative int64 parameters`。
pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts
- 非零退出,或摘要行出现 "1 failed" / "failed"(int64 往返仍不精确),或输出出现 "no tests found"
+ 非零退出,或摘要行出现 "1 failed" / "failed"(int64 往返仍不精确,或负值 / 2^53-1 对照失败),或输出出现 "no tests found"
- `array.ts` 中 `value = low + high * 2147483648;` 存在,且不再存在 `2147483647` 解码乘数
+ - `array.ts` 编码侧 `low = num - high * 2147483648` 存在(A8),且非负值往返逐位不变
- `array.test.ts` 的 `round-trips int64 values above the int32 range` 已取消 skip
+ - 已新增负 int64 聚焦用例(`-1`、`-2147483649`、`-4294967297`)与非负对照(含 2^53-1),既有断言一字未改
- `pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿
- int64 解码精确往返、目标用例取消 skip;已一个原子提交入库
+ int64 解码精确往返(含负数)且向后兼容、目标 skip 取消、负值聚焦用例转绿;已一个原子提交入库
- Task 2: 修复 #06-04-2 —— 多字节 bigint 编码按位取字节、解码无符号读取
+ Task 2: 修复 #06-04-2 —— 多字节 bigint 编码按位取字节、解码无符号读取,并按方法 α 支持负 bigint
packages-user/data-common/src/replay/array.ts, packages-user/data-common/src/replay/array.test.ts
- packages-user/data-common/src/replay/array.ts(:255-275 bigint 编码、:626-635 bigint 解码、:376-380 setParamArray 的长度前缀写入)
- packages-user/data-common/src/replay/array.test.ts(:269-275 与 :335-340 既有绿用例、:278-284 与 :294-304 目标 skip、:371-376 告警 152)
-把 `array.ts` bigint 编码(`:264-270`)的 `total`/`base`/`remain` 循环体替换为按位取字节:对 `i` 从 0 到 `length - 1` 写入 `Number((param >> (8n * BigInt(i))) & 0xffn)`。保留 `const bit = param.toString(2);`(`:261`)、`const length = Math.ceil(bit.length / 8);`(`:262`)与 `byteLength: arr.length + 2`(`:274`)不变。
+修 `#06-04-2` 的多字节 bigint 编解码,并按方法 α 补齐负 bigint 往返(D-09 裁决新增,见 Task 0 ``)。
-把解码(`:628` 与 `:631`)的有符号读取改为无符号:长度前缀用 `this.paramView.getUint8(startIndex + 1)`,字节用 `this.paramView.getUint8(startIndex + 2 + i)`。其余解码逻辑与告警分支不变。
+(1) 非负多字节 bigint 编码(`:264-270`):把 `total`/`base`/`remain` 循环体替换为按位取字节:对 `i` 从 0 到 `length - 1` 写入 `Number((param >> (8n * BigInt(i))) & 0xffn)`。保留 `const bit = param.toString(2);`(`:261`)、`const length = Math.ceil(bit.length / 8);`(`:262`)与 `byteLength: arr.length + 2`(`:274`)不变。
+
+(2) 解码无符号读取(`:628` 与 `:631`):长度前缀用 `this.paramView.getUint8(startIndex + 1)`,字节用 `this.paramView.getUint8(startIndex + 2 + i)`。其余解码逻辑与告警分支不变。
+
+(3) 负 bigint(Task 0 裁决 A9,方法 α):复用长度前缀字节最高位作符号位。
+- 编码(`:255-275`):当 `param < 0n` 时,令 `length = Math.ceil(param.toString(2).length / 8)`(沿用同一长度算式,负号使位数多 1 位,补码在 `length` 字节内可容纳),载荷用 `BigInt.asUintN(8 * length, param)` 转无符号补码后按 (1) 同一取字节写法填入 `Uint8Array`;`byteLength` 仍为 `arr.length + 2`。在 `setParamArray` 写入长度前缀处(`:378`)对负值写 `arr.length | 0x80`,非负值仍写 `arr.length`。
+- 解码(`:626-635`):先 `const lengthByte = this.paramView.getUint8(startIndex + 1)`,令 `const length = lengthByte & 0x7f`,按 (2) 无符号累加得 `base`;若 `(lengthByte & 0x80) !== 0` 则 `value = BigInt.asIntN(8 * length, base)`,否则 `value = base`;`byte = length + 2`。
+- **向后兼容**:`param` 非负时符号位为 0,长度前缀与载荷字节与修前完全一致(既有 `100n`、`0x0102030405060708n` 用例逐字节不变)。`BigInt.asUintN`/`asIntN` 为语言内建,**不引入新依赖**。
+- **范围取舍(须写入 SUMMARY)**:负值载荷上限 127 字节(约 2^1016),窄于非负上限 255 字节——这是方法 α 借用最高位的已知取舍。非负路径保留既有 255 字节上限,warn-152 墙(`2n ** 2047n`,`:257-259`)行为不变。
按 D-10 取消 `array.test.ts:278-284`(`round-trips a multi-byte bigint`)与 `:294-304`(`round-trips a heterogeneous step mixing a multi-byte bigint and an int64 value`)两处 skip,断言一字不改,同步 it 前的中文注释。
-不做的事:不处理负 bigint(未登记缺口,Task 0 已确认不纳入);不改 `normalizeParamList` 的位宽告警(152)逻辑;不新增用例。
+按用户显式授权(A7,D-10 本计划唯一例外)在同一 `describe('ReplayArray param codec')` 内新增聚焦用例,不改动任何既有断言:
+- 负 bigint 往返:对 `-1n`、`-128n`、`-129n`、`-256n`、`-65537n` 各自 `array.add(0, [v])` 后断言 `array.get(0).params` 读回原值(优先复用既有 `expectParamTyped` 做 typeof + 严格相等);
+- 非负对照:多字节正 bigint(既有 skip 已覆盖)之外,补一条单字节正 bigint 对照以确认符号位为 0 时路径不变;
+- 用例前写中文单行注释说明「负 bigint 用长度前缀最高位符号位 + `BigInt.asUintN`/`asIntN`(A9)」,命名反映意图(如 `round-trips negative bigint values`)。
-验证:`pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿(重点确认 `:269-275` 单字节 bigint 与 `:335-340` 异质序列既有绿用例不回归)→ D-44 门禁三步。提交信息:`fix(07-03): #06-04-2 round-trip multi-byte bigint parameters`。
+不做的事:不改 `normalizeParamList` 的位宽告警(152)逻辑与非负长度上限;不改类型 token 表;不新增除上述负值 / 非负对照以外的用例。
+
+验证:`pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿(重点确认 `:269-275` 单字节 bigint、`:335-340` 异质序列既有绿用例与 `warns code 152` 不回归,新增负值与对照转绿)→ D-44 门禁三步。提交信息:`fix(07-03): #06-04-2 round-trip multi-byte and negative bigint parameters`。
pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts
- 非零退出,或摘要行出现 "1 failed" / "failed"(多字节 bigint 往返仍不精确),或输出出现 "no tests found"
+ 非零退出,或摘要行出现 "1 failed" / "failed"(多字节 / 负 bigint 往返仍不精确,或既有单字节与告警 152 用例回归),或输出出现 "no tests found"
- `array.ts` 的 bigint 编码循环体内出现 `& 0xffn`,且不再出现 `total += remain <<` 写法
- `array.ts` 解码的长度前缀与字节均使用 `getUint8`
+ - 负 bigint(方法 α):编码对负值以 `BigInt.asUintN` 取补码并写 `arr.length | 0x80`,解码按符号位以 `BigInt.asIntN` 还原;非负路径字节不变
- `array.test.ts` 的 `round-trips a multi-byte bigint` 与 `round-trips a heterogeneous step mixing a multi-byte bigint and an int64 value` 已取消 skip
+ - 已新增负 bigint 聚焦用例(`-1n`、`-128n`、`-129n`、`-256n`、`-65537n`)与非负对照,既有断言一字未改
- `pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿
- 多字节 bigint 编解码可逆、两条 skip 取消并转绿;已一个原子提交入库
+ 多字节与负 bigint 均可逆、两条 skip 取消并转绿、负值聚焦用例转绿且非负路径向后兼容;已一个原子提交入库
@@ -237,13 +293,13 @@ Output: `array.ts` 四处最小修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04
- packages-user/data-common/src/replay/array.test.ts(:185-195 零参数 insert 既有绿用例、:498-508 与 :523-539 目标 skip)
-按 Task 0 用户确认的范围修正 `array.ts` 的 `insert`(:412-440)位移方向:`paramArray.copyWithin` 的 target 改为 `paramStart + length`、start 改为 `paramStart`;`indexArray.copyWithin` 的 target 改为 `index + 1`、start 改为 `index`。若用户选择「严格最小」,只改 `paramArray` 一处,并在 SUMMARY 中如实标注 `array.get(i)` 的插入语义仍依赖 `indexArray` 因而未完全修正(A6 覆盖真相)。
+按 Task 0 用户裁决(`approve-both`,**无条件**)修正 `array.ts` 的 `insert`(:412-440)**两处**位移方向:`paramArray.copyWithin` 的 target 改为 `paramStart + length`、start 改为 `paramStart`;`indexArray.copyWithin` 的 target 改为 `index + 1`、start 改为 `index`。**不保留「严格最小」分支**——两处同修是本计划的锁定范围(A6),使 `array.get(i)` 的插入语义一并正确。
其余保持不变:`:421-422` 的 `commandStart`/`paramStart` 取值、`:427` 的注释(说明插入命令的起始索引不变)、`:428-429` 的赋值、`:431-432` 的 `length`/`paramUsed` 递增、`:435-437` 的尾段补偿循环(在修正后的位移方向上已恰好覆盖 `[index+1, newLength-1]`,不再改)。
按 D-10 取消 `array.test.ts:498-508`(`reads the new order after inserting a step`)与 `:523-539`(`reads the new order after inserting a step into a heterogeneous route`)两处 skip,断言一字不改,同步 it 前的中文注释。
-不做的事:不改 `insert(index === length)` 的末位追加路径(未登记缺口);不改 `add`/`set`/`delete`;不新增用例。
+不做的事:不改 `insert(index === length)` 的末位追加路径(未登记同族缺口,只登记不修);不改 `add`/`set`/`delete`;不新增用例。
验证:`pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿(重点确认 `:185-195` 零参数 insert 既有绿用例不回归)→ D-44 门禁三步。提交信息:`fix(07-03): #06-04-4 shift replay params in the insert direction`。
@@ -252,27 +308,31 @@ Output: `array.ts` 四处最小修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04
非零退出,或摘要行出现 "1 failed" / "failed"(插入后读回顺序错乱,或零参数 insert 既有用例回归),或输出出现 "no tests found"
- - `array.ts` 的 `insert` 中 `paramArray.copyWithin` 的目标为 `paramStart + length`
- - 选择两处同修时,`indexArray.copyWithin` 的目标为 `index + 1`;选择严格最小时,SUMMARY 明确标注 `get` 语义未完全修正
+ - `array.ts` 的 `insert` 中 `paramArray.copyWithin` 的 target 为 `paramStart + length`、start 为 `paramStart`
+ - `array.ts` 的 `insert` 中 `indexArray.copyWithin` 的 target 为 `index + 1`、start 为 `index`(两处同修,无严格最小分支)
- `array.test.ts` 的 `reads the new order after inserting a step` 与 `reads the new order after inserting a step into a heterogeneous route` 已取消 skip
- `pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿
- insert 位移方向正确、两条 skip 取消并转绿、覆盖真相已在 SUMMARY 记录;已一个原子提交入库
+ insert 两处位移方向均正确(paramArray + indexArray)、两条 skip 取消并转绿、array.get(i) 插入语义正确;已一个原子提交入库
- Task 5: D-44 门禁、全量套件、WINDOWS.md id 23 结清与同族缺口登记
+ Task 5: D-44 门禁、全量套件、WINDOWS.md id 23 结清与 findings↔ledger / 同族缺口登记
packages-user/data-common/src/replay/array.ts, packages-user/data-common/src/replay/array.test.ts
- - .planning/phases/07-data-fixes/07-RESEARCH.md(§Validation Architecture、§Open Questions A/B/C、§Common Pitfalls 2/4/5/6/7)
+ - .planning/phases/07-data-fixes/07-RESEARCH.md(§Validation Architecture、§Common Pitfalls 2/4/5/6/7;§Open Questions A/B/C 已由 D-09 裁决:A/B 纳入修复、C 仍只登记)
- .planning/WINDOWS.md(id 23 行)
对两个改动文件串行执行 D-12(沿用 D-44 的文件级判定)三步门禁:`pnpm exec eslint --fix` 后 `pnpm exec eslint` 0 错误;`pnpm exec vue-tsc --noEmit` 后按两文件相对路径过滤输出 0 类型错误(不以整仓退出码判定);`pnpm test:ci` 全绿。
-按 D-14 结清账本:`gsd_run windows fixed 23`(id 23 描述「受阻塞缺口 G-06-04-A:多字节 bigint / 超 int32 int64 编解码缺陷」)。`#06-04-3`/`#06-04-4` 无账本条目,**不新建条目**(Task 0 已确认),改为在 SUMMARY 记录完整对照表。
+按 D-14 结清账本:`gsd_run windows fixed 23`(id 23 描述「受阻塞缺口 G-06-04-A:多字节 bigint / 超 int32 int64 编解码缺陷」)。`#06-04-3`/`#06-04-4` 无账本条目,**不新建条目**(Task 0 已确认),改为在 SUMMARY 记录完整 findings↔ledger 对照表。
-在 SUMMARY 中显式登记本计划**未修复**的同族缺口(不得静默丢弃):负 int64 不可逆、负 bigint 不可往返、`ReplayArray.set` 的末步 `nextParam` 与回退起点、`insert(index === length)` 的 `paramStart`、码 128 占位符不足(属 07-05)。每条注明「未登记缺陷、无既有测试、需用户决定是否另立条目」。
+在 SUMMARY 中显式登记**完整对照**与**未修复**的同族缺口(不得静默丢弃):
+- 账本对照:id 23(覆盖 `#06-04-1` + `#06-04-2`)→ `fixed`;`#06-04-3`、`#06-04-4` 在账本中**无对应条目**,逐条注明「无 ledger entry」,不做 `fixed`/`waive` 动作。
+- 已修复(本计划,**不再登记为缺口**):负 int64 往返(A8)、负 bigint 往返(方法 α,A9,含负值载荷 127 字节上限的取舍说明)。
+- 仍未修复的同族缺口:`ReplayArray.set` 的末步 `nextParam` 与回退起点 `paramStart + 1`、`insert(index === length)` 的 `paramStart`。每条注明「未登记缺陷、无既有测试、需用户决定是否另立条目」。
+- 另记(不在本计划范围):码 128 占位符不足,属 07-05。
pnpm test:ci
@@ -282,10 +342,11 @@ Output: `array.ts` 四处最小修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04
- `pnpm exec eslint` 对两文件 0 错误
- `pnpm exec vue-tsc --noEmit` 按两文件路径过滤后无命中
- `pnpm test:ci` 全绿,且本计划未新增 `it.skip`
+ - 经用户授权的负 int64 / 负 bigint 聚焦用例已入库并通过,既有断言一字未改
- WINDOWS.md 中 id 23 状态为 `fixed`
- - SUMMARY 含同族未修复缺口清单(4 条)
+ - SUMMARY 含 findings↔ledger 对照(id 23 fixed;`#06-04-3`/`#06-04-4` 注明无 ledger entry)与同族未修复缺口清单(2 条:`set`、末位 `insert`)
- D-44 三步全过、全量套件绿、id 23 结清、同族缺口已登记于 SUMMARY(提交粒度按 D-13,按根因族各成原子提交)
+ D-44 三步全过、全量套件绿、id 23 结清、findings↔ledger 对照与同族未修复缺口(`set`、末位 `insert`)已登记于 SUMMARY(提交粒度按 D-13,按根因族各成原子提交)
@@ -303,33 +364,36 @@ Output: `array.ts` 四处最小修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
-| T-7-02 | Tampering | `array.ts` `decodeParam`/`normalizeParam` | medium | mitigate | Task 1/2 使 int64 与多字节 bigint 编解码精确往返;`getUint8` 修正顺带消除「长度前缀 ≥128 被当作负数」的静默错读 |
+| T-7-02 | Tampering | `array.ts` `decodeParam`/`normalizeParam` | medium | mitigate | Task 1/2 使 int64(含负数,A8)与多字节 / 负 bigint(方法 α,A9)编解码精确往返;`getUint8` 修正顺带消除「长度前缀 ≥128 被当作负数」的静默错读 |
| T-7-08 | Tampering | `array.ts` `delete`/`insert` 的索引位移 | medium | mitigate | Task 3/4 使命令索引与参数字节偏移严格区分,避免删除/插入后回放读到错误参数(错误参数会被下游当作合法指令执行) |
| T-7-SC | Tampering | npm/pip/cargo 安装 | high | accept | 本阶段不安装任何包(`package.json`/`pnpm-lock.yaml` 不变);若执行中出现安装需求,立即停止并汇报 |
- `pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿(7 条 skip 取消后)
+- 经授权的负 int64 / 负 bigint 聚焦用例全部通过,且既有断言一字未改
- `pnpm test:ci` 全绿且本计划不新增 `it.skip`
- 两个改动文件 eslint 0 错误、vue-tsc 过滤后 0 类型错误
-- WINDOWS.md id 23 为 fixed;同族未修复缺口在 SUMMARY 可见
+- WINDOWS.md id 23 为 fixed;findings↔ledger 对照与同族未修复缺口(`set`、末位 `insert`)在 SUMMARY 可见
1. `#06-04-1`、`#06-04-2`、`#06-04-3`、`#06-04-4` 对应 7 条正确预期用例全部取消 skip 并转绿
2. `delete(0)`、零参数 `insert`、`set`、位宽切换与扩容等既有绿用例不回归
-3. 按根因两个族(编解码 / 索引编辑)各成原子提交,共 4 个提交
-4. 同族未登记缺口在 SUMMARY 明确登记,未被静默忽略
+3. 负 int64(`|n| ≤ 2^53-1` 含负数)与负 bigint(方法 α)均精确往返,且非负路径字节级向后兼容
+4. 经用户授权的负值聚焦用例(负值 + 非负对照)随同原子提交入库并通过
+5. 按根因两个族(编解码 / 索引编辑)各成原子提交,共 4 个提交
+6. `ReplayArray.set` 与 `insert(index === length)` 在 SUMMARY 明确登记为未修复同族缺口,未被静默忽略;负 int64 / 负 bigint 不再登记为缺口
## Artifacts this phase produces
| 类型 | 路径 | 内容 |
|------|------|------|
-| 生产源码(修改) | `packages-user/data-common/src/replay/array.ts` | int64 乘数、bigint 编解码、`getParamRange` 助手、`delete` 索引回退、`insert` 位移方向 |
-| 测试(修改) | `packages-user/data-common/src/replay/array.test.ts` | 7 条 skip 取消 |
-| 账本 | `.planning/WINDOWS.md` | id 23 → fixed |
-| 摘要 | `.planning/phases/07-data-fixes/07-03-SUMMARY.md` | 逐条修复记录、覆盖真相(读流 vs get)、同族未修复缺口清单、门禁结果 |
+| 生产源码(修改) | `packages-user/data-common/src/replay/array.ts` | int64 乘数 + 负 int64 编码(A8)、bigint 编解码 + 负 bigint 符号位(A9)、`getParamRange` 助手、`delete` 索引回退、`insert` 两处位移方向 |
+| 测试(修改) | `packages-user/data-common/src/replay/array.test.ts` | 7 条 skip 取消 + 经授权的负 int64 / 负 bigint 聚焦用例(含非负对照) |
+| 账本 | `.planning/WINDOWS.md` | id 23 → fixed(`#06-04-3`/`#06-04-4` 无条目,不新建) |
+| 摘要 | `.planning/phases/07-data-fixes/07-03-SUMMARY.md` | 逐条修复记录、负值编码约定与 127 字节取舍、findings↔ledger 对照、覆盖真相(读流 vs get)、同族未修复缺口清单(`set`、末位 `insert`)、门禁结果 |