You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds portable dated activity projection for message, chat, and meeting evidence, with source-aware dates and owner-relative direction.
Materializes contact recency, cadence, daily aggregates, and authored daily notes for bounded person and day queries.
Adds REST, OpenAPI, generated client, and msgvault activity build surfaces with SQLite and PostgreSQL parity.
Keeps projection work and contact state consistent across identity changes, message mutations, deletions, and cache builds.
Why
People and relationship views need reliable recency and cadence without scanning the full archive on every request. The archive also contains mixed date quality and source-specific ownership signals, so this needs one durable, explainable projection instead of separate query-time heuristics.
Usage
Run the incremental projection:
msgvault activity build
Force a complete archive backstop scan when needed:
msgvault activity build --backstop
The scheduled incremental job uses the [activity] configuration and runs at 17 * * * * by default.
Code review found two medium-severity correctness issues in activity projection freshness and counterpart attribution.
Medium
internal/store/activity_queries.go:386 — Sender attribution can be lost
Counterpart resolution ignores messages.sender_id when an imported chat message has no from recipient. This can omit the sender and misclassify outgoing activity as inbound. A mention recipient can also incorrectly suppress the conversation-member fallback.
Suggested fix: Add a synthetic from counterpart from sender_id when no explicit from row exists. Ensure non-addressing rows such as mentions do not disable membership fallback. Test sender-only and sender-plus-mention message shapes.
internal/store/activity_views.go:739 — Contact freshness ignores pending projection work
Freshness checks only consider dirty and identity revisions for existing rows, while missing rows inspect already-projected direct links. Queued message or recipient changes—and newly bound people—can therefore return stale or empty contact data with stale=false until projection runs.
Suggested fix: Include projection-queue and reconciled-epoch state in freshness decisions, or mark potentially affected people dirty when work is queued and clear that state only after acknowledgement.
The PR needs changes for two medium-severity activity projection consistency issues.
Medium
internal/store/activity_queries.go:421 — A lone from recipient incorrectly suppresses conversation-member fallback. Channel importers such as Slack store the sender in message_recipients while keeping the audience in conversation_participants, causing inbound channel messages to become observed and outbound messages to have no linked counterparts. Suppress fallback only when explicit addressee rows (to, cc, or bcc) exist, and add coverage for a channel message containing both a from row and conversation members.
internal/activity/projector.go:133 — max_direct_counterparts affects persisted evidence classification but is neither recorded nor reconciled. Changing it leaves existing events classified under the old policy while new events use the new value, producing inconsistent contact counts and cadence until a manual rebuild. Persist the threshold as projection metadata and trigger a complete reconciliation when it changes.
Changes need revision: three medium-severity attribution and classification inconsistencies were identified.
Medium
internal/store/messages.go:1016, internal/store/migrations.go:21 — The new envelope-authoritative attribution rule changes derived identity_is_from_me, but reconciliation remains gated by the already-applied message_attribution_provenance_v2 migration. Upgraded archives may retain legacy is_from_me=true values when a non-owner From envelope should make them false, causing message APIs to disagree with cache and activity derivations. Add a versioned migration that refreshes attribution with the new predicate and advances the account-identity/cache revision.
internal/store/messages.go:755 — upsertMessageSQL treats every matching email identifier as ownership, while the canonical fallback accepts email identifiers only when the participant has no primary email. UpsertMessage and granular recipient APIs can therefore persist attribution inconsistent with activity/cache results because they skip PersistMessage’s final canonical refresh. Mirror the primary-email guard in the upsert logic and ensure granular From-recipient replacement performs a final refreshMessageAttributionWith, or migrate callers to the atomic persistence path.
internal/activity/classify.go:47 — Classification considers only the first from counterpart a sender despite support for multiple distinct From participants. If a later From participant is the owner, the message may be classified as inbound based on participant-ID ordering, while additional authors receive addressed/co-presence roles. Track all From counterparts as senders, classify as outbound when any From counterpart is an owner, and classify as inbound only when ownership appears exclusively among non-From counterparts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
msgvault activity buildsurfaces with SQLite and PostgreSQL parity.Why
People and relationship views need reliable recency and cadence without scanning the full archive on every request. The archive also contains mixed date quality and source-specific ownership signals, so this needs one durable, explainable projection instead of separate query-time heuristics.
Usage
Run the incremental projection:
Force a complete archive backstop scan when needed:
The scheduled incremental job uses the
[activity]configuration and runs at17 * * * *by default.Refs #534