Skip to content

id: a torn final line in petnames.jsonl bricks the whole world — Petnames::open refuses the crash-torn tail that FileJournal and Outbox drop and truncate #1499

Closed

Description

@dezren39

id: a torn final line in petnames.jsonl bricks the whole world — Petnames::open refuses a crash-torn tail that FileJournal and Outbox both drop and truncate

Summary

Petnames::open (pkgs/id/src/petname.rs:56-70) replays petnames.jsonl with text.lines() and fails the entire open on any malformed line — including a torn final line left by a crash mid-append. Petnames::commit (petname.rs:186-199) appends one JSON line + sync_data() per change, so a crash or power loss during write_all leaves exactly that: a partial JSON record with no trailing newline at the tail of the file.

That failure is not contained to petnames. open_world (pkgs/id/src/world_store.rs:389-392) propagates it with ??, so the whole world fails to open — on every restart, forever, until someone hand-edits the journal. There is no recovery path: appends can't happen because open never succeeds.

The two sibling JSONL stores loaded by the same open_world already handle this exact case, deliberately:

  • FileJournal (world_store.rs:52-117): "A torn final line, left by a crash mid-append, is truncated away" — unterminated last line is dropped, the file is truncated to the last good byte, and mid-file corruption is still refused. Test: torn_final_entry_is_dropped_but_mid_file_corruption_is_refused (world_store.rs:1044).
  • Outbox (envelope_outbox.rs:141-161): iterates split_inclusive('\n'); an unparseable line that does not end with '\n' ends the replay, and the file is truncated to the kept prefix. Test: a_torn_last_line_is_dropped_and_not_mixed_into_the_next_record (envelope_outbox.rs:523).

Petnames (and Directory::replay, directory.rs:517-535 — same strict lines() + ? shape, also propagated by open_world at world_store.rs:385-387) never got the same treatment.

Evidence (verified this run, main 67e0def)

Probe crate on the laptop (~/git/fleet/probe-2026-10-27-laneB-petname) compiling the real petname.rs (sha256 ff6cba651a19ad5814a2540df12433d1444b44ff5fb258a8478cb891be88723e, identical to the checkout) against a stub directory::Refusal:

  • Control — clean journal (two records written through Petnames::set itself): open OK, both names replay.
  • Torn tail — the same bytes plus a partial record {"op":"set","owner":"00ff","account":"cc33","name":"Car (no trailing newline): open FAILED: petname journal line 3 is malformed: EOF while parsing a string at line 1 column 55.
  • Control — malformed line in the middle (newline-terminated, valid record after it): open FAILED, as it should — mid-file corruption must stay refused.

Log: goal bookkeeping hidden_files/probe-2026-10-27-laneB-petname/out.log.

Why it matters

Petnames are a small, private, append-only store; losing the ability to open them should never take down an entire world. Today one unlucky crash during a rename makes the world permanently unopenable without manual file surgery — while the world's main journal and its outbox, loaded seconds earlier in the same function, already survive the identical failure by design.

Fix direction

Bring Petnames::open in line with the house pattern (Outbox is the closest model — same one-record-per-line JSONL, same append commit):

  • Replay with split_inclusive('\n'); a final line that is unterminated and unparseable is a torn append: drop it and truncate the file to the kept prefix (so the next append is not glued onto the partial record — the failure the Outbox test name calls out).
  • Any other malformed line (terminated, or mid-file) stays a hard error, exactly as today.
  • Consider the same treatment for Directory::replay in the same pass — it is the other strict replay on the open_world path; if it is deliberately out of scope, say so on the issue.

Acceptance criteria

  • A petnames.jsonl whose final line is a partial JSON record (no trailing newline) opens successfully with all complete records replayed; the torn bytes are truncated from the file.
  • After such a recovery, a subsequent set/remove appends a clean record that replays correctly on the next open (no gluing onto the torn bytes).
  • A malformed line anywhere else (newline-terminated, or followed by more records) still fails the open with the line-numbered error.
  • The existing test a_malformed_line_fails_the_open is updated/narrowed to the mid-file case, and a new test mirrors torn_final_entry_is_dropped_but_mid_file_corruption_is_refused for petnames.
  • cargo test for the petname module passes; behaviour of clean journals is unchanged.

Activity

  1. developing-today commented on Oct 27, 2026

    @developing-today
    Contributor
  2. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/highImportant and time-sensitive
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Oct 27, 2026
  3. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (lane B, new-issue-creator-code-b)
    • Session / ID: cron new-issue-creator-code-b, run 2026-10-27 13:50 CDT
    • Found via: filed this run
    • Task: Triage the petnames.jsonl torn-tail defect: Petnames::open (petname.rs:56-70) fails on ANY malformed line, so a crash mid-append (commit writes one JSON line + sync_data, petname.rs:186-199) leaves a partial final record that makes open_world (world_store.rs:389-392, propagated with ??) fail on every restart — the whole world is bricked until the file is hand-edited. Verified on main 67e0def with a probe crate on the laptop compiling the REAL petname.rs (sha256 ff6cba65…, identical both sides): clean journal opens; torn tail -> "petname journal line 3 is malformed: EOF while parsing a string"; mid-file corruption correctly still refused. FileJournal (world_store.rs:52-117) and Outbox (envelope_outbox.rs:141-161) already drop + truncate exactly this torn tail, with tests — petnames (and Directory::replay) never got the pattern.
    • Expectation: effort/s — mirror the Outbox replay (split_inclusive, drop unterminated unparseable tail, truncate to kept prefix so the next append is not glued on), keep mid-file corruption a hard error, add the torn-tail test mirroring world_store.rs:1044, and rule on whether Directory::replay is in the same pass.
    • Links: queue packet packet-1499.json (repo-health-code-pilot, triage)
  4. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/queuedHas a queued task in the Supabase agent queue
    on Oct 28, 2026
  5. 53 remaining items

  6. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Agent note — parked (main tier still running; red legs re-verified as pre-existing)

    • Task: 1763 (lane D, second resume this shift), PR fix(id): drop + truncate a torn final petnames.jsonl line on open (#1499) #2918 @ a9e6d01
    • Fresh state: all basic-tier test shards pass; Codex clean at head. full (main tier): nix flake check still in progress (~1h). Red basic legs are the known pre-existing set.
    • New verification this shift: cargo fmt --check over pkgs/id on the PR worktree reports diffs in many files the PR never touched (peers.rs, tag.rs, event_store/sync.rs, groups/tests.rs, web/mod.rs, world_ssh.rs, world_store.rs, tests/*) and none attributable to petname.rs — the fmt-check failures are repo-wide pre-existing debt (rustfmt drift), not branch-owned. test-web-unit red matches the search-panel failure verified failing on clean main this shift.
    • A laptop cargo test --lib petname verification was started but did not finish within the shift window (pkgs/id compiles are slow under fleet load); the petname tests themselves pass in CI test shards 0-3.
    • Resume: when the main tier settles, merge PR 2918 (closes this issue) and complete task 1763 — no code change is owed.
  7. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Oct 31, 2026
  8. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Agent note — pickup (resume, merge shift)

  9. added a commit that references this issue on Oct 31, 2026
  10. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Agent note — done (merged)

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

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/highImportant and time-sensitivesource/creatorFiled by the new-issue-creator jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions