Repository navigation
Description
Problem
FileIntentStore::unresolved() (pkgs/id/src/currency_poc/intent.rs:165-181) aborts on the first file it cannot read or parse:
let bytes = fs::read(&path)?;
let record: IntentRecord = serde_json::from_slice(&bytes)
.map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;and recovery::reconcile propagates that error before reconciling anything (recovery.rs:54, store.unresolved().context("listing unresolved intents")?). One stray file in the intent directory — a truncated or hand-edited record, a file written by an older/newer record schema, any unrelated *.json file someone dropped in the directory, or a subdirectory named *.json (fs::read fails on it) — therefore blocks recovery of every pending conversion, indefinitely, with a single opaque listing error. The scanner's own design already rejects this failure mode for resubmissions ("One rejected resubmission must not abort recovery of every later record", recovery.rs) but not for the listing step that gates the whole scan.
Note the store's writes are atomic (tmp + rename + fsync, intent.rs put), so this is not about torn writes from this implementation — it is about the directory being a long-lived recovery surface that must survive schema evolution, operator handling, and foreign files.
Evidence (verified this run, laptop probe, main caacf3a)
Probe crate with verbatim copies of the currency_poc modules (md5 intent.rs 4b413d6505f423428eabe27ba5e0b753, recovery.rs f4885f69754643cb92e946aaf33ff758, matching main caacf3a):
- Control directory (one valid
Recordedintent, conversion 777):reconcile(Resubmit)returns[(777, Resubmitted)], 3 legs posted. - Same valid record plus one file
deadbeef.jsoncontaining{not json:unresolved()returnsErr("data did not match any variant of untagged enum AdjacentValue @ line 1 column 12.."),reconcilereturns the same error, 0 legs posted, and the valid record is stillRecorded— total recovery blockage from one bad file.
Why it matters
The recovery scanner is the component that runs precisely when something has already gone wrong (a crash mid-conversion, #2289). A design where one damaged record silently freezes reconciliation of all the healthy ones turns a single-record incident into a full stop, and the operator gets no list of what was skipped — the valid records are not even reported.
Acceptance criteria
-
reconcileprocesses every readable unresolved record even when other files in the store directory are unreadable or unparseable; the bad entries are surfaced (returned in the outcome list under a distinct outcome, logged, or collected into the error report) rather than silently dropped or fatal. - A subdirectory named
*.jsonand a non-JSON-content*.jsonfile are both covered by tests. - The valid-record control scenario (resubmission posts its legs) still passes with a corrupt sibling file present, and the corrupt entry remains identifiable for an operator afterwards (not deleted by the scanner).
- All existing currency_poc tests pass unchanged; the
IntentStore::unresolvedtrait contract documents whatever error/skip semantics are chosen so the in-memory store and future stores agree.
Related: #2289 (the PoC this store belongs to).
Activity
dezren39 commentedon Nov 2, 2026
🤖 Agent note · filed
new-issue-creator-code-a)deadbeef.jsoncontaining{not json→ reconcile errors, 0 legs posted, valid record still Recorded; control without the bad file resubmits cleanly.~/git/fleet/probe-2026-11-01-laneA-currencypoc/(laptop); queue packet enqueued this run (repo-health-code-pilot).15 remaining items
dezren39 commentedon Nov 2, 2026
🤖 Agent note · picked up
Merge pull request #4527 from developing-today/queue/6324-4125-intent…
dezren39 commentedon Nov 2, 2026
🤖 Agent note · done
cargo test --lib currency_poc19/19 on branch head and 22/22 on branch+current-main combination (incl. both new corrupt-sibling and *.json-directory tests and main's id(currency_poc): InMemoryLedger posts an unterminated linked chain — a batch ending with linked=true commits instead of being rejected #4121 tests), 0 failed; diff limited to intent.rs/recovery.rs/tests.rs; bad entries are skipped, reported via unreadable_entries(), and left in place