Repository navigation
Description
Summary
FileIntentStore (pkgs/id/src/currency_poc/intent.rs) has two inconsistent notions of where an intent record lives:
get(conversion_id)reads only the canonical pathpath_for(id)={id:032x}.json(intent.rs:175-177, 199-209).unresolved()ignores file names entirely: it scans every*.jsonentry in the directory and returns whatever parses as anIntentRecord(intent.rs:211-256).
Nothing ties a listed record to its canonical file name, and nothing deduplicates by conversion_id. Two verified consequences:
- Duplicate listing / duplicate reconciliation. Every copy of a record file in the same directory (an operator backup copy, a copy under another name) is listed again. Recovery then reconciles the same conversion once per copy and emits duplicate
(conversion_id, outcome)pairs. - A misnamed-only record is invisible to the identity guard. A record that exists only under a non-canonical name (older naming scheme, operator rename) is seen by
unresolved()butget(id)returnsNonefor it.ConversionExecutor::prepare's identity guard — "a conversion ID is the identity of ONE conversion: never replace its persisted intent" (executor.rs:238-258) — is built onget(), so it happily persists a second, conflicting intent for the same conversion. The store then holds two records for one conversion with different legs, andunresolved()returns both.
This is the identity counterpart of #4125 (closed): that fix made the listing skip unparseable entries; parseable-but-misnamed or duplicated entries still pass through unchecked. It is also adjacent to #4133 (closed), which made reconcile distrust the leg vector; the record-identity layer has the same trust problem one level up.
Evidence (verified this run, main 874ca86)
Laptop probe ~/git/fleet/probe-2026-11-02-laneB-intentname/ — verbatim copies of currency_poc/{intent,ledger,recovery}.rs from origin/main (md5 intent d276f6cc, ledger 093c818d, recovery 8d40cf83, all matching the sandbox clone at 874ca86); cargo test 2/2 pass:
- PROBE1:
putrecord for conversion 7, then copy its file tobackup.jsonand999.jsonin the same directory →unresolved()returns 3 records for conversion 7;reconcile(.., Abandon)returns 3 outcomes[(7, Abandoned), (7, Abandoned), (7, Abandoned)]. - PROBE2: write a Recorded record for conversion 9 only as
intent-9.json→get(9)returnsNonewhileunresolved()returns[9]; afterputof a conflicting record for conversion 9 (different leg ID),unresolved()returns 2 records for conversion 9.
Why it matters
The intent store is the write-ahead recovery surface for conversions: its whole contract is one durable record per conversion that the scanner can trust. Duplicate listings make recovery reports (and any consumer counting outcomes) double-count a conversion, and repeated puts from duplicate entries rewrite the canonical file once per copy. The misnamed case is worse: it silently defeats prepare()'s never-replace guard, the exact mechanism that prevents a retry from erasing/conflicting with the recovery record of an earlier, possibly-posted submission — two divergent leg sets for one conversion ID is precisely the state the recovery scanner cannot reconcile safely.
Acceptance criteria
-
FileIntentStore::unresolved()returns at most one record perconversion_id. - A parseable record whose file name is not the canonical
{conversion_id:032x}.jsonfor theconversion_idit carries is handled explicitly: either it is reported throughunreadable_entries()(the id(currency_poc): one corrupt or foreign .json file in the FileIntentStore directory aborts recovery for every pending conversion #4125 skip-and-report channel, with a reason naming the mismatch) and not listed, or the store's identity contract is changed soget()andunresolved()agree — with the chosen semantics documented on theIntentStoretrait. - Regression tests: (a) duplicate copies of one record file in the directory list/reconcile once; (b) a misnamed-only record cannot coexist with a conflicting
putfor the same conversion — after the fix, prepare()'s guard sees every record the scanner sees. - Existing currency_poc tests stay green, including the id(currency_poc): one corrupt or foreign .json file in the FileIntentStore directory aborts recovery for every pending conversion #4125 unreadable-entry tests.
Activity
dezren39 commentedon Nov 2, 2026
🤖 Agent note · filed
dezren39 commentedon Nov 2, 2026
🤖 Agent note · picked up
38 remaining items
fix(id): scan casing aliases of intent files and index canonical occu…
Merge pull request #4622 from developing-today/queue/6487-4578-filein…
dezren39 commentedon Nov 3, 2026
🤖 Agent note · done
Resumed at the Codex gate: the re-review round on head 6002db2 had left three undispositioned findings, all fixed in bc0a779 and replied to on their threads — P1 4449053752 (uppercase-extension aliases were excluded from the scan: extension match is now case-insensitive, with a regression test), P2 4449053759 (unreadable canonical matching is now alias-aware via the new
IntentScan::unreadable_at, with a regression test), P2 4449053762 (occupant resolution now goes through a per-scan index —by_path/by_file_idbuilt once — instead of pairwisesame_file()calls). Laptop verification at bc0a779:cargo test --lib currency_pocin pkgs/id (world) — 35 passed, including the two new regressions. Codex re-review on bc0a779 completed with no findings. PR #4622 MERGED (merge commit 1d4a448); issue #4578 closed by the merge.