feat(cli): add EML import, sender repair, conversation search, and GC - #725
Conversation
roborev: Combined Review (
|
c2eb844 to
768fc23
Compare
roborev: Combined Review (
|
768fc23 to
5188f61
Compare
roborev: Combined Review (
|
5188f61 to
4cd15e8
Compare
roborev: Combined Review (
|
4cd15e8 to
b3d9c52
Compare
roborev: Combined Review (
|
651c5a8 to
63fd9d4
Compare
roborev: Combined Review (
|
63fd9d4 to
d48ff46
Compare
roborev: Combined Review (
|
d48ff46 to
7aa801b
Compare
roborev: Combined Review (
|
0846005 to
6bce506
Compare
roborev: Combined Review (
|
6bce506 to
a3b260a
Compare
roborev: Combined Review (
|
a3b260a to
6afd3f4
Compare
roborev: Combined Review (
|
6afd3f4 to
d79b942
Compare
roborev: Combined Review (
|
d79b942 to
9c032f2
Compare
roborev: Combined Review (
|
9c032f2 to
962ad28
Compare
roborev: Combined Review (
|
962ad28 to
17b8e15
Compare
17b8e15 to
82d75e5
Compare
roborev: Combined Review (
|
82d75e5 to
f7025a1
Compare
roborev: Combined Review (
|
f7025a1 to
cf8b262
Compare
roborev: Combined Review (
|
cf8b262 to
da1d294
Compare
roborev: Combined Review (
|
da1d294 to
09f47eb
Compare
roborev: Combined Review (
|
09f47eb to
0e77fa3
Compare
roborev: Combined Review (
|
|
looking |
repair-senders planned repairs its own apply step would reject. The plan only checked that a recovered From address was non-empty, while the apply path also requires a single valid bare address. A salvaged address like "x..y@example.test" was reported as repairable in the dry run, then failed during --apply and made the command exit non-zero. Planning now uses the same validation rule as apply (store.ValidateRepairSender), so such candidates are counted as unresolved up front, and undecodable raw MIME is logged instead of silently folded into the unresolved count. import-eml counted a mailbox as imported even when the user cancelled mid-mailbox, which overstated "Mailboxes: N/M" in the summary. The counter now increments only for mailboxes whose files were all processed. Test fixtures used a real company name and domain (noreply@electrolux.com). The project requires obviously synthetic test data, so the fixtures now use fridgeco.example. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
Reviewed the full diff against the five linked issues (#158, #175, #192, #200, #214). Findings: 1 P2, 3 P3, 3 P4 — 4 fixed in 5d79572, 3 dismissed/informational. Fixed
Dismissed / informational
Verification
Notes for the record: |
roborev: Combined Review (
|
repair-dates, repair-encoding, and remove-account call buildCacheLocked with the configured database DSN. When [data].database_url is a file: URI, those rebuilds failed: the builder appends ?mode=ro to a DSN that may already carry query parameters, hands the literal URI to the DuckDB sqlite attach, and derives its staging directory with filepath.Dir on the URI string. The build-cache entry points were normalized earlier, but these three callers bypassed that layer. Normalization now lives inside buildCacheLocked itself, so every current and future caller gets a plain filesystem path, and the redundant copy in buildCacheImpl is removed. The new regression test fails without the normalization (DuckDB cannot attach the URI) and passes with it. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
Verified and fixed the roborev medium finding in 1bab95e. Confirmed reachable: Fix: normalization now lives inside |
roborev: Combined Review (
|
repair-senders installed only the first recovered From address, so a repaired multi-From message lost the other addresses from its envelope snapshot — unlike a normally ingested message, which keeps every From row. The repair now persists the complete recovered set, with the first address (still strictly validated) as sender_id and the remaining rows written under the ingest path's lenient rules so a repaired message matches an ingested one. A new test proves both rows survive and fails against the single-address behavior. import-eml could return from a fatal store error after creating its sync run without marking it failed, leaving the run stuck as 'running'. Fatal paths now record FailSync the way the mbox and pst importers do; intentional cancellation still keeps 'running' so the checkpoint stays resumable. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
Verified and fixed both roborev medium findings in e98496a. Multi-From envelope loss ( Stuck
|
roborev: Combined Review (
|
gc exists to reclaim disk space (issue kenn-io#214), but its message delete only cascaded away the attachment database rows. The loose blob files those rows were the last reference to stayed on disk forever: the daemon's post-command repack reclaims packed dead bytes only, and is a no-op entirely when [data].loose_attachments disables packing. After a successful delete and compaction, gc now runs the same unreferenced-loose-blob sweep purge-excluded-media uses, reports the removed count, and surfaces sweep failures in the command error while still reporting that deletion and compaction succeeded. The whole gc subprocess already runs under the daemon's attachment mutation lease, so the sweep needs no extra locking. Blobs still referenced by a surviving message are untouched; the extended test proves both the sweep and the shared-blob case. Generated with Claude Code (claude-fable-5) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
Verified and fixed the roborev medium finding in 4d003fa. Attachment blob leak after gc — confirmed. The message delete cascades away Fix: after successful delete + VACUUM, gc runs the same
|
roborev: Combined Review (
|
gc reclaimed loose attachment blobs only after it deleted at least one source-deleted message. If the message delete succeeded but the blob removal then failed, a rerun reported "Nothing to purge" and returned before the sweep, so the orphaned blob files stayed on disk with no command able to reclaim them. The sweep now runs on every confirmed gc run. When there are no rows to purge, gc still asks for confirmation, skips the backup and compaction that only protect and follow a row delete, sweeps orphaned loose blobs, and reports the removed count. The cache-rebuild guidance is printed only when rows were actually deleted. Generated with Claude Code (claude-fable-5-1) Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
roborev: Combined Review (
|
What changed
msgvault list-accounts(table and JSON) with CLI tests; the column itself already existed.msgvault import-emlsupport for MailMate-style mailbox trees, with mailbox labels and raw-byte deduplication.conversation_id:filtering across lexical search, SQLite and DuckDB analytics, drill-down queries, and vector/hybrid search.msgvault repair-senderscommand that can restore missing sender data from bounded archived MIME headers.msgvault gcfor source-deleted rows, with confirmation, a backup by default, optimistic plan validation, reply-pointer cleanup, and compaction. Dedup-hidden rows remain intact, and PostgreSQL is refused before mutation.Why
Several older archive workflows still required manual database work or could not be done at all: standalone EML trees were not importable, missing senders had no safe repair path, conversation IDs were not searchable, account listings omitted source type, and retained source-deleted rows could not be purged and compacted from the CLI. This closes those gaps while keeping destructive maintenance explicit and recoverable by default.
Usage
msgvault list-accounts msgvault import-eml /path/to/Mail --identifier me@example.com msgvault search 'conversation_id:123' msgvault repair-senders msgvault repair-senders --apply msgvault gcCloses #158
Closes #175
Closes #192
Closes #200
Closes #214