feat(search): scope message recency

This commit is contained in:
2026-06-07 07:22:42 +08:00
parent 4bde740c80
commit 112ae43955
8 changed files with 101 additions and 10 deletions
+7 -4
View File
@@ -49,11 +49,11 @@ Hermes task landing checklist:
## Current Baseline
- Current tracking checkpoint: 2026-06-07 P3.77 search inbox access parity, prepared as `feat(search): scope inbox search access`.
- Latest implementation checkpoint: this checkpoint, prepared as `feat(search): scope inbox search access`.
- Latest documentation/tooling checkpoint: this tracker update records Chatwoot SearchService inbox visibility parity for global/entity conversation and message search. No route artifacts change.
- Current tracking checkpoint: 2026-06-07 P3.78 search message recency parity, prepared as `feat(search): scope message recency`.
- Latest implementation checkpoint: this checkpoint, prepared as `feat(search): scope message recency`.
- Latest documentation/tooling checkpoint: this tracker update records Chatwoot SearchService `message_base_query` recency parity for global/entity message search. No route artifacts change.
- Plan landing status: complete for the current known Hermes plans and user-confirmed scope. Future work should update this file directly instead of opening a parallel tracker.
- Worktree status at this implementation checkpoint: reused SearchAPI conversation/message search now carries the current user into the search filter, resolves Chatwoot-style accessible inboxes from account membership and inbox assignments, scopes DB and Meilisearch conversation/message results to those inboxes unless the user is an administrator or assigned to every account inbox, and ignores inaccessible explicit `inbox_id` params by falling back to the accessible base relation. Existing P3.76 conversation filter query message-type parity, P3.75 automation message filter parity, P3.74 contact type default parity, P3.73 conversation filter permission parity, P3.72 account enabled-feature serialization parity, P3.71 contact referer filter parity, P5.8c contact import search indexing, P3.70 global contact search resolved-scope parity, and prior checkpoints remain implemented. Live API/browser/enterprise smoke still needs the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack.
- Worktree status at this implementation checkpoint: reused SearchAPI global/entity message search now mirrors Chatwoot `SearchService#message_base_query` by applying a message-only `created_at >= 3.months.ago` base scope before text/filter search. DB fallback applies the scope on `messages.created_at`, Meilisearch message filters emit the same `created_at_ts` lower bound, and explicit older `since` params cannot expand message search beyond the recent base. Existing P3.77 search inbox access parity, P3.76 conversation filter query message-type parity, P3.75 automation message filter parity, P3.74 contact type default parity, P3.73 conversation filter permission parity, P3.72 account enabled-feature serialization parity, P3.71 contact referer filter parity, P5.8c contact import search indexing, P3.70 global contact search resolved-scope parity, and prior checkpoints remain implemented. Live API/browser/enterprise smoke still needs the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack.
- Next executable implementation checkpoint: continue Phase 2/3 drift audit for the next reused-frontend mismatch, or run B12 live smoke when the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack is available. Re-run Phase 6 placeholder audit after future route/smoke changes.
- `go test ./...` passes when run outside the restricted socket sandbox for the latest implementation baseline; the latest docs/tooling checkpoint verified `scripts/parity_frontend_smoke.sh --check` with workspace-local temp/cache dirs after `/tmp` was full.
- Route dump succeeds with `967` registered routes after profile MFA route tracking.
@@ -156,6 +156,7 @@ This table is the shortest authoritative handoff view. If an older lower section
| Priority | Workstream | Current state | Next checkpoint | Commit close rule |
| --- | --- | --- | --- | --- |
| 0 | P3.78 search message recency parity | Implemented for reused global/entity message search: GoChat now applies Chatwoot `SearchService#message_base_query`'s recent-message base relation (`created_at >= 3.months.ago`) before message content search and filters. The DB fallback uses `messages.created_at`; Meilisearch message searches add the same `created_at_ts` lower bound, while contact/conversation/article search remains unaffected. | Keep in Review; reopen from B12 search smoke or fresh reference evidence for exact Rails `3.months.ago` calendar edge cases, `cap_until_time` future bounds, or advanced-search feature-gated date filter drift. | Focused search service, Meilisearch engine, and SearchRepo message-recency tests, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. |
| 0 | P3.77 search inbox access parity | Implemented for reused global/entity search: GoChat now mirrors Chatwoot `SearchService` by scoping conversation and message search to the current user's assigned inboxes unless the account user is an administrator or assigned to every inbox. The same access filter is applied to DB fallback and Meilisearch filters; inaccessible explicit `inbox_id` params are ignored so the base accessible relation is preserved like the reference. | Keep in Review; reopen from B12 search smoke or fresh reference evidence for custom-role search visibility, exact `advanced_search` feature-gated filters, or 90-day message base-query drift. | Focused search handler, search service, Meilisearch engine, and SearchRepo access/filter tests, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. |
| 0 | P3.76 conversation filter query message-type parity | Implemented for reused conversation filter/list query flows: `q` message search now filters against persisted GoChat string message types (`incoming`, `outgoing`) while preserving the Chatwoot reference restriction to incoming/outgoing messages and excluding activity/template matches. | Keep in Review; reopen from B12 conversation search/filter smoke or fresh reference evidence for duplicate join count behavior, `processed_message_content` query drift, or exact PostgreSQL ILIKE collation differences. | Focused conversation service query filter test, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. |
| 0 | P3.75 automation message filter parity | Implemented for reused automation `message_created` rules: GoChat now evaluates `message_type`, `private_note`, and `content` against the triggering message payload, including processed-content fallback and Chatwoot numeric message-type values. | Keep in Review; reopen from B12 automation smoke or fresh reference evidence for SQL-relation based message filtering beyond the triggering message, additional processed-content sources, or exact cross-group query precedence. | Focused automation condition validator/filter/listener tests, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. |
@@ -241,6 +242,7 @@ These rows are the executable development plan from this point forward. A checkp
| ID | Owner files | Reference files | Work to land | Exit gate |
| --- | --- | --- | --- | --- |
| P3.78 search message recency parity | `internal/search/search_filter.go`, `internal/search/search_service.go`, `internal/search/engine_meili.go`, `internal/repository/search_repo.go`, search service/engine/repository tests | `reference/chatwoot/app/services/search_service.rb#message_base_query`, `reference/chatwoot/app/services/search_service.rb#filter_messages_with_like`, `reference/chatwoot/app/services/search_service.rb#filter_messages_with_gin`, `reference/chatwoot/app/javascript/dashboard/api/search.js`, `reference/chatwoot/app/javascript/dashboard/api/specs/search.spec.js` | Global/entity message search now follows Chatwoot's recent-message base relation: the search service stamps message search filters with a `3.months.ago` lower bound, DB fallback applies it to `messages.created_at` before content/filter/date predicates, Meilisearch message searches apply `created_at_ts >= cutoff`, and older explicit `since` params cannot expand the message result set beyond the reference base query. | Review by `feat(search): scope message recency`; focused tests cover service cutoff stamping, DB exclusion of older messages, and Meilisearch message filter emission; full `go test ./...` and `git diff --check` must pass. No route artifacts change. |
| P3.77 search inbox access parity | `internal/handler/api/v1/search_handler.go`, `internal/search/search_filter.go`, `internal/search/search_service.go`, `internal/search/engine_meili.go`, `internal/repository/search_repo.go`, search handler/service/engine/repository tests | `reference/chatwoot/app/controllers/api/v1/accounts/search_controller.rb`, `reference/chatwoot/app/services/search_service.rb`, `reference/chatwoot/app/javascript/dashboard/api/search.js`, `reference/chatwoot/app/javascript/dashboard/api/specs/search.spec.js` | Global/entity search now follows Chatwoot `SearchService#message_base_query` and `#filter_conversations` inbox visibility boundaries for reused SearchAPI callers: current user id is captured from request context, account search access is resolved from `account_users`, `inbox_members`, and account inbox count, agents are restricted to assigned inboxes, administrators and agents assigned to all inboxes skip inbox filtering, DB and Meilisearch conversation/message searches apply the same accessible inbox set, and unauthorized explicit `inbox_id` filters are ignored rather than emptying the accessible base relation. | Review by `feat(search): scope inbox search access`; focused tests cover handler current-user propagation, service access injection, Meilisearch inbox filters and unauthorized inbox param fallback, SearchRepo access resolution for agents/admins/all-inbox agents, DB message search restriction, and inaccessible inbox param fallback; full `go test ./...` and `git diff --check` must pass. No route artifacts change. |
| P3.76 conversation filter query message-type parity | `internal/service/conversation_service.go`, `internal/service/conversation_service_test.go` | `reference/chatwoot/app/finders/conversation_finder.rb#filter_by_query`, `reference/chatwoot/app/models/message.rb`, `internal/model/enums.go`, `internal/repository/conversation_repo.go` | Conversation service `Filter` query search now follows the same incoming/outgoing restriction as Chatwoot `ConversationFinder#filter_by_query`, but uses GoChat's persisted message type strings instead of raw Chatwoot enum integers. The query also uses a dialect-aware content predicate so SQLite focused tests exercise the same matching path while PostgreSQL keeps `ILIKE`. | Review by `feat(conversations): align filter query message types`; focused service test proves `q` matches incoming message content, excludes activity-only conversations, and ignores non-matching content; full `go test ./...` and `git diff --check` must pass. No route artifacts change. |
| P3.75 automation message filter parity | `internal/automation/condition_filter.go`, `internal/automation/condition_validator.go`, `internal/automation/listener.go`, `internal/automation/model.go`, `internal/automation/condition_filter_test.go`, `internal/automation/condition_validator_test.go`, `internal/automation/listener_test.go` | `reference/chatwoot/app/services/automation_rules/conditions_filter_service.rb`, `reference/chatwoot/app/services/automation_rules/condition_validation_service.rb`, `reference/chatwoot/lib/filters/filter_keys.yml`, `reference/chatwoot/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js`, `reference/chatwoot/app/javascript/dashboard/helper/automationHelper.js` | Automation `message_created` conditions now evaluate the triggering message payload like the inspected reference message filters: `content` uses `processed_message_content` from content attributes when available and falls back to `messages.content`; `private_note` maps to `messages.private`; `message_type` accepts Chatwoot enum integers (`incoming=0`, `outgoing=1`, `activity=2`, `template=3`) and local strings. Validation now restricts message filter operators to the Chatwoot filter key contract. | Review by `feat(automation): align message filters`; focused matcher tests cover content/private/message-type matches, non-matches, missing message data, and OR message groups; validator tests cover supported/unsupported message operators; listener test proves `message_created` rules execute or skip from message filters. Full `go test ./...` and `git diff --check` must pass. No route artifacts change. |
@@ -2691,3 +2693,4 @@ Verification milestone gates:
- 2026-06-07: P3.75 automation message filter checkpoint prepared as `feat(automation): align message filters`; audited Chatwoot automation `ConditionsFilterService`, `ConditionValidationService`, `filter_keys.yml`, and reused automation builder constants/helper. GoChat `message_created` automation rules now evaluate `message_type`, `private_note`, and `content` from the triggering message payload, including Chatwoot numeric message type values, processed-content fallback, boolean private-note matching, content equality/containment operators, and validator rejection for unsupported message operators. Focused automation matcher/validator/listener tests passed; full `go test ./...` and `git diff --check` passed. No route artifacts change.
- 2026-06-07: P3.76 conversation filter query message-type checkpoint prepared as `feat(conversations): align filter query message types`; audited Chatwoot `ConversationFinder#filter_by_query`, message enum behavior, and GoChat persisted message type storage. GoChat conversation filter `q` searches now keep the Chatwoot incoming/outgoing-only restriction while comparing against local string message types instead of numeric enum values, so activity/template messages remain excluded and valid incoming/outgoing content is found. Focused conversation service query-filter test passed; full `go test ./...` and `git diff --check` passed. No route artifacts change.
- 2026-06-07: P3.77 search inbox access checkpoint prepared as `feat(search): scope inbox search access`; audited Chatwoot `SearchController`, `SearchService`, and reused dashboard SearchAPI callers. GoChat global/entity conversation and message search now propagates the current user, resolves accessible inboxes from account membership and inbox assignments, scopes DB fallback and Meilisearch filters for non-admin agents, skips the scope for administrators and all-inbox agents, and ignores inaccessible explicit `inbox_id` params by preserving the accessible base relation. Focused search handler/service/engine/repository tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change.
- 2026-06-07: P3.78 search message recency checkpoint prepared as `feat(search): scope message recency`; audited Chatwoot `SearchService#message_base_query`, message search SQL/GIN branches, and reused dashboard SearchAPI callers. GoChat global/entity message search now stamps message filters with the Chatwoot `3.months.ago` lower bound, applies it to DB fallback `messages.created_at`, emits the same `created_at_ts` lower bound for Meilisearch message searches, and keeps the recent base relation even when older `since` params are provided. Focused search service/engine/repository tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change.