docs(07-03): amend replay plan to dedicated type codes for negative int64/bigint (D-09 round 2)

This commit is contained in:
unanmed 2026-09-15 18:19:42 +08:00
parent f0278dff7e
commit c7e5a643c2

View File

@ -20,47 +20,50 @@ 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 墙行为不变"
- "负 int64(-1、-2147483649、-4294967297)以独立 type 5 编码(载荷 = |n|,解码取负)精确往返;非负 int64(含 2^53-1)仍为 type 4,幅值编码不变"
- "负 bigint(-1n、-128n、-129n、-256n、-65537n)以独立 type 8 编码(载荷 = |n|,解码取负)精确往返;非负 bigint 仍为 type 7,保留完整 255 字节载荷范围且 warn-152 墙行为不变"
- "类型码整体重编号(float 6、bigint 7、负 bigint 8、长字符串 9、短字符串 length + 9)后,array.test.ts 中断言类型字节 / 字符串长度阈值的用例同步更新并全绿"
- "取消 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 = type 5、负 bigint = type 8,载荷均为幅值并在解码取负)与命令索引/参数字节偏移严格区分;类型码表整体重编号"
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)"
- "array.ts:621 解码乘数 ↔ array.ts:369-372 编码侧的 2147483648(int64 幅值按高低 32 位拆分)"
- "负 int64:type 5 的幅值 |n| 经 array.ts:369-372 的 8 字节槽位编码 ↔ array.ts:616-621 同式解码后再取负"
- "负 bigint:type 8 的幅值 |n| 经 array.ts:376-379 的长度前缀 + 幅值字节写入 ↔ 解码以 getUint8 读出长度与字节后取负"
- "array.ts:278-281 短字符串 type = arr.length + 9(阈值 ≤ 246)↔ array.ts:643-649 解码 length = type - 9"
- "类型码常量表(array.ts:11-24 的 INormalizedParam 类型注释)↔ normalizeParam(:216-300)/ decodeParam(:586-656)的 type 分支"
- "indexArray[i] = 第 i 条命令的参数起始字节(array.ts:405 写入、:749-767 rebuildIndexArray 重建)↔ delete/insert 的位移与回退起点"
- "末条命令的终点哨兵 ↔ paramUsed(array.ts:63/406/456/495/766 维护)"
assumptions:
- "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 字节"
- "A7(D-10 例外,用户显式授权且仅限本计划):允许为负 int64 / 负 bigint 往返路径新增聚焦 it 用例(负值用例 + 非负对照),并允许同步更新 array.test.ts 中断言类型字节 / 字符串长度阈值的既有用例以匹配重编号后的类型表;除类型表迁移外既有断言一律一字不改,且不得为四条已登记缺陷新增用例"
- "A8(负 int64 编码约定,用户裁决——取代旧的 low = num - high * 2^31 方案):为负 int64 分配独立参数类型码 type 5,载荷只存幅值 |n|(编码前取绝对值,按与 type 4 相同的 8 字节高低 32 位拆分写入),解码读回幅值后再取负还原;type 4 保持「非负 int64 = 幅值」语义不变"
- "A9(负 bigint 编码约定,用户裁决——取代旧的方法 α 符号位方案):为负 bigint 分配独立参数类型码 type 8,载荷只存幅值 |n|(编码前取绝对值后按 #06-04-2 的按位取字节写入长度前缀 + 幅值字节),解码读回幅值后再取负;不再借用长度前缀最高位作符号位,故非负(type 7)与负值(type 8)均保留完整 255 字节载荷范围"
- "A10(类型码整体重编号,用户裁决;项目未发布、无向后兼容要求):最终类型表为 0 boolean、1 int8、2 int16、3 int32、4 int64(非负,载荷 = 幅值)、5 负 int64(载荷 = |n|)、6 float、7 bigint(非负)、8 负 bigint(载荷 = |n|)、9 长字符串(int32 长度前缀)、10–255 短字符串(type = length + 9,长度 1..246);旧码 float 5 / bigint 6 / 长字符串 7 / 短字符串 length + 7 全部作废,array.test.ts 中断言类型字节或字符串长度阈值的既有用例须同步更新到新表,不保留任何兼容分支"
prohibitions:
- "不新增测试用例(D-10)——唯一例外:本计划经用户显式授权(A7),仅可为负 int64 / 负 bigint 往返路径及其非负对照新增聚焦 it 用例;既有断言一律一字不改,且不得为四条已登记缺陷新增用例"
- "不新增测试用例(D-10)——唯一例外:本计划经用户显式授权(A7),仅可为负 int64 / 负 bigint 往返路径及其非负对照新增聚焦 it 用例,并同步更新 array.test.ts 中断言类型字节 / 字符串长度阈值的既有用例以匹配重编号后的类型表;除该类型表迁移外既有断言一律一字不改,且不得为四条已登记缺陷新增用例"
- "不弱化断言,不改写为可跑绿假象"
- "不修改渲染端 @user/client-* 与 legacy 渲染接线"
- "不引入新依赖、不新建文件"
- "不改动非负 bigint 的编码字节布局与 255 字节长度上限,也不改变 warn-152 墙行为;负值载荷 127 字节上限为方法 α 的已知取舍(A9),不得以牺牲非负兼容性换取负值范围"
- "不改动 bigint 的 255 字节长度上限与 warn-152 墙行为(负 bigint 仅换用 type 8 存幅值,不缩小载荷范围);不保留任何旧的类型码或兼容分支(A10)"
- "不修改本计划 files_modified 之外的源码;相邻未登记的同族缺口(ReplayArray.set 的末步 nextParam 与回退起点 paramStart + 1、insert(index === length) 的 paramStart)一律只登记不修"
---
<objective>
修复录像数组(`@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` 位移方向相反。
在 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 含负数可逆;并为负值路径新增经用户显式授权的聚焦用例。
在 D-09 裁决(`approve-both`)基础上扩大范围:同时补齐**负值路径**,采用**独立参数类型码**方案(用户最终裁决,取代旧 A8/A9)——为负 int64 分配 type 5、为负 bigint 分配 type 8,载荷**只存绝对值 `|n|`**,解码读回幅值后取负;float / bigint / 长字符串 / 短字符串的既有类型码整体后移(float 5→6、bigint 6→7、长字符串 7→9、短字符串 `type = length + 9` 且长度阈值 ≤ 246)。类型表重编号后 `array.test.ts` 中断言类型字节 / 字符串长度阈值的用例同步更新。项目未发布,**无向后兼容要求**,不保留任何旧码分支;负 int64 与负 bigint 均保留与对应非负类型相同的载荷范围(bigint 仍为 255 字节)。
Purpose: 录像数组是回放一致性的唯一数据载体;编解码不可逆或索引错位会让回放在首个分歧处失败,且诊断码(151/152/154/155/2001–2008)无法反映真实原因。负值(坐标 / 伤害中间值等)此前在同一编解码家族内静默失真,属完整性缺口。
Output: `array.ts` 四处登记修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04-3`/`#06-04-4` 各成一族)+ 两处负值支持(负 int64、负 bigint 方法 α)+ 7 条既有 skip 取消 + 经授权的负值聚焦用例(含非负对照)+ WINDOWS.md id 23 结清 + 同族未修复缺口登记。
Output: `array.ts` 四处登记修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04-3`/`#06-04-4` 各成一族)+ 两处负值支持(负 int64 = type 5、负 bigint = type 8,均存幅值)+ 类型码表整体重编号 + 7 条既有 skip 取消 + 经授权的负值聚焦用例(含非负对照)+ 类型表相关断言同步更新 + WINDOWS.md id 23 结清 + 同族未修复缺口登记。
执行序:本计划在 07-02(enemy)之后串行执行;`depends_on` 表示 D-09 逐步用户确认与 D-12c 全量门禁的串行约束,源码层面与本系统无耦合。D-09 预执行检查点已由用户裁决(`approve-both` + 负值支持 + 授权新增负值用例),本修订记录该裁决(见 Task 0 的 `<record>`)。
执行序:本计划在 07-02(enemy)之后串行执行;`depends_on` 表示 D-09 逐步用户确认与 D-12c 全量门禁的串行约束,源码层面与本系统无耦合。D-09 预执行检查点已由用户裁决(`approve-both` + 负值支持 + 类型码独立分配 + 授权新增负值用例),本修订记录该裁决(见 Task 0 的 `<record>`)。
</objective>
<execution_context>
@ -93,7 +96,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)
</read_first>
<action>
以下为 D-09 预执行汇报稿(**历史记录**:该检查点已由用户裁决,锁定结果见本任务 `<record>` 与 `<decision>`;下述 ⑥ 的越界项已被用户裁决部分纳入——负 int64、负 bigint 方法 α 本次修复,`set` 与末位 `insert` 仍只登记不修)。
以下为 D-09 预执行汇报稿(**历史记录**:该检查点已由用户裁决,锁定结果见本任务 `<record>` 与 `<decision>`;下述 ⑥ 的越界项已被用户裁决部分纳入——负 int64 与负 bigint 经独立类型码(type 5 / type 8)本次修复,`set` 与末位 `insert` 仍只登记不修)。
① `#06-04-1`:`array.ts:621` 解码乘数 `2147483647` 改为 `2147483648`(与 `:369-370` 编码侧一致)。
@ -105,23 +108,23 @@ 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 与负 bigint 纳入本次修复(A8/A9),`set` 与末位 `insert` 仍只登记不修。**
⑥ **越界清单(请用户确认是否纳入)**:负 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,不再重新询问;裁决全文见本任务 `<record>`。
**本检查点已裁决**(用户回复 `approve-both` 并扩大范围:负 int64 = type 5、负 bigint = type 8(均存幅值)、类型码表整体重编号、授权新增负值聚焦用例与同步更新类型表断言)。以下为锁定结果,执行者直接进入 Task 1,不再重新询问;裁决全文见本任务 `<record>`。
</action>
<decision>replay 四条修复方案的执行范围与负值纳入(D-09 已裁决):`#06-04-4` 两处同修;负 int64 与负 bigint(方法 α)纳入;负值路径新增聚焦用例经用户授权;账本仅结清 id 23、不新建条目</decision>
<decision>replay 四条修复方案的执行范围与负值纳入(D-09 已裁决):`#06-04-4` 两处同修;负 int64(type 5)与负 bigint(type 8)经独立类型码纳入,载荷只存幅值并在解码取负,其余类型码整体后移;负值路径新增聚焦用例与类型表断言同步更新经用户授权;账本仅结清 id 23、不新建条目</decision>
<context>
replay 四条共享「命令索引与参数字节偏移混用 + 末步缺终点哨兵」根因。`#06-04-4` 的两条 skip 只经读流顺序读,因此只修 `:424` 也能转绿,但 array.get(i) 的插入语义仍错(Assumption A6),用户据此选择两处同修。`ReplayArray.set` 与末位 `insert` 仍属同族未登记缺口(只登记不修);负 int64 与负 bigint 原为越界项,经用户裁决**本次一并修复**(A8/A9),并授权为负值路径新增聚焦用例(A7)。
replay 四条共享「命令索引与参数字节偏移混用 + 末步缺终点哨兵」根因。`#06-04-4` 的两条 skip 只经读流顺序读,因此只修 `:424` 也能转绿,但 array.get(i) 的插入语义仍错(Assumption A6),用户据此选择两处同修。`ReplayArray.set` 与末位 `insert` 仍属同族未登记缺口(只登记不修);负 int64 与负 bigint 原为越界项,经用户裁决**本次一并修复**(A8/A9:独立 type 5 / type 8,载荷 = |n|),并授权为负值路径新增聚焦用例、同步更新类型表相关断言(A7/A10)。
</context>
<options>
<option id="approve-both-plus-negatives">
<name>【已选】approve-both + 负 int64 + 负 bigint(方法 α)+ 授权负值聚焦用例</name>
<pros>两处同修使 array.get(i) 的插入语义正确;负值路径补齐后整数(|n| ≤ 2^53-1)与 bigint 含负数全可逆且非负路径向后兼容;负值聚焦用例为新路径提供回归保护</pros>
<cons>超出登记点名的单行范围与 D-10 默认口径,故 D-10 例外与范围扩张须记录在案(A7/A8/A9);负 bigint 载荷上限收窄至 127 字节</cons>
<name>【已选】approve-both + 负 int64(type 5)+ 负 bigint(type 8)+ 授权负值聚焦用例与类型表断言同步更新</name>
<pros>两处同修使 array.get(i) 的插入语义正确;独立类型码 + 幅值载荷使整数(|n| ≤ 2^53-1)与 bigint 含负数全可逆,且无需借用符号位或改写非负语义;负 bigint 与非负一样保留 255 字节载荷范围;负值聚焦用例为新路径提供回归保护</pros>
<cons>类型码表整体重编号(float 6、bigint 7、长字符串 9、短字符串 length + 9),array.test.ts 中断言类型字节 / 字符串阈值的既有用例须同步更新;超出登记点名的单行范围与 D-10 默认口径,故 D-10 例外与范围扩张须记录在案(A7/A8/A9/A10)</cons>
</option>
<option id="approve-both">
<name>(历史选项,已被 approve-both-plus-negatives 取代)四条方案 + `#06-04-4` 两处同修;越界清单不纳入;不新建账本条目</name>
@ -145,113 +148,139 @@ Output: `array.ts` 四处登记修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04
</option>
</options>
<record>
用户最终裁决(`approve-both` 扩展版,以下为**锁定结果**,执行者不得再询问):
用户最终裁决(`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 对照表。
3. **新增**负 int64 支持(用户最终选择,取代旧 A8):分配**独立参数类型码 type 5**,载荷只存幅值 `|n|`(编码按与 type 4 相同的 8 字节高低 32 位拆分写入幅值),解码读回幅值后再取负还原;type 4 仍是「非负 int64」。**不采用** `low = num - high * 2^31` 方案。
4. **新增**负 bigint 支持(用户最终选择,取代旧方法 α):分配**独立参数类型码 type 8**,载荷只存幅值 `|n|`(取绝对值后按 #06-04-2 的按位取字节写入长度前缀 + 幅值字节),解码读回幅值后再取负;**不借用**长度前缀最高位作符号位,`BigInt.asUintN`/`asIntN` 不再需要。非负与负值均保留完整 255 字节载荷范围。
5. **类型码整体重编号**(A10;项目未发布、无向后兼容要求):0 boolean、1 int8、2 int16、3 int32、4 int64(非负)、**5 负 int64(新)**、6 float(原 5)、7 bigint(原 6)、**8 负 bigint(新)**、9 长字符串(原 7)、10–255 短字符串(`type = length + 9`,长度 1..246,原 `length + 7`、阈值 248)。不保留任何旧码或兼容分支。
6. **新增聚焦用例获授权**(D-10 的本计划唯一例外 A7):负 int64 往返(`-1`、`-2147483649`、`-4294967297`)+ 非负对照;负 bigint 往返(`-1n`、`-128n`、`-129n`、`-256n`、`-65537n`)+ 非负对照。
7. **类型表相关既有断言获授权同步更新**(A7/A10):`array.test.ts` 中断言类型字节或字符串长度阈值的用例(float token 5→6、单字节 bigint token 6→7、短字符串 token 9→`length + 9`、长 / 空字符串 token 7→9,以及相应注释)改为新表;除该类型表迁移外既有断言一字不改。
8. 越界清单处置:`ReplayArray.set` 与 `insert(index === length)` 仍为**只登记不修**;负 int64 / 负 bigint **不再登记为缺口**(本次已修)。
9. 账本处置:仅结清 id 23;`#06-04-3`/`#06-04-4` 无条目,**不新建条目**,改在 SUMMARY 记录完整 findings↔ledger 对照表。
</record>
<resume-signal>裁决已完成并锁入本计划(见 `<record>`):直接进入 Task 1。若负值编码约定执行失败,按 D-09 退出并修订本 PLAN.md,不得自行另辟他法;若发现本记录与用户实际意图不符,暂停等待用户澄清。</resume-signal>
<resume-signal>裁决已完成并锁入本计划(见 `<record>`):直接进入 Task 1。若负值类型码方案(type 5 / type 8)执行失败,按 D-09 退出并修订本 PLAN.md,不得自行另辟他法;若发现本记录与用户实际意图不符,暂停等待用户澄清。</resume-signal>
<verify>
<human-check>裁决记录(`<record>`)与用户最终选择一致:approve-both + 负 int64 + 负 bigint 方法 α + 授权负值聚焦用例;已由用户确认,无需再次询问</human-check>
<human-check>裁决记录(`<record>`)与用户最终选择一致:approve-both + 负 int64(type 5)+ 负 bigint(type 8,均存幅值)+ 类型码整体重编号 + 授权负值聚焦用例与类型表断言同步更新;已由用户确认,无需再次询问</human-check>
<fails_when>记录与用户实际意图不符 —— 暂停并请用户澄清,不得自行改写范围</fails_when>
</verify>
<acceptance_criteria>
- D-09 裁决已记录在案:`#06-04-4` 两处同修(`approve-both`)
- 负 int64 与负 bigint(方法 α)已纳入本计划范围,编码约定与范围取舍已记录(A8/A9)
- 负值聚焦用例的新增授权已记录(D-10 本计划唯一例外 A7),且既有断言要求一字不改
- 负 int64(type 5)与负 bigint(type 8)已纳入本计划范围,独立类型码 + 幅值载荷与完整类型表重编号已记录(A8/A9/A10)
- 负值聚焦用例的新增授权与类型表相关断言的同步更新授权已记录(D-10 本计划唯一例外 A7),且除类型表迁移外既有断言要求一字不改
- 越界清单与账本条目处置已明确(`set` / 末位 `insert` 只登记;不新建账本条目)
- 执行者据本记录直接进入 Task 1,不再重新询问
</acceptance_criteria>
<done>D-09 裁决结果已完整记录(approve-both + 负 int64 + 负 bigint α + 授权负值用例 + 账本/越界处置);执行者据此直接进入 Task 1,不再重新索取确认</done>
<done>D-09 裁决结果已完整记录(approve-both + 负 int64 type 5 + 负 bigint type 8 + 类型码重编号 + 授权负值用例与类型表断言更新 + 账本/越界处置);执行者据此直接进入 Task 1,不再重新索取确认</done>
</task>
<task type="auto">
<name>Task 1: 修复 #06-04-1 —— int64 解码乘数改为 2147483648 并补齐负 int64 往返</name>
<name>Task 1: 修复 #06-04-1 —— int64 解码乘数改为 2147483648、负 int64 独立为 type 5,并迁移类型码表</name>
<files>packages-user/data-common/src/replay/array.ts, packages-user/data-common/src/replay/array.test.ts</files>
<read_first>
- 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 断言助手)
- packages-user/data-common/src/replay/array.ts(:11-24 INormalizedParam 类型注释、:216-300 normalizeParam、:347-390 setParamArray、:586-656 decodeParam)
- packages-user/data-common/src/replay/array.test.ts(:82-96 paramCases、:248-275 位宽与单字节 bigint 用例、:287-291 目标 skip、:306-332 字符串 token 用例、:51-63 typed 断言助手)
</read_first>
<action>
修 `#06-04-1` 的解码乘数,并补齐负 int64 往返(D-09 裁决新增,见 Task 0 `<record>`)。
修 `#06-04-1` 的解码乘数,按用户裁决把负 int64 独立为参数类型码 type 5(载荷 = 幅值),并整体迁移类型码表。见 Task 0 `<record>` 第 3、5、6、7 条(A8/A10)。
(1) 解码乘数:把 `array.ts:621` 的解码乘数由 `2147483647` 改为 `2147483648`。不改 `getInt32` 的读取偏移(`:618`/`:619`)与 `byte = 9`(`:620`)。
(1) 解码乘数(int64 幅值):把 `array.ts:621` 的 `value = low + high * 2147483647` 改为 `value = low + high * 2147483648`。不改高低 32 位的读取偏移(`: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`)。
(2) 负 int64 = **独立 type 5**(A8,取代旧的 `low = num - high * 2^31` 方案):
- `normalizeParam` 数字分支(`:227-249`):整数时把超出 int32 的 `else` 依符号拆成两支——`param >= 2147483648` → `type = 4`(非负 int64),`param < -2147483648` → `type = 5`(负 int64);两支均 `byte = 9`。float 由 `type = 5` 改为 `type = 6`(`:245-248`)。
- `setParamArray`(`:367-375`):把 type 4 分支改为 `param.paramType === 4 || param.paramType === 5`,先取幅值 `const magnitude = num < 0 ? -num : num;`,再 `const high = Math.floor(magnitude / 2147483648);`、`const low = magnitude - high * 2147483648;`,仍 `setInt32(index + 1, low)` / `setInt32(index + 5, high)`。type 4 的 `num` 必为非负,取幅值后逐位不变;type 5 存的是 `|n|`,无符号位、无格式借用。float 分支由 5 改为 6(`:373-375`)。
- `decodeParam`:新增 `else if (type === 5)` 分支(负 int64)——按 type 4 同一读法取 `low`/`high`,`byte = 9`,`value = -(low + high * 2147483648)`(读回幅值后取负);原 `type === 5` 的 float 分支改为 `type === 6`。
- **类型码表是一条唯一性约束链**:type 5 被负 int64 占用后,float 必须离开 5(→6),随之 bigint 必须离开 6(→7)、长字符串必须离开 7(→9)、短字符串整体后移到 10–255。因此第 (3) 步的类型码迁移必须与第 (2) 步在**同一原子提交**内完成;只改一半会让 `decodeParam` 出现重复且不可达的 `type === N` 分支,套件无法全绿。
(3) 类型码表迁移(A10;项目未发布、无向后兼容要求,不保留任何旧码或兼容分支):
- `normalizeParam`:bigint(非负)`paramType: 6` → `7`(`:272`);短字符串 `paramType: arr.length + 7` → `arr.length + 9`(`:281`)且阈值 `arr.length > 0 && arr.length <= 248` → `<= 246`(`:278`);长字符串 `paramType: 7` → `9`(`:288`)。
- `setParamArray`:bigint 分支 `param.paramType === 6` → `7`(`:376`);长字符串分支 `param.paramType === 7` → `9`(`:380`);短字符串 `else` 分支注释 `8 ~ 256 - string` → `10 ~ 255 - string`(`:385`)。(type 8 负 bigint 的接线在 Task 2,本任务只在类型表内为其留位。)
- `decodeParam`:bigint `type === 6` → `7`(`:626`);长字符串 `type === 7` → `9`(`:636`);短字符串 `else` 分支的 `const length = type - 7` → `type - 9`(`:645`),注释 `8 ~ 255 - string` → `10 ~ 255 - string`(`:644`)。
- `INormalizedParam.paramType` 的 jsDoc 类型表(`:13-24`)更新为最终表:0 boolean、1 int8、2 int16、3 int32、4 int64(非负)、5 负 int64、6 float、7 bigint(非负)、8 负 bigint、9 长字符串、10 ~ 255 短字符串(长度 = type - 9,1..246)。
(4) 同步更新 `array.test.ts` 中断言类型字节 / 字符串长度阈值的既有用例(A7/A10 授权;仅限类型表迁移,其他断言一字不改):
- `paramCases`(`:94-95`)float 期望 token 由 `5` 改为 `6`。
- `round-trips a single byte bigint`(`:274`)期望 token 由 `6` 改为 `7`。
- `round-trips a short string with an inline type token`(`:312`)期望 token 由 `9` 改为 `11`(`'hi'` 长度 2 → `2 + 9`)。
- `round-trips a long string through the length-prefixed type`(`:322`)与 `round-trips an empty string`(`:331`)期望 token 由 `7` 改为 `9`。
- 同步相应中文注释(`type 6` → `type 7`、`type 7` → `type 9`、内联 token 说明等),使注释与新表一致。
按 D-10 取消 `array.test.ts:287-291`(`round-trips int64 values above the int32 range`)的 skip,断言一字不改,同步 it 前的中文注释。
按用户显式授权(A7,D-10 本计划唯一例外)在同一 `describe('ReplayArray param codec')` 内新增聚焦用例,不改动任何既有断言:
- 负 int64 往返:对 `-1`、`-2147483649`、`-4294967297` 各自 `array.add(0, [v])` 后断言 `array.get(0).params` 读回原值(优先复用既有 `expectParamTyped` 做 typeof + 严格相等,风格与相邻用例一致);
- 类型字节断言:对上述负 int64 各断言 `firstParamToken(array)` 为 `5`,并对同组非负对照断言为 `4`,锁定「负值走 type 5、非负走 type 4」;
- 非负对照:`2147483648`(既有 skip 已覆盖)之外补一条 `9007199254740991`(2^53-1)往返,确认上限仍精确;
- 用例前写中文单行注释说明「负 int64 依赖 `low = num - high * 2^31`(A8)」,命名反映意图(如 `round-trips negative int64 values below the int32 range`)。
- 用例前写中文单行注释说明「负 int64 使用独立类型码 5,载荷为幅值 |n|,解码后取负(A8)」,命名反映意图(如 `round-trips negative int64 values through the dedicated type`)。
不做的事:不改类型 token 与字节布局;不碰其他参数类型分支;不新增除上述负值 / 非负对照以外的用例。
不做的事:不为负 int64 借用符号位、不改动非负 int64 的字节布局语义;不碰 bigint 分支的编码逻辑(Task 2 负责)与索引编辑分支;不新增除上述负值 / 非负对照以外的用例;不修改本计划 files_modified 之外的源码。
验证:`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` 全绿(重点确认 `:259-266` 位宽切换用例(float token 已改 6)、单字节 bigint(token 已改 7)、字符串 token 用例(已改 9 / 11)、告警码用例与既有 int64 skip 转绿后不回归,新增负值与 2^53-1 对照转绿)→ D-44 门禁三步。提交信息:`fix(07-03): #06-04-1 round-trip negative int64 and renumber param type codes`。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts</automated>
<fails_when>非零退出,或摘要行出现 "1 failed" / "failed"(int64 往返仍不精确,或负值 / 2^53-1 对照失败),或输出出现 "no tests found"</fails_when>
<fails_when>非零退出,或摘要行出现 "1 failed" / "failed"(int64 往返仍不精确,或类型码迁移后位宽 / bigint / 字符串 token 断言失败,或负值 / 2^53-1 对照失败),或输出出现 "no tests found"</fails_when>
</verify>
<acceptance_criteria>
- `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),既有断言一字未改
- `array.ts` 的 `normalizeParam` 对整数 `param >= 2147483648` 返回 type 4、对 `param < -2147483648` 返回 type 5,两者 `byteLength` 均为 9
- `array.ts` 的 type 4/5 分支以幅值 `magnitude = num < 0 ? -num : num` 拆分高低 32 位写入;`decodeParam` 的 type 5 分支读回幅值后取负
- 类型码表已迁移:float 6、bigint 7、长字符串 9、短字符串 `type = length + 9` 且阈值 ≤ 246,`INormalizedParam` jsDoc 与三处实现一致,无旧码残留
- `array.test.ts` 的 `round-trips int64 values above the int32 range` 已取消 skip;float / 单字节 bigint / 短串 / 长串 / 空串的 token 断言已同步到新表
- 已新增负 int64 聚焦用例(`-1`、`-2147483649`、`-4294967297`,含 type 5 token 断言)与非负对照(含 2^53-1 与 type 4 token 断言),除类型表迁移外既有断言一字未改
- `pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿
</acceptance_criteria>
<done>int64 解码精确往返(含负数)且向后兼容、目标 skip 取消、负值聚焦用例转绿;已一个原子提交入库</done>
<done>int64 解码精确往返(负值为 type 5 幅值载荷、非负为 type 4)、类型码表重编号后类型字节断言一致、目标 skip 取消、负值聚焦用例转绿;已一个原子提交入库</done>
</task>
<task type="auto">
<name>Task 2: 修复 #06-04-2 —— 多字节 bigint 编码按位取字节、解码无符号读取,并按方法 α 支持负 bigint</name>
<name>Task 2: 修复 #06-04-2 —— 多字节 bigint 按位取字节、解码无符号读取,并支持负 bigint(type 8)</name>
<files>packages-user/data-common/src/replay/array.ts, packages-user/data-common/src/replay/array.test.ts</files>
<read_first>
- 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)
- packages-user/data-common/src/replay/array.ts(:255-275 bigint 编码分支、:376-390 setParamArray 的 bigint / 字符串分支、:624-655 decodeParam 的 bigint 与字符串分支)
- packages-user/data-common/src/replay/array.test.ts(:269-275 单字节 bigint 绿用例、:335-340 异质序列绿用例、:278-284 与 :294-304 目标 skip、:371-376 告警 152)
</read_first>
<action>
修 `#06-04-2` 的多字节 bigint 编解码,并按方法 α 补齐负 bigint 往返(D-09 裁决新增,见 Task 0 `<record>`)。
修 `#06-04-2` 的多字节 bigint 编解码,并按用户裁决把负 bigint 独立为参数类型码 type 8(载荷 = 幅值)。见 Task 0 `<record>` 第 4、5、6、7 条(A9/A10)。
(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`)不变。
(1) 多字节 bigint 编码按位取字节(`:264-270`):把 `total` / `base` / `remain` 累加循环体替换为对 `i` 从 0 到 `length - 1` 写入 `Number((magnitude >> (8n * BigInt(i))) & 0xffn)`。保留 `const bit = magnitude.toString(2);`、`const length = Math.ceil(bit.length / 8);` 与 `byteLength: arr.length + 2`;`new Uint8Array(length)` 不变。
(2) 解码无符号读取(`:628` 与 `:631`):长度前缀用 `this.paramView.getUint8(startIndex + 1)`,字节用 `this.paramView.getUint8(startIndex + 2 + i)`。其余解码逻辑与告警分支不变。
(2) 解码无符号读取(bigint 分支):长度前缀改用 `this.paramView.getUint8(startIndex + 1)`,幅值字节改用 `this.paramView.getUint8(startIndex + 2 + i)`(原为 `getInt8`,会把 ≥128 的长度前缀/字节当作负数)。其余解码逻辑与告警分支不变。
(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`)行为不变。
(3) 负 bigint = **独立 type 8**(A9,取代旧的方法 α 符号位借用;`BigInt.asUintN` / `asIntN` 不再需要):
- `normalizeParam` bigint 分支(`:255-275`):先取幅值 `const magnitude = param < 0n ? -param : param;`,`bit` / `length` / `arr` 均基于 `magnitude` 计算;`paramType` 由固定 `7` 改为 `param < 0n ? 8 : 7`;`byteLength: arr.length + 2` 与 `logger.warn(152)` 的墙体判定(以 `param` 本身对 ±`2n ** 2047n` 比较)不变。
- `setParamArray`:bigint 分支改为 `param.paramType === 7 || param.paramType === 8`,写入 `this.paramArray[index + 1] = arr.length; this.paramArray.set(arr, index + 2);`(`arr` 为幅值字节)。
- `decodeParam`:bigint 分支改为 `type === 7 || type === 8`——`const length = this.paramView.getUint8(startIndex + 1);`,按 (2) 以 `getUint8` 无符号累加得 `base`,`byte = length + 2`,`value = type === 8 ? -base : base`。
- **载荷范围**:type 7 与 type 8 的长度前缀都是普通长度字节(写入 `arr.length`、读取 `getUint8`),故负值与非负一样保留完整 255 字节载荷范围,不再有 127 字节取舍。
(4) 复核并保持 Task 1 建立的最终类型码表在 `normalizeParam` / `setParamArray` / `decodeParam` 三处一致:0 boolean、1 int8、2 int16、3 int32、4 int64(非负)、5 负 int64、6 float、7 bigint(非负)、8 负 bigint、9 长字符串(int32 长度前缀)、10–255 短字符串(`type = length + 9`,长度 1..246)。若 `array.test.ts` 中仍有断言移位后类型字节 / 字符串长度阈值的用例未被 Task 1 覆盖(如 bigint token 应为 7、长 / 空字符串 token 应为 9、短串 token 应为 `长度 + 9`),在本任务一并补齐到新表;除该类型表迁移外不改任何断言。
按 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 前的中文注释。
按用户显式授权(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`)。
- 类型字节断言:对上述负 bigint 各断言 `firstParamToken(array)` 为 `8`;对非负对照(`100n` 与一条多字节正 bigint)断言为 `7`;
- 非负对照:单字节正 bigint(既有绿用例已覆盖)之外补一条多字节正 bigint 对照,确认幅值路径对非负值逐字节不变;
- 用例前写中文单行注释说明「负 bigint 使用独立类型码 8,载荷为幅值 |n|,解码后取负(A9)」,命名反映意图(如 `round-trips negative bigint values through the dedicated type`)。
不做的事:不改 `normalizeParamList` 的位宽告警(152)逻辑与非负长度上限;不改类型 token 表;不新增除上述负值 / 非负对照以外的用例。
不做的事:不改 `normalizeParamList` 的位宽告警(152)逻辑与 255 字节长度上限;不再借用长度前缀最高位做符号位;不碰 int64 / 负 int64 分支的逻辑(Task 1 已定稿);不新增除上述负值 / 非负对照以外的用例;不修改本计划 files_modified 之外的源码。
验证:`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` 全绿(重点确认单字节 bigint、异质序列既有绿用例与 `warns code 152` 不回归,多字节 / 负 bigint 与对照转绿)→ D-44 门禁三步。提交信息:`fix(07-03): #06-04-2 round-trip multi-byte and negative bigint parameters`。
</action>
<verify>
<automated>pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts</automated>
<fails_when>非零退出,或摘要行出现 "1 failed" / "failed"(多字节 / 负 bigint 往返仍不精确,或既有单字节与告警 152 用例回归),或输出出现 "no tests found"</fails_when>
<fails_when>非零退出,或摘要行出现 "1 failed" / "failed"(多字节 / 负 bigint 往返仍不精确,或既有单字节、异质序列、告警 152 用例回归),或输出出现 "no tests found"</fails_when>
</verify>
<acceptance_criteria>
- `array.ts` 的 bigint 编码循环体内出现 `& 0xffn`,且不再出现 `total += remain <<` 写法
- `array.ts` 解码的长度前缀与字节均使用 `getUint8`
- 负 bigint(方法 α):编码对负值以 `BigInt.asUintN` 取补码并写 `arr.length | 0x80`,解码按符号位以 `BigInt.asIntN` 还原;非负路径字节不变
- `array.ts` 的 bigint 编码循环体内出现 `& 0xffn` 且以幅值 `magnitude` 取字节,不再出现 `total += remain <<` 写法
- `array.ts` 解码的 bigint 长度前缀与幅值字节均使用 `getUint8`
- 负 bigint(type 8):`normalizeParam` 对 `param < 0n` 返回 type 8 且载荷为 `|n|`,解码读回幅值后取负;非负 bigint 仍为 type 7 且字节布局不变
- 类型码表 float 6 / bigint 7 / 负 bigint 8 / 长字符串 9 / 短字符串 `length + 9`(阈值 ≤ 246)在 `normalizeParam`、`setParamArray`、`decodeParam` 三处一致,`array.test.ts` 相关断言已到新表
- `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`)与非负对照,既有断言一字未改
- 已新增负 bigint 聚焦用例(`-1n`、`-128n`、`-129n`、`-256n`、`-65537n`,含 type 8 token 断言)与非负对照(含 type 7 token 断言),除类型表迁移外既有断言一字未改
- `pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿
</acceptance_criteria>
<done>多字节与负 bigint 均可逆、两条 skip 取消并转绿、负值聚焦用例转绿且非负路径向后兼容;已一个原子提交入库</done>
<done>多字节与负 bigint 均可逆(负值 type 8 幅值载荷、非负 type 7)、两条 skip 取消并转绿、负值聚焦用例转绿且非负路径字节不变;已一个原子提交入库</done>
</task>
<task type="auto">
@ -330,7 +359,8 @@ Output: `array.ts` 四处登记修复(其中 `#06-04-1`/`#06-04-2` 与 `#06-04
在 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 字节上限的取舍说明)。
- 已修复(本计划,**不再登记为缺口**):负 int64 往返(独立类型码 type 5,载荷 = 幅值 `|n|`,解码取负,A8)、负 bigint 往返(独立类型码 type 8,载荷 = 幅值 `|n|`,解码取负,A9)。两者均保留与对应非负类型相同的载荷范围(bigint 仍为 255 字节),无符号位借用、无范围取舍。
- 类型码表重编号记录(A10):0 boolean、1 int8、2 int16、3 int32、4 int64(非负)、5 负 int64、6 float、7 bigint(非负)、8 负 bigint、9 长字符串、10–255 短字符串(`type = length + 9`,长度 1..246);项目未发布、无向后兼容要求,`array.test.ts` 中断言类型字节 / 字符串长度阈值的用例已同步更新(float token 5→6、单字节 bigint 6→7、短串 9→长度+9、长/空串 7→9),除该类型表迁移外既有断言一字未改。
- 仍未修复的同族缺口:`ReplayArray.set` 的末步 `nextParam` 与回退起点 `paramStart + 1`、`insert(index === length)` 的 `paramStart`。每条注明「未登记缺陷、无既有测试、需用户决定是否另立条目」。
- 另记(不在本计划范围):码 128 占位符不足,属 07-05。
</action>
@ -342,11 +372,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 聚焦用例已入库并通过,既有断言一字未改
- 经用户授权的负 int64 / 负 bigint 聚焦用例已入库并通过;类型表相关断言已同步到新表,除该迁移外既有断言一字未改
- WINDOWS.md 中 id 23 状态为 `fixed`
- SUMMARY 含 findings↔ledger 对照(id 23 fixed;`#06-04-3`/`#06-04-4` 注明无 ledger entry)与同族未修复缺口清单(2 条:`set`、末位 `insert`)
- SUMMARY 含 findings↔ledger 对照(id 23 fixed;`#06-04-3`/`#06-04-4` 注明无 ledger entry)、类型码表重编号记录(A10)与同族未修复缺口清单(2 条:`set`、末位 `insert`)
</acceptance_criteria>
<done>D-44 三步全过、全量套件绿、id 23 结清、findings↔ledger 对照与同族未修复缺口(`set`、末位 `insert`)已登记于 SUMMARY(提交粒度按 D-13,按根因族各成原子提交)</done>
<done>D-44 三步全过、全量套件绿、id 23 结清、findings↔ledger 对照(含类型码表重编号记录)与同族未修复缺口(`set`、末位 `insert`)已登记于 SUMMARY(提交粒度按 D-13,按根因族各成原子提交)</done>
</task>
</tasks>
@ -364,36 +394,38 @@ 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(含负数,A8)与多字节 / 负 bigint(方法 α,A9)编解码精确往返;`getUint8` 修正顺带消除「长度前缀 ≥128 被当作负数」的静默错读 |
| T-7-02 | Tampering | `array.ts` `decodeParam`/`normalizeParam` | medium | mitigate | Task 1/2 使 int64(负值独立 type 5)与多字节 / 负 bigint(负值独立 type 8)编解码精确往返,载荷只存幅值并在解码取负;`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` 不变);若执行中出现安装需求,立即停止并汇报 |
</threat_model>
<verification>
- `pnpm exec vitest run packages-user/data-common/src/replay/array.test.ts` 全绿(7 条 skip 取消后)
- 经授权的负 int64 / 负 bigint 聚焦用例全部通过,且既有断言一字未改
- 经授权的负 int64(type 5)/ 负 bigint(type 8)聚焦用例全部通过;类型表相关断言已同步到新表,除该迁移外既有断言一字未改
- 类型码表在 `normalizeParam`、`setParamArray`、`decodeParam` 三处一致:0 boolean、1 int8、2 int16、3 int32、4 int64、5 负 int64、6 float、7 bigint、8 负 bigint、9 长字符串、10–255 短字符串(`length + 9`),无旧码残留
- `pnpm test:ci` 全绿且本计划不新增 `it.skip`
- 两个改动文件 eslint 0 错误、vue-tsc 过滤后 0 类型错误
- WINDOWS.md id 23 为 fixed;findings↔ledger 对照与同族未修复缺口(`set`、末位 `insert`)在 SUMMARY 可见
- WINDOWS.md id 23 为 fixed;findings↔ledger 对照、类型码表重编号记录(A10)与同族未修复缺口(`set`、末位 `insert`)在 SUMMARY 可见
</verification>
<success_criteria>
1. `#06-04-1`、`#06-04-2`、`#06-04-3`、`#06-04-4` 对应 7 条正确预期用例全部取消 skip 并转绿
2. `delete(0)`、零参数 `insert`、`set`、位宽切换与扩容等既有绿用例不回归
3. 负 int64(`|n| ≤ 2^53-1` 含负数)与负 bigint(方法 α)均精确往返,且非负路径字节级向后兼容
4. 经用户授权的负值聚焦用例(负值 + 非负对照)随同原子提交入库并通过
5. 按根因两个族(编解码 / 索引编辑)各成原子提交,共 4 个提交
6. `ReplayArray.set` 与 `insert(index === length)` 在 SUMMARY 明确登记为未修复同族缺口,未被静默忽略;负 int64 / 负 bigint 不再登记为缺口
3. 负 int64(`|n| ≤ 2^53-1`)与负 bigint(含多字节幅值)均经独立类型码(5 / 8)以幅值载荷精确往返,非负路径语义不变、bigint 保留 255 字节载荷范围
4. 经用户授权的负值聚焦用例(负值 + 非负对照 + 类型码断言)随同原子提交入库并通过;类型表相关既有断言已同步更新,除该迁移外一字未改
5. 类型码表整体重编号(float 6、bigint 7、负 bigint 8、长字符串 9、短字符串 `length + 9`)在编解码两侧一致且无旧码残留
6. 按根因两个族(编解码 / 索引编辑)各成原子提交,共 4 个提交
7. `ReplayArray.set` 与 `insert(index === length)` 在 SUMMARY 明确登记为未修复同族缺口,未被静默忽略;负 int64 / 负 bigint 不再登记为缺口
</success_criteria>
## Artifacts this phase produces
| 类型 | 路径 | 内容 |
|------|------|------|
| 生产源码(修改) | `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 聚焦用例(含非负对照) |
| 生产源码(修改) | `packages-user/data-common/src/replay/array.ts` | int64 乘数 + 负 int64(type 5,幅值载荷)、bigint 按位取字节 + 无符号读取 + 负 bigint(type 8,幅值载荷)、类型码表重编号、`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`)、门禁结果 |
| 摘要 | `.planning/phases/07-data-fixes/07-03-SUMMARY.md` | 逐条修复记录、类型码表重编号(A10)与负值编码约定(type 5 / type 8,载荷 = 幅值)、findings↔ledger 对照、覆盖真相(读流 vs get)、同族未修复缺口清单(`set`、末位 `insert`)、门禁结果 |
<output>
Create `.planning/phases/07-data-fixes/07-03-SUMMARY.md` when done