Skip to content

Fix P0 correctness issues in jail enforcement, merge, and mutation log#6

Merged
zac merged 1 commit into
mainfrom
fix/p0-correctness
Jul 9, 2026
Merged

Fix P0 correctness issues in jail enforcement, merge, and mutation log#6
zac merged 1 commit into
mainfrom
fix/p0-correctness

Conversation

@zac

@zac zac commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

Four correctness fixes surfaced by a review of the filesystem and checkpoint layers, each with regression tests.

ReadWriteFilesystem: close a jail escape through destination symlinks

Write paths only containment-checked the parent directory of the destination. A pre-existing symlink at the final component pointing outside the configured root was therefore followed by the append path (FileHandle(forWritingTo:)), writing outside the jail. Destinations whose final component is a symlink are now resolved and containment-checked; symlinks that stay inside the root keep working.

Workspace.merge: stop silently discarding uncheckpointed parent edits

The merge guard compared checkpoint heads only, and tracked writes never move the head — so a parent that edited files after branch() without checkpointing passed the guard and had those edits clobbered by the branch snapshot restore. branch() now records the parent's mutation cursor, and merge() throws the new WorkspaceError.mergeUncheckpointedChanges when the parent has tracked-but-uncheckpointed changes. Writes made before the branch still merge fine.

FileCheckpointStore: survive a torn mutations.jsonl tail

A crash mid-append left a partial trailing line that made every subsequent log read throw, poisoning the whole history. Reads now skip a torn tail (an undecodable final line with no trailing newline), and the next append truncates it before writing. Corruption anywhere before the final line still surfaces as an error.

FileCheckpointStore: O(1) sequence derivation on append

appendMutation re-read the entire log to compute max(sequence) + 1 on every append — O(n²) across a session. It now reads only the log's final record (backwards chunked scan, with a full-scan fallback for unusual tails). Appends still happen under the existing flock sidecar lock, so cross-process monotonicity is unchanged.

Tests

Six new regression tests:

  • append and overwrite through an escaping symlink are rejected, and the outside file is untouched
  • append through a symlink inside the root still works
  • merge refuses uncheckpointed parent changes (and the parent's file survives)
  • merge allows parent writes made before the branch
  • a torn trailing mutation line is skipped on read and repaired by the next append, with the sequence continuing correctly
  • corruption before the final line still surfaces as an error

Full suite: 124 tests in 9 suites passed on macOS CI against this same content.

🤖 Generated with Claude Code

https://claude.ai/code/session_01G29SXqzHqDycqXGa4dRr4x


Generated by Claude Code

- ReadWriteFilesystem: reject writes whose destination's final component
  is a symlink resolving outside the root. The parent-only containment
  check let appends follow a pre-existing symlink out of the jail.
- Workspace.merge: record the parent's mutation sequence at branch time
  and refuse to merge over tracked-but-uncheckpointed parent edits with
  the new WorkspaceError.mergeUncheckpointedChanges, instead of silently
  clobbering them. The checkpoint-head comparison alone cannot see
  writes that never advanced the head.
- FileCheckpointStore: tolerate a torn trailing mutations.jsonl line
  left by a crashed append (skip on read, truncate before the next
  append) instead of failing every subsequent load. Mid-file corruption
  still surfaces as an error.
- FileCheckpointStore: derive the next mutation sequence from the log's
  final record rather than re-reading the entire log on every append,
  which was O(n^2) across a session.

Adds regression tests for all four fixes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G29SXqzHqDycqXGa4dRr4x
@zac zac merged commit eb3b506 into main Jul 9, 2026
1 check passed
@zac zac deleted the fix/p0-correctness branch July 9, 2026 03:21
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.

2 participants