Skip to content

Instantly share code, notes, and snippets.

@jesserobbins
Created May 1, 2026 14:41
Show Gist options
  • Select an option

  • Save jesserobbins/b07b1fa564fe66dad084907e1becf48d to your computer and use it in GitHub Desktop.

Select an option

Save jesserobbins/b07b1fa564fe66dad084907e1becf48d to your computer and use it in GitHub Desktop.
msgvault#301 ↔ #304 merge assessment (by Claude Opus 4.7)

PR #301 ↔ #304 merge assessment

Evaluation of the changes required to wesm/msgvault#301 ("feat: expose HTML email content and inline MIME parts via API") once wesm/msgvault#304 (identities-collections-dedup) is merged into upstream main.

Conflict surface (mechanical)

Zero textual conflicts. Every overlapping file edits a different function or different line range:

File PR301 hunks #304 hunks Conflict?
internal/query/duckdb.go @@ -1448,6 @@ (new GetMessageRaw between GetAttachment and existing methods) @@ -297 -653 -857 -1118 -1469 -1558 -1667 -2309 -2466 @@ No — PR301's insertion sits between #304's 1118 and 1469 hunks
internal/query/sqlite.go @@ -837,6 @@ (new GetMessageRaw after GetAttachment) @@ -189 -263 -885 -1003 -1141 -1290 -1431 @@ No — PR301's insertion is before #304's 885 hunk; clean rebase
internal/query/engine.go @@ -27 @@ (interface addition) untouched on #304 No
internal/query/querytest/mock_engine.go @@ -36 -121 @@ untouched on #304 No
internal/query/shared.go @@ -183,6 @@ (new getMessageRawShared helper) untouched on #304 No
internal/query/sqlite_crud_test.go @@ -1 -1131 @@ untouched on #304 No
internal/remote/engine.go @@ -569,6 @@ (new stub method) @@ -589 @@ No — different functions, 20 lines apart
internal/api/handlers.go, handlers_test.go, server.go new endpoints, new test cases untouched on #304 No

After rebase, line numbers will adjust automatically; no <<<<<<< markers expected.

Semantic interactions to verify

1. GetMessageRaw and the messages/message_raw split (already correct)

PR301's GetMessageRaw reads from message_raw via direct PK lookup (WHERE message_id = ? in getMessageRawShared). This is exactly the pattern #304's contributor guidelines mandate for body/raw access ("Only access message_bodies via direct PK lookup … when displaying a single message detail view"). PR301 is compliant by construction. No action needed.

2. Engine interface addition (already correct)

PR301 adds GetMessageRaw(ctx context.Context, id int64) ([]byte, error) to the query.Engine interface and provides implementations in sqlite, duckdb (delegates to sqlite), remote/engine (returns ErrNotSupported), and querytest/mock_engine. #304 doesn't add or modify any methods on this interface — it only changes MessageFilter/AggregateOptions/StatsOptions struct shapes. The two changes are layered, not stacked. No action needed.

3. Inline MIME endpoint and dedup-soft-deleted rows (REQUIRED VERIFICATION)

PR301 adds GET /messages/{id}/inline/{cid} which:

  1. Loads message detail (engine.GetMessageDetail or store fallback).
  2. Calls engine.GetMessageRaw for the same ID.
  3. Parses MIME, finds the part with matching CID, returns the decoded bytes.

#304 adds deleted_at IS NULL to Search, buildSearchQueryParts, buildSearchConditions, etc. — but these are search/list paths. GetMessageDetail and GetMessageRaw are single-message-by-PK lookups; #304 does not filter these by deleted_at. This means a client can still fetch a dedup-hidden message detail and its inline images by direct ID — which is the right behavior (the message hasn't been hard-deleted, just hidden from search). No action needed for PR301, but worth confirming the test suite doesn't pin "deleted message detail returns 404" behavior.

4. Service-account / multi-source filter (no interaction)

PR301's GET /messages/{id} and GET /messages/{id}/inline/{cid} are PK-keyed and ignore source/account scope. #304's appendSourceFilter only applies to list/search paths. No interaction.

5. Live-message contract for HTML body (informational)

When the engine path is available, GET /messages/{id} returns body and body_html from the engine's MessageDetail. The MessageDetail struct already carries BodyText and BodyHTML as separate fields on main, so PR301 only changes the API serialization, not the underlying read query. #304 doesn't redefine MessageDetail. No action needed.

6. Inline-image security gates (no interaction)

PR301's content-type filter (rejects SVG/XHTML, applies Cache-Control: private, requires IsInline flag) is purely API-layer logic that doesn't intersect with #304's store/query/scope changes. No action needed.

Resolution recipe (when #301 rebases onto post-#304 main)

  1. git rebase origin/main — should apply cleanly with zero textual conflicts.
  2. Run make test. PR301's existing test coverage (CRUD round-trip on GetMessageRaw, inline endpoint with PNG/SVG/XHTML/CID-not-found/no-engine/message-not-found, engine-path body_html) should still pass.
  3. (Optional) Add a coverage test: confirm GET /messages/{id} and GET /messages/{id}/inline/{cid} both still work for dedup-soft-deleted messages (rows where deleted_at IS NOT NULL). This is a property of the PK-lookup design, not something PR301 needs to change, but worth pinning so it doesn't regress later.
  4. (Optional, not required for merge) When #304 is final, check MessageDetail for any new fields (e.g. DeletedAt, DeletedFromSourceAt) and decide whether to surface them in the API JSON response. Default behavior — fields exist on the struct but aren't serialized — is acceptable.

Verdict

Effectively orthogonal merge. PR301 operates on a different axis from #304: it adds a single-message HTML/inline read path, while #304 reshapes scope, identity, and dedup of the message corpus. They share three files (duckdb.go, sqlite.go, remote/engine.go) but in entirely different functions, and they share zero semantic surface.

Total fix surface after rebase: 0 lines required. Optional coverage additions are recommended but not blocking. This is the cleanest merge in the queue alongside PR284 (which has zero textual conflicts but does need ~15 lines of identity wiring).

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