diff --git a/.planning/phases/07-data-fixes/07-10-SUMMARY.md b/.planning/phases/07-data-fixes/07-10-SUMMARY.md new file mode 100644 index 0000000..2f14282 --- /dev/null +++ b/.planning/phases/07-data-fixes/07-10-SUMMARY.md @@ -0,0 +1,271 @@ +--- +phase: 07-data-fixes +plan: 10 +subsystem: data-layer +tags: [replay, replay-array, index-maintenance, codec, bigint, read-stream, logger-code-table, loadstate-audit] + +requires: + - phase: 07-data-fixes + provides: "07-03 delete() fix + the two 'registered-not-fixed' replay items (set(), insert(index === length)) taken over by this plan" + - phase: 07-data-fixes + provides: "07-REVIEW.md CR-01/WR-01/WR-02/WR-03/WR-07/IN-03 and 07-LOADSTATE-AUDIT.md row H (#06-17-3)" + - phase: 06-unit-tests + provides: "replay array/stream/saveLoad regression suites and D-44 file-level three-step gate" +provides: + - "ReplayArray.set() uses getParamRange(index) (last-step end = paramUsed) and shifts indexArray from command index `index + 1`, same contract as delete() (CR-01 closed)" + - "insert/delete/set validate index before touching any buffer; out-of-range is a no-op plus warn 179 (operation/index/length)" + - "insert(index === length) is a legal append with add() semantics; insert legal range is the closed interval [0, length]" + - "setReplayArray() calls expireStreams(), so an active ReplaySandbox reader is expired by ReplaySystem.loadState (#06-17-3 / audit H closed)" + - "normalizeParam rejects |bigint| >= 2^2040 with warn 152 and drops that param; normalizeParamList filters it so counts stay honest" + - "add/insert/set write the truncated (normalized.length) param count, matching normalizeParamList's 255 cap (WR-02 closed)" + - "Param type codes are an append-only format: new semantics must take new codes, never reuse; 5/6/7/8 + short-string base reallocation recorded as a one-time pre-release break" +affects: [07-data-fixes, data-common-replay, data-state-replay, replay-verification] + +actuals: + tokens: 4456 + tasks: 5 + commits: 6 + plan_head_before: b299d348a7c0541f33869b53ae0e3e860dcef9b9 + +tech-stack: + added: [] + patterns: + - "Single index-maintenance contract: insert/delete/set all derive the param range from indexArray[index] + (indexArray[index + 1] | paramUsed)" + - "Out-of-range edit = observable no-op: guard before any buffer read/write, warn with operation/index/length, buffers byte-identical" + - "Representability guard at encode time: values that do not fit the wire field are dropped before any count is computed" + - "Append-only wire format convention (no version field) for a pre-release project" + +key-files: + created: [] + modified: + - packages-user/data-common/src/replay/array.ts + - packages-user/data-common/src/replay/types.ts + - packages-user/data-common/src/replay/array.test.ts + - packages-user/data-common/src/replay/saveLoad.test.ts + - packages/common/src/logger.json + +key-decisions: + - "Q1=A: set() supports param-length changes via getParamRange(index); the last-step end is paramUsed, so negative paramLength / giant deltaLength can no longer occur" + - "Q2=A: insert(index === length) is a legal append written with add() semantics (paramStart = paramUsed, indexArray[length] = paramUsed); insert legal range is [0, length]" + - "Q3=B: new replay-specific warn code 179 in logger.json (first free code after the max warn 178), carrying operation name / index / current length" + - "Q4=A: a command with >255 params writes the truncated count (normalized.length); overflow params stay ignored via the existing warn 153" + - "Q5=A+reject: |bigint| >= 2^2040 is unrepresentable in a one-byte length field -> warn 152 and drop that param (command kept, param count reduced)" + - "Q6=B: no format-version field on IReplaySystemSave; instead an append-only 'type codes are only added, never changed' convention plus the recorded one-time 5/6/7/8 + short-string-base reallocation" + +patterns-established: + - "One param-range helper (getParamRange) is the only source of param byte ranges for all three mutating methods" + - "Boundary diagnostics carry structural integers only (operation/index/length), never recording content" + +requirements-completed: [FIX-01] + +coverage: + - id: D1 + description: "CR-01: set() keeps indexArray consistent with the param bytes for growing, shrinking and last-step edits (with get() and read-stream traversal agreeing)" + requirement: FIX-01 + verification: + - kind: unit + ref: "packages-user/data-common/src/replay/array.test.ts#ReplayArray set index maintenance" + status: pass + human_judgment: false + - id: D2 + description: "WR-07 / Q2 / Q3: out-of-range insert/delete/set change nothing and warn code 179; insert(index === length) appends" + requirement: FIX-01 + verification: + - kind: unit + ref: "packages-user/data-common/src/replay/array.test.ts#ReplayArray index bounds" + status: pass + human_judgment: false + - id: D3 + description: "#06-17-3 / audit H: an active sandbox read stream is expired by ReplaySystem.loadState and warns code 156 instead of decoding at stale offsets" + requirement: FIX-01 + verification: + - kind: unit + ref: "packages-user/data-common/src/replay/saveLoad.test.ts#expires an active sandbox read stream when a snapshot is loaded" + status: pass + human_judgment: false + - id: D4 + description: "WR-01 / Q5: |bigint| >= 2^2040 warns 152 and is dropped; 2^2040 - 1 still round-trips with length byte 255 and no downstream desync" + requirement: FIX-01 + verification: + - kind: unit + ref: "packages-user/data-common/src/replay/array.test.ts#round-trips the largest bigint that fits the length byte; #drops the unrepresentable bigint param and keeps the rest of the command; #keeps the read stream aligned after dropping an unrepresentable bigint" + status: pass + human_judgment: false + - id: D5 + description: "WR-02 / Q4: add/insert/set write the truncated param count (255 for 256/300 params) so multi-command read streams stay aligned" + requirement: FIX-01 + verification: + - kind: unit + ref: "packages-user/data-common/src/replay/array.test.ts#writes the truncated param count when a command exceeds 255 params; #writes the truncated param count through insert and set" + status: pass + human_judgment: false + - id: D6 + description: "WR-03 / Q6=B: the param type-code table is documented as append-only (never reuse a code) with the historical reallocation recorded, and no format-version field is added" + verification: + - kind: unit + ref: "packages-user/data-common/src/replay/array.test.ts#ReplayArray param type table" + status: pass + human_judgment: true + rationale: "The mapping is anchored by a test, but the convention wording itself (array.ts table comment + IReplaySystemSave.paramArray / getParamArray jsDoc) can only be confirmed by a human reading it against the Q6=B ruling." + +# Metrics +duration: 28min +completed: 2026-09-17 +status: complete +--- + +# Phase 7 Plan 10: ReplayArray index/stream/codec fixes Summary + +**`ReplayArray` 的三类缺陷一次收口:`set()` 与 `delete()` 共用同一套索引维护口径(CR-01)、越界编辑变为可观测无操作(warn 179)且 `insert(length)` 合法追加、活跃读流由 `setReplayArray` 主动过期(`#06-17-3`/审计 H),并把 bigint 长度字节与参数计数收敛到实际写出的字节(WR-01/WR-02)、把类型码表冻结为只增不改的追加式格式(WR-03)。** + +## Performance + +- **Duration:** ~28 min +- **Started:** 2026-09-17T04:33Z(本地 12:33) +- **Completed:** 2026-09-17T05:01Z(本地 13:01) +- **Tasks:** 5(Task 0 已由用户裁决,见 ``;实际执行 Task 1–5) +- **Files modified:** 5(`system.ts` 因 Q6=B 无需改动,未提交) + +## Task Commits + +Each task was committed atomically (measuring `git rev-list --count b299d34..HEAD` = **6**,含本 SUMMARY 的元数据提交): + +1. **Task 1: CR-01 `set()` 索引数组损坏修复** — `9ae28c6` (fix) +2. **Task 2: 写入路径边界校验 + `insert(index === length)` 追加 + `setReplayArray` 补 `expireStreams()`** — `1f9fb18` (fix) +3. **Task 3: bigint 长度字节守卫(WR-01)+ 参数计数取截断后长度(WR-02)** — `35d4ad6` (fix) +4. **Task 4: WR-03 追加式类型码约定(Q6=B)** — `5b26539` (docs) +5. **Task 1 补强: 末步 `set` 用例改为断言 `paramUsed` 的外部可观测偏移** — `0c6812f` (test) +6. **Plan metadata:** 本 SUMMARY 的 `docs(07-10): …` 提交 (docs) + +提交清单(`git log --name-only b299d34..HEAD`):仅触及 frontmatter `files_modified` 中的 5 个文件,零 `client-*`、零 `replay/system.ts`、零 `data-common/src/save/*`、零 `.planning/WINDOWS.md`。 + +## Files Created/Modified + +- `packages-user/data-common/src/replay/array.ts` — `set()` 改用 `getParamRange(index)` 且回退起点改为 `index + 1`;`insert/delete/set` 前置 `index` 校验(越界告警 179 无操作);`insert(index === length)` 走追加分支;`setReplayArray()` 补 `expireStreams()`;`normalizeParam` 的 bigint 分支改为 2^2040 守卫并返回 `null`,`normalizeParamList` 过滤被丢弃项;`add/insert/set` 的 `setCommandArray` 计数改用 `normalized.length`;类型码表注释补只增不改约定 +- `packages-user/data-common/src/replay/types.ts` — `insert`/`delete`/`set` 的合法区间、越界契约与 `set()` 长度变化语义写入 jsDoc;`getParamArray` 与 `IReplaySystemSave.paramArray` 补追加式约定与「不承诺读回旧格式录像」 +- `packages-user/data-common/src/replay/array.test.ts` — 新增 18 条用例:`set` 索引维护 6 条、`index` 边界 5 条、bigint 边界与丢弃 3 条、截断计数 2 条、读流对齐/类型码锚定 2 条;并加 `expectArrayUnchanged` 不变量辅助函数 +- `packages-user/data-common/src/replay/saveLoad.test.ts` — 新增 `#06-17-3` 回归(活跃沙箱读流跨 `loadState` 过期 → 告警 156) +- `packages/common/src/logger.json` — 新增 warn 179;warn 152 文案对齐到真实可表示范围(见 Deviations 1) + +## Decisions Made + +Task 0 的六条用户裁决(`07-10-PLAN.md` 的 ``,提交 `b299d34`)逐条落地,无自行改选: + +| 裁决 | 落地位置 | 可观测证据 | +| --- | --- | --- | +| Q1=A `set()` 支持长度变化、末步终点 `paramUsed`、回退起点 `index` | `array.ts:545-585`(关键 `:553-556` 取 `getParamRange`,`:583-585` 从 `index + 1` 起平移) | `array.test.ts`「ReplayArray set index maintenance」6 条(增长/缩短/末步×2/读流×2) | +| Q2=A `insert(index === length)` 合法追加、区间 `[0, length]` | `array.ts:467-478`(追加分支)、`types.ts:202` | 「appends when the insert index equals the current length」 | +| Q3=B 新增 warn 179(操作名/索引/长度) | `logger.json` warn 179;`array.ts:452-457`(insert)/ `:504-509`(delete)/ `:548`(set) | 5 条边界用例断言 `toContain(179)` 且缓冲区逐字节不变 | +| Q4=A 写入截断后计数 | `array.ts:438`、`:469`、`:487`、`:566` | 「writes the truncated param count …」2 条(256 与 300 参数) | +| Q5=A 拒绝该参数 | `array.ts:276-280`(守卫)、`:328-333`(过滤) | 「drops the unrepresentable bigint param …」+「… read stream aligned …」 | +| Q6=B 不加版本字段 + 只增不改约定 | `array.ts:26-29`、`types.ts:265`/`:358` | 「ReplayArray param type table」锚定 5/6/7/8/9/短字符串基址 | + +## Findings ↔ Ledger / Registry 对照(缺口登记) + +| 条目 | 来源 | 结论 | 锚点 | +| --- | --- | --- | --- | +| `ReplayArray.set` 「只登记不修」 | `07-03-PLAN.md` | **已关闭** | `9ae28c6` + `0c6812f` | +| `insert(index === length)` 「只登记不修」 | `07-03-PLAN.md` | **已关闭**(判为合法追加,Q2=A) | `1f9fb18` | +| CR-01 `set()` 损坏索引数组 | `07-REVIEW.md:64-96` | **已修** | `9ae28c6` | +| WR-01 bigint 长度字节溢出 | `07-REVIEW.md:136-154` | **已修**(2^2040 守卫 + 丢弃参数) | `35d4ad6` | +| WR-02 命令参数计数取未截断长度 | `07-REVIEW.md:156-175` | **已修** | `35d4ad6` | +| WR-03 类型码重编号且存档无版本标记 | `07-REVIEW.md:177-182` | **已修**(取方向 (a):追加式约定;不加版本) | `5b26539` | +| WR-07 `insert`/`delete`/`set` 不校验 `index` | `07-REVIEW.md:232-237` | **已修** | `1f9fb18` | +| IN-03 `set()` 用例无法发现 CR-01 | `07-REVIEW.md:253-257` | **已修**(增长/缩短/末步/读流 6 条新用例) | `9ae28c6` + `0c6812f` | +| 审计 H(`#06-17-3`)`setReplayArray` 漏 `expireStreams()` | `07-LOADSTATE-AUDIT.md` H 行 | **已修 / 关闭** | `1f9fb18` | + +`WINDOWS.md`:本计划 6 项均无入账 id(`07-03` 的 id 23 已 fixed;review/审计条目未入账),按 D-14 **只登记对照、不新建条目、不执行 `fixed`/`waive`**;`git diff b299d34..HEAD -- .planning/WINDOWS.md` 为空。 + +**Phase 7 验证已失效**:`07-VERIFICATION.md`(`2026-09-16T06:04:11Z`,覆盖 8/8 计划)在 07-09 执行前即已过期,本轮又追加 07-10..07-14。本计划执行完毕后**必须重跑 `/gsd-verify-work`(或 `gsd-verify-work 7`)**重新出具验证结论;`ROADMAP.md` 的 Phase 7 计划数与 Wave 列表由 orchestrator 同步(见 Deviations 5)。 + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 2 - 缺失的关键正确性] `logger.json` warn 152 文案与真实可表示范围对齐** +- **Found during:** Task 3(WR-01 修复) +- **Issue:** 原文案为 `min for -(2n ** 2047n), and max for 2n ** 2047n - 1n`,而修复后 |值| ≥ 2^2040 即被拒绝并告警 152 —— 若保留原文案,落在 [2^2040, 2^2047) 的合法值会发出**自相矛盾**的诊断(「未超限」的告警)。 +- **Fix:** 文案改为 `Bigint replay param out of representable range, min for -(2n ** 2040n - 1n), and max for 2n ** 2040n - 1n. The overflowed param will be ignored.`,同时写明「溢出参数会被忽略」(Q5 裁决的处置)。 +- **Files modified:** `packages/common/src/logger.json`(本就在 `files_modified` 内;未新增码、未改码号) +- **Verification:** 无任何测试断言 152 文案;`array.test.ts:431-436` 既有 152 用例(`2n ** 2048n`)语义不变且通过。 +- **Committed in:** `35d4ad6` + +**2. [Rule 2 - 测试可观测性] 末步 `set` 用例补参数缓冲区原始字节断言(额外 `test` 提交)** +- **Found during:** Task 1 自检(对末步子缺陷做反向验证) +- **Issue:** 计划要求「对末步 set 前后 `get(last)` 与 `length`、`paramUsed` 断言」,但 `paramUsed` 是私有成员;反向验证(临时把 `nextParam` 换回 `indexArray[index + 1]`)显示:仅断言 `get(i)`/`length`/读流时,末步子缺陷**不会**让用例失败(`copyWithin(target>source)` 只右移,`indexArray` 仍自洽,仅 `paramUsed` 被污染)。 +- **Fix:** 两条末步用例补断言「后续 `add` 的参数落在正确偏移」(`paramBytes[8]/[9]` 与 `paramBytes[4]/[5]`),使 `paramUsed` 的正确性成为外部可观测不变量。反向验证后两条用例在未修复时**确定性失败**(`expected 1 to be 30`)。 +- **Files modified:** `packages-user/data-common/src/replay/array.test.ts` +- **Verification:** 反向验证(临时回退末步终点)→ 2 failed;恢复后 66/66 通过。 +- **Committed in:** `0c6812f`(独立 `test(07-10)` 提交;未回改 `9ae28c6`,避免重写已提交历史) + +### 结裁导致的既有改动的逐条说明(A1 要求) + +- **既有断言无一条被削弱或删除**(`git diff b299d34..HEAD -- *.test.ts` 的删除行仅 1 行): + - `saveLoad.test.ts:1` 的文件头注释由「ReplaySystem 同实例往返与多类型参数读回」改为「…、多类型参数读回与活跃读流跨读档过期」——`dev.md:85` 要求测试范围变化时同步更新注释。 + - `array.test.ts:431-436`(152)、`:439-449`(153)两条用例**逐字保留**,语义不变且通过。 + - 未新增任何 `it.skip`(`git diff b299d34..HEAD | Select-String "^\+.*it\.skip"` 为空),跳过项仍仅 `equipment.test.ts:306`(码 147,D-06 设计如此)。 +- **`logger.json` 152 文案**:见 Deviations 1(用户未否决码表改动;Q3=B 已授权本计划改动该文件)。 + +### 计划未落地的声明项 + +- **`packages-user/data-common/src/replay/system.ts` 未修改、未提交**:Q6=B 不加版本字段,故 `saveState`/`loadState` 无改动(`files_modified` 允许此情形不提交)。`data-common/src/save/system.ts` 亦未被本计划改动。 +- **`ROADMAP.md` / `STATE.md` 未由本计划更新**:本次执行上下文明确「orchestrator owns those」,覆盖 `07-10-PLAN.md` Task 5(4) 的 `docs(phase-07)` 同步项;Phase 7 计划数与 Wave 列表待 orchestrator 处理。 +- **`WINDOWS.md` 未改动**(D-14 + Task 5 验收项)。 + +## Issues Encountered + +**1. `pnpm test:ci` 当日基线与计划记载的基线不一致(非本计划引入)** + +| 时点 | 文件 | 用例 | 失败 | 跳过 | +| --- | --- | --- | --- | --- | +| 执行前实测(本会话重录) | 66(1 failed / 65 passed) | 689 | 2 | 1 | +| 执行后(冻结态,Task 5 重跑) | 66(1 failed / 65 passed) | 707 | 2 | 1 | +| `07-10-PLAN.md` 记载(2026-09-16) | 66 | 691 | 0 | 1 | + +- 计划记载为「66 文件 / 690 通过 / 1 跳过」,但**执行前实测即为 686 通过 / 2 失败**(总数 689)。两条失败均在 `packages-user/data-state/src/replay/commands.test.ts`:`assembles an independent command set per CoreState` 与 `keeps registration direct and the command module self-contained`,原因是测试仍期望旧类名 `ReplayMoveCommand` 等,而生产代码已被用户提交 `49aacb0`(`refactor: 调整录像命名`)重命名 —— 属**本计划之前就存在、与 `ReplayArray` 无关**的失败。 +- 该 2 条失败**也不是**用户 `client-*` 并发 WIP 导致的(文件属 `data-state`),故按约束「不得以跳过换取全绿」如实登记,未修复、未抑制。 +- 执行前后**失败集合完全相同**(同样 2 条、同一文件),**通过数 +18**,恰等于本计划新增用例数(Task1 6 + Task2 6 + Task3 5 + Task4 1 = 18),零新增失败、零新增跳过。 + +**2. 用户并发 WIP 在工作树中继续演进(本计划零触碰)** + +执行期间工作树中出现了 `packages-user/client-base/`、`packages-user/client-modules/` 的改动,以及 `packages-user/data-common/src/save/{index,types}.ts` 的修改与 `save/system.ts` 的**删除**(属用户把存档系统迁往渲染层的在制品)。本计划所有提交仅包含 frontmatter `files_modified` 的 5 个文件;`git diff --name-only b299d34..HEAD` 对这些路径为空。 + +## User Setup Required + +None - no external service configuration required. + +## D-44 文件级门禁结果 + +| 门禁 | 命令 | 结果 | +| --- | --- | --- | +| ESLint(改动文件) | `pnpm exec eslint --fix ` → `pnpm exec eslint ` | 0 errors(`logger.json` 报「File ignored because no matching configuration」为 JSON 无 eslint 配置的既有提示,非错误) | +| 类型(文件级) | `pnpm exec vue-tsc --noEmit` 过滤改动文件路径 | 0 命中(**未**以整仓退出码判定;`array.test.ts` 首轮曾出现 1 处 `(number \| undefined)[]` 类型错误,已在 `1f9fb18` 内修正) | +| 全量套件 | `pnpm test:ci`(执行前 + Task 5 重跑) | 失败集合不变(同 2 条既有失败)、通过 +18、跳过仍 1(`equipment.test.ts:306` 码 147) | +| 聚焦套件 | `pnpm exec vitest run …/array.test.ts …/saveLoad.test.ts` | 66/66 通过 | + +## Reverse-Verification(用例有效性证据) + +| 反向实验 | 期望 | 实测 | +| --- | --- | --- | +| 临时注释 `setReplayArray` 的 `expireStreams()` | `#06-17-3` 用例失败 | **失败**:`expected [] to include 156`(无 156) | +| 临时把 `nextParam` 换回 `indexArray[index + 1]` | 两条末步用例失败 | **失败**:2 failed(`expected 1 to be 30`) | +| 临时放宽 `delete`/`set` 越界守卫 | 越界用例失败 | **失败**:3 failed(`index bounds` 组内) | + +## Next Phase Readiness + +- `07-03` 遗留的两条「只登记不修」与 `#06-17-3` 均已关闭;`ReplayArray` 的三条写路径(`insert`/`delete`/`set`)现在共用同一套索引维护与边界契约,`add` 与 `insert(length)` 共用同一套追加语义。 +- **待办(非本计划):** ① 重跑 `/gsd-verify-work 7` 重新出具 Phase 7 验证结论(现结论已失效);② orchestrator 同步 `ROADMAP.md` 的 Phase 7 计划数/Wave 列表与 `STATE.md`;③ `data-state/src/replay/commands.test.ts` 的两条既有失败(类名重命名遗留)需另立修复项,本计划未纳入范围。 +- 本计划未新增依赖、未新建源码文件、未引入 `import type`、未使用连续 `as` 断言。 + +--- + +## Self-Check: PASSED + +- FOUND: `9ae28c6`、`1f9fb18`、`35d4ad6`、`5b26539`、`0c6812f`(`git log --oneline b299d34..HEAD`) +- FOUND: `packages-user/data-common/src/replay/array.ts`、`types.ts`、`array.test.ts`、`saveLoad.test.ts`、`packages/common/src/logger.json` +- VERIFIED: 仅声明文件被提交;`replay/system.ts`、`data-common/src/save/*`、`.planning/{WINDOWS,STATE,ROADMAP}.md`、`packages-user/client-*` 在本计划提交范围内零改动 + +--- + +*Phase: 07-data-fixes* +*Completed: 2026-09-17*