Skip to content

fix(fs): 崩溃残留半行截断,杜绝事件流永久不可读(P0) - #29

Open
echoVic wants to merge 1 commit into
mainfrom
fix-append-crash-residue
Open

fix(fs): 崩溃残留半行截断,杜绝事件流永久不可读(P0)#29
echoVic wants to merge 1 commit into
mainfrom
fix-append-crash-residue

Conversation

@echoVic

@echoVic echoVic commented Jul 31, 2026

Copy link
Copy Markdown
Owner

缺陷(P0,ROBUSTNESS,两检测器独立复现,置信度 9)

appendLineSync(事件溯源真相源 events.jsonl 的唯一追加入口)用 O_APPEND 打开后直接写入,从不检查既有文件末字节是否为换行

失效链:

  1. 进程在原子追加中途被杀 → 留下无结尾换行的截断半行(如 {"id":1}\n{"id":2}\n{"id":3)。
  2. 恢复运行时 state.ts#appendEventreadJsonlTolerant 跳过损坏尾行推导 id 后再 appendLineSync → 新记录被粘到半行上形成一条中间损坏行。
  3. 再追加一条后,该损坏行不再是末行 → readJsonlTolerant 按「非末行损坏」抛错 → 整个 events.jsonl 永久不可读

这与该基础设施 docstring / PRIVACY / CHANGELOG 明确承诺的「崩溃最多留一条可恢复末行、视作该事件未写入」直接矛盾。

修复

appendLineSync 改用 'a+'O_APPEND | O_RDWR)打开;追加前 fstat 取大小并读末字节,非 \n 即判定为崩溃残留半行,用 ftruncate 截断到最后一个完整行之后(反向分块扫描定位换行,避免整文件读入)再写新记录。

安全性论证:本函数保证每条已提交记录都以 \n 结尾,因此「文件不以换行收尾」唯一标识一条 readJsonlTolerant 本就视作「从未写入」的崩溃半行 —— 物理丢弃它不会损失任何已提交事件。

测试

新增崩溃尾态回归覆盖:截断而非粘连、空文件不受影响、全文件无换行、多字节 UTF-8 中途截断、完整 JSON 缺换行(未 fsync 残留),以及崩溃后连追两条整表仍可读的缺陷复现。

  • 全量 759 tests 全绿(含新增用例)
  • npm run build / npm run typecheck 通过

已知后续项(不在本 PR 范围)

审查另提出一条 P2 并发副作用(多写入者并发 append 时 fstat→ftruncate 非原子,可能误截并发写入者数据)。boss 事件流实际为单写入者,故留待后续处理,本 PR 不含该改动。

Summary by CodeRabbit

  • Bug Fixes

    • Improved event log recovery after interrupted writes by removing incomplete trailing records before appending new events.
    • Preserved log readability and existing records across repeated recovery appends, including files containing partial UTF-8 data.
  • Tests

    • Added coverage for crash-truncated logs, empty files, partial records, and repeated recovery scenarios.

appendLineSync 用 O_APPEND 打开后直接写入,从不检查既有文件末字节。崩溃残留
的无换行半行会被下一次追加粘成中间损坏行,再追加一条后 readJsonlTolerant 按
「非末行损坏」抛错,整个 events.jsonl 永久不可读。

改用 'a+' 打开,追加前 fstat + 读末字节,非 '\n' 即判定为崩溃残留半行并
ftruncate 截断到最后一个完整行之后(反向分块扫描定位换行),再写新记录。因每条
已提交记录都以 '\n' 结尾,「不以换行收尾」唯一标识 readJsonlTolerant 本就视作
未写入的半行,物理丢弃安全。

补充崩溃尾态回归单测:截断而非粘连、空文件、全文件无换行、多字节 UTF-8 中途
截断、完整 JSON 缺换行,以及崩溃后连追两条整表仍可读的缺陷复现。
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid input: expected array, received object at "reviews.path_filters"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

appendLineSync now repairs unterminated JSONL tails before appending records. New tests cover crash residue, UTF-8 truncation, missing final newlines, fresh files, and repeated recovery appends.

Changes

JSONL append recovery

Layer / File(s) Summary
Repair incomplete JSONL tails
packages/boss-cli/src/infrastructure/fs.ts
appendLineSync opens the file for read/write append access, truncates incomplete trailing data, appends the new record, and fsyncs the descriptor. Helpers scan backward to locate the last newline.
Validate crash recovery
test/infrastructure/append-after-crash.defect.test.ts, test/infrastructure/jsonl-atomicity.test.ts
Tests cover partial files, UTF-8 truncation, missing final newlines, fresh files, and repeated appends that preserve readable records.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • echoVic/boss-skill#28: Adds the earlier appendLineSync JSONL atomicity behavior extended by this recovery change.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the crash-residue truncation fix and its purpose of preserving event-stream readability.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-append-crash-residue

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/infrastructure/append-after-crash.defect.test.ts`:
- Around line 24-45: Update the documentation comment for the appendLineSync
regression tests to remove claims that the implementation currently fails or
leaves appended events corrupt. Describe that the tests verify recovery from an
unterminated trailing line and that appendLineSync repairs the missing newline
before appending, with the tests expected to pass.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4470d13a-1fca-4881-a666-55dc8616050f

📥 Commits

Reviewing files that changed from the base of the PR and between 878c127 and ed05872.

📒 Files selected for processing (3)
  • packages/boss-cli/src/infrastructure/fs.ts
  • test/infrastructure/append-after-crash.defect.test.ts
  • test/infrastructure/jsonl-atomicity.test.ts

Comment on lines +24 to +45
/**
* DEFECT PROOF — appendLineSync does not repair a missing trailing newline.
*
* The documented atomicity contract (infrastructure/fs.ts) states that a
* process killed mid-append leaves "at most one incomplete trailing line"
* which is "recoverable (treated as if the event was never written)". The
* recovery path (state.ts#appendEvent) derives the next id from the parseable
* records and appends the new event with appendLineSync.
*
* A real crash mid-append leaves the file WITHOUT a trailing newline. Because
* appendLineSync only ensures the line it writes ends with '\n' (it never
* checks whether the existing file content is newline-terminated), the new
* event is glued onto the truncated tail, producing a single corrupt line.
* The freshly-written event is then unreadable, and once one more event is
* appended the corrupt line is no longer the tail — readJsonlTolerant THROWS,
* making the entire events.jsonl (the event-sourcing source of truth)
* permanently unreadable. This is the exact failure mode the design claims to
* prevent.
*
* These tests are EXPECTED TO FAIL against the current implementation; they
* pin the correct behavior a fix must satisfy (appendLineSync should ensure
* the previous content is newline-terminated before appending).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the obsolete defect-status comment.

appendLineSync now repairs an unterminated tail. Lines 25 and 33-45 describe the old behavior as current and state that these tests should fail. Update the comment to describe regression coverage and the expected passing behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/infrastructure/append-after-crash.defect.test.ts` around lines 24 - 45,
Update the documentation comment for the appendLineSync regression tests to
remove claims that the implementation currently fails or leaves appended events
corrupt. Describe that the tests verify recovery from an unterminated trailing
line and that appendLineSync repairs the missing newline before appending, with
the tests expected to pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant