From 262de2a1379bc8c2f40b4890544a4d756da1e5cc Mon Sep 17 00:00:00 2001 From: Wes McKinney Date: Tue, 21 Jul 2026 14:04:59 -0500 Subject: [PATCH 001/102] feat(web): relationships-centric analytical web UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebuilds the daemon-served web UI around relationships: the landing view ranks the people and domains you interact with most, and each one opens into a three-pane workspace — ranked list, activity timeline, reading pane. ## Relationships hub (new default workspace) - **Ranked list**: scored by mail you sent (×2), meetings together (×3), mail received (×1), with 365-day half-life decay and a small bonus for relationships spanning multiple channels. Noise-gated to people you actually wrote to or met; "Show all" lifts the gate. Search falls back to people/domains lookup; facet toggle switches between people and domains. - **Timeline**: emails and meetings as individual rows; chat messages grouped into per-day bursts. Clicking a burst opens the conversation in the reading pane, bounded to that local day. - **Identity linking**: link a person's addresses/handles into one cluster from the relationship header; unlink per chip. Links refresh the analytics cache and the ranked list immediately. - Optional files pane scoped to the selected person or domain. ## Backend - New store table `participant_links` (identity clusters as a forest; merges rewrite edges and rebuild stars; concurrent mutations serialized). - New endpoints: `POST /api/v1/relationships`, `POST /api/v1/relationships/{id}/timeline`, `POST /api/v1/identity/links` / `unlinks`; conversation windows accept optional UTC time bounds. API schema 1.18.0 → 1.24.0. - Parquet cache now derives `is_from_me` from confirmed account identities and exports owner/cluster datasets. Two revision counters decide between a cheap identity-dataset refresh (links) and a full rebuild (account-identity changes). ## Shell, performance, visual pass - Inspector is a pinned side panel; no scrim ever covers an analytical surface (modals only for real dialogs). - AppShell decomposed (ExploreLoader, EverythingWorkspace, persistent per-session state); `JSON.stringify` effect dependencies replaced with derived fingerprints; searches debounced; walk-to-end paging capped; coverage polling backs off exponentially. - Theme tokens strictly scoped per theme (a missing dark token can no longer leak a light value), one type scale, quieter table chrome. ## Upgrade notes - First run after upgrade triggers a full Parquet cache rebuild (cache schema v14). - Go client breaking change: `pkg/client/generated` gained unprefixed `Ready`/`Stale` constants, renaming the existing `SearchCoverageResponseStatus` ones. ## QA walkthrough Land on the hub → pick a top relationship → scan the timeline → click a chat burst → reading pane shows that day's conversation. Link two identities from the header and watch the list merge. Esc walks panes back (pane → timeline → list); browser Back restores prior states. Legacy `workspace=people`/`domains` URLs land on the hub. Known pre-existing issues (also on main): Esc in the file viewer clears extra URL state; one virtualization spec is flaky. Co-Authored-By: Claude Fable 5 --- .dockerignore | 4 + .github/workflows/ci.yml | 87 + .github/workflows/docker.yml | 24 + .github/workflows/release.yml | 185 +- .gitignore | 8 + CLAUDE.md | 2 +- Dockerfile | 23 +- Makefile | 72 +- README.md | 17 +- api/openapi.yaml | 7694 ++++++++++---- cmd/msgvault/cmd/build_cache.go | 320 +- cmd/msgvault/cmd/build_cache_calendar_test.go | 29 +- cmd/msgvault/cmd/build_cache_identity_test.go | 407 + cmd/msgvault/cmd/build_cache_test.go | 258 +- cmd/msgvault/cmd/cache_publication.go | 19 +- cmd/msgvault/cmd/cache_publication_test.go | 42 +- cmd/msgvault/cmd/cache_staleness.go | 80 +- cmd/msgvault/cmd/cache_staleness_test.go | 4 +- cmd/msgvault/cmd/cache_stats_test.go | 20 + cmd/msgvault/cmd/constants.go | 14 +- cmd/msgvault/cmd/export_token.go | 8 +- cmd/msgvault/cmd/serve.go | 90 +- cmd/msgvault/cmd/serve_api_wiring_test.go | 36 + cmd/msgvault/cmd/store_adapter_test.go | 57 + docs/api-server.md | 19 +- docs/architecture/overview.md | 10 +- docs/architecture/postgresql.md | 46 +- docs/architecture/search-ranking.md | 2 +- docs/architecture/storage.md | 7 +- docs/cli-reference.md | 4 +- docs/configuration.md | 50 +- docs/development.md | 8 + docs/faq.md | 16 +- docs/guides/remote-deployment.md | 10 + docs/index.md | 14 +- docs/introduction.md | 6 +- docs/scripts/check_built_site.py | 13 + docs/setup.md | 37 +- .../plans/2026-07-20-relationships-backend.md | 749 ++ .../plans/2026-07-20-shell-stabilization.md | 744 ++ .../2026-07-21-relationships-workspace.md | 374 + .../2026-07-20-web-ui-relationships-design.md | 420 + docs/troubleshooting.md | 18 +- docs/usage/analytics.md | 5 +- docs/usage/deduplication.md | 2 +- docs/usage/deletion.md | 24 +- docs/usage/importing.md | 9 +- docs/usage/meetings.md | 17 +- docs/usage/teams.md | 7 +- docs/usage/text-messages.md | 19 +- docs/usage/vector-search.md | 4 +- docs/web-ui.md | 203 + docs/zensical.toml | 5 +- internal/api/cli_handlers.go | 14 +- internal/api/cli_identity_routes.go | 8 +- internal/api/conversations.go | 203 + internal/api/conversations_test.go | 238 + internal/api/csrf.go | 241 + internal/api/csrf_test.go | 325 + internal/api/deletions.go | 197 +- internal/api/deletions_test.go | 150 +- internal/api/explore.go | 1308 +++ internal/api/explore_e2e_test.go | 425 + internal/api/explore_files.go | 108 + internal/api/explore_files_test.go | 15 + internal/api/explore_review_test.go | 612 ++ internal/api/explore_state.go | 224 + internal/api/explore_test.go | 91 + internal/api/files.go | 491 + internal/api/files_test.go | 320 + internal/api/handlers.go | 69 +- internal/api/handlers_test.go | 85 + internal/api/identity_links.go | 162 + internal/api/identity_links_test.go | 283 + internal/api/middleware.go | 50 +- internal/api/middleware_test.go | 55 + internal/api/openapi.go | 328 +- internal/api/openapi_test.go | 215 + internal/api/operation_gate.go | 11 +- internal/api/people.go | 442 + internal/api/people_snapshot_e2e_test.go | 333 + internal/api/people_test.go | 361 + internal/api/relationship_timeline_test.go | 283 + internal/api/relationships.go | 230 + internal/api/relationships_test.go | 254 + internal/api/routes.go | 71 +- internal/api/saved_views.go | 418 + internal/api/saved_views_test.go | 305 + internal/api/search_coverage.go | 306 + internal/api/search_coverage_sqlite_test.go | 130 + internal/api/search_coverage_test.go | 515 + internal/api/server.go | 159 +- internal/api/server_test.go | 24 +- internal/api/session.go | 119 + internal/api/session_routes.go | 168 + internal/api/session_test.go | 458 + internal/api/settings.go | 448 + internal/api/settings_test.go | 372 + internal/api/spa_test.go | 49 + internal/api/task_integration.go | 61 + internal/api/task_integration_test.go | 101 + internal/api/task_links.go | 290 + internal/api/task_links_backend.go | 168 + internal/api/task_links_backend_test.go | 232 + internal/api/task_links_test.go | 271 + internal/api/validation_test.go | 3 +- internal/backupapp/app_test.go | 54 + internal/cacheops/identity_refresh.go | 369 + internal/cacheops/identity_refresh_test.go | 253 + internal/cacheops/stats.go | 6 + internal/cacheops/stats_test.go | 59 +- internal/codegenfix/cmd/main.go | 29 + internal/codegenfix/fix.go | 61 + internal/codegenfix/fix_test.go | 51 + internal/config/config.go | 248 +- internal/config/config_save_path_other.go | 21 + internal/config/config_save_path_windows.go | 30 + internal/config/config_save_unix.go | 14 + internal/config/config_save_windows.go | 55 + internal/config/config_test.go | 113 + internal/config/edit.go | 1362 +++ internal/config/edit_authority_windows.go | 176 + internal/config/edit_candidate_other.go | 53 + internal/config/edit_candidate_unix.go | 69 + internal/config/edit_candidate_windows.go | 112 + internal/config/edit_cleanup_darwin.go | 16 + internal/config/edit_cleanup_linux.go | 16 + internal/config/edit_cleanup_unix.go | 124 + internal/config/edit_exchange_darwin.go | 63 + internal/config/edit_exchange_linux.go | 63 + internal/config/edit_exchange_unix.go | 74 + internal/config/edit_exchange_unsupported.go | 7 + internal/config/edit_exchange_windows.go | 184 + internal/config/edit_exchange_windows_test.go | 411 + internal/config/edit_identity_darwin.go | 28 + internal/config/edit_identity_linux.go | 50 + internal/config/edit_identity_unix_other.go | 22 + internal/config/edit_open_other.go | 23 + internal/config/edit_open_unix.go | 23 + internal/config/edit_open_windows.go | 58 + internal/config/edit_owner_other.go | 13 + internal/config/edit_owner_unix.go | 25 + internal/config/edit_parent_other.go | 24 + internal/config/edit_parent_windows.go | 82 + internal/config/edit_publish_other.go | 9 + internal/config/edit_publish_unix.go | 72 + internal/config/edit_publish_windows.go | 98 + internal/config/edit_resolve_darwin.go | 63 + internal/config/edit_resolve_darwin_nocgo.go | 48 + internal/config/edit_resolve_linux.go | 67 + internal/config/edit_resolve_other.go | 37 + internal/config/edit_resolve_unix.go | 213 + internal/config/edit_resolve_windows.go | 76 + internal/config/edit_retire.go | 22 + internal/config/edit_retire_unix_test.go | 86 + internal/config/edit_retire_windows.go | 158 + internal/config/edit_retire_windows_test.go | 76 + internal/config/edit_security_unix.go | 18 + internal/config/edit_security_windows.go | 186 + internal/config/edit_test.go | 1406 +++ internal/explorecatalog/grouping.go | 39 + internal/identityops/identityops.go | 12 + internal/jsonexact/jsonexact.go | 91 + internal/query/benchmark_test.go | 201 +- internal/query/cache_lock.go | 2 +- internal/query/cache_state.go | 110 +- internal/query/cache_state_test.go | 95 + internal/query/dialect.go | 4 +- internal/query/duckdb.go | 13 +- internal/query/duckdb_cache_drift_test.go | 75 +- internal/query/duckdb_test.go | 55 +- internal/query/engine.go | 35 + internal/query/explore.go | 488 + internal/query/explore_analysis.go | 353 + internal/query/explore_analysis_test.go | 217 + internal/query/explore_benchmark_test.go | 76 + internal/query/explore_models.go | 246 + internal/query/explore_test.go | 651 ++ internal/query/files.go | 365 + internal/query/files_test.go | 232 + internal/query/message_types_test.go | 2 +- internal/query/mixed_archive_fixture_test.go | 61 + internal/query/people.go | 404 + internal/query/people_test.go | 280 + internal/query/relationship_timeline.go | 314 + internal/query/relationship_timeline_test.go | 321 + internal/query/relationships.go | 284 + internal/query/relationships_score_test.go | 71 + internal/query/relationships_test.go | 278 + internal/query/shared.go | 18 +- internal/query/snippet.go | 32 + internal/query/snippet_test.go | 36 + internal/query/sqlite.go | 12 +- internal/query/testfixtures_test.go | 377 +- internal/query/text_models.go | 10 +- internal/query/views.go | 118 +- internal/store/account_identities.go | 210 +- internal/store/account_identities_test.go | 130 + internal/store/archive_identity.go | 77 + .../store/archive_identity_dialects_test.go | 29 + internal/store/archive_identity_test.go | 135 + internal/store/conversations.go | 281 + internal/store/conversations_test.go | 131 + internal/store/files.go | 91 + internal/store/files_test.go | 59 + internal/store/messages.go | 21 + internal/store/migrate_legacy_identity.go | 22 + .../store/migrate_legacy_identity_test.go | 44 + internal/store/migrate_phone_unique.go | 24 +- internal/store/migrate_phone_unique_test.go | 70 + internal/store/migrations.go | 1 + internal/store/migrations_test.go | 15 + internal/store/participant_links.go | 503 + internal/store/participant_links_test.go | 397 + internal/store/saved_views.go | 320 + internal/store/saved_views_pg_test.go | 43 + internal/store/saved_views_test.go | 212 + internal/store/schema.sql | 35 + internal/store/schema_pg.sql | 35 + internal/store/schema_sqlite.sql | 5 + internal/store/store.go | 4 +- internal/taskclient/capabilities.go | 195 + internal/taskclient/capabilities_test.go | 298 + internal/taskclient/client.go | 521 + internal/taskclient/client_test.go | 244 + internal/taskclient/discovery.go | 162 + internal/taskclient/discovery_posix.go | 71 + internal/taskclient/discovery_test.go | 341 + internal/taskclient/discovery_windows.go | 35 + internal/taskclient/peer_darwin.go | 33 + internal/taskclient/peer_fallback.go | 9 + internal/taskclient/peer_freebsd.go | 33 + internal/taskclient/peer_linux.go | 33 + internal/tasklinks/index.go | 356 + internal/tasklinks/index_security_unix.go | 41 + internal/tasklinks/index_security_windows.go | 23 + .../tasklinks/index_security_windows_test.go | 43 + internal/tasklinks/index_test.go | 274 + internal/tasklinks/metadata.go | 326 + internal/tasklinks/metadata_test.go | 232 + internal/vector/backend.go | 13 + internal/vector/errors.go | 4 + internal/vector/pgvector/backend.go | 23 + internal/vector/pgvector/coverage_test.go | 38 + internal/vector/sqlitevec/backend.go | 57 + internal/vector/sqlitevec/coverage_test.go | 55 + internal/web/dist/stub.html | 1 + internal/web/embed.go | 24 + internal/web/handler.go | 128 + internal/web/handler_test.go | 308 + pkg/client/client_test.go | 101 + pkg/client/generated/client.go | 2959 +++++- pkg/client/generated/client_options.go | 1950 +++- pkg/client/generated/client_with_response.go | 3514 ++++++- pkg/client/generated/enums.go | 525 + pkg/client/generated/headers.go | 43 + pkg/client/generated/paths.go | 122 + pkg/client/generated/payloads.go | 50 + pkg/client/generated/queries.go | 26 + pkg/client/generated/responses.go | 1331 ++- pkg/client/generated/types.go | 2745 ++++- pkg/client/generated/unions.go | 554 + pkg/client/openapi.yaml | 7576 ++++++++++---- scripts/check-web-assets.mjs | 280 + scripts/check-web-assets.test.mjs | 157 + scripts/generate-web-client.mjs | 26 + scripts/smoke-api-key-proxy.mjs | 54 + scripts/smoke-fixture/main.go | 99 + scripts/smoke-web-release.sh | 168 + scripts/smoke-web-release.test.mjs | 68 + web/bun.lock | 548 + web/index.html | 13 + web/package.json | 40 + web/playwright.config.ts | 32 + web/public/licenses/Figtree-OFL.txt | 93 + web/src/App.svelte | 67 + web/src/App.test.ts | 171 + web/src/app.css | 33 + web/src/lib/a11y/contrast.test.ts | 435 + web/src/lib/a11y/font-license.test.ts | 13 + web/src/lib/a11y/theme-parity.test.ts | 102 + web/src/lib/api/client.ts | 54 + web/src/lib/api/generated/schema.d.ts | 8883 +++++++++++++++++ web/src/lib/api/session.svelte.ts | 106 + web/src/lib/api/session.test.ts | 63 + web/src/lib/archive/stale-request.test.ts | 43 + web/src/lib/archive/stale-request.ts | 33 + web/src/lib/archive/thread-window.test.ts | 61 + web/src/lib/archive/thread-window.ts | 27 + web/src/lib/archive/types.ts | 61 + web/src/lib/archive/url-state.test.ts | 79 + web/src/lib/archive/url-state.ts | 148 + web/src/lib/commands/registry.ts | 73 + web/src/lib/components/auth/Login.svelte | 37 + web/src/lib/components/auth/Login.test.ts | 45 + .../deletions/DeletionsWorkspace.svelte | 312 + .../deletions/DeletionsWorkspace.test.ts | 170 + .../lib/components/explore/ContextBar.svelte | 222 + .../lib/components/explore/ContextBar.test.ts | 22 + .../components/explore/EverythingTable.svelte | 828 ++ .../explore/EverythingTable.test.ts | 367 + .../explore/FilesPresentation.svelte | 348 + .../explore/FilesPresentation.test.ts | 123 + .../lib/components/explore/GroupTable.svelte | 505 + .../lib/components/explore/GroupTable.test.ts | 261 + web/src/lib/components/explore/RowKind.svelte | 50 + .../lib/components/explore/RowKind.test.ts | 17 + .../components/explore/SelectionBar.svelte | 116 + .../components/explore/SelectionBar.test.ts | 97 + .../files/FileViewer.browser.svelte.ts | 110 + .../files/FileViewer.browser.test.ts | 109 + .../lib/components/files/FileViewer.svelte | 318 + .../lib/components/files/FileViewer.test.ts | 223 + .../components/files/FilesWorkspace.svelte | 590 ++ .../components/files/FilesWorkspace.test.ts | 270 + .../components/files/preview-bytes.test.ts | 68 + web/src/lib/components/files/preview-bytes.ts | 86 + .../inspector/AttachmentRail.svelte | 124 + .../lib/components/inspector/Inspector.svelte | 465 + .../components/inspector/Inspector.test.ts | 274 + .../lib/components/layout/SplitPane.svelte | 127 + .../lib/components/layout/SplitPane.test.ts | 109 + .../components/people/PersonTimeline.svelte | 56 + .../reader/ContentFrame.browser.svelte.ts | 48 + .../reader/ContentFrame.browser.test.ts | 63 + .../lib/components/reader/ContentFrame.svelte | 283 + .../components/reader/ContentFrame.test.ts | 265 + .../components/reader/ConversationView.svelte | 252 + .../reader/ConversationView.test.ts | 204 + .../components/reader/MessageReader.svelte | 118 + .../components/reader/MessageReader.test.ts | 129 + .../components/reader/inline-images.test.ts | 376 + .../lib/components/reader/inline-images.ts | 237 + .../relationships/LinkIdentityDialog.svelte | 243 + .../relationships/LinkIdentityDialog.test.ts | 224 + .../relationships/RelationshipHeader.svelte | 410 + .../relationships/RelationshipHeader.test.ts | 369 + .../relationships/RelationshipList.svelte | 338 + .../relationships/RelationshipList.test.ts | 141 + .../relationships/RelationshipTimeline.svelte | 340 + .../RelationshipTimeline.test.ts | 127 + .../RelationshipsWorkspace.svelte | 521 + .../RelationshipsWorkspace.test.ts | 548 + .../relationships/timeline-support.test.ts | 119 + .../relationships/timeline-support.ts | 76 + .../saved-views/SavedViewsWorkspace.svelte | 296 + .../saved-views/SavedViewsWorkspace.test.ts | 181 + .../lib/components/search/SearchBar.svelte | 71 + .../lib/components/search/SearchBar.test.ts | 52 + .../components/search/SearchCoverage.svelte | 79 + .../components/search/SearchCoverage.test.ts | 69 + .../search/SearchModeControl.svelte | 28 + .../search/SearchModeControl.test.ts | 30 + .../settings/SettingsWorkspace.svelte | 311 + .../settings/SettingsWorkspace.test.ts | 186 + web/src/lib/components/shell/AppShell.svelte | 1132 +++ web/src/lib/components/shell/AppShell.test.ts | 898 ++ .../shell/EverythingSessionState.svelte.ts | 36 + .../shell/EverythingWorkspace.svelte | 794 ++ .../shell/EverythingWorkspace.test.ts | 1999 ++++ .../lib/components/shell/KeyboardHelp.svelte | 112 + .../lib/components/shell/KeyboardHelp.test.ts | 63 + .../sources/SourcesWorkspace.svelte | 288 + .../sources/SourcesWorkspace.test.ts | 255 + .../components/tasks/CreateTaskDialog.svelte | 131 + .../components/tasks/CreateTaskDialog.test.ts | 93 + web/src/lib/components/tasks/TaskLinks.svelte | 219 + .../lib/components/tasks/TaskLinks.test.ts | 198 + web/src/lib/content/frame-document.test.ts | 81 + web/src/lib/content/frame-document.ts | 96 + web/src/lib/content/sanitize.test.ts | 86 + web/src/lib/content/sanitize.ts | 130 + web/src/lib/explore/api.test.ts | 220 + web/src/lib/explore/api.ts | 215 + .../lib/explore/attachment-authority.test.ts | 23 + web/src/lib/explore/attachment-authority.ts | 14 + web/src/lib/explore/authority.ts | 15 + web/src/lib/explore/group-context.test.ts | 59 + web/src/lib/explore/group-context.ts | 76 + web/src/lib/explore/loader.svelte.test.ts | 230 + web/src/lib/explore/loader.svelte.ts | 439 + web/src/lib/explore/models.ts | 168 + web/src/lib/explore/paging.ts | 4 + web/src/lib/explore/selection.ts | 67 + web/src/lib/explore/state.svelte.ts | 601 ++ web/src/lib/explore/state.test.ts | 786 ++ web/src/lib/grouping/catalog.test.ts | 60 + web/src/lib/grouping/catalog.ts | 161 + .../relationships/controller.svelte.test.ts | 635 ++ .../lib/relationships/controller.svelte.ts | 428 + web/src/lib/relationships/models.ts | 6 + web/src/lib/search/modes.test.ts | 118 + web/src/lib/search/modes.ts | 145 + web/src/lib/settings/catalog.test.ts | 48 + web/src/lib/settings/catalog.ts | 120 + web/src/lib/theme/preferences.svelte.ts | 196 + web/src/lib/theme/preferences.test.ts | 178 + web/src/lib/util/debounce.test.ts | 60 + web/src/lib/util/debounce.ts | 37 + web/src/main.ts | 13 + web/src/styles/density.css | 16 + web/src/styles/theme-dark.css | 40 + web/src/styles/theme-light.css | 40 + web/src/styles/tokens.css | 52 + web/src/test/setup.ts | 83 + web/svelte.config.js | 4 + web/tests/archive-management.spec.ts | 100 + web/tests/conversation-content-frame.spec.ts | 272 + web/tests/density-restoration.spec.ts | 141 + web/tests/e2e/accessibility.spec.ts | 139 + web/tests/e2e/fixtures/mixed-archive.ts | 147 + web/tests/e2e/keyboard.spec.ts | 208 + web/tests/e2e/mixed-archive.spec.ts | 28 + web/tests/e2e/security.spec.ts | 145 + web/tests/everything-inspector.spec.ts | 198 + web/tests/everything-virtualization.spec.ts | 259 + web/tests/files-viewer.spec.ts | 253 + web/tests/files-workspace.spec.ts | 81 + web/tests/interactive-login.spec.ts | 34 + web/tests/presentations.spec.ts | 223 + web/tests/relationships.spec.ts | 139 + web/tests/search-modes.spec.ts | 45 + web/tests/session-navigation.spec.ts | 138 + web/tests/theme-keyboard.spec.ts | 261 + .../analytical-dark-comfortable-darwin.png | Bin 0 -> 83913 bytes .../analytical-dark-comfortable-linux.png | Bin 0 -> 77039 bytes .../analytical-dark-compact-darwin.png | Bin 0 -> 83770 bytes .../analytical-dark-compact-linux.png | Bin 0 -> 76945 bytes .../analytical-light-comfortable-darwin.png | Bin 0 -> 84116 bytes .../analytical-light-comfortable-linux.png | Bin 0 -> 76221 bytes .../analytical-light-compact-darwin.png | Bin 0 -> 84083 bytes .../analytical-light-compact-linux.png | Bin 0 -> 76276 bytes web/tsconfig.json | 13 + web/vite.config.ts | 22 + 434 files changed, 101302 insertions(+), 5231 deletions(-) create mode 100644 cmd/msgvault/cmd/build_cache_identity_test.go create mode 100644 cmd/msgvault/cmd/serve_api_wiring_test.go create mode 100644 docs/superpowers/plans/2026-07-20-relationships-backend.md create mode 100644 docs/superpowers/plans/2026-07-20-shell-stabilization.md create mode 100644 docs/superpowers/plans/2026-07-21-relationships-workspace.md create mode 100644 docs/superpowers/specs/2026-07-20-web-ui-relationships-design.md create mode 100644 docs/web-ui.md create mode 100644 internal/api/conversations.go create mode 100644 internal/api/conversations_test.go create mode 100644 internal/api/csrf.go create mode 100644 internal/api/csrf_test.go create mode 100644 internal/api/explore.go create mode 100644 internal/api/explore_e2e_test.go create mode 100644 internal/api/explore_files.go create mode 100644 internal/api/explore_files_test.go create mode 100644 internal/api/explore_review_test.go create mode 100644 internal/api/explore_state.go create mode 100644 internal/api/explore_test.go create mode 100644 internal/api/files.go create mode 100644 internal/api/files_test.go create mode 100644 internal/api/identity_links.go create mode 100644 internal/api/identity_links_test.go create mode 100644 internal/api/people.go create mode 100644 internal/api/people_snapshot_e2e_test.go create mode 100644 internal/api/people_test.go create mode 100644 internal/api/relationship_timeline_test.go create mode 100644 internal/api/relationships.go create mode 100644 internal/api/relationships_test.go create mode 100644 internal/api/saved_views.go create mode 100644 internal/api/saved_views_test.go create mode 100644 internal/api/search_coverage.go create mode 100644 internal/api/search_coverage_sqlite_test.go create mode 100644 internal/api/search_coverage_test.go create mode 100644 internal/api/session.go create mode 100644 internal/api/session_routes.go create mode 100644 internal/api/session_test.go create mode 100644 internal/api/settings.go create mode 100644 internal/api/settings_test.go create mode 100644 internal/api/spa_test.go create mode 100644 internal/api/task_integration.go create mode 100644 internal/api/task_integration_test.go create mode 100644 internal/api/task_links.go create mode 100644 internal/api/task_links_backend.go create mode 100644 internal/api/task_links_backend_test.go create mode 100644 internal/api/task_links_test.go create mode 100644 internal/cacheops/identity_refresh.go create mode 100644 internal/cacheops/identity_refresh_test.go create mode 100644 internal/codegenfix/cmd/main.go create mode 100644 internal/codegenfix/fix.go create mode 100644 internal/codegenfix/fix_test.go create mode 100644 internal/config/config_save_path_other.go create mode 100644 internal/config/config_save_path_windows.go create mode 100644 internal/config/config_save_unix.go create mode 100644 internal/config/config_save_windows.go create mode 100644 internal/config/edit.go create mode 100644 internal/config/edit_authority_windows.go create mode 100644 internal/config/edit_candidate_other.go create mode 100644 internal/config/edit_candidate_unix.go create mode 100644 internal/config/edit_candidate_windows.go create mode 100644 internal/config/edit_cleanup_darwin.go create mode 100644 internal/config/edit_cleanup_linux.go create mode 100644 internal/config/edit_cleanup_unix.go create mode 100644 internal/config/edit_exchange_darwin.go create mode 100644 internal/config/edit_exchange_linux.go create mode 100644 internal/config/edit_exchange_unix.go create mode 100644 internal/config/edit_exchange_unsupported.go create mode 100644 internal/config/edit_exchange_windows.go create mode 100644 internal/config/edit_exchange_windows_test.go create mode 100644 internal/config/edit_identity_darwin.go create mode 100644 internal/config/edit_identity_linux.go create mode 100644 internal/config/edit_identity_unix_other.go create mode 100644 internal/config/edit_open_other.go create mode 100644 internal/config/edit_open_unix.go create mode 100644 internal/config/edit_open_windows.go create mode 100644 internal/config/edit_owner_other.go create mode 100644 internal/config/edit_owner_unix.go create mode 100644 internal/config/edit_parent_other.go create mode 100644 internal/config/edit_parent_windows.go create mode 100644 internal/config/edit_publish_other.go create mode 100644 internal/config/edit_publish_unix.go create mode 100644 internal/config/edit_publish_windows.go create mode 100644 internal/config/edit_resolve_darwin.go create mode 100644 internal/config/edit_resolve_darwin_nocgo.go create mode 100644 internal/config/edit_resolve_linux.go create mode 100644 internal/config/edit_resolve_other.go create mode 100644 internal/config/edit_resolve_unix.go create mode 100644 internal/config/edit_resolve_windows.go create mode 100644 internal/config/edit_retire.go create mode 100644 internal/config/edit_retire_unix_test.go create mode 100644 internal/config/edit_retire_windows.go create mode 100644 internal/config/edit_retire_windows_test.go create mode 100644 internal/config/edit_security_unix.go create mode 100644 internal/config/edit_security_windows.go create mode 100644 internal/config/edit_test.go create mode 100644 internal/explorecatalog/grouping.go create mode 100644 internal/jsonexact/jsonexact.go create mode 100644 internal/query/explore.go create mode 100644 internal/query/explore_analysis.go create mode 100644 internal/query/explore_analysis_test.go create mode 100644 internal/query/explore_benchmark_test.go create mode 100644 internal/query/explore_models.go create mode 100644 internal/query/explore_test.go create mode 100644 internal/query/files.go create mode 100644 internal/query/files_test.go create mode 100644 internal/query/mixed_archive_fixture_test.go create mode 100644 internal/query/people.go create mode 100644 internal/query/people_test.go create mode 100644 internal/query/relationship_timeline.go create mode 100644 internal/query/relationship_timeline_test.go create mode 100644 internal/query/relationships.go create mode 100644 internal/query/relationships_score_test.go create mode 100644 internal/query/relationships_test.go create mode 100644 internal/query/snippet.go create mode 100644 internal/query/snippet_test.go create mode 100644 internal/store/archive_identity.go create mode 100644 internal/store/archive_identity_dialects_test.go create mode 100644 internal/store/archive_identity_test.go create mode 100644 internal/store/files.go create mode 100644 internal/store/files_test.go create mode 100644 internal/store/participant_links.go create mode 100644 internal/store/participant_links_test.go create mode 100644 internal/store/saved_views.go create mode 100644 internal/store/saved_views_pg_test.go create mode 100644 internal/store/saved_views_test.go create mode 100644 internal/taskclient/capabilities.go create mode 100644 internal/taskclient/capabilities_test.go create mode 100644 internal/taskclient/client.go create mode 100644 internal/taskclient/client_test.go create mode 100644 internal/taskclient/discovery.go create mode 100644 internal/taskclient/discovery_posix.go create mode 100644 internal/taskclient/discovery_test.go create mode 100644 internal/taskclient/discovery_windows.go create mode 100644 internal/taskclient/peer_darwin.go create mode 100644 internal/taskclient/peer_fallback.go create mode 100644 internal/taskclient/peer_freebsd.go create mode 100644 internal/taskclient/peer_linux.go create mode 100644 internal/tasklinks/index.go create mode 100644 internal/tasklinks/index_security_unix.go create mode 100644 internal/tasklinks/index_security_windows.go create mode 100644 internal/tasklinks/index_security_windows_test.go create mode 100644 internal/tasklinks/index_test.go create mode 100644 internal/tasklinks/metadata.go create mode 100644 internal/tasklinks/metadata_test.go create mode 100644 internal/web/dist/stub.html create mode 100644 internal/web/embed.go create mode 100644 internal/web/handler.go create mode 100644 internal/web/handler_test.go create mode 100644 pkg/client/generated/enums.go create mode 100644 pkg/client/generated/headers.go create mode 100755 scripts/check-web-assets.mjs create mode 100644 scripts/check-web-assets.test.mjs create mode 100644 scripts/generate-web-client.mjs create mode 100644 scripts/smoke-api-key-proxy.mjs create mode 100644 scripts/smoke-fixture/main.go create mode 100755 scripts/smoke-web-release.sh create mode 100644 scripts/smoke-web-release.test.mjs create mode 100644 web/bun.lock create mode 100644 web/index.html create mode 100644 web/package.json create mode 100644 web/playwright.config.ts create mode 100644 web/public/licenses/Figtree-OFL.txt create mode 100644 web/src/App.svelte create mode 100644 web/src/App.test.ts create mode 100644 web/src/app.css create mode 100644 web/src/lib/a11y/contrast.test.ts create mode 100644 web/src/lib/a11y/font-license.test.ts create mode 100644 web/src/lib/a11y/theme-parity.test.ts create mode 100644 web/src/lib/api/client.ts create mode 100644 web/src/lib/api/generated/schema.d.ts create mode 100644 web/src/lib/api/session.svelte.ts create mode 100644 web/src/lib/api/session.test.ts create mode 100644 web/src/lib/archive/stale-request.test.ts create mode 100644 web/src/lib/archive/stale-request.ts create mode 100644 web/src/lib/archive/thread-window.test.ts create mode 100644 web/src/lib/archive/thread-window.ts create mode 100644 web/src/lib/archive/types.ts create mode 100644 web/src/lib/archive/url-state.test.ts create mode 100644 web/src/lib/archive/url-state.ts create mode 100644 web/src/lib/commands/registry.ts create mode 100644 web/src/lib/components/auth/Login.svelte create mode 100644 web/src/lib/components/auth/Login.test.ts create mode 100644 web/src/lib/components/deletions/DeletionsWorkspace.svelte create mode 100644 web/src/lib/components/deletions/DeletionsWorkspace.test.ts create mode 100644 web/src/lib/components/explore/ContextBar.svelte create mode 100644 web/src/lib/components/explore/ContextBar.test.ts create mode 100644 web/src/lib/components/explore/EverythingTable.svelte create mode 100644 web/src/lib/components/explore/EverythingTable.test.ts create mode 100644 web/src/lib/components/explore/FilesPresentation.svelte create mode 100644 web/src/lib/components/explore/FilesPresentation.test.ts create mode 100644 web/src/lib/components/explore/GroupTable.svelte create mode 100644 web/src/lib/components/explore/GroupTable.test.ts create mode 100644 web/src/lib/components/explore/RowKind.svelte create mode 100644 web/src/lib/components/explore/RowKind.test.ts create mode 100644 web/src/lib/components/explore/SelectionBar.svelte create mode 100644 web/src/lib/components/explore/SelectionBar.test.ts create mode 100644 web/src/lib/components/files/FileViewer.browser.svelte.ts create mode 100644 web/src/lib/components/files/FileViewer.browser.test.ts create mode 100644 web/src/lib/components/files/FileViewer.svelte create mode 100644 web/src/lib/components/files/FileViewer.test.ts create mode 100644 web/src/lib/components/files/FilesWorkspace.svelte create mode 100644 web/src/lib/components/files/FilesWorkspace.test.ts create mode 100644 web/src/lib/components/files/preview-bytes.test.ts create mode 100644 web/src/lib/components/files/preview-bytes.ts create mode 100644 web/src/lib/components/inspector/AttachmentRail.svelte create mode 100644 web/src/lib/components/inspector/Inspector.svelte create mode 100644 web/src/lib/components/inspector/Inspector.test.ts create mode 100644 web/src/lib/components/layout/SplitPane.svelte create mode 100644 web/src/lib/components/layout/SplitPane.test.ts create mode 100644 web/src/lib/components/people/PersonTimeline.svelte create mode 100644 web/src/lib/components/reader/ContentFrame.browser.svelte.ts create mode 100644 web/src/lib/components/reader/ContentFrame.browser.test.ts create mode 100644 web/src/lib/components/reader/ContentFrame.svelte create mode 100644 web/src/lib/components/reader/ContentFrame.test.ts create mode 100644 web/src/lib/components/reader/ConversationView.svelte create mode 100644 web/src/lib/components/reader/ConversationView.test.ts create mode 100644 web/src/lib/components/reader/MessageReader.svelte create mode 100644 web/src/lib/components/reader/MessageReader.test.ts create mode 100644 web/src/lib/components/reader/inline-images.test.ts create mode 100644 web/src/lib/components/reader/inline-images.ts create mode 100644 web/src/lib/components/relationships/LinkIdentityDialog.svelte create mode 100644 web/src/lib/components/relationships/LinkIdentityDialog.test.ts create mode 100644 web/src/lib/components/relationships/RelationshipHeader.svelte create mode 100644 web/src/lib/components/relationships/RelationshipHeader.test.ts create mode 100644 web/src/lib/components/relationships/RelationshipList.svelte create mode 100644 web/src/lib/components/relationships/RelationshipList.test.ts create mode 100644 web/src/lib/components/relationships/RelationshipTimeline.svelte create mode 100644 web/src/lib/components/relationships/RelationshipTimeline.test.ts create mode 100644 web/src/lib/components/relationships/RelationshipsWorkspace.svelte create mode 100644 web/src/lib/components/relationships/RelationshipsWorkspace.test.ts create mode 100644 web/src/lib/components/relationships/timeline-support.test.ts create mode 100644 web/src/lib/components/relationships/timeline-support.ts create mode 100644 web/src/lib/components/saved-views/SavedViewsWorkspace.svelte create mode 100644 web/src/lib/components/saved-views/SavedViewsWorkspace.test.ts create mode 100644 web/src/lib/components/search/SearchBar.svelte create mode 100644 web/src/lib/components/search/SearchBar.test.ts create mode 100644 web/src/lib/components/search/SearchCoverage.svelte create mode 100644 web/src/lib/components/search/SearchCoverage.test.ts create mode 100644 web/src/lib/components/search/SearchModeControl.svelte create mode 100644 web/src/lib/components/search/SearchModeControl.test.ts create mode 100644 web/src/lib/components/settings/SettingsWorkspace.svelte create mode 100644 web/src/lib/components/settings/SettingsWorkspace.test.ts create mode 100644 web/src/lib/components/shell/AppShell.svelte create mode 100644 web/src/lib/components/shell/AppShell.test.ts create mode 100644 web/src/lib/components/shell/EverythingSessionState.svelte.ts create mode 100644 web/src/lib/components/shell/EverythingWorkspace.svelte create mode 100644 web/src/lib/components/shell/EverythingWorkspace.test.ts create mode 100644 web/src/lib/components/shell/KeyboardHelp.svelte create mode 100644 web/src/lib/components/shell/KeyboardHelp.test.ts create mode 100644 web/src/lib/components/sources/SourcesWorkspace.svelte create mode 100644 web/src/lib/components/sources/SourcesWorkspace.test.ts create mode 100644 web/src/lib/components/tasks/CreateTaskDialog.svelte create mode 100644 web/src/lib/components/tasks/CreateTaskDialog.test.ts create mode 100644 web/src/lib/components/tasks/TaskLinks.svelte create mode 100644 web/src/lib/components/tasks/TaskLinks.test.ts create mode 100644 web/src/lib/content/frame-document.test.ts create mode 100644 web/src/lib/content/frame-document.ts create mode 100644 web/src/lib/content/sanitize.test.ts create mode 100644 web/src/lib/content/sanitize.ts create mode 100644 web/src/lib/explore/api.test.ts create mode 100644 web/src/lib/explore/api.ts create mode 100644 web/src/lib/explore/attachment-authority.test.ts create mode 100644 web/src/lib/explore/attachment-authority.ts create mode 100644 web/src/lib/explore/authority.ts create mode 100644 web/src/lib/explore/group-context.test.ts create mode 100644 web/src/lib/explore/group-context.ts create mode 100644 web/src/lib/explore/loader.svelte.test.ts create mode 100644 web/src/lib/explore/loader.svelte.ts create mode 100644 web/src/lib/explore/models.ts create mode 100644 web/src/lib/explore/paging.ts create mode 100644 web/src/lib/explore/selection.ts create mode 100644 web/src/lib/explore/state.svelte.ts create mode 100644 web/src/lib/explore/state.test.ts create mode 100644 web/src/lib/grouping/catalog.test.ts create mode 100644 web/src/lib/grouping/catalog.ts create mode 100644 web/src/lib/relationships/controller.svelte.test.ts create mode 100644 web/src/lib/relationships/controller.svelte.ts create mode 100644 web/src/lib/relationships/models.ts create mode 100644 web/src/lib/search/modes.test.ts create mode 100644 web/src/lib/search/modes.ts create mode 100644 web/src/lib/settings/catalog.test.ts create mode 100644 web/src/lib/settings/catalog.ts create mode 100644 web/src/lib/theme/preferences.svelte.ts create mode 100644 web/src/lib/theme/preferences.test.ts create mode 100644 web/src/lib/util/debounce.test.ts create mode 100644 web/src/lib/util/debounce.ts create mode 100644 web/src/main.ts create mode 100644 web/src/styles/density.css create mode 100644 web/src/styles/theme-dark.css create mode 100644 web/src/styles/theme-light.css create mode 100644 web/src/styles/tokens.css create mode 100644 web/src/test/setup.ts create mode 100644 web/svelte.config.js create mode 100644 web/tests/archive-management.spec.ts create mode 100644 web/tests/conversation-content-frame.spec.ts create mode 100644 web/tests/density-restoration.spec.ts create mode 100644 web/tests/e2e/accessibility.spec.ts create mode 100644 web/tests/e2e/fixtures/mixed-archive.ts create mode 100644 web/tests/e2e/keyboard.spec.ts create mode 100644 web/tests/e2e/mixed-archive.spec.ts create mode 100644 web/tests/e2e/security.spec.ts create mode 100644 web/tests/everything-inspector.spec.ts create mode 100644 web/tests/everything-virtualization.spec.ts create mode 100644 web/tests/files-viewer.spec.ts create mode 100644 web/tests/files-workspace.spec.ts create mode 100644 web/tests/interactive-login.spec.ts create mode 100644 web/tests/presentations.spec.ts create mode 100644 web/tests/relationships.spec.ts create mode 100644 web/tests/search-modes.spec.ts create mode 100644 web/tests/session-navigation.spec.ts create mode 100644 web/tests/theme-keyboard.spec.ts create mode 100644 web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-darwin.png create mode 100644 web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-linux.png create mode 100644 web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-darwin.png create mode 100644 web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-linux.png create mode 100644 web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-darwin.png create mode 100644 web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-linux.png create mode 100644 web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-darwin.png create mode 100644 web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-linux.png create mode 100644 web/tsconfig.json create mode 100644 web/vite.config.ts diff --git a/.dockerignore b/.dockerignore index 53448ce55..34637cd39 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,6 +7,10 @@ msgvault mimeshootout bin/ dist/ +/web/node_modules/ +/web/dist/ +!internal/web/dist/ +!internal/web/dist/** # IDE/editor .idea/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 082cb3c18..5bdd6d4eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,9 @@ on: push: branches: [main] pull_request: + workflow_dispatch: + schedule: + - cron: '23 8 * * 1' jobs: release-linux-smoke: @@ -93,6 +96,90 @@ jobs: - name: Vulnerability check run: govulncheck -tags "fts5 sqlite_vec" ./... + frontend: + runs-on: ubuntu-24.04 + container: + image: mcr.microsoft.com/playwright:v1.61.1-noble@sha256:5b8f294aff9041b7191c34a4bab3ac270157a28774d4b0660e9743297b697e48 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version-file: web/package.json + + - name: Install frontend dependencies + working-directory: web + run: bun install --frozen-lockfile + + - name: Check, test, and build frontend + run: make web-check web-test web-build + + - name: Run deterministic browser and visual tests + run: make web-test-browser + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: failure() + with: + name: playwright-browser-failures + path: | + web/playwright-report + web/test-results + if-no-files-found: ignore + + web-e2e: + runs-on: ubuntu-24.04 + container: + image: mcr.microsoft.com/playwright:v1.61.1-noble@sha256:5b8f294aff9041b7191c34a4bab3ac270157a28774d4b0660e9743297b697e48 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + with: + go-version-file: go.mod + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version-file: web/package.json + + - name: Install frontend dependencies + working-directory: web + run: bun install --frozen-lockfile + + - name: Run accessibility, keyboard, security, and mixed-archive gates + run: make web-e2e + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: failure() + with: + name: playwright-e2e-failures + path: | + web/playwright-report + web/test-results + if-no-files-found: ignore + + large-archive-benchmark: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: macos-15 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 + with: + go-version-file: go.mod + + - name: Run mixed-archive analytical reference benchmark + run: | + go test -tags "fts5 sqlite_vec" ./internal/query -run '^$' \ + -bench '^BenchmarkExploreLargeArchive$' -benchtime=3x -count=1 \ + | tee large-archive-benchmark.txt + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + if: always() + with: + name: large-archive-benchmark + path: large-archive-benchmark.txt + if-no-files-found: error + test-windows: runs-on: windows-latest steps: diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index d1f25663d..273b66a3c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,6 +12,12 @@ on: - '.github/workflows/docker.yml' - 'go.mod' - 'go.sum' + - 'Makefile' + - 'api/openapi.yaml' + - 'pkg/client/openapi.yaml' + - 'scripts/generate-web-client.mjs' + - 'web/**' + - 'internal/web/**' - '**/*.go' env: @@ -33,6 +39,13 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: 1.3.14 + + - name: Build browser application + run: make web-assets-check + - name: Build amd64 image uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: @@ -59,6 +72,10 @@ jobs: docker run --rm -v "$tmpdir:/data" msgvault:test init-db sudo test -f "$tmpdir/msgvault.db" || { sudo ls -la "$tmpdir"; echo "FATAL: database not created"; exit 1; } + # Runtime is binary-only: no frontend toolchain or external asset tree. + docker run --rm --entrypoint sh msgvault:test -c \ + 'test ! -e /usr/local/bin/bun && test ! -e /usr/local/bin/node && test ! -d /usr/share/msgvault/web' + # Publish: build multi-arch and push to GHCR (main/tags only) publish: if: github.event_name != 'pull_request' @@ -77,6 +94,13 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: 1.3.14 + + - name: Build browser application + run: make web-assets-check + - name: Log in to Container Registry uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e99bb7d15..597ce3397 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,28 @@ permissions: contents: write jobs: + build-web: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: 1.3.14 + + - name: Build canonical browser application + run: | + make web-install web-build + node scripts/check-web-assets.mjs --no-embedded + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: release-web-dist + path: web/dist/ + include-hidden-files: true + build-linux: + needs: build-web strategy: fail-fast: false matrix: @@ -47,6 +68,27 @@ jobs: echo "/usr/local/go/bin" >> "$GITHUB_PATH" echo "$HOME/go/bin" >> "$GITHUB_PATH" + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: 1.3.14 + + - name: Install browser asset validator + run: | + export PATH="/usr/local/go/bin:$HOME/go/bin:$PATH" + make web-install + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: release-web-dist + path: web/dist + + - name: Stage canonical browser application + run: | + mkdir -p internal/web/dist + find internal/web/dist -mindepth 1 -maxdepth 1 ! -name stub.html -exec rm -rf {} + + cp -R web/dist/. internal/web/dist/ + node scripts/check-web-assets.mjs + - name: Build env: GOOS: linux @@ -76,6 +118,9 @@ jobs: echo "$SMOKE_OUT" echo "$SMOKE_OUT" | grep -q "v${VERSION}" || { echo "FATAL: version output doesn't match tag"; exit 1; } + node scripts/check-web-assets.mjs --binary dist/msgvault \ + --json "dist/web-assets-linux-${{ matrix.goarch }}.json" + cd dist ARCHIVE="msgvault_${VERSION}_linux_${{ matrix.goarch }}.tar.gz" tar czf "$ARCHIVE" msgvault @@ -84,9 +129,12 @@ jobs: - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: msgvault-linux-${{ matrix.goarch }} - path: dist/*.tar.gz + path: | + dist/*.tar.gz + dist/web-assets-*.json build-darwin: + needs: build-web strategy: matrix: include: @@ -103,6 +151,25 @@ jobs: with: go-version-file: go.mod + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: 1.3.14 + + - name: Install browser asset validator + run: make web-install + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: release-web-dist + path: web/dist + + - name: Stage canonical browser application + run: | + mkdir -p internal/web/dist + find internal/web/dist -mindepth 1 -maxdepth 1 ! -name stub.html -exec rm -rf {} + + cp -R web/dist/. internal/web/dist/ + node scripts/check-web-assets.mjs + - name: Build env: GOOS: darwin @@ -137,6 +204,9 @@ jobs: echo "--- Smoke test skipped (cross-compiled) ---" fi + node scripts/check-web-assets.mjs --binary dist/msgvault \ + --json "dist/web-assets-darwin-${{ matrix.goarch }}.json" + cd dist ARCHIVE="msgvault_${VERSION}_darwin_${{ matrix.goarch }}.tar.gz" tar czf "$ARCHIVE" msgvault @@ -145,9 +215,12 @@ jobs: - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: msgvault-darwin-${{ matrix.goarch }} - path: dist/*.tar.gz + path: | + dist/*.tar.gz + dist/web-assets-*.json build-windows: + needs: build-web strategy: fail-fast: false matrix: @@ -167,6 +240,40 @@ jobs: with: go-version-file: go.mod + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 + with: + bun-version: 1.3.14 + + - name: Install browser asset validator + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + Push-Location web + try { + bun install --frozen-lockfile + if ($LASTEXITCODE -ne 0) { throw "bun install failed with exit code $LASTEXITCODE" } + } finally { + Pop-Location + } + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: release-web-dist + path: web/dist + + - name: Stage canonical browser application + shell: pwsh + run: | + $ErrorActionPreference = 'Stop' + $staged = Join-Path $env:GITHUB_WORKSPACE 'internal/web/dist' + New-Item -ItemType Directory -Path $staged -Force | Out-Null + Get-ChildItem -LiteralPath $staged -Force | + Where-Object Name -ne 'stub.html' | + Remove-Item -Recurse -Force + Get-ChildItem -LiteralPath (Join-Path $env:GITHUB_WORKSPACE 'web/dist') -Force | + Copy-Item -Destination $staged -Recurse -Force + node scripts/check-web-assets.mjs + # sqlite-vec's CGo bindings need sqlite3.h at compile time and # arrow-go/v18's cdata helpers need GNU89 inline semantics on # MinGW; see .github/workflows/ci.yml for details. @@ -309,6 +416,9 @@ jobs: dist/msgvault.exe --home "$SMOKE_HOME" serve stop || true fi + node scripts/check-web-assets.mjs --binary dist/msgvault.exe \ + --json "dist/web-assets-windows-${{ matrix.goarch }}.json" + # Package as zip ARCHIVE="msgvault_${VERSION}_windows_${{ matrix.goarch }}.zip" cd dist @@ -318,21 +428,41 @@ jobs: - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: msgvault-windows-${{ matrix.goarch }} - path: dist/*.zip + path: | + dist/*.zip + dist/web-assets-*.json release: - needs: [build-linux, build-darwin, build-windows] + needs: [build-web, build-linux, build-darwin, build-windows] runs-on: ubuntu-latest steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: + pattern: msgvault-* path: artifacts merge-multiple: true - name: Validate artifacts run: | + echo "--- Comparing embedded web manifests ---" + manifest_count=0 + reference_manifest="" + for manifest in artifacts/web-assets-*.json; do + test -f "$manifest" || { echo "FATAL: release web manifests are missing"; exit 1; } + manifest_count=$((manifest_count + 1)) + if [ -z "$reference_manifest" ]; then + reference_manifest="$manifest" + else + cmp "$reference_manifest" "$manifest" || { + echo "FATAL: web asset manifests differ: $reference_manifest and $manifest" + exit 1 + } + fi + done + test "$manifest_count" -eq 6 || { echo "FATAL: expected 6 platform manifests, found $manifest_count"; exit 1; } + echo "--- Validating all archives ---" for archive in artifacts/*.tar.gz; do echo "Checking $archive..." @@ -347,6 +477,37 @@ jobs: cat /tmp/smoke_out.txt VERSION="${GITHUB_REF#refs/tags/v}" grep -q "v${VERSION}" /tmp/smoke_out.txt || { echo "FATAL: extracted linux_amd64 binary version mismatch"; exit 1; } + + smoke_home=$(mktemp -d) + smoke_pid="" + cleanup_smoke() { + if [ -n "$smoke_pid" ]; then + kill "$smoke_pid" 2>/dev/null || true + wait "$smoke_pid" 2>/dev/null || true + fi + rm -rf "$smoke_home" + } + trap cleanup_smoke EXIT + printf '[server]\napi_port = 0\nbind_addr = "127.0.0.1"\n' > "$smoke_home/config.toml" + MSGVAULT_HOME="$smoke_home" "$tmpdir/msgvault" serve > "$smoke_home/serve.log" 2>&1 & + smoke_pid=$! + attempt=0 + smoke_url="" + while [ "$attempt" -lt 60 ]; do + attempt=$((attempt + 1)) + smoke_url=$(sed -n 's/^ API server: //p' "$smoke_home/serve.log" | tail -1) + if [ -n "$smoke_url" ] && curl -fsS "$smoke_url/" > "$smoke_home/index.html"; then + break + fi + sleep 0.25 + done + test -s "$smoke_home/index.html" || { cat "$smoke_home/serve.log"; echo "FATAL: embedded shell was not served"; exit 1; } + asset=$(grep -oE '/assets/[^" ]+\.(js|css)' "$smoke_home/index.html" | head -1) + test -n "$asset" || { cat "$smoke_home/index.html"; echo "FATAL: shell did not reference a hashed asset"; exit 1; } + curl -fsS "$smoke_url$asset" > "$smoke_home/asset" + test -s "$smoke_home/asset" || { echo "FATAL: embedded asset was empty"; exit 1; } + cleanup_smoke + trap - EXIT echo " linux_amd64 smoke: OK" fi @@ -370,7 +531,7 @@ jobs: - name: Generate checksums run: | cd artifacts - sha256sum *.tar.gz *.zip > SHA256SUMS + sha256sum -- ./*.tar.gz ./*.zip > SHA256SUMS cat SHA256SUMS - name: Get tag message @@ -381,7 +542,7 @@ jobs: TAG_TYPE=$(git cat-file -t "$TAG_NAME") if [ "$TAG_TYPE" != "tag" ]; then echo "Warning: $TAG_NAME is a lightweight tag, using auto-generated notes" - echo "has_body=false" >> $GITHUB_OUTPUT + echo "has_body=false" >> "$GITHUB_OUTPUT" exit 0 fi @@ -389,12 +550,14 @@ jobs: if [ -n "$TAG_MSG" ]; then DELIM="TAGMSG_$(date +%s%N)" - echo "body<<$DELIM" >> $GITHUB_OUTPUT - echo "$TAG_MSG" >> $GITHUB_OUTPUT - echo "$DELIM" >> $GITHUB_OUTPUT - echo "has_body=true" >> $GITHUB_OUTPUT + { + echo "body<<$DELIM" + echo "$TAG_MSG" + echo "$DELIM" + echo "has_body=true" + } >> "$GITHUB_OUTPUT" else - echo "has_body=false" >> $GITHUB_OUTPUT + echo "has_body=false" >> "$GITHUB_OUTPUT" fi - name: Create Release diff --git a/.gitignore b/.gitignore index cd4d8ce50..828e1f170 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,14 @@ oauth_client*.json # Python __pycache__/ +# Browser application dependencies and build output +/web/node_modules/ +/web/dist/ +/web/playwright-report/ +/web/test-results/ +/internal/web/dist/* +!/internal/web/dist/stub.html + # Go caches .gocache/ .gopath/ diff --git a/CLAUDE.md b/CLAUDE.md index c7a1de3c0..4ce79dda6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -192,12 +192,12 @@ The TUI automatically builds/updates the Parquet cache on launch when new messag - **Parquet ETL**: DuckDB-based SQLite → Parquet export with incremental updates - **Query Engine**: DuckDB over Parquet for fast aggregate analytics - **TUI**: Full-featured TUI with drill-down navigation, search, selection, deletion staging +- **Web UI**: First-party daemon-served analytical interface for cross-modality search, grouping, timelines, files, people/domains, Saved Views, source status, deletions, settings, and optional task links - **UTF-8 Repair**: Comprehensive encoding repair for all string fields - **Deletion Execution**: Execute staged deletions via Gmail API (trash or permanent delete) ### Not Yet Implemented - **App-level encryption**: Encrypt database and attachments at rest -- **Web UI**: Browser-based interface ## Testing with Real Gmail Data diff --git a/Dockerfile b/Dockerfile index ab2657e50..5018c39c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,17 @@ -# Build stage -# Pin by digest for reproducibility; update periodically +# Frontend build stage. Bun is present only here; the release image contains +# neither a JavaScript runtime nor a filesystem web distribution. +FROM oven/bun:1.3.14@sha256:e10577f0db68676a7024391c6e5cb4b879ebd17188ab750cf10024a6d700e5c4 AS web-builder + +WORKDIR /src +COPY web/package.json web/bun.lock ./web/ +RUN cd web && bun install --frozen-lockfile +COPY api/openapi.yaml ./api/openapi.yaml +COPY scripts/generate-web-client.mjs ./scripts/generate-web-client.mjs +COPY web/ ./web/ +RUN cd web && bun run generate && bun run build + +# Go build stage. +# Pin by digest for reproducibility; update periodically. FROM golang:1.26-bookworm@sha256:18aedc16aa19b3fd7ded7245fc14b109e054d65d22ed53c355c899582bbb2113 AS builder # Install build dependencies for CGO (SQLite, DuckDB) @@ -8,6 +20,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-ins g++ \ make \ git \ + libsqlite3-dev \ && rm -rf /var/lib/apt/lists/* WORKDIR /src @@ -18,6 +31,10 @@ RUN go mod download # Copy source and build COPY . . +# Never trust an ambient or host-staged frontend bundle. The pinned frontend +# stage is the sole source of release assets for this image build. +RUN find internal/web/dist -mindepth 1 -maxdepth 1 ! -name stub.html -exec rm -rf {} + +COPY --from=web-builder /src/web/dist/ ./internal/web/dist/ ARG VERSION=dev ARG COMMIT=unknown @@ -25,7 +42,7 @@ ARG BUILD_DATE=unknown # Note: Module path must match go.mod (go.kenn.io/msgvault) RUN CGO_ENABLED=1 go build \ - -tags fts5 \ + -tags "fts5 sqlite_vec" \ -trimpath \ -ldflags="-s -w \ -X go.kenn.io/msgvault/cmd/msgvault/cmd.Version=${VERSION} \ diff --git a/Makefile b/Makefile index 67507be16..d0f1994c6 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ BUILD_TAGS := fts5 sqlite_vec PG_TEST_TAGS := fts5 sqlite_vec pgvector OPENAPI_ARTIFACTS := api/openapi.yaml pkg/client/openapi.yaml pkg/client/generated +WEB_INSTALL_STAMP := web/node_modules/.msgvault-install-stamp # Keep golangci-lint results scoped to this git worktree. Its cache can contain # absolute source paths, so sharing the default user cache across worktrees can @@ -35,20 +36,20 @@ DEFAULT_GOLANGCI_LINT_CACHE := $(shell git rev-parse --path-format=absolute --gi GOLANGCI_LINT_CACHE ?= $(DEFAULT_GOLANGCI_LINT_CACHE) export GOLANGCI_LINT_CACHE -.PHONY: build build-release install clean test test-v test-pg fmt lint lint-ci testify-helper-check tidy openapi api-generate openapi-check api-check shootout run-shootout install-hooks bench docs-install docs-build docs-serve docs-check docs-screenshots docs-assets-branch docs-generated-assets-branch docs-deploy-staging docs-deploy help +.PHONY: build build-release install clean test test-v test-pg fmt lint lint-ci testify-helper-check tidy openapi api-generate openapi-check api-check web-install web-generate web-check web-test web-test-browser web-e2e web-build web-embed web-assets-check smoke-web-release shootout run-shootout install-hooks bench docs-install docs-build docs-serve docs-check docs-screenshots docs-assets-branch docs-generated-assets-branch docs-deploy-staging docs-deploy help # Build the binary (debug) -build: +build: web-embed CGO_ENABLED=1 go build -tags "$(BUILD_TAGS)" -ldflags="$(LDFLAGS)" -o msgvault ./cmd/msgvault @chmod +x msgvault # Build with optimizations (release) -build-release: +build-release: web-embed CGO_ENABLED=1 go build -tags "$(BUILD_TAGS)" -ldflags="$(LDFLAGS_RELEASE)" -trimpath -o msgvault ./cmd/msgvault @chmod +x msgvault # Install to ~/.local/bin, $GOBIN, or $GOPATH/bin -install: +install: web-embed @if [ -d "$(HOME)/.local/bin" ]; then \ echo "Installing to ~/.local/bin/msgvault"; \ CGO_ENABLED=1 go build -tags "$(BUILD_TAGS)" -ldflags="$(LDFLAGS)" -o "$(HOME)/.local/bin/msgvault" ./cmd/msgvault; \ @@ -96,6 +97,7 @@ api-generate: set -e; tmp="$$(mktemp)"; trap 'rm -f "$$tmp"' EXIT; go run ./cmd/msgvault openapi > "$$tmp"; if [ -f api/openapi.yaml ] && cmp -s "$$tmp" api/openapi.yaml; then rm "$$tmp"; else mv "$$tmp" api/openapi.yaml; fi; trap - EXIT set -e; tmp="$$(mktemp)"; trap 'rm -f "$$tmp"' EXIT; go run ./cmd/msgvault openapi --version 3.0 --format yaml > "$$tmp"; if [ -f pkg/client/openapi.yaml ] && cmp -s "$$tmp" pkg/client/openapi.yaml; then rm "$$tmp"; else mv "$$tmp" pkg/client/openapi.yaml; fi; trap - EXIT cd pkg/client/generated && find . -maxdepth 1 -type f -name '*.go' ! -name 'generate.go' -delete && go run github.com/doordash-oss/oapi-codegen-dd/v3/cmd/oapi-codegen@v3.75.5 -config config.yaml ../openapi.yaml + go run ./internal/codegenfix/cmd pkg/client/generated/types.go openapi-check: api-generate @git diff --exit-code -- $(OPENAPI_ARTIFACTS) || (echo "OpenAPI generated assets are stale; run 'make api-generate' and commit the changes." >&2; exit 1) @@ -109,6 +111,59 @@ api-check: openapi-check openapi: api-generate +# Install, generate, validate, test, and build the browser application. Web +# generation is intentionally separate from the Go-only OpenAPI targets so +# API client checks remain runnable on systems without Bun. +web-install: $(WEB_INSTALL_STAMP) + +$(WEB_INSTALL_STAMP): web/package.json web/bun.lock + cd web && bun install --frozen-lockfile + @touch $(WEB_INSTALL_STAMP) + +web-generate: web-install + cd web && bun run generate + +web-check: web-generate + @git diff --exit-code -- web/src/lib/api/generated/schema.d.ts || (echo "Web API generated types are stale; run 'make web-generate' and commit the changes." >&2; exit 1) + @if [ -n "$$(git status --porcelain --untracked-files=all -- web/src/lib/api/generated/schema.d.ts)" ]; then \ + git status --short --untracked-files=all -- web/src/lib/api/generated/schema.d.ts; \ + echo "Web API generated types are stale; run 'make web-generate' and commit the changes." >&2; \ + exit 1; \ + fi + cd web && bun run check + +web-test: + cd web && bun run test + +web-test-browser: + cd web && bun run test:browser + +# Task 20 browser gates use the same digest-pinned Playwright environment as +# web-test-browser in CI. Traces, screenshots, and video are retained only for +# failures by web/playwright.config.ts. +web-e2e: + cd web && bun run test:e2e + +web-build: web-generate + cd web && bun run build + +# Replace prior generated output while preserving the compilation stub, then +# copy Vite's complete production distribution into the Go embed tree. +web-embed: web-build + @mkdir -p internal/web/dist + @find internal/web/dist -mindepth 1 -maxdepth 1 ! -name stub.html -exec rm -rf {} + + @cp -R web/dist/. internal/web/dist/ + +# Validate Vite's parsed release graph against the staged embed. The node test +# drives the same validator through missing, escaping, external, and stale cases. +web-assets-check: web-embed + node --test scripts/check-web-assets.test.mjs + node scripts/check-web-assets.mjs + +smoke-web-release: + node --test scripts/smoke-web-release.test.mjs + bash scripts/smoke-web-release.sh + # Format code fmt: go fmt ./... @@ -219,6 +274,15 @@ help: @echo " openapi - Regenerate OpenAPI specs and generated Go client" @echo " openapi-check - Check committed OpenAPI specs and generated Go client are up to date" @echo " api-check - Alias for openapi-check" + @echo " web-install - Install pinned browser application dependencies" + @echo " web-generate - Regenerate browser API types from the OpenAPI schema" + @echo " web-check - Check browser types and generated API artifacts" + @echo " web-test - Run browser application unit tests" + @echo " web-test-browser - Run browser application Playwright tests" + @echo " web-build - Build the browser application" + @echo " web-embed - Build and stage browser assets for Go embedding" + @echo " web-assets-check - Validate the complete release asset graph and staged embed" + @echo " smoke-web-release - Build and exercise an isolated release-style daemon" @echo " install-hooks - Install pre-commit hook via prek" @echo " clean - Remove build artifacts" @echo "" diff --git a/README.md b/README.md index bc69ccbab..2d234f44e 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ export formats. - **IMAP sync**: archive mail from any standard IMAP server - **Incremental backup snapshots**: verifiable `msgvault backup` repositories for the SQLite archive and attachments - **MBOX / Apple Mail / PST import**: import email from local export formats +- **First-party web UI**: dense, keyboard-driven search, grouping, people/domain, file, source, and deletion workspaces served directly by the daemon - **Interactive TUI**: drill-down analytics over your entire message history, powered by DuckDB over Parquet — connects to a remote `msgvault serve` instance or runs locally - **Full-text search**: FTS5 with Gmail-like query syntax (`from:`, `has:attachment`, date ranges) - **MCP server**: access your full archive at the speed of thought in Claude Desktop and other MCP-capable AI agents @@ -61,7 +62,8 @@ powershell -ExecutionPolicy ByPass -c "irm https://msgvault.io/install.ps1 | iex The installer detects your OS and architecture, downloads the latest release from [GitHub Releases](https://github.com/kenn-io/msgvault/releases), verifies the SHA-256 checksum, and installs the binary. You can review the script ([bash](https://msgvault.io/install.sh), [PowerShell](https://msgvault.io/install.ps1)) before running, or download a release binary directly from GitHub. -To build from source instead (requires **Go 1.26+** and a C/C++ compiler for CGO and to statically link DuckDB): +To build from source instead (requires **Go 1.26+**, **Bun 1.3.14+**, and a +C/C++ compiler for CGO and to statically link DuckDB): ```bash git clone https://github.com/kenn-io/msgvault.git @@ -87,9 +89,15 @@ conda install -c conda-forge msgvault msgvault init-db msgvault add-account you@gmail.com # opens browser for OAuth msgvault sync-full you@gmail.com --limit 100 -msgvault tui +msgvault serve ``` +Open the `API server` URL printed by `msgvault serve`. The same release binary +serves the complete browser application; Node, Bun, and a separate asset +directory are not needed at runtime. See the [Web UI guide](docs/web-ui.md) for +search modes, keyboard controls, and secure remote access. The TUI remains +available with `msgvault tui`. + ## Commands | Command | Description | @@ -378,7 +386,7 @@ msgvault daemon restart Archive-access CLI commands use the HTTP API by default. If `[remote].url` is configured, the CLI talks to that remote server. Otherwise, it discovers or starts the local background daemon instead of opening the SQLite database itself. This keeps local and remote CLI behavior aligned and avoids repeated startup cost on large archives. Use `--local` to force the local daemon when a remote server is configured. -The server exposes its generated OpenAPI document at `/openapi.json` and interactive API docs at `/docs`. +The server exposes the first-party analytical web UI at `/`, its generated OpenAPI document at `/openapi.json`, and interactive API docs at `/docs`. Configure scheduled syncs in `config.toml`: @@ -400,7 +408,7 @@ api_key = "your-secret-key" daemon_idle_timeout = "20m" # background daemon idle timeout; "0s" disables ``` -`daemon_idle_timeout` applies to lifecycle-managed background daemons started by `msgvault daemon start` or auto-started by a CLI command. A foreground `msgvault serve` keeps running until you stop it. See the [Web Server reference](https://msgvault.io/api-server/) or `/openapi.json` on a running server for the HTTP API. +`daemon_idle_timeout` applies to lifecycle-managed background daemons started by `msgvault daemon start` or auto-started by a CLI command. A foreground `msgvault serve` keeps running until you stop it. See the [Web UI & API Server reference](https://msgvault.io/api-server/) or `/openapi.json` on a running server for the HTTP API. ## Documentation @@ -409,6 +417,7 @@ daemon_idle_timeout = "20m" # background daemon idle timeout; "0s" disables - [Search ranking across backends](https://msgvault.io/architecture/search-ranking/): how result order differs between SQLite and PostgreSQL - [PostgreSQL backend](https://msgvault.io/architecture/postgresql/): run msgvault on PostgreSQL with pgvector semantic/hybrid search - [Interactive TUI](https://msgvault.io/usage/tui/): keybindings, views, deletion staging +- [Web UI](docs/web-ui.md): analytical browser interface and deployment security - [Discord](https://msgvault.io/usage/discord/): guild bot setup, sync behavior, filters, and media backfill - [CLI Reference](https://msgvault.io/cli-reference/): all commands and flags - [Multi-Account](https://msgvault.io/usage/multi-account/): managing multiple Gmail accounts diff --git a/api/openapi.yaml b/api/openapi.yaml index c9bf3ce6b..61aac0a77 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -91,6 +91,11 @@ components: properties: account: type: string + cache_state: + enum: + - ready + - stale + type: string identifier: type: string outcome: @@ -1101,6 +1106,36 @@ components: required: - stats type: object + ConversationResponse: + additionalProperties: true + properties: + anchor_id: + format: int64 + type: integer + has_after: + type: boolean + has_before: + type: boolean + id: + format: int64 + type: integer + messages: + items: + $ref: "#/components/schemas/MessageDetail" + type: + - array + - "null" + total: + format: int64 + type: integer + required: + - id + - anchor_id + - messages + - has_before + - has_after + - total + type: object CreateRequest: additionalProperties: false properties: @@ -1116,6 +1151,23 @@ components: - name - accounts type: object + CreateSavedViewRequest: + additionalProperties: false + properties: + canonical_state: + $ref: "#/components/schemas/SavedViewStateEnvelope" + description: + type: string + name: + type: string + schema_version: + format: int64 + type: integer + required: + - name + - canonical_state + - schema_version + type: object DeepSearchResponse: additionalProperties: true properties: @@ -1154,6 +1206,37 @@ components: - offset - limit type: object + DeletionManifestDetail: + additionalProperties: true + properties: + account: + type: string + created_at: + format: date-time + type: string + created_by: + type: string + description: + type: string + execution: + $ref: "#/components/schemas/Execution" + id: + type: string + message_count: + format: int64 + type: integer + status: + type: string + summary: + $ref: "#/components/schemas/Summary" + required: + - id + - status + - created_at + - created_by + - description + - message_count + type: object DeletionManifestSummary: additionalProperties: true properties: @@ -1179,6 +1262,166 @@ components: - description - message_count type: object + DomainContextSummaryHTTPResponse: + additionalProperties: true + properties: + cache_revision: + type: string + candidate_snapshot_id: + type: string + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + summary: + $ref: "#/components/schemas/DomainSummary" + required: + - summary + - cache_revision + - search_provenance + type: object + DomainSearchHTTPResponse: + additionalProperties: true + properties: + cache_revision: + type: string + candidate_snapshot_id: + type: string + next_cursor: + type: string + rows: + items: + $ref: "#/components/schemas/DomainSummary" + type: + - array + - "null" + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: + format: int64 + type: integer + required: + - rows + - total_count + - cache_revision + - search_provenance + type: object + DomainSummary: + additionalProperties: true + properties: + activity_count: + format: int64 + type: integer + cache_revision: + type: string + domain: + type: string + file_count: + format: int64 + type: integer + first_at: + format: date-time + type: string + last_at: + format: date-time + type: string + person_count: + format: int64 + type: integer + source_counts: + items: + $ref: "#/components/schemas/SourceCount" + type: + - array + - "null" + required: + - domain + - activity_count + - person_count + - file_count + - source_counts + - first_at + - last_at + - cache_revision + type: object + EntryRow: + additionalProperties: true + properties: + anchor_message_id: + format: int64 + type: integer + attachment_count: + format: int64 + type: integer + attachment_size: + format: int64 + type: integer + conversation_id: + format: int64 + type: integer + conversation_type: + type: string + counterpart_participant_id: + format: int64 + type: integer + deleted_from_source: + type: boolean + has_attachments: + type: boolean + key: + type: string + kind: + type: string + match: + $ref: "#/components/schemas/MatchSummary" + message_count: + format: int64 + type: integer + message_type: + type: string + occurred_at: + format: date-time + type: string + participant_ids: + items: + format: int64 + type: integer + type: + - array + - "null" + participant_labels: + items: + type: string + type: + - array + - "null" + preview: + type: string + source_id: + format: int64 + type: integer + source_identifier: + type: string + source_type: + type: string + title: + type: string + required: + - key + - kind + - occurred_at + - match + - source_id + - source_type + - source_identifier + - message_type + - conversation_type + - title + - preview + - message_count + - has_attachments + - attachment_count + - attachment_size + - deleted_from_source + type: object ErrorResponse: additionalProperties: true properties: @@ -1222,1267 +1465,4471 @@ components: - failed - last_processed_index type: object - FilteredMessagesResponse: + ExploreActionTarget: additionalProperties: true properties: - count: - format: int64 - type: integer - has_more: - type: boolean - limit: - format: int64 - type: integer - messages: - items: - $ref: "#/components/schemas/MessageSummary" - type: - - array - - "null" - offset: + action: + type: string + filename: + type: string + message_id: format: int64 type: integer required: - - count - - has_more - - offset - - limit - - messages + - action + - message_id + - filename type: object - Filters: - additionalProperties: false + ExploreCacheUnavailableResponse: + additionalProperties: true properties: - account: + error: type: string - after: + message: type: string - before: + readiness: + enum: + - absent + - interrupted + - stale_schema + - drifted type: string - labels: - items: - type: string - type: - - array - - "null" - recipients: - items: - type: string - type: - - array - - "null" - sender_domains: - items: - type: string - type: - - array - - "null" - senders: - items: - type: string - type: - - array - - "null" + recovery_action: + type: string + required: + - error + - message + - readiness + - recovery_action type: object - GenerationSummary: + ExploreFileFact: additionalProperties: true properties: - activated_at: - type: string - dimension: + conversation_id: format: int64 type: integer - fingerprint: + entry_key: + type: string + filename: type: string id: format: int64 type: integer - message_count: + key: + type: string + message_id: format: int64 type: integer - model: + mime_type: type: string - state: + occurred_at: + format: date-time + type: string + size: + format: int64 + type: integer + source_id: + format: int64 + type: integer + source_identifier: + type: string + title: type: string required: - id - - model - - dimension - - fingerprint - - state - - message_count + - key + - entry_key + - message_id + - conversation_id + - occurred_at + - source_id + - source_identifier + - title + - filename + - mime_type + - size type: object - GmailIDsResponse: - additionalProperties: true + ExploreFilesHTTPRequest: + additionalProperties: false properties: - gmail_ids: - items: - type: string - type: - - array - - "null" + cursor: + type: string + limit: + format: int64 + maximum: 100 + minimum: 0 + type: integer + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" required: - - gmail_ids + - predicate type: object - HealthResponse: + ExploreFilesHTTPResponse: additionalProperties: true properties: - analytics_engine: + cache_revision: type: string - operation: - $ref: "#/components/schemas/OperationHealth" - status: + candidate_snapshot_id: type: string - vector: - $ref: "#/components/schemas/VectorHealth" + files: + items: + $ref: "#/components/schemas/ExploreFileFact" + type: array + next_cursor: + type: string + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: + format: int64 + type: integer required: - - status + - files + - total_count + - cache_revision + - search_provenance type: object - HybridGenerationSummary: - additionalProperties: true + ExploreFilter: + additionalProperties: false properties: dimension: + enum: + - source + - participant + - domain + - message_type + - after + - before + - deletion + type: string + values: + items: + type: string + minItems: 1 + type: array + required: + - dimension + - values + type: object + ExploreGroupDimension: + enum: + - source + - participant + - domain + - message_type + - kind + - year + - month + type: string + ExploreGroupRow: + additionalProperties: true + properties: + count: format: int64 type: integer - fingerprint: - type: string - id: + estimated_bytes: format: int64 type: integer - model: + key: type: string - state: + label: + type: string + latest_at: + format: date-time type: string required: - - id - - model - - dimension - - fingerprint - - state + - key + - label + - count + - estimated_bytes + - latest_at type: object - HybridSearchItem: - additionalProperties: true + ExploreGroupSort: + additionalProperties: false properties: - bcc: + direction: + enum: + - asc + - desc + type: string + field: + enum: + - key + - count + - estimated_bytes + - latest_at + type: string + required: + - field + - direction + type: object + ExploreGroupsHTTPRequest: + additionalProperties: false + properties: + cursor: + type: string + filters: items: - type: string + $ref: "#/components/schemas/ExploreFilter" type: - array - "null" - cc: + grouping: items: - type: string - type: - - array - - "null" - conversation_id: + $ref: "#/components/schemas/ExploreGroupDimension" + maxItems: 1 + minItems: 1 + type: array + limit: format: int64 + maximum: 500 + minimum: 0 type: integer - deleted_at: + presentation: + enum: + - table type: string - from: + query: type: string - from_email: + search_mode: + enum: + - full_text + - semantic + - hybrid type: string - from_name: + sort: + items: + $ref: "#/components/schemas/ExploreGroupSort" + maxItems: 1 + type: + - array + - "null" + required: + - grouping + type: object + ExploreGroupsHTTPResponse: + additionalProperties: true + properties: + cache_revision: type: string - from_phone: + candidate_snapshot_id: type: string - has_attachments: - type: boolean - id: + next_cursor: + type: string + rows: + items: + $ref: "#/components/schemas/ExploreGroupRow" + type: array + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: format: int64 type: integer - labels: + required: + - rows + - total_count + - cache_revision + - search_provenance + type: object + ExploreHTTPRequest: + additionalProperties: false + properties: + candidate_snapshot_id: + type: string + cursor: + type: string + filters: items: - type: string + $ref: "#/components/schemas/ExploreFilter" type: - array - "null" - matches: + grouping: items: - $ref: "#/components/schemas/HybridSearchMatch" + $ref: "#/components/schemas/ExploreGroupDimension" type: - array - "null" - matches_truncated: - type: boolean - message_type: - type: string - score: - $ref: "#/components/schemas/ScoreBreakdown" - sent_at: - type: string - size_bytes: + limit: format: int64 + maximum: 500 + minimum: 0 type: integer - snippet: + presentation: + enum: + - table + - timeline + - files type: string - source_id: - format: int64 - type: integer - source_message_id: + query: type: string - subject: + search_mode: + enum: + - full_text + - semantic + - hybrid type: string - to: + sort: items: - type: string + $ref: "#/components/schemas/ExploreSort" type: - array - "null" - required: - - id - - subject - - from - - to - - sent_at - - snippet - - labels - - has_attachments - - size_bytes type: object - HybridSearchMatch: + ExploreHTTPResponse: additionalProperties: true properties: - char_offset: - format: int64 - type: integer - line: + cache_revision: + type: string + candidate_pool_saturated: + type: boolean + candidate_snapshot_id: + type: string + next_cursor: + type: string + rows: + items: + $ref: "#/components/schemas/EntryRow" + type: array + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: format: int64 type: integer - score: - format: double - type: number - snippet: - type: string required: - - snippet - - score + - rows + - cache_revision + - search_provenance type: object - HybridSearchResponse: + ExploreMatchCountsRequest: + additionalProperties: false + properties: + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" + row_keys: + items: + type: string + maxItems: 500 + minItems: 1 + type: array + required: + - predicate + - row_keys + type: object + ExploreMatchCountsResponse: additionalProperties: true properties: - generation: - $ref: "#/components/schemas/HybridGenerationSummary" - has_more: - type: boolean - mode: + cache_revision: type: string - pool_saturated: - type: boolean - query: + canonical_query_hash: type: string - results: + counts: items: - $ref: "#/components/schemas/HybridSearchItem" - type: - - array - - "null" - returned: - format: int64 - type: integer - scope_label: + $ref: "#/components/schemas/ExploreRowMatchCount" + type: array + lexical_index_revision: type: string - scope_source_count: + required: + - counts + - cache_revision + - lexical_index_revision + - canonical_query_hash + type: object + ExplorePreflightRequest: + additionalProperties: false + properties: + selection: + $ref: "#/components/schemas/ExploreSelection" + required: + - selection + type: object + ExplorePreflightResponse: + additionalProperties: true + properties: + action_targets: + items: + $ref: "#/components/schemas/ExploreActionTarget" + type: array + cache_revision: + type: string + count: format: int64 type: integer - took_ms: + estimated_bytes: format: int64 type: integer + expires_at: + format: date-time + type: string + operation_token: + type: string + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + unavailable_actions: + items: + $ref: "#/components/schemas/ExploreUnavailableAction" + type: array required: - - query - - mode - - returned - - pool_saturated - - has_more - - generation - - took_ms - - results + - count + - estimated_bytes + - cache_revision + - search_provenance + - unavailable_actions + - action_targets + - operation_token + - expires_at type: object - ListDeletionsResponse: + ExploreRowMatchCount: additionalProperties: true properties: - manifests: - items: - $ref: "#/components/schemas/DeletionManifestSummary" - type: - - array - - "null" + count: + format: int64 + type: integer + row_key: + type: string required: - - manifests + - row_key + - count type: object - Manifest: + ExploreSelection: additionalProperties: false properties: - created_at: - format: date-time - type: string - created_by: + cache_revision: type: string - description: + candidate_snapshot_id: type: string - execution: - $ref: "#/components/schemas/Execution" - filters: - $ref: "#/components/schemas/Filters" - gmail_ids: + exclusions: items: type: string type: - array - "null" - id: + mode: + enum: + - explicit + - all_matching type: string - raw_filter: {} - status: + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" + row_keys: + items: + type: string + type: + - array + - "null" + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + required: + - mode + - predicate + - cache_revision + - search_provenance + type: object + ExploreSort: + additionalProperties: false + properties: + direction: + enum: + - desc + type: string + field: + enum: + - occurred_at type: string - summary: - $ref: "#/components/schemas/Summary" - version: - format: int64 - type: integer required: - - version - - id - - created_at - - created_by - - description - - filters - - gmail_ids - - status + - field + - direction type: object - MessageDetail: + ExploreUnavailableAction: additionalProperties: true properties: - attachments: + action: + type: string + reason: + type: string + required: + - action + - reason + type: object + FileGroupsHTTPRequest: + additionalProperties: false + properties: + cursor: + type: string + filename_query: + type: string + grouping: items: - $ref: "#/components/schemas/AttachmentInfo" + $ref: "#/components/schemas/ExploreGroupDimension" + maxItems: 1 + minItems: 1 + type: array + limit: + format: int64 + maximum: 500 + minimum: 0 + type: integer + mime_families: + items: + type: string type: - array - "null" - bcc: + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" + sort: items: - type: string + $ref: "#/components/schemas/ExploreGroupSort" + maxItems: 1 type: - array - "null" - body: + required: + - predicate + - grouping + type: object + FileGroupsHTTPResponse: + additionalProperties: true + properties: + cache_revision: type: string - body_html: + candidate_snapshot_id: type: string - cc: + next_cursor: + type: string + rows: items: - type: string - type: - - array - - "null" - conversation_id: + $ref: "#/components/schemas/ExploreGroupRow" + type: array + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: format: int64 type: integer - deleted_at: - type: string - from: - type: string - from_email: + required: + - rows + - total_count + - cache_revision + - search_provenance + type: object + FileMetadataResponse: + additionalProperties: true + properties: + content_available: + type: boolean + content_hash: type: string - from_name: + content_state: + enum: + - metadata_only + - url_only + - missing_blob + - local_content type: string - from_phone: + conversation_id: + format: int64 + type: integer + filename: type: string - has_attachments: - type: boolean id: format: int64 type: integer - labels: - items: - type: string - type: - - array - - "null" - message_type: - type: string - sent_at: - type: string - size_bytes: + message_id: format: int64 type: integer - snippet: + mime_type: type: string - source_id: + size_bytes: format: int64 type: integer - source_message_id: + url: type: string - subject: + required: + - id + - message_id + - conversation_id + - filename + - mime_type + - size_bytes + - content_state + - content_available + type: object + FileSearchHTTPRequest: + additionalProperties: false + properties: + cursor: type: string - to: + filename_query: + type: string + limit: + format: int64 + maximum: 500 + minimum: 0 + type: integer + mime_families: items: type: string type: - array - "null" + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" + sort: + $ref: "#/components/schemas/FileSearchSort" required: - - body - - attachments - - id - - subject - - from - - to - - sent_at - - snippet - - labels - - has_attachments - - size_bytes + - predicate + - sort type: object - MessageListResponse: + FileSearchHTTPResponse: additionalProperties: true properties: - messages: + cache_revision: + type: string + candidate_snapshot_id: + type: string + files: items: - $ref: "#/components/schemas/MessageSummary" + $ref: "#/components/schemas/FileSearchRow" type: - array - "null" - page: - format: int64 - type: integer - page_size: - format: int64 - type: integer - total: + next_cursor: + type: string + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: format: int64 type: integer required: - - total - - page - - page_size - - messages + - files + - total_count + - cache_revision + - search_provenance type: object - MessageSummary: + FileSearchRow: additionalProperties: true properties: - bcc: + containing_title: + type: string + content_available: + type: boolean + content_state: + enum: + - metadata_only + - url_only + - missing_blob + - local_content + type: string + conversation_id: + format: int64 + type: integer + entry_key: + type: string + filename: + type: string + id: + format: int64 + type: integer + key: + type: string + message_id: + format: int64 + type: integer + mime_family: + type: string + mime_type: + type: string + occurred_at: + format: date-time + type: string + participant_domains: items: type: string type: - array - "null" - cc: + participant_ids: items: - type: string + format: int64 + type: integer type: - array - "null" - conversation_id: - format: int64 - type: integer - deleted_at: - type: string - from: - type: string - from_email: - type: string - from_name: - type: string - from_phone: - type: string - has_attachments: - type: boolean - id: - format: int64 - type: integer - labels: + participant_labels: items: type: string type: - array - "null" - message_type: - type: string - sent_at: - type: string size_bytes: format: int64 type: integer - snippet: - type: string source_id: format: int64 type: integer - source_message_id: + source_identifier: type: string - subject: + source_type: type: string - to: - items: - type: string - type: - - array - - "null" required: - id - - subject - - from - - to - - sent_at - - snippet - - labels - - has_attachments + - key + - entry_key + - message_id + - conversation_id + - occurred_at + - source_id + - source_type + - source_identifier + - containing_title + - filename + - mime_type + - mime_family - size_bytes + - content_state + - content_available type: object - MutationResult: - additionalProperties: true - properties: - name: - type: string - source_count: - format: int64 - type: integer - required: - - name - type: object - OperationHealth: - additionalProperties: true + FileSearchSort: + additionalProperties: false properties: - busy: - type: boolean - label: + direction: type: string - started_at: - format: date-time + field: type: string required: - - busy + - field + - direction type: object - PingInfo: + FilteredMessagesResponse: additionalProperties: true properties: - ok: + count: + format: int64 + type: integer + has_more: type: boolean - pid: + limit: format: int64 type: integer - service: - type: string - version: - type: string - required: - - ok - type: object - Progress: - additionalProperties: true - properties: - done: - format: int64 - type: integer - total: - format: int64 - type: integer - required: - - done - - total - type: object - QueryRequest: - additionalProperties: false - properties: - sql: - type: string - required: - - sql - type: object - QueryResult: - additionalProperties: true - properties: - columns: + messages: items: - type: string + $ref: "#/components/schemas/MessageSummary" type: - array - "null" - row_count: + offset: format: int64 type: integer - rows: - items: - items: {} - type: - - array - - "null" - type: - - array - - "null" required: - - columns - - rows - - row_count + - count + - has_more + - offset + - limit + - messages type: object - RemoveRequest: + Filters: additionalProperties: false properties: account: type: string - identifier: - type: string - required: - - account - - identifier - type: object - RemoveResult: - additionalProperties: true - properties: - account: + after: type: string - identifier: + before: type: string - no_identity: - type: boolean - removed: - format: int64 - type: integer - required: - - account - - identifier - - removed - type: object - SchedulerStatusResponse: - additionalProperties: true - properties: - accounts: + labels: items: - $ref: "#/components/schemas/AccountStatus" + type: string type: - array - "null" - running: - type: boolean - required: - - running - - accounts - type: object - ScoreBreakdown: - additionalProperties: true - properties: - bm25: - format: double - type: number - rrf: - format: double - type: number - subject_boosted: - type: boolean - vector: - format: double - type: number - type: object - SearchFastResponse: - additionalProperties: true - properties: - applied_source_ids: + recipients: items: - format: int64 - type: integer + type: string type: - array - "null" - messages: + sender_domains: items: - $ref: "#/components/schemas/MessageSummary" + type: string type: - array - "null" - query: - type: string - stats: - $ref: "#/components/schemas/TotalStatsResponse" - total_count: - format: int64 - type: integer - required: - - query - - messages - - total_count - type: object - SearchResult: - additionalProperties: true - properties: - messages: + senders: items: - $ref: "#/components/schemas/MessageSummary" + type: string type: - array - "null" - page: - format: int64 - type: integer - page_size: + type: object + GenerationSummary: + additionalProperties: true + properties: + activated_at: + type: string + dimension: format: int64 type: integer - query: + fingerprint: type: string - total: + id: format: int64 type: integer - required: - - query - - total - - page - - page_size - - messages - type: object - SenderCount: - additionalProperties: false - properties: - count: + message_count: format: int64 type: integer - sender: + model: + type: string + state: type: string required: - - sender - - count + - id + - model + - dimension + - fingerprint + - state + - message_count type: object - SimilarSearchResponse: + GmailIDsResponse: additionalProperties: true properties: - generation: - $ref: "#/components/schemas/HybridGenerationSummary" - messages: + gmail_ids: items: - $ref: "#/components/schemas/MessageSummary" + type: string type: - array - "null" - returned: - format: int64 - type: integer - seed_message_id: - format: int64 - type: integer required: - - seed_message_id - - returned - - generation - - messages + - gmail_ids type: object - SourceStatus: + HealthResponse: additionalProperties: true properties: - active_sync: - $ref: "#/components/schemas/SyncRunStatus" - display_name: - type: - - string - - "null" - id: + analytics_engine: + type: string + operation: + $ref: "#/components/schemas/OperationHealth" + status: + type: string + vector: + $ref: "#/components/schemas/VectorHealth" + required: + - status + type: object + HybridGenerationSummary: + additionalProperties: true + properties: + dimension: format: int64 type: integer - identifier: + fingerprint: type: string - last_successful_sync: - $ref: "#/components/schemas/SyncRunStatus" - last_sync_at: - type: - - string - - "null" - latest_sync: - $ref: "#/components/schemas/SyncRunStatus" - source_type: + id: + format: int64 + type: integer + model: type: string - updated_at: + state: type: string required: - id - - source_type - - identifier - - display_name - - last_sync_at - - updated_at - - active_sync - - latest_sync - - last_successful_sync + - model + - dimension + - fingerprint + - state type: object - SourceStatusResponse: + HybridSearchItem: additionalProperties: true properties: - sources: + bcc: items: - $ref: "#/components/schemas/SourceStatus" + type: string type: - array - "null" - required: - - sources - type: object - SourcesRequest: - additionalProperties: false - properties: - accounts: + cc: items: type: string type: - array - "null" - required: - - accounts - type: object - StageDeletionFilter: - additionalProperties: false - properties: - after: - type: string - before: - type: string - domain: - type: string - label: + conversation_id: + format: int64 + type: integer + deleted_at: type: string - recipient: + from: type: string - recipient_name: + from_email: type: string - sender: + from_name: type: string - sender_name: + from_phone: type: string - source_id: + has_attachments: + type: boolean + id: format: int64 type: integer - type: object - StageDeletionRequest: - additionalProperties: false - properties: - description: - type: string - dry_run: - type: boolean - filter: - $ref: "#/components/schemas/StageDeletionFilter" - message_ids: + labels: items: - format: int64 - type: integer + type: string type: - array - "null" - type: object - StageDeletionResponse: - additionalProperties: true - properties: - account: - type: string - dry_run: + matches: + items: + $ref: "#/components/schemas/HybridSearchMatch" + type: + - array + - "null" + matches_truncated: type: boolean - id: + message_type: type: string - message_count: + score: + $ref: "#/components/schemas/ScoreBreakdown" + sent_at: + type: string + size_bytes: format: int64 type: integer - sample_gmail_ids: + snippet: + type: string + source_id: + format: int64 + type: integer + source_message_id: + type: string + subject: + type: string + to: items: type: string type: - array - "null" - status: - type: string required: - - dry_run - - message_count + - id + - subject + - from + - to + - sent_at + - snippet + - labels + - has_attachments + - size_bytes type: object - StatsResponse: + HybridSearchMatch: additionalProperties: true properties: - active_messages: - format: int64 - type: integer - database_size_bytes: + char_offset: format: int64 type: integer - source_deleted_messages: + line: format: int64 type: integer - total_accounts: + score: + format: double + type: number + snippet: + type: string + required: + - snippet + - score + type: object + HybridSearchResponse: + additionalProperties: true + properties: + generation: + $ref: "#/components/schemas/HybridGenerationSummary" + has_more: + type: boolean + mode: + type: string + pool_saturated: + type: boolean + query: + type: string + results: + items: + $ref: "#/components/schemas/HybridSearchItem" + type: + - array + - "null" + returned: format: int64 type: integer - total_attachments: + scope_label: + type: string + scope_source_count: format: int64 type: integer - total_labels: + took_ms: format: int64 type: integer - total_messages: + required: + - query + - mode + - returned + - pool_saturated + - has_more + - generation + - took_ms + - results + type: object + IdentityLinkRequest: + additionalProperties: false + properties: + participant_a: format: int64 type: integer - total_threads: + participant_b: format: int64 type: integer - vector_search: - $ref: "#/components/schemas/StatsView" - vector_status: - type: string required: - - total_messages - - active_messages - - source_deleted_messages - - total_threads - - total_accounts - - total_labels - - total_attachments - - database_size_bytes + - participant_a + - participant_b type: object - StatsView: + IdentityLinkResponse: additionalProperties: true properties: - active_generation: - $ref: "#/components/schemas/GenerationSummary" - building_generation: - $ref: "#/components/schemas/BuildingSummary" - enabled: - type: boolean - missing_embeddings_total: + cache_state: + enum: + - ready + - stale + type: string + identity_revision: format: int64 type: integer required: - - enabled - - active_generation - - missing_embeddings_total + - identity_revision + - cache_state type: object - StatusMessageResponse: - additionalProperties: true + IdentitySearchHTTPRequest: + additionalProperties: false properties: - message: + cursor: type: string - status: + identity_query: type: string + limit: + format: int64 + maximum: 500 + minimum: 0 + type: integer + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" + sort: + $ref: "#/components/schemas/IdentitySearchSort" required: - - status - - message + - predicate + - sort type: object - Summary: + IdentitySearchSort: additionalProperties: false properties: - accounts: + direction: + enum: + - asc + - desc + type: string + field: + enum: + - activity_count + - latest_at + - display_label + type: string + required: + - field + - direction + type: object + ListDeletionsResponse: + additionalProperties: true + properties: + manifests: items: - type: string + $ref: "#/components/schemas/DeletionManifestSummary" type: - array - "null" - date_range: + required: + - manifests + type: object + Manifest: + additionalProperties: false + properties: + created_at: + format: date-time + type: string + created_by: + type: string + description: + type: string + execution: + $ref: "#/components/schemas/Execution" + filters: + $ref: "#/components/schemas/Filters" + gmail_ids: items: type: string - maxItems: 2 - minItems: 2 - type: - - array - - "null" - message_count: - format: int64 - type: integer - top_senders: - items: - $ref: "#/components/schemas/SenderCount" type: - array - "null" - total_size_bytes: + id: + type: string + raw_filter: {} + status: + type: string + summary: + $ref: "#/components/schemas/Summary" + version: format: int64 type: integer required: - - message_count - - total_size_bytes - - date_range - - accounts - - top_senders + - version + - id + - created_at + - created_by + - description + - filters + - gmail_ids + - status type: object - SyncRunItemStatus: + MatchSummary: additionalProperties: true properties: - created_at: - type: string - error_kind: - type: string - error_message: + lexical_match_count: + format: int64 + type: integer + semantic_score: + format: double + type: number + strongest_excerpt: type: string - phase: - type: string - source_message_id: - type: string - required: - - source_message_id - - phase - - error_kind - - error_message - - created_at type: object - SyncRunStatus: + MessageDetail: additionalProperties: true properties: - completed_at: - type: - - string - - "null" - cursor_after: + attachments: + items: + $ref: "#/components/schemas/AttachmentInfo" type: - - string + - array - "null" - cursor_before: + bcc: + items: + type: string type: - - string + - array - "null" - error_message: + body: + type: string + body_html: + type: string + cc: + items: + type: string type: - - string + - array - "null" - errors_count: + conversation_id: format: int64 type: integer + deleted_at: + type: string + from: + type: string + from_email: + type: string + from_name: + type: string + from_phone: + type: string + has_attachments: + type: boolean id: format: int64 type: integer - item_errors: + labels: items: - $ref: "#/components/schemas/SyncRunItemStatus" + type: string type: - array - "null" - messages_added: - format: int64 - type: integer - messages_processed: - format: int64 - type: integer - messages_updated: - format: int64 - type: integer - skipped_count: - format: int64 - type: integer - source_id: - format: int64 - type: integer - started_at: + message_type: type: string - status: + sent_at: type: string - required: - - id - - source_id - - started_at - - completed_at - - status - - messages_processed - - messages_added - - messages_updated - - errors_count - - error_message - - cursor_before - - cursor_after - type: object - TextConversationRow: - additionalProperties: true - properties: - conversation_id: + size_bytes: format: int64 type: integer - last_message_at: - type: string - last_preview: + snippet: type: string - message_count: - format: int64 - type: integer - participant_count: + source_id: format: int64 type: integer - source_type: + source_message_id: type: string - title: + subject: type: string + to: + items: + type: string + type: + - array + - "null" required: - - conversation_id - - title - - source_type - - message_count - - participant_count - - last_preview + - body + - attachments + - id + - subject + - from + - to + - sent_at + - snippet + - labels + - has_attachments + - size_bytes type: object - TextConversationsResponse: + MessageListResponse: additionalProperties: true properties: - conversations: + messages: items: - $ref: "#/components/schemas/TextConversationRow" + $ref: "#/components/schemas/MessageSummary" type: - array - "null" - count: + page: format: int64 type: integer - has_more: - type: boolean - limit: + page_size: format: int64 type: integer - offset: + total: format: int64 type: integer required: - - count - - has_more - - offset - - limit - - conversations + - total + - page + - page_size + - messages type: object - TextMessagesResponse: + MessageSummary: additionalProperties: true properties: - count: - format: int64 - type: integer - has_more: - type: boolean - limit: - format: int64 - type: integer - messages: + bcc: items: - $ref: "#/components/schemas/CLIQueryMessageSummary" + type: string type: - array - "null" - offset: + cc: + items: + type: string + type: + - array + - "null" + conversation_id: format: int64 type: integer - required: - - count - - has_more - - offset - - limit - - messages - type: object - TokenUploadRequest: - additionalProperties: false - properties: - access_token: + deleted_at: type: string - client_id: + from: type: string - expiry: - format: date-time + from_email: type: string - refresh_token: + from_name: type: string - scopes: + from_phone: + type: string + has_attachments: + type: boolean + id: + format: int64 + type: integer + labels: items: type: string type: - array - "null" - tenant_id: + message_type: type: string - token_type: + sent_at: type: string - required: - - refresh_token - type: object - TotalStatsResponse: - additionalProperties: true - properties: - account_count: + size_bytes: format: int64 type: integer - active_messages: + snippet: + type: string + source_id: format: int64 type: integer - applied_search_scope: - type: boolean - applied_source_ids: + source_message_id: + type: string + subject: + type: string + to: items: - format: int64 - type: integer + type: string type: - array - "null" - attachment_count: - format: int64 - type: integer - attachment_size: - format: int64 - type: integer - label_count: - format: int64 - type: integer - message_count: + required: + - id + - subject + - from + - to + - sent_at + - snippet + - labels + - has_attachments + - size_bytes + type: object + MutationResult: + additionalProperties: true + properties: + name: + type: string + source_count: format: int64 type: integer - source_deleted_messages: + required: + - name + type: object + OperationHealth: + additionalProperties: true + properties: + busy: + type: boolean + label: + type: string + started_at: + format: date-time + type: string + required: + - busy + type: object + PatchSavedViewRequest: + additionalProperties: false + properties: + canonical_state: + $ref: "#/components/schemas/SavedViewStateEnvelope" + description: + type: string + name: + type: string + schema_version: format: int64 type: integer - total_size: + type: object + PersonCluster: + additionalProperties: true + properties: + canonical_id: + format: int64 + type: integer + edges: + items: + $ref: "#/components/schemas/PersonClusterEdge" + type: + - array + - "null" + member_ids: + items: + format: int64 + type: integer + type: + - array + - "null" + required: + - canonical_id + - member_ids + - edges + type: object + PersonClusterEdge: + additionalProperties: true + properties: + participant_a: + format: int64 + type: integer + participant_b: format: int64 type: integer required: - - message_count - - active_messages - - source_deleted_messages - - total_size - - attachment_count - - attachment_size - - label_count - - account_count + - participant_a + - participant_b type: object - UpdateRequest: - additionalProperties: false + PersonContextSummaryHTTPResponse: + additionalProperties: true properties: - display_name: + cache_revision: type: string - email: + candidate_snapshot_id: type: string + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + summary: + $ref: "#/components/schemas/PersonSummary" required: - - email - - display_name + - summary + - cache_revision + - search_provenance type: object - UpdateResult: + PersonIdentifier: + additionalProperties: true + properties: + display_value: + type: string + is_primary: + type: boolean + participant_id: + format: int64 + type: integer + provenance: + type: string + type: + type: string + value: + type: string + required: + - type + - value + - is_primary + - provenance + - participant_id + type: object + PersonSearchHTTPResponse: + additionalProperties: true + properties: + cache_revision: + type: string + candidate_snapshot_id: + type: string + next_cursor: + type: string + rows: + items: + $ref: "#/components/schemas/PersonSummary" + type: + - array + - "null" + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: + format: int64 + type: integer + required: + - rows + - total_count + - cache_revision + - search_provenance + type: object + PersonSummary: additionalProperties: true properties: + activity_count: + format: int64 + type: integer + cache_revision: + type: string + cluster: + $ref: "#/components/schemas/PersonCluster" + display_label: + type: string display_name: type: string - email: + file_count: + format: int64 + type: integer + first_at: + format: date-time + type: string + id: + format: int64 + type: integer + identifiers: + items: + $ref: "#/components/schemas/PersonIdentifier" + type: + - array + - "null" + last_at: + format: date-time type: string + partial_label: + type: boolean + source_counts: + items: + $ref: "#/components/schemas/SourceCount" + type: + - array + - "null" required: - - email - - display_name + - id + - display_label + - partial_label + - identifiers + - activity_count + - file_count + - source_counts + - first_at + - last_at + - cache_revision type: object - VectorHealth: + PingInfo: additionalProperties: true properties: - error: + ok: + type: boolean + pid: + format: int64 + type: integer + service: type: string - status: + version: type: string required: - - status + - ok type: object - securitySchemes: - apiKey: - in: header - name: X-Api-Key - type: apiKey -info: - title: msgvault API - version: 1.5.0 -openapi: 3.1.0 -paths: - /api/ping: - get: - operationId: daemonPing + Progress: + additionalProperties: true + properties: + done: + format: int64 + type: integer + total: + format: int64 + type: integer + required: + - done + - total + type: object + QueryRequest: + additionalProperties: false + properties: + sql: + type: string + required: + - sql + type: object + QueryResult: + additionalProperties: true + properties: + columns: + items: + type: string + type: + - array + - "null" + row_count: + format: int64 + type: integer + rows: + items: + items: {} + type: + - array + - "null" + type: + - array + - "null" + required: + - columns + - rows + - row_count + type: object + RelationshipRow: + additionalProperties: true + properties: + canonical_id: + format: int64 + type: integer + display_label: + type: string + last_at: + format: date-time + type: string + member_ids: + items: + format: int64 + type: integer + type: + - array + - "null" + score: + format: double + type: number + signals: + $ref: "#/components/schemas/RelationshipSignals" + required: + - canonical_id + - display_label + - member_ids + - score + - signals + - last_at + type: object + RelationshipSignals: + additionalProperties: true + properties: + last_interaction_at: + format: date-time + type: string + meeting_count: + format: int64 + type: integer + meetings_together: + format: double + type: number + modalities: + format: int64 + type: integer + received_from_them: + format: double + type: number + sent_count: + format: int64 + type: integer + sent_to_them: + format: double + type: number + required: + - sent_to_them + - received_from_them + - meetings_together + - sent_count + - meeting_count + - modalities + - last_interaction_at + type: object + RelationshipTimelineHTTPRequest: + additionalProperties: false + properties: + cursor: + type: string + filters: + items: + $ref: "#/components/schemas/ExploreFilter" + type: + - array + - "null" + limit: + format: int64 + maximum: 500 + minimum: 0 + type: integer + timezone: + type: string + type: object + RelationshipTimelineHTTPResponse: + additionalProperties: true + properties: + cache_revision: + type: string + canonical_id: + format: int64 + type: integer + identity_revision: + format: int64 + type: integer + next_cursor: + type: string + rows: + items: + $ref: "#/components/schemas/TimelineRow" + type: + - array + - "null" + total_count: + format: int64 + type: integer + required: + - canonical_id + - rows + - total_count + - cache_revision + - identity_revision + type: object + RelationshipsHTTPRequest: + additionalProperties: false + properties: + cursor: + type: string + filters: + items: + $ref: "#/components/schemas/ExploreFilter" + type: + - array + - "null" + limit: + format: int64 + maximum: 500 + minimum: 0 + type: integer + show_all: + type: boolean + type: object + RelationshipsHTTPResponse: + additionalProperties: true + properties: + cache_revision: + type: string + identity_revision: + format: int64 + type: integer + next_cursor: + type: string + rows: + items: + $ref: "#/components/schemas/RelationshipRow" + type: + - array + - "null" + total_count: + format: int64 + type: integer + required: + - rows + - total_count + - cache_revision + - identity_revision + type: object + RemoveRequest: + additionalProperties: false + properties: + account: + type: string + identifier: + type: string + required: + - account + - identifier + type: object + RemoveResult: + additionalProperties: true + properties: + account: + type: string + cache_state: + enum: + - ready + - stale + type: string + identifier: + type: string + no_identity: + type: boolean + removed: + format: int64 + type: integer + required: + - account + - identifier + - removed + type: object + SavedView: + additionalProperties: true + properties: + canonical_state: + $ref: "#/components/schemas/SavedViewStateEnvelope" + created_at: + format: date-time + type: string + description: + type: string + id: + format: int64 + type: integer + name: + type: string + revision: + format: int64 + type: integer + schema_version: + format: int64 + type: integer + updated_at: + format: date-time + type: string + required: + - id + - name + - canonical_state + - schema_version + - revision + - created_at + - updated_at + type: object + SavedViewFilter: + additionalProperties: false + properties: + field: + type: string + operator: + type: string + values: + description: Exact filter values; numeric identifiers use decimal strings + items: + type: string + type: array + required: + - field + - operator + - values + type: object + SavedViewSort: + additionalProperties: false + properties: + direction: + enum: + - asc + - desc + type: string + field: + type: string + required: + - field + - direction + type: object + SavedViewStateEnvelope: + additionalProperties: false + properties: + columns: + items: + type: string + type: array + filters: + items: + $ref: "#/components/schemas/SavedViewFilter" + type: array + grouping: + items: + type: string + type: array + inspector_pinned: + type: boolean + presentation: + enum: + - table + - timeline + - files + type: string + query: + type: string + search_mode: + type: string + sort: + items: + $ref: "#/components/schemas/SavedViewSort" + type: array + type: object + SavedViewsResponse: + additionalProperties: true + properties: + saved_views: + items: + $ref: "#/components/schemas/SavedView" + type: + - array + - "null" + required: + - saved_views + type: object + SchedulerStatusResponse: + additionalProperties: true + properties: + accounts: + items: + $ref: "#/components/schemas/AccountStatus" + type: + - array + - "null" + running: + type: boolean + required: + - running + - accounts + type: object + ScoreBreakdown: + additionalProperties: true + properties: + bm25: + format: double + type: number + rrf: + format: double + type: number + subject_boosted: + type: boolean + vector: + format: double + type: number + type: object + SearchCoverageRequest: + additionalProperties: false + properties: + filters: + items: + $ref: "#/components/schemas/ExploreFilter" + type: + - array + - "null" + type: object + SearchCoverageResponse: + additionalProperties: true + properties: + actions: + items: + enum: + - retry + - build_index + type: string + type: array + cache_revision: + type: string + detail: + type: string + eligible_count: + format: int64 + type: integer + embedded_count: + format: int64 + type: integer + percentage: + format: double + type: number + status: + enum: + - disabled + - initializing + - stale + - incomplete + - unavailable + - ready + type: string + vector_fingerprint: + type: string + vector_generation: + format: int64 + type: integer + required: + - eligible_count + - embedded_count + - percentage + - cache_revision + - status + - actions + type: object + SearchFastResponse: + additionalProperties: true + properties: + applied_source_ids: + items: + format: int64 + type: integer + type: + - array + - "null" + messages: + items: + $ref: "#/components/schemas/MessageSummary" + type: + - array + - "null" + query: + type: string + stats: + $ref: "#/components/schemas/TotalStatsResponse" + total_count: + format: int64 + type: integer + required: + - query + - messages + - total_count + type: object + SearchProvenance: + additionalProperties: false + properties: + lexical_index_revision: + type: string + vector_generation: + format: int64 + type: integer + type: object + SearchResult: + additionalProperties: true + properties: + messages: + items: + $ref: "#/components/schemas/MessageSummary" + type: + - array + - "null" + page: + format: int64 + type: integer + page_size: + format: int64 + type: integer + query: + type: string + total: + format: int64 + type: integer + required: + - query + - total + - page + - page_size + - messages + type: object + SecretSettingState: + additionalProperties: true + properties: + configured: + type: boolean + required: + - configured + type: object + SecretSettingUpdate: + additionalProperties: false + properties: + action: + enum: + - set + - clear + type: string + value: + type: string + required: + - action + type: object + SenderCount: + additionalProperties: false + properties: + count: + format: int64 + type: integer + sender: + type: string + required: + - sender + - count + type: object + SessionLoginRequest: + additionalProperties: false + properties: + api_key: + type: string + required: + - api_key + type: object + SessionStatus: + additionalProperties: true + properties: + auth_mode: + enum: + - loopback + - api_key + - session + - required + type: string + csrf_token: + type: string + https: + type: boolean + plain_http_warning: + type: boolean + required: + - auth_mode + - https + - plain_http_warning + type: object + Setting: + additionalProperties: true + properties: + group: + enum: + - browser + - server + - archive + - search + - sources + - integrations + type: string + key: + type: string + kind: + enum: + - string + - integer + - number + - boolean + - string_array + - secret + type: string + options: + items: + type: string + type: + - array + - "null" + restart_required: + type: boolean + secret: + $ref: "#/components/schemas/SecretSettingState" + testable: + type: boolean + value: + $ref: "#/components/schemas/SettingValue" + required: + - key + - group + - kind + - restart_required + type: object + SettingUpdate: + additionalProperties: false + properties: + key: + type: string + secret: + $ref: "#/components/schemas/SecretSettingUpdate" + value: + $ref: "#/components/schemas/SettingValue" + required: + - key + type: object + SettingValue: + oneOf: + - additionalProperties: false + properties: + string: + type: string + required: + - string + type: object + - additionalProperties: false + properties: + integer: + format: int64 + type: integer + required: + - integer + type: object + - additionalProperties: false + properties: + number: + format: double + type: number + required: + - number + type: object + - additionalProperties: false + properties: + boolean: + type: boolean + required: + - boolean + type: object + - additionalProperties: false + properties: + strings: + items: + type: string + type: array + required: + - strings + type: object + SettingsPatchRequest: + additionalProperties: false + properties: + confirm_api_key_restart: + type: boolean + updates: + items: + $ref: "#/components/schemas/SettingUpdate" + minItems: 1 + type: array + required: + - updates + type: object + SettingsResponse: + additionalProperties: true + properties: + pending_restart: + type: boolean + settings: + items: + $ref: "#/components/schemas/Setting" + type: array + required: + - settings + - pending_restart + type: object + SimilarSearchResponse: + additionalProperties: true + properties: + generation: + $ref: "#/components/schemas/HybridGenerationSummary" + messages: + items: + $ref: "#/components/schemas/MessageSummary" + type: + - array + - "null" + returned: + format: int64 + type: integer + seed_message_id: + format: int64 + type: integer + required: + - seed_message_id + - returned + - generation + - messages + type: object + SourceCount: + additionalProperties: true + properties: + count: + format: int64 + type: integer + source_type: + type: string + required: + - source_type + - count + type: object + SourceStatus: + additionalProperties: true + properties: + active_sync: + oneOf: + - $ref: "#/components/schemas/SyncRunStatus" + - type: "null" + can_sync: + type: boolean + display_name: + type: + - string + - "null" + id: + format: int64 + type: integer + identifier: + type: string + last_successful_sync: + oneOf: + - $ref: "#/components/schemas/SyncRunStatus" + - type: "null" + last_sync_at: + type: + - string + - "null" + latest_sync: + oneOf: + - $ref: "#/components/schemas/SyncRunStatus" + - type: "null" + next_sync_at: + type: + - string + - "null" + schedule: + type: string + scheduled: + type: boolean + scheduler_last_error: + type: string + source_type: + type: string + sync_unavailable_reason: + type: string + updated_at: + type: string + required: + - id + - source_type + - identifier + - display_name + - last_sync_at + - updated_at + - active_sync + - latest_sync + - last_successful_sync + - can_sync + - scheduled + - next_sync_at + type: object + SourceStatusResponse: + additionalProperties: true + properties: + sources: + items: + $ref: "#/components/schemas/SourceStatus" + type: + - array + - "null" + required: + - sources + type: object + SourcesRequest: + additionalProperties: false + properties: + accounts: + items: + type: string + type: + - array + - "null" + required: + - accounts + type: object + StageDeletionFilter: + additionalProperties: false + properties: + after: + type: string + before: + type: string + domain: + type: string + label: + type: string + recipient: + type: string + recipient_name: + type: string + sender: + type: string + sender_name: + type: string + source_id: + format: int64 + type: integer + type: object + StageDeletionRequest: + additionalProperties: false + properties: + description: + type: string + dry_run: + type: boolean + filter: + $ref: "#/components/schemas/StageDeletionFilter" + message_ids: + items: + format: int64 + type: integer + type: + - array + - "null" + operation_token: + type: string + selection: + $ref: "#/components/schemas/ExploreSelection" + type: object + StageDeletionResponse: + additionalProperties: true + properties: + account: + type: string + dry_run: + type: boolean + id: + type: string + message_count: + format: int64 + type: integer + sample_gmail_ids: + items: + type: string + type: + - array + - "null" + status: + type: string + required: + - dry_run + - message_count + type: object + StatsResponse: + additionalProperties: true + properties: + active_messages: + format: int64 + type: integer + database_size_bytes: + format: int64 + type: integer + source_deleted_messages: + format: int64 + type: integer + total_accounts: + format: int64 + type: integer + total_attachments: + format: int64 + type: integer + total_labels: + format: int64 + type: integer + total_messages: + format: int64 + type: integer + total_threads: + format: int64 + type: integer + vector_search: + $ref: "#/components/schemas/StatsView" + vector_status: + type: string + required: + - total_messages + - active_messages + - source_deleted_messages + - total_threads + - total_accounts + - total_labels + - total_attachments + - database_size_bytes + type: object + StatsView: + additionalProperties: true + properties: + active_generation: + $ref: "#/components/schemas/GenerationSummary" + building_generation: + $ref: "#/components/schemas/BuildingSummary" + enabled: + type: boolean + missing_embeddings_total: + format: int64 + type: integer + required: + - enabled + - active_generation + - missing_embeddings_total + type: object + StatusMessageResponse: + additionalProperties: true + properties: + message: + type: string + status: + type: string + required: + - status + - message + type: object + Summary: + additionalProperties: false + properties: + accounts: + items: + type: string + type: + - array + - "null" + date_range: + items: + type: string + maxItems: 2 + minItems: 2 + type: + - array + - "null" + message_count: + format: int64 + type: integer + top_senders: + items: + $ref: "#/components/schemas/SenderCount" + type: + - array + - "null" + total_size_bytes: + format: int64 + type: integer + required: + - message_count + - total_size_bytes + - date_range + - accounts + - top_senders + type: object + SyncRunItemStatus: + additionalProperties: true + properties: + created_at: + type: string + error_kind: + type: string + error_message: + type: string + phase: + type: string + source_message_id: + type: string + required: + - source_message_id + - phase + - error_kind + - error_message + - created_at + type: object + SyncRunStatus: + additionalProperties: true + properties: + completed_at: + type: + - string + - "null" + cursor_after: + type: + - string + - "null" + cursor_before: + type: + - string + - "null" + error_message: + type: + - string + - "null" + errors_count: + format: int64 + type: integer + id: + format: int64 + type: integer + item_errors: + items: + $ref: "#/components/schemas/SyncRunItemStatus" + type: + - array + - "null" + messages_added: + format: int64 + type: integer + messages_processed: + format: int64 + type: integer + messages_updated: + format: int64 + type: integer + skipped_count: + format: int64 + type: integer + source_id: + format: int64 + type: integer + started_at: + type: string + status: + type: string + required: + - id + - source_id + - started_at + - completed_at + - status + - messages_processed + - messages_added + - messages_updated + - errors_count + - error_message + - cursor_before + - cursor_after + type: object + TaskIntegrationStatusResponse: + additionalProperties: true + properties: + message: + description: Credential-free status explanation + type: string + project: + description: Configured project when available + type: string + security_note: + description: Platform-specific local socket security limitation + type: string + state: + description: Current task integration state + enum: + - disabled + - not_found + - authentication_required + - unreachable + - incompatible + - wrong_project + - ready + type: string + required: + - state + - message + type: object + TaskLinkLookupResponse: + additionalProperties: true + properties: + complete: + type: boolean + last_scan: + format: date-time + type: string + outbound_metadata: + $ref: "#/components/schemas/TaskLinkOutboundMetadata" + reason: + type: string + remote_revision: + type: string + state: + type: string + tasks: + items: + $ref: "#/components/schemas/TaskSummary" + type: array + required: + - tasks + - outbound_metadata + - state + - complete + - last_scan + type: object + TaskLinkMutationRequest: + additionalProperties: false + properties: + added_at: + description: Browser-generated retry-stable RFC3339 timestamp for the outbound mail link + type: string + description: + type: string + labels: + items: + type: string + type: + - array + - "null" + priority: + type: string + task_id: + description: Existing task to link; omit to create a task + type: string + title: + type: string + required: + - added_at + type: object + TaskLinkMutationResponse: + additionalProperties: true + properties: + task: + $ref: "#/components/schemas/TaskLinkTask" + required: + - task + type: object + TaskLinkOutboundMetadata: + additionalProperties: true + properties: + archive_uid: + type: string + conversation_id: + format: int64 + type: integer + from: + type: string + message_id: + format: int64 + type: integer + sent_at: + type: string + source_identifier: + type: string + source_message_id: + type: string + source_type: + type: string + subject: + type: string + required: + - archive_uid + - message_id + - conversation_id + - subject + - from + - sent_at + - source_type + - source_identifier + - source_message_id + type: object + TaskLinkTask: + additionalProperties: true + properties: + id: + type: string + project: + type: string + revision: + type: string + title: + type: string + required: + - id + - project + - title + type: object + TaskSearchResponse: + additionalProperties: true + properties: + tasks: + items: + $ref: "#/components/schemas/TaskSummary" + type: + - array + - "null" + required: + - tasks + type: object + TaskSummary: + additionalProperties: true + properties: + id: + type: string + revision: + type: string + title: + type: string + required: + - id + - title + type: object + TextConversationRow: + additionalProperties: true + properties: + conversation_id: + format: int64 + type: integer + last_message_at: + type: string + last_preview: + type: string + message_count: + format: int64 + type: integer + participant_count: + format: int64 + type: integer + source_type: + type: string + title: + type: string + required: + - conversation_id + - title + - source_type + - message_count + - participant_count + - last_preview + type: object + TextConversationsResponse: + additionalProperties: true + properties: + conversations: + items: + $ref: "#/components/schemas/TextConversationRow" + type: + - array + - "null" + count: + format: int64 + type: integer + has_more: + type: boolean + limit: + format: int64 + type: integer + offset: + format: int64 + type: integer + required: + - count + - has_more + - offset + - limit + - conversations + type: object + TextMessagesResponse: + additionalProperties: true + properties: + count: + format: int64 + type: integer + has_more: + type: boolean + limit: + format: int64 + type: integer + messages: + items: + $ref: "#/components/schemas/CLIQueryMessageSummary" + type: + - array + - "null" + offset: + format: int64 + type: integer + required: + - count + - has_more + - offset + - limit + - messages + type: object + TimelineRow: + additionalProperties: true + properties: + anchor_message_id: + format: int64 + type: integer + conversation_id: + format: int64 + type: integer + first_at: + format: date-time + type: string + has_attachments: + type: boolean + key: + type: string + kind: + type: string + message_count: + format: int64 + type: integer + occurred_at: + format: date-time + type: string + preview: + type: string + source_id: + format: int64 + type: integer + title: + type: string + required: + - key + - kind + - title + - preview + - occurred_at + - message_count + - source_id + - has_attachments + type: object + TokenUploadRequest: + additionalProperties: false + properties: + access_token: + type: string + client_id: + type: string + expiry: + format: date-time + type: string + refresh_token: + type: string + scopes: + items: + type: string + type: + - array + - "null" + tenant_id: + type: string + token_type: + type: string + required: + - refresh_token + type: object + TotalStatsResponse: + additionalProperties: true + properties: + account_count: + format: int64 + type: integer + active_messages: + format: int64 + type: integer + applied_search_scope: + type: boolean + applied_source_ids: + items: + format: int64 + type: integer + type: + - array + - "null" + attachment_count: + format: int64 + type: integer + attachment_size: + format: int64 + type: integer + label_count: + format: int64 + type: integer + message_count: + format: int64 + type: integer + source_deleted_messages: + format: int64 + type: integer + total_size: + format: int64 + type: integer + required: + - message_count + - active_messages + - source_deleted_messages + - total_size + - attachment_count + - attachment_size + - label_count + - account_count + type: object + UpdateRequest: + additionalProperties: false + properties: + display_name: + type: string + email: + type: string + required: + - email + - display_name + type: object + UpdateResult: + additionalProperties: true + properties: + display_name: + type: string + email: + type: string + required: + - email + - display_name + type: object + VectorHealth: + additionalProperties: true + properties: + error: + type: string + status: + type: string + required: + - status + type: object + securitySchemes: + apiKey: + in: header + name: X-Api-Key + type: apiKey +info: + title: msgvault API + version: 1.24.0 +openapi: 3.1.0 +paths: + /api/ping: + get: + operationId: daemonPing + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/PingInfo" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + summary: Daemon discovery ping + tags: + - Daemon + /api/session: + delete: + operationId: logoutSession + responses: + "204": + description: No Content + "429": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + summary: Delete the current browser session + tags: + - Session + get: + operationId: getSession + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SessionStatus" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + summary: Get browser authentication status + tags: + - Session + /api/session/login: + post: + operationId: loginSession + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SessionLoginRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SessionStatus" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + summary: Create an in-memory browser session + tags: + - Session + /api/v1/accounts: + get: + operationId: listAccounts + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/AccountListResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: List scheduler-configured accounts (with sync schedules); use /cli/accounts for all archived sources + tags: + - API + post: + operationId: addAccount + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/AddAccountRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/StatusMessageResponse" + description: OK + "201": + content: + application/json: + schema: + $ref: "#/components/schemas/StatusMessageResponse" + description: Created + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Add an account + tags: + - API + /api/v1/aggregates: + get: + operationId: getAggregates + parameters: + - description: Aggregate view type + in: query + name: view_type + schema: + type: string + - description: "Sort field: count, size, attachment_size, or name" + in: query + name: sort + schema: + type: string + - description: "Sort direction: asc or desc" + in: query + name: direction + schema: + type: string + - description: Maximum number of rows to return (default 100; values below 1 fall back to the default) + in: query + name: limit + schema: + format: int64 + type: integer + - description: Time bucket granularity + in: query + name: time_granularity + schema: + type: string + - description: Source ID + in: query + name: source_id + schema: + format: int64 + type: integer + - description: Only include messages with attachments + in: query + name: attachments_only + schema: + type: boolean + - description: Exclude deleted messages + in: query + name: hide_deleted + schema: + type: boolean + - description: Search query + in: query + name: search_query + schema: + type: string + - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: after + schema: + type: string + - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: before + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/AggregateResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get aggregate rows + tags: + - API + /api/v1/aggregates/sub: + get: + operationId: getSubAggregates + parameters: + - description: Aggregate view type + in: query + name: view_type + required: true + schema: + type: string + - description: "Sort field: count, size, attachment_size, or name" + in: query + name: sort + schema: + type: string + - description: "Sort direction: asc or desc" + in: query + name: direction + schema: + type: string + - description: Maximum number of rows to return (default 100; values below 1 fall back to the default) + in: query + name: limit + schema: + format: int64 + type: integer + - description: Time bucket granularity + in: query + name: time_granularity + schema: + type: string + - description: Source ID + in: query + name: source_id + schema: + format: int64 + type: integer + - description: Only include messages with attachments + in: query + name: attachments_only + schema: + type: boolean + - description: Exclude deleted messages + in: query + name: hide_deleted + schema: + type: boolean + - description: Search query + in: query + name: search_query + schema: + type: string + - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: after + schema: + type: string + - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: before + schema: + type: string + - description: Sender email/address filter + in: query + name: sender + schema: + type: string + - description: Sender display-name filter + in: query + name: sender_name + schema: + type: string + - description: Recipient email/address filter + in: query + name: recipient + schema: + type: string + - description: Recipient display-name filter + in: query + name: recipient_name + schema: + type: string + - description: Domain filter + in: query + name: domain + schema: + type: string + - description: Label filter + in: query + name: label + schema: + type: string + - description: Message type filter + in: query + name: message_type + schema: + type: string + - description: Named time period + in: query + name: time_period + schema: + type: string + - description: Conversation ID + in: query + name: conversation_id + schema: + format: int64 + type: integer + - description: Comma-separated aggregate view names to match empty values + in: query + name: empty_targets + schema: + type: string + - description: Zero-based row offset + in: query + name: offset + schema: + format: int64 + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/AggregateResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get nested aggregate rows + tags: + - API + /api/v1/attachments/{hash}/content: + get: + operationId: getAttachmentContent + parameters: + - description: Attachment SHA-256 content hash + in: path + name: hash + required: true + schema: + type: string + responses: + "200": + content: + "*/*": + schema: + contentMediaType: application/octet-stream + format: binary + type: string + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Download attachment content by SHA-256 hash + tags: + - API + /api/v1/attachments/{id}: + get: + operationId: getAttachment + parameters: + - description: Attachment ID + in: path + name: id + required: true + schema: + format: int64 + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/AttachmentInfo" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get attachment metadata + tags: + - API + /api/v1/auth/token/{email}: + post: + operationId: uploadToken + parameters: + - description: Account email address + in: path + name: email + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/TokenUploadRequest" + required: true + responses: + "201": + content: + application/json: + schema: + $ref: "#/components/schemas/StatusMessageResponse" + description: Created + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Upload an OAuth token + tags: + - API + /api/v1/backup/freeze/begin: + post: + operationId: beginBackupFreeze + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/BackupFreezeBeginResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Begin a backup freeze window + tags: + - API + /api/v1/backup/freeze/end: + post: + operationId: endBackupFreeze + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/BackupFreezeEndRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/BackupFreezeEndResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: End a backup freeze window + tags: + - API + /api/v1/cli/account: + post: + operationId: updateCLIAccount + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Not Found + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Update an account for CLI use + tags: + - CLI + /api/v1/cli/accounts: + get: + operationId: listCLIAccounts + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliAccountsResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: List accounts for CLI output + tags: + - API + /api/v1/cli/add-calendar/plan: + post: + operationId: planCLIAddCalendar + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CLIAddCalendarPlanRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CLIAddCalendarPlanResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Plan CLI Calendar account setup + tags: + - API + /api/v1/cli/attachment: + get: + operationId: getCLIAttachment + parameters: + - description: Attachment SHA-256 content hash + in: query + name: content_hash + required: true + schema: + type: string + responses: + "200": + content: + application/octet-stream: + schema: + contentMediaType: application/octet-stream + format: binary + type: string + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get one attachment for CLI export + tags: + - API + /api/v1/cli/build-cache: + post: + operationId: buildCLICache + parameters: + - description: Rebuild all cache files from scratch + in: query + name: full_rebuild + schema: + type: boolean + responses: + "200": + content: + application/x-ndjson: + schema: + $ref: "#/components/schemas/CLICacheBuildEvent" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Build the CLI analytics cache + tags: + - API + /api/v1/cli/cache-stats: + get: + operationId: getCLICacheStats + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CacheStats" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get CLI-compatible analytics cache statistics + tags: + - API + /api/v1/cli/collection: + get: + operationId: getCLICollection + parameters: + - description: Collection name + in: query + name: name + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliCollectionEnvelope" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get one collection for CLI output + tags: + - API + /api/v1/cli/collections: + get: + operationId: listCLICollections + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliCollectionsResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: List collections for CLI output + tags: + - API + post: + operationId: createCLICollection + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/MutationResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Not Found + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Create a collection for CLI use + tags: + - CLI + /api/v1/cli/collections/{name}: + delete: + operationId: deleteCLICollection + parameters: + - description: Collection name + in: path + name: name + required: true + schema: + description: Collection name + type: string + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/MutationResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Not Found + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Delete a CLI collection + tags: + - CLI + /api/v1/cli/collections/{name}/sources: + delete: + operationId: removeCLICollectionSources + parameters: + - description: Collection name + in: path + name: name + required: true + schema: + description: Collection name + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SourcesRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/MutationResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Not Found + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Remove accounts from a CLI collection + tags: + - CLI + patch: + operationId: addCLICollectionSources + parameters: + - description: Collection name + in: path + name: name + required: true + schema: + description: Collection name + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SourcesRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/MutationResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Not Found + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Add accounts to a CLI collection + tags: + - CLI + /api/v1/cli/deduplicate/plan: + post: + operationId: planCLIDeduplicate + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CLIDeduplicatePlanRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CLIDeduplicatePlanResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Plan interactive deduplication for CLI use + tags: + - CLI + /api/v1/cli/delete-deduped: + post: + operationId: executeCLIDeleteDeduped + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CliDeleteDedupedExecuteRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliDeleteDedupedExecuteResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Conflict + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Delete dedup-hidden messages for CLI use + tags: + - CLI + /api/v1/cli/delete-deduped/plan: + post: + operationId: planCLIDeleteDeduped + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CliDeleteDedupedPlanRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliDeleteDedupedPlanResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Plan deletion of dedup-hidden messages for CLI use + tags: + - CLI + /api/v1/cli/delete-staged/plan: + post: + operationId: planCLIDeleteStaged + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CLIDeleteStagedPlanRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CLIDeleteStagedPlanResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Plan CLI staged deletion execution + tags: + - API + /api/v1/cli/deletion-manifests: + post: + operationId: createCLIDeletionManifest + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Manifest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CLIDeletionManifestResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Create a staged deletion manifest + tags: + - API + /api/v1/cli/embeddings/plan: + post: + operationId: planCLIEmbeddings + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CLIEmbeddingsPlanRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CLIEmbeddingsPlanResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Plan CLI embeddings management + tags: + - API + /api/v1/cli/identities: + delete: + operationId: removeCLIIdentity + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/RemoveRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/RemoveResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Remove a confirmed identifier from an account identity + tags: + - CLI + get: + operationId: listCLIIdentities + parameters: + - description: Restrict to a single account + explode: false + in: query + name: account + schema: + description: Restrict to a single account + type: string + - description: Restrict to all member accounts of one collection + explode: false + in: query + name: collection + schema: + description: Restrict to all member accounts of one collection + type: string + - description: For account scope, return only the primary source instead of related sources + explode: false + in: query + name: primary_only + schema: + description: For account scope, return only the primary source instead of related sources + type: boolean + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliIdentitiesResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: List confirmed account identities + tags: + - CLI + post: + operationId: addCLIIdentity + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/AddRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/AddResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Add a confirmed identifier to an account identity + tags: + - CLI + /api/v1/cli/init-db: + post: + operationId: initCLIArchive + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliInitDBResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Initialize the archive for CLI use + tags: + - API + /api/v1/cli/message: + get: + operationId: getCLIMessage + parameters: + - description: Message numeric ID or source message ID + in: query + name: id + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliMessageResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get one message for CLI output + tags: + - API + /api/v1/cli/message/raw: + get: + operationId: getCLIMessageRaw + parameters: + - description: Message numeric ID or source message ID + in: query + name: id + required: true + schema: + type: string + responses: + "200": + content: + message/rfc822: + schema: + contentMediaType: application/octet-stream + format: binary + type: string + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get one raw message for CLI export + tags: + - API + /api/v1/cli/rebuild-fts: + post: + operationId: rebuildCLIFTS responses: "200": content: - application/json: + application/x-ndjson: schema: - $ref: "#/components/schemas/PingInfo" + $ref: "#/components/schemas/CliRebuildFTSEvent" description: OK default: content: @@ -2490,18 +5937,20 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - summary: Daemon discovery ping + security: + - apiKey: [] + summary: Rebuild the CLI full-text search index tags: - - Daemon - /api/v1/accounts: - get: - operationId: listAccounts + - API + /api/v1/cli/repair-encoding: + post: + operationId: repairEncodingCLI responses: "200": content: - application/json: + application/x-ndjson: schema: - $ref: "#/components/schemas/AccountListResponse" + $ref: "#/components/schemas/CLIRepairEncodingEvent" description: OK default: content: @@ -2511,30 +5960,25 @@ paths: description: Error security: - apiKey: [] - summary: List scheduler-configured accounts (with sync schedules); use /cli/accounts for all archived sources + summary: Repair CLI archive encoding tags: - API + /api/v1/cli/run: post: - operationId: addAccount + operationId: runCLI requestBody: content: application/json: schema: - $ref: "#/components/schemas/AddAccountRequest" + $ref: "#/components/schemas/CLIRunRequest" required: true responses: "200": content: - application/json: + application/x-ndjson: schema: - $ref: "#/components/schemas/StatusMessageResponse" + $ref: "#/components/schemas/CLIRunEvent" description: OK - "201": - content: - application/json: - schema: - $ref: "#/components/schemas/StatusMessageResponse" - description: Created default: content: application/json: @@ -2543,68 +5987,44 @@ paths: description: Error security: - apiKey: [] - summary: Add an account + summary: Run an allowlisted CLI command tags: - API - /api/v1/aggregates: + /api/v1/cli/search: get: - operationId: getAggregates + operationId: searchCLI parameters: - - description: Aggregate view type - in: query - name: view_type - schema: - type: string - - description: "Sort field: count, size, attachment_size, or name" - in: query - name: sort - schema: - type: string - - description: "Sort direction: asc or desc" + - description: Search query in: query - name: direction + name: q + required: true schema: type: string - - description: Maximum number of rows to return (default 100; values below 1 fall back to the default) + - description: Maximum number of rows to return in: query name: limit schema: format: int64 type: integer - - description: Time bucket granularity - in: query - name: time_granularity - schema: - type: string - - description: Source ID + - description: Zero-based row offset in: query - name: source_id + name: offset schema: format: int64 type: integer - - description: Only include messages with attachments - in: query - name: attachments_only - schema: - type: boolean - - description: Exclude deleted messages - in: query - name: hide_deleted - schema: - type: boolean - - description: Search query + - description: Message type filter; repeat or comma-separate for multiple values in: query - name: search_query + name: message_type schema: type: string - - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + - description: Restrict to one account/source in: query - name: after + name: account schema: type: string - - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + - description: Restrict to one collection in: query - name: before + name: collection schema: type: string responses: @@ -2612,7 +6032,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/AggregateResponse" + $ref: "#/components/schemas/CliSearchResponse" description: OK default: content: @@ -2622,134 +6042,153 @@ paths: description: Error security: - apiKey: [] - summary: Get aggregate rows + summary: Search messages for CLI output tags: - API - /api/v1/aggregates/sub: + /api/v1/cli/stats: get: - operationId: getSubAggregates + operationId: getCLIStats parameters: - - description: Aggregate view type + - description: Restrict to one account/source in: query - name: view_type - required: true + name: account schema: type: string - - description: "Sort field: count, size, attachment_size, or name" + - description: Restrict to one collection in: query - name: sort + name: collection schema: type: string - - description: "Sort direction: asc or desc" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliStatsResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get CLI-compatible archive statistics + tags: + - API + /api/v1/cli/sync: + post: + operationId: syncCLI + parameters: + - description: Account email or display name to sync in: query - name: direction + name: email schema: type: string - - description: Maximum number of rows to return (default 100; values below 1 fall back to the default) - in: query - name: limit - schema: - format: int64 - type: integer - - description: Time bucket granularity + responses: + "200": + content: + application/x-ndjson: + schema: + $ref: "#/components/schemas/CLISyncEvent" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Run CLI incremental sync + tags: + - API + /api/v1/cli/sync-full: + post: + operationId: syncFullCLI + parameters: + - description: Account email or display name to sync in: query - name: time_granularity + name: email schema: type: string - - description: Source ID - in: query - name: source_id - schema: - format: int64 - type: integer - - description: Only include messages with attachments - in: query - name: attachments_only - schema: - type: boolean - - description: Exclude deleted messages - in: query - name: hide_deleted - schema: - type: boolean - - description: Search query + - description: Gmail search query in: query - name: search_query + name: query schema: type: string - - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + - description: Only messages on or after this YYYY-MM-DD date in: query name: after schema: type: string - - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + - description: Only messages before this YYYY-MM-DD date in: query name: before schema: type: string - - description: Sender email/address filter - in: query - name: sender - schema: - type: string - - description: Sender display-name filter - in: query - name: sender_name - schema: - type: string - - description: Recipient email/address filter - in: query - name: recipient - schema: - type: string - - description: Recipient display-name filter - in: query - name: recipient_name - schema: - type: string - - description: Domain filter - in: query - name: domain - schema: - type: string - - description: Label filter + - description: Maximum messages to sync in: query - name: label + name: limit schema: - type: string - - description: Message type filter + format: int64 + type: integer + - description: Ignore checkpoints and start fresh in: query - name: message_type + name: noresume schema: - type: string - - description: Named time period + type: boolean + responses: + "200": + content: + application/x-ndjson: + schema: + $ref: "#/components/schemas/CLISyncEvent" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Run CLI full sync + tags: + - API + /api/v1/cli/verify: + post: + operationId: verifyCLI + parameters: + - description: Account email to verify in: query - name: time_period + name: email + required: true schema: type: string - - description: Conversation ID + - description: Number of messages to sample for MIME verification in: query - name: conversation_id + name: sample schema: format: int64 type: integer - - description: Comma-separated aggregate view names to match empty values + - description: Skip SQLite integrity check in: query - name: empty_targets + name: skip_db_check schema: - type: string - - description: Zero-based row offset + type: boolean + - description: Emit JSON summary output in: query - name: offset + name: json schema: - format: int64 - type: integer + type: boolean responses: "200": content: - application/json: + application/x-ndjson: schema: - $ref: "#/components/schemas/AggregateResponse" + $ref: "#/components/schemas/CLIVerifyEvent" description: OK default: content: @@ -2759,58 +6198,56 @@ paths: description: Error security: - apiKey: [] - summary: Get nested aggregate rows + summary: Verify the CLI archive against Gmail tags: - API - /api/v1/attachments/{hash}/content: + /api/v1/conversations/{id}: get: - operationId: getAttachmentContent + operationId: getConversation parameters: - - description: Attachment SHA-256 content hash + - description: Conversation ID in: path - name: hash + name: id + required: true + schema: + format: int64 + type: integer + - description: Selected message ID anchoring the chronological window + in: query + name: anchor required: true + schema: + format: int64 + type: integer + - description: Messages before the anchor (default 25, max 50) + in: query + name: before + schema: + format: int64 + type: integer + - description: Messages after the anchor (default 25, max 50) + in: query + name: after + schema: + format: int64 + type: integer + - description: Lower UTC bound, inclusive (RFC3339). Restricts the window, before/after counts, and has_before/has_after to messages in [start, end) + in: query + name: start + schema: + type: string + - description: Upper UTC bound, exclusive (RFC3339). Restricts the window, before/after counts, and has_before/has_after to messages in [start, end) + in: query + name: end schema: type: string responses: "200": - content: - "*/*": - schema: - contentMediaType: application/octet-stream - format: binary - type: string - description: OK - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "401": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "503": content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error + $ref: "#/components/schemas/ConversationResponse" + description: OK default: content: application/json: @@ -2819,26 +6256,24 @@ paths: description: Error security: - apiKey: [] - summary: Download attachment content by SHA-256 hash + summary: Get a bounded containing conversation tags: - API - /api/v1/attachments/{id}: + /api/v1/deletions: get: - operationId: getAttachment + operationId: listDeletions parameters: - - description: Attachment ID - in: path - name: id - required: true + - description: Filter manifests by status (pending, in_progress, completed, failed, cancelled) + in: query + name: status schema: - format: int64 - type: integer + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/AttachmentInfo" + $ref: "#/components/schemas/ListDeletionsResponse" description: OK default: content: @@ -2848,31 +6283,29 @@ paths: description: Error security: - apiKey: [] - summary: Get attachment metadata + summary: List staged deletion manifests tags: - API - /api/v1/auth/token/{email}: post: - operationId: uploadToken - parameters: - - description: Account email address - in: path - name: email - required: true - schema: - type: string + operationId: stageDeletion requestBody: content: application/json: schema: - $ref: "#/components/schemas/TokenUploadRequest" + $ref: "#/components/schemas/StageDeletionRequest" required: true responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/StageDeletionResponse" + description: OK "201": content: application/json: schema: - $ref: "#/components/schemas/StatusMessageResponse" + $ref: "#/components/schemas/StageDeletionResponse" description: Created default: content: @@ -2882,18 +6315,25 @@ paths: description: Error security: - apiKey: [] - summary: Upload an OAuth token + summary: Stage messages for deletion tags: - API - /api/v1/backup/freeze/begin: - post: - operationId: beginBackupFreeze + /api/v1/deletions/{id}: + delete: + operationId: cancelDeletion + parameters: + - description: Deletion manifest ID + in: path + name: id + required: true + schema: + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/BackupFreezeBeginResponse" + $ref: "#/components/schemas/CancelDeletionResponse" description: OK default: content: @@ -2903,24 +6343,24 @@ paths: description: Error security: - apiKey: [] - summary: Begin a backup freeze window + summary: Cancel a staged deletion manifest tags: - API - /api/v1/backup/freeze/end: - post: - operationId: endBackupFreeze - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/BackupFreezeEndRequest" - required: true + get: + operationId: getDeletion + parameters: + - description: Deletion manifest ID + in: path + name: id + required: true + schema: + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/BackupFreezeEndResponse" + $ref: "#/components/schemas/DeletionManifestDetail" description: OK default: content: @@ -2930,69 +6370,72 @@ paths: description: Error security: - apiKey: [] - summary: End a backup freeze window + summary: Inspect a staged deletion manifest tags: - API - /api/v1/cli/account: + /api/v1/domains/search: post: - operationId: updateCLIAccount + operationId: searchDomains requestBody: content: application/json: schema: - $ref: "#/components/schemas/UpdateRequest" + $ref: "#/components/schemas/IdentitySearchHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/UpdateResult" + $ref: "#/components/schemas/DomainSearchHTTPResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Not Found - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Update an account for CLI use + summary: Search analytical domains tags: - - CLI - /api/v1/cli/accounts: + - Exploration + /api/v1/domains/{domain}: get: - operationId: listCLIAccounts + operationId: getDomain + parameters: + - description: Exact normalized domain fact + in: path + name: domain + required: true + schema: + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliAccountsResponse" + $ref: "#/components/schemas/DomainSummary" description: OK default: content: @@ -3002,25 +6445,52 @@ paths: description: Error security: - apiKey: [] - summary: List accounts for CLI output + summary: Get one analytical domain tags: - API - /api/v1/cli/add-calendar/plan: + /api/v1/domains/{domain}/files/search: post: - operationId: planCLIAddCalendar + operationId: searchDomainFiles + parameters: + - description: Exact normalized domain fact + in: path + name: domain + required: true + schema: + type: string requestBody: content: application/json: schema: - $ref: "#/components/schemas/CLIAddCalendarPlanRequest" + $ref: "#/components/schemas/FileSearchHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CLIAddCalendarPlanResponse" + $ref: "#/components/schemas/FileSearchHTTPResponse" description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -3029,27 +6499,31 @@ paths: description: Error security: - apiKey: [] - summary: Plan CLI Calendar account setup + summary: Search one domain's analytical files tags: - - API - /api/v1/cli/attachment: - get: - operationId: getCLIAttachment + - Exploration + /api/v1/domains/{domain}/summary: + post: + operationId: getDomainContextSummary parameters: - - description: Attachment SHA-256 content hash - in: query - name: content_hash + - description: Exact normalized domain fact + in: path + name: domain required: true schema: type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreHTTPRequest" + required: true responses: "200": content: - application/octet-stream: + application/json: schema: - contentMediaType: application/octet-stream - format: binary - type: string + $ref: "#/components/schemas/DomainContextSummaryHTTPResponse" description: OK "400": content: @@ -3057,19 +6531,7 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - "401": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "500": + "409": content: application/json: schema: @@ -3079,8 +6541,10 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -3089,46 +6553,52 @@ paths: description: Error security: - apiKey: [] - summary: Get one attachment for CLI export + summary: Get one domain's contextual analytical summary tags: - - API - /api/v1/cli/build-cache: + - Exploration + /api/v1/domains/{domain}/timeline: post: - operationId: buildCLICache + operationId: getDomainTimeline parameters: - - description: Rebuild all cache files from scratch - in: query - name: full_rebuild + - description: Exact normalized domain fact + in: path + name: domain + required: true schema: - type: boolean + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreHTTPRequest" + required: true responses: "200": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLICacheBuildEvent" + $ref: "#/components/schemas/ExploreHTTPResponse" description: OK - default: + "400": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - security: - - apiKey: [] - summary: Build the CLI analytics cache - tags: - - API - /api/v1/cli/cache-stats: - get: - operationId: getCLICacheStats - responses: - "200": + "409": content: application/json: schema: - $ref: "#/components/schemas/CacheStats" - description: OK + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -3137,47 +6607,45 @@ paths: description: Error security: - apiKey: [] - summary: Get CLI-compatible analytics cache statistics + summary: Get one domain's canonical activity timeline tags: - - API - /api/v1/cli/collection: - get: - operationId: getCLICollection - parameters: - - description: Collection name - in: query - name: name - required: true - schema: - type: string + - Exploration + /api/v1/explore: + post: + operationId: explore + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreHTTPRequest" + required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliCollectionEnvelope" + $ref: "#/components/schemas/ExploreHTTPResponse" description: OK - default: + "400": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - security: - - apiKey: [] - summary: Get one collection for CLI output - tags: - - API - /api/v1/cli/collections: - get: - operationId: listCLICollections - responses: - "200": + "409": content: application/json: schema: - $ref: "#/components/schemas/CliCollectionsResponse" - description: OK + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -3186,385 +6654,417 @@ paths: description: Error security: - apiKey: [] - summary: List collections for CLI output + summary: Explore canonical archive entries tags: - - API + - Exploration + /api/v1/explore/files: post: - operationId: createCLICollection + operationId: listExploreFiles requestBody: content: application/json: schema: - $ref: "#/components/schemas/CreateRequest" + $ref: "#/components/schemas/ExploreFilesHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/MutationResult" + $ref: "#/components/schemas/ExploreFilesHTTPResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Not Found - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Create a collection for CLI use + summary: List bounded chronological attachment facts tags: - - CLI - /api/v1/cli/collections/{name}: - delete: - operationId: deleteCLICollection - parameters: - - description: Collection name - in: path - name: name - required: true - schema: - description: Collection name - type: string + - Exploration + /api/v1/explore/groups: + post: + operationId: exploreGroups + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreGroupsHTTPRequest" + required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/MutationResult" + $ref: "#/components/schemas/ExploreGroupsHTTPResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Not Found - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Delete a CLI collection + summary: Group canonical archive entries tags: - - CLI - /api/v1/cli/collections/{name}/sources: - delete: - operationId: removeCLICollectionSources - parameters: - - description: Collection name - in: path - name: name - required: true - schema: - description: Collection name - type: string + - Exploration + /api/v1/explore/match-counts: + post: + operationId: countExploreMatches requestBody: content: application/json: schema: - $ref: "#/components/schemas/SourcesRequest" + $ref: "#/components/schemas/ExploreMatchCountsRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/MutationResult" + $ref: "#/components/schemas/ExploreMatchCountsResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Not Found - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Remove accounts from a CLI collection + summary: Count exact lexical matches in visible rows tags: - - CLI - patch: - operationId: addCLICollectionSources - parameters: - - description: Collection name - in: path - name: name - required: true - schema: - description: Collection name - type: string + - Exploration + /api/v1/explore/preflight: + post: + operationId: preflightExploreSelection requestBody: content: application/json: schema: - $ref: "#/components/schemas/SourcesRequest" + $ref: "#/components/schemas/ExplorePreflightRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/MutationResult" + $ref: "#/components/schemas/ExplorePreflightResponse" description: OK - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Not Found - "422": + "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error + $ref: "#/components/schemas/ErrorResponse" + description: Error "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" description: Service Unavailable + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Add accounts to a CLI collection + summary: Preflight a revision-pinned analytical selection tags: - - CLI - /api/v1/cli/deduplicate/plan: + - Exploration + /api/v1/files/groups: post: - operationId: planCLIDeduplicate + operationId: groupFiles requestBody: content: application/json: schema: - $ref: "#/components/schemas/CLIDeduplicatePlanRequest" + $ref: "#/components/schemas/FileGroupsHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CLIDeduplicatePlanResponse" + $ref: "#/components/schemas/FileGroupsHTTPResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Plan interactive deduplication for CLI use + summary: Group analytical files tags: - - CLI - /api/v1/cli/delete-deduped: + - Exploration + /api/v1/files/search: post: - operationId: executeCLIDeleteDeduped + operationId: searchFiles requestBody: content: application/json: schema: - $ref: "#/components/schemas/CliDeleteDedupedExecuteRequest" + $ref: "#/components/schemas/FileSearchHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliDeleteDedupedExecuteResponse" + $ref: "#/components/schemas/FileSearchHTTPResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request + $ref: "#/components/schemas/ErrorResponse" + description: Error "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Conflict - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Search analytical files + tags: + - Exploration + /api/v1/files/{id}: + get: + operationId: getFile + parameters: + - description: File attachment ID + in: path + name: id + required: true + schema: + format: int64 + type: integer + responses: + "200": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/FileMetadataResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Delete dedup-hidden messages for CLI use + summary: Get authoritative file metadata tags: - - CLI - /api/v1/cli/delete-deduped/plan: - post: - operationId: planCLIDeleteDeduped - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CliDeleteDedupedPlanRequest" - required: true + - API + /api/v1/files/{id}/content: + get: + operationId: getFileContent + parameters: + - description: File attachment ID + in: path + name: id + required: true + schema: + format: int64 + type: integer responses: "200": content: - application/json: + "*/*": schema: - $ref: "#/components/schemas/CliDeleteDedupedPlanResponse" + contentMediaType: application/octet-stream + format: binary + type: string description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "401": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity + $ref: "#/components/schemas/ErrorResponse" + description: Error + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error "500": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error + $ref: "#/components/schemas/ErrorResponse" + description: Error "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Plan deletion of dedup-hidden messages for CLI use + summary: Download content for one authoritative file row tags: - - CLI - /api/v1/cli/delete-staged/plan: + - API + /api/v1/health: + get: + operationId: getHealth + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/HealthResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get authenticated health details + tags: + - API + /api/v1/identity/links: post: - operationId: planCLIDeleteStaged + operationId: linkIdentityParticipants requestBody: content: application/json: schema: - $ref: "#/components/schemas/CLIDeleteStagedPlanRequest" + $ref: "#/components/schemas/IdentityLinkRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CLIDeleteStagedPlanResponse" + $ref: "#/components/schemas/IdentityLinkResponse" description: OK default: content: @@ -3574,24 +7074,24 @@ paths: description: Error security: - apiKey: [] - summary: Plan CLI staged deletion execution + summary: Assert two participants are the same person tags: - API - /api/v1/cli/deletion-manifests: + /api/v1/identity/unlinks: post: - operationId: createCLIDeletionManifest + operationId: unlinkIdentityParticipants requestBody: content: application/json: schema: - $ref: "#/components/schemas/Manifest" + $ref: "#/components/schemas/IdentityLinkRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CLIDeletionManifestResponse" + $ref: "#/components/schemas/IdentityLinkResponse" description: OK default: content: @@ -3601,24 +7101,101 @@ paths: description: Error security: - apiKey: [] - summary: Create a staged deletion manifest + summary: Remove a link edge between two participants tags: - API - /api/v1/cli/embeddings/plan: + /api/v1/integrations/tasks/search: + get: + operationId: searchIntegrationTasks + parameters: + - description: Task title search within the configured project + in: query + name: q + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/TaskSearchResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Search tasks in the configured project + tags: + - API + /api/v1/integrations/tasks/status: + get: + operationId: getTaskIntegrationStatus + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/TaskIntegrationStatusResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get task integration capability status + tags: + - API + /api/v1/integrations/tasks/test: post: - operationId: planCLIEmbeddings - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CLIEmbeddingsPlanRequest" - required: true + operationId: testTaskIntegration + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/TaskIntegrationStatusResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Test task integration discovery, authentication, capabilities, and project + tags: + - API + /api/v1/messages: + get: + operationId: listMessages + parameters: + - description: One-based page number (default 1; values below 1 are clamped to 1). Non-numeric values are rejected with 400. + in: query + name: page + schema: + format: int64 + type: integer + - description: Page size (default 20, max 100; out-of-range values are clamped). Non-numeric values are rejected with 400. + in: query + name: page_size + schema: + format: int64 + type: integer responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CLIEmbeddingsPlanResponse" + $ref: "#/components/schemas/MessageListResponse" description: OK default: content: @@ -3628,167 +7205,249 @@ paths: description: Error security: - apiKey: [] - summary: Plan CLI embeddings management + summary: List messages tags: - API - /api/v1/cli/identities: - delete: - operationId: removeCLIIdentity - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RemoveRequest" - required: true + /api/v1/messages/filter: + get: + operationId: filterMessages + parameters: + - description: Sender email/address filter + in: query + name: sender + schema: + type: string + - description: Sender display-name filter + in: query + name: sender_name + schema: + type: string + - description: Recipient email/address filter + in: query + name: recipient + schema: + type: string + - description: Recipient display-name filter + in: query + name: recipient_name + schema: + type: string + - description: Domain filter + in: query + name: domain + schema: + type: string + - description: Label filter + in: query + name: label + schema: + type: string + - description: Message type filter + in: query + name: message_type + schema: + type: string + - description: Named time period + in: query + name: time_period + schema: + type: string + - description: Time bucket granularity + in: query + name: time_granularity + schema: + type: string + - description: Conversation ID + in: query + name: conversation_id + schema: + format: int64 + type: integer + - description: Source ID + in: query + name: source_id + schema: + format: int64 + type: integer + - description: Only include messages with attachments + in: query + name: attachments_only + schema: + type: boolean + - description: Exclude deleted messages + in: query + name: hide_deleted + schema: + type: boolean + - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: after + schema: + type: string + - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: before + schema: + type: string + - description: Comma-separated aggregate view names to match empty values + in: query + name: empty_targets + schema: + type: string + - description: Zero-based row offset + in: query + name: offset + schema: + format: int64 + type: integer + - description: Maximum number of rows to return (default and max 500; larger values are clamped) + in: query + name: limit + schema: + format: int64 + type: integer + - description: "Sort field: date, size, or subject" + in: query + name: sort + schema: + type: string + - description: "Sort direction: asc or desc" + in: query + name: direction + schema: + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/RemoveResult" + $ref: "#/components/schemas/FilteredMessagesResponse" description: OK - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "422": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Remove a confirmed identifier from an account identity + summary: List filtered messages tags: - - CLI + - API + /api/v1/messages/gmail-ids: get: - operationId: listCLIIdentities + operationId: getGmailIDsByFilter parameters: - - description: Restrict to a single account - explode: false + - description: Sender email/address filter in: query - name: account + name: sender schema: - description: Restrict to a single account type: string - - description: Restrict to all member accounts of one collection - explode: false + - description: Sender display-name filter + in: query + name: sender_name + schema: + type: string + - description: Recipient email/address filter + in: query + name: recipient + schema: + type: string + - description: Recipient display-name filter + in: query + name: recipient_name + schema: + type: string + - description: Domain filter + in: query + name: domain + schema: + type: string + - description: Label filter + in: query + name: label + schema: + type: string + - description: Message type filter + in: query + name: message_type + schema: + type: string + - description: Named time period + in: query + name: time_period + schema: + type: string + - description: Time bucket granularity + in: query + name: time_granularity + schema: + type: string + - description: Conversation ID + in: query + name: conversation_id + schema: + format: int64 + type: integer + - description: Source ID + in: query + name: source_id + schema: + format: int64 + type: integer + - description: Only include messages with attachments + in: query + name: attachments_only + schema: + type: boolean + - description: Exclude deleted messages + in: query + name: hide_deleted + schema: + type: boolean + - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: after + schema: + type: string + - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: before + schema: + type: string + - description: Comma-separated aggregate view names to match empty values + in: query + name: empty_targets + schema: + type: string + - description: Zero-based row offset + in: query + name: offset + schema: + format: int64 + type: integer + - description: Maximum number of rows to return (default and max 500; larger values are clamped) + in: query + name: limit + schema: + format: int64 + type: integer + - description: "Sort field: date, size, or subject" in: query - name: collection + name: sort schema: - description: Restrict to all member accounts of one collection type: string - - description: For account scope, return only the primary source instead of related sources - explode: false + - description: "Sort direction: asc or desc" in: query - name: primary_only + name: direction schema: - description: For account scope, return only the primary source instead of related sources - type: boolean - responses: - "200": - content: - application/json: - schema: - $ref: "#/components/schemas/CliIdentitiesResponse" - description: OK - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "422": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable - security: - - apiKey: [] - summary: List confirmed account identities - tags: - - CLI - post: - operationId: addCLIIdentity - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/AddRequest" - required: true - responses: - "200": - content: - application/json: - schema: - $ref: "#/components/schemas/AddResult" - description: OK - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "422": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable - security: - - apiKey: [] - summary: Add a confirmed identifier to an account identity - tags: - - CLI - /api/v1/cli/init-db: - post: - operationId: initCLIArchive + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliInitDBResponse" + $ref: "#/components/schemas/GmailIDsResponse" description: OK default: content: @@ -3798,25 +7457,26 @@ paths: description: Error security: - apiKey: [] - summary: Initialize the archive for CLI use + summary: List Gmail message IDs matching a filter tags: - API - /api/v1/cli/message: + /api/v1/messages/{id}: get: - operationId: getCLIMessage + operationId: getMessage parameters: - - description: Message numeric ID or source message ID - in: query + - description: Message ID + in: path name: id required: true schema: - type: string + format: int64 + type: integer responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliMessageResponse" + $ref: "#/components/schemas/MessageDetail" description: OK default: content: @@ -3826,23 +7486,30 @@ paths: description: Error security: - apiKey: [] - summary: Get one message for CLI output + summary: Get one message tags: - API - /api/v1/cli/message/raw: + /api/v1/messages/{id}/inline: get: - operationId: getCLIMessageRaw + operationId: getMessageInlinePart parameters: - - description: Message numeric ID or source message ID - in: query + - description: Message ID + in: path name: id required: true + schema: + format: int64 + type: integer + - description: Inline MIME Content-ID + in: query + name: cid + required: true schema: type: string responses: "200": content: - message/rfc822: + application/octet-stream: schema: contentMediaType: application/octet-stream format: binary @@ -3866,39 +7533,30 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - "500": + "415": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - "503": + "500": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - default: + "501": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - security: - - apiKey: [] - summary: Get one raw message for CLI export - tags: - - API - /api/v1/cli/rebuild-fts: - post: - operationId: rebuildCLIFTS - responses: - "200": + "503": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CliRebuildFTSEvent" - description: OK + $ref: "#/components/schemas/ErrorResponse" + description: Error default: content: application/json: @@ -3907,18 +7565,26 @@ paths: description: Error security: - apiKey: [] - summary: Rebuild the CLI full-text search index + summary: Get an inline MIME part tags: - API - /api/v1/cli/repair-encoding: - post: - operationId: repairEncodingCLI + /api/v1/messages/{id}/tasks: + get: + operationId: listMessageTasks + parameters: + - description: Archived email message ID + in: path + name: id + required: true + schema: + format: int64 + type: integer responses: "200": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLIRepairEncodingEvent" + $ref: "#/components/schemas/TaskLinkLookupResponse" description: OK default: content: @@ -3928,25 +7594,38 @@ paths: description: Error security: - apiKey: [] - summary: Repair CLI archive encoding + summary: List tasks linked to an archived email tags: - API - /api/v1/cli/run: post: - operationId: runCLI + operationId: createOrLinkMessageTask + parameters: + - description: Archived email message ID + in: path + name: id + required: true + schema: + format: int64 + type: integer + - description: Browser-generated retry-stable request ID + in: header + name: X-Request-Id + required: true + schema: + type: string requestBody: content: application/json: schema: - $ref: "#/components/schemas/CLIRunRequest" + $ref: "#/components/schemas/TaskLinkMutationRequest" required: true responses: - "200": + "201": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLIRunEvent" - description: OK + $ref: "#/components/schemas/TaskLinkMutationResponse" + description: Created default: content: application/json: @@ -3955,44 +7634,24 @@ paths: description: Error security: - apiKey: [] - summary: Run an allowlisted CLI command + summary: Create or link a task for an archived email tags: - API - /api/v1/cli/search: - get: - operationId: searchCLI + /api/v1/messages/{id}/tasks/{task_id}: + delete: + operationId: unlinkMessageTask parameters: - - description: Search query - in: query - name: q + - description: Archived email message ID + in: path + name: id required: true - schema: - type: string - - description: Maximum number of rows to return - in: query - name: limit schema: format: int64 type: integer - - description: Zero-based row offset - in: query - name: offset - schema: - format: int64 - type: integer - - description: Message type filter; repeat or comma-separate for multiple values - in: query - name: message_type - schema: - type: string - - description: Restrict to one account/source - in: query - name: account - schema: - type: string - - description: Restrict to one collection - in: query - name: collection + - description: External task ID + in: path + name: task_id + required: true schema: type: string responses: @@ -4000,8 +7659,55 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/CliSearchResponse" - description: OK + $ref: "#/components/schemas/TaskLinkMutationResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Unlink a task from an archived email + tags: + - API + /api/v1/people/search: + post: + operationId: searchPeople + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/IdentitySearchHTTPRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/PersonSearchHTTPResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -4010,29 +7716,26 @@ paths: description: Error security: - apiKey: [] - summary: Search messages for CLI output + summary: Search analytical people tags: - - API - /api/v1/cli/stats: + - Exploration + /api/v1/people/{id}: get: - operationId: getCLIStats + operationId: getPerson parameters: - - description: Restrict to one account/source - in: query - name: account - schema: - type: string - - description: Restrict to one collection - in: query - name: collection + - description: Durable participant ID + in: path + name: id + required: true schema: - type: string + format: int64 + type: integer responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliStatsResponse" + $ref: "#/components/schemas/PersonSummary" description: OK default: content: @@ -4042,25 +7745,53 @@ paths: description: Error security: - apiKey: [] - summary: Get CLI-compatible archive statistics + summary: Get one analytical person tags: - API - /api/v1/cli/sync: + /api/v1/people/{id}/files/search: post: - operationId: syncCLI + operationId: searchPersonFiles parameters: - - description: Account email or display name to sync - in: query - name: email + - description: Durable participant ID + in: path + name: id + required: true schema: - type: string + format: int64 + type: integer + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/FileSearchHTTPRequest" + required: true responses: "200": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLISyncEvent" + $ref: "#/components/schemas/FileSearchHTTPResponse" description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -4069,51 +7800,53 @@ paths: description: Error security: - apiKey: [] - summary: Run CLI incremental sync + summary: Search one person's analytical files tags: - - API - /api/v1/cli/sync-full: + - Exploration + /api/v1/people/{id}/summary: post: - operationId: syncFullCLI + operationId: getPersonContextSummary parameters: - - description: Account email or display name to sync - in: query - name: email - schema: - type: string - - description: Gmail search query - in: query - name: query - schema: - type: string - - description: Only messages on or after this YYYY-MM-DD date - in: query - name: after - schema: - type: string - - description: Only messages before this YYYY-MM-DD date - in: query - name: before - schema: - type: string - - description: Maximum messages to sync - in: query - name: limit + - description: Durable participant ID + in: path + name: id + required: true schema: format: int64 type: integer - - description: Ignore checkpoints and start fresh - in: query - name: noresume - schema: - type: boolean + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreHTTPRequest" + required: true responses: "200": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLISyncEvent" + $ref: "#/components/schemas/PersonContextSummaryHTTPResponse" description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -4122,42 +7855,53 @@ paths: description: Error security: - apiKey: [] - summary: Run CLI full sync + summary: Get one person's contextual analytical summary tags: - - API - /api/v1/cli/verify: + - Exploration + /api/v1/people/{id}/timeline: post: - operationId: verifyCLI + operationId: getPersonTimeline parameters: - - description: Account email to verify - in: query - name: email + - description: Durable participant ID + in: path + name: id required: true - schema: - type: string - - description: Number of messages to sample for MIME verification - in: query - name: sample schema: format: int64 type: integer - - description: Skip SQLite integrity check - in: query - name: skip_db_check - schema: - type: boolean - - description: Emit JSON summary output - in: query - name: json - schema: - type: boolean + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreHTTPRequest" + required: true responses: "200": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLIVerifyEvent" + $ref: "#/components/schemas/ExploreHTTPResponse" description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -4166,24 +7910,24 @@ paths: description: Error security: - apiKey: [] - summary: Verify the CLI archive against Gmail + summary: Get one person's canonical activity timeline tags: - - API - /api/v1/deletions: - get: - operationId: listDeletions - parameters: - - description: Filter manifests by status (pending, in_progress, completed, failed, cancelled) - in: query - name: status - schema: - type: string + - Exploration + /api/v1/query: + post: + operationId: runQuery + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/QueryRequest" + required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/ListDeletionsResponse" + $ref: "#/components/schemas/QueryResult" description: OK default: content: @@ -4193,30 +7937,45 @@ paths: description: Error security: - apiKey: [] - summary: List staged deletion manifests + summary: Run an aggregate query tags: - API + /api/v1/relationships: post: - operationId: stageDeletion + operationId: listRelationships requestBody: content: application/json: schema: - $ref: "#/components/schemas/StageDeletionRequest" + $ref: "#/components/schemas/RelationshipsHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/StageDeletionResponse" + $ref: "#/components/schemas/RelationshipsHTTPResponse" description: OK - "201": + "400": content: application/json: schema: - $ref: "#/components/schemas/StageDeletionResponse" - description: Created + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -4225,81 +7984,53 @@ paths: description: Error security: - apiKey: [] - summary: Stage messages for deletion + summary: Rank counterparts by reciprocity-weighted interaction tags: - - API - /api/v1/deletions/{id}: - delete: - operationId: cancelDeletion + - Exploration + /api/v1/relationships/{id}/timeline: + post: + operationId: getRelationshipTimeline parameters: - - description: Deletion manifest ID + - description: Any member participant ID of the counterpart's identity cluster in: path name: id required: true schema: - type: string + format: int64 + type: integer + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/RelationshipTimelineHTTPRequest" + required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CancelDeletionResponse" + $ref: "#/components/schemas/RelationshipTimelineHTTPResponse" description: OK - default: - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - security: - - apiKey: [] - summary: Cancel a staged deletion manifest - tags: - - API - /api/v1/health: - get: - operationId: getHealth - responses: - "200": + "400": content: application/json: schema: - $ref: "#/components/schemas/HealthResponse" - description: OK - default: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - security: - - apiKey: [] - summary: Get authenticated health details - tags: - - API - /api/v1/messages: - get: - operationId: listMessages - parameters: - - description: One-based page number (default 1; values below 1 are clamped to 1). Non-numeric values are rejected with 400. - in: query - name: page - schema: - format: int64 - type: integer - - description: Page size (default 20, max 100; out-of-range values are clamped). Non-numeric values are rejected with 400. - in: query - name: page_size - schema: - format: int64 - type: integer - responses: - "200": + "503": content: application/json: schema: - $ref: "#/components/schemas/MessageListResponse" - description: OK + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -4308,123 +8039,18 @@ paths: description: Error security: - apiKey: [] - summary: List messages + summary: Get one counterpart's interaction timeline, with chat grouped into local-day bursts tags: - - API - /api/v1/messages/filter: + - Exploration + /api/v1/saved-views: get: - operationId: filterMessages - parameters: - - description: Sender email/address filter - in: query - name: sender - schema: - type: string - - description: Sender display-name filter - in: query - name: sender_name - schema: - type: string - - description: Recipient email/address filter - in: query - name: recipient - schema: - type: string - - description: Recipient display-name filter - in: query - name: recipient_name - schema: - type: string - - description: Domain filter - in: query - name: domain - schema: - type: string - - description: Label filter - in: query - name: label - schema: - type: string - - description: Message type filter - in: query - name: message_type - schema: - type: string - - description: Named time period - in: query - name: time_period - schema: - type: string - - description: Time bucket granularity - in: query - name: time_granularity - schema: - type: string - - description: Conversation ID - in: query - name: conversation_id - schema: - format: int64 - type: integer - - description: Source ID - in: query - name: source_id - schema: - format: int64 - type: integer - - description: Only include messages with attachments - in: query - name: attachments_only - schema: - type: boolean - - description: Exclude deleted messages - in: query - name: hide_deleted - schema: - type: boolean - - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) - in: query - name: after - schema: - type: string - - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) - in: query - name: before - schema: - type: string - - description: Comma-separated aggregate view names to match empty values - in: query - name: empty_targets - schema: - type: string - - description: Zero-based row offset - in: query - name: offset - schema: - format: int64 - type: integer - - description: Maximum number of rows to return (default and max 500; larger values are clamped) - in: query - name: limit - schema: - format: int64 - type: integer - - description: "Sort field: date, size, or subject" - in: query - name: sort - schema: - type: string - - description: "Sort direction: asc or desc" - in: query - name: direction - schema: - type: string + operationId: listSavedViews responses: "200": content: application/json: schema: - $ref: "#/components/schemas/FilteredMessagesResponse" + $ref: "#/components/schemas/SavedViewsResponse" description: OK default: content: @@ -4434,153 +8060,47 @@ paths: description: Error security: - apiKey: [] - summary: List filtered messages + summary: List shared analytical Saved Views tags: - API - /api/v1/messages/gmail-ids: - get: - operationId: getGmailIDsByFilter - parameters: - - description: Sender email/address filter - in: query - name: sender - schema: - type: string - - description: Sender display-name filter - in: query - name: sender_name - schema: - type: string - - description: Recipient email/address filter - in: query - name: recipient - schema: - type: string - - description: Recipient display-name filter - in: query - name: recipient_name - schema: - type: string - - description: Domain filter - in: query - name: domain - schema: - type: string - - description: Label filter - in: query - name: label - schema: - type: string - - description: Message type filter - in: query - name: message_type - schema: - type: string - - description: Named time period - in: query - name: time_period - schema: - type: string - - description: Time bucket granularity - in: query - name: time_granularity - schema: - type: string - - description: Conversation ID - in: query - name: conversation_id - schema: - format: int64 - type: integer - - description: Source ID - in: query - name: source_id - schema: - format: int64 - type: integer - - description: Only include messages with attachments - in: query - name: attachments_only - schema: - type: boolean - - description: Exclude deleted messages - in: query - name: hide_deleted - schema: - type: boolean - - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) - in: query - name: after - schema: - type: string - - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) - in: query - name: before - schema: - type: string - - description: Comma-separated aggregate view names to match empty values - in: query - name: empty_targets - schema: - type: string - - description: Zero-based row offset - in: query - name: offset - schema: - format: int64 - type: integer - - description: Maximum number of rows to return (default and max 500; larger values are clamped) - in: query - name: limit - schema: - format: int64 - type: integer - - description: "Sort field: date, size, or subject" - in: query - name: sort - schema: - type: string - - description: "Sort direction: asc or desc" - in: query - name: direction - schema: - type: string + post: + operationId: createSavedView + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateSavedViewRequest" + required: true responses: - "200": + "201": content: application/json: schema: - $ref: "#/components/schemas/GmailIDsResponse" - description: OK - default: + $ref: "#/components/schemas/SavedView" + description: Created + headers: + ETag: + description: Strong Saved View revision tag for optimistic concurrency + schema: + type: string + "400": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - security: - - apiKey: [] - summary: List Gmail message IDs matching a filter - tags: - - API - /api/v1/messages/{id}: - get: - operationId: getMessage - parameters: - - description: Message ID - in: path - name: id - required: true - schema: - format: int64 - type: integer - responses: - "200": + "409": content: application/json: schema: - $ref: "#/components/schemas/MessageDetail" - description: OK + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error default: content: application/json: @@ -4589,35 +8109,29 @@ paths: description: Error security: - apiKey: [] - summary: Get one message + summary: Create a shared analytical Saved View tags: - API - /api/v1/messages/{id}/inline: - get: - operationId: getMessageInlinePart + /api/v1/saved-views/{id}: + delete: + operationId: deleteSavedView parameters: - - description: Message ID + - description: Saved View ID in: path name: id required: true schema: format: int64 type: integer - - description: Inline MIME Content-ID - in: query - name: cid + - description: Strong ETag returned by the latest Saved View read + in: header + name: If-Match required: true schema: type: string responses: - "200": - content: - application/octet-stream: - schema: - contentMediaType: application/octet-stream - format: binary - type: string - description: OK + "204": + description: No Content "400": content: application/json: @@ -4636,7 +8150,13 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - "415": + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "428": content: application/json: schema: @@ -4648,7 +8168,46 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - "501": + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Delete a shared analytical Saved View + tags: + - API + get: + operationId: getSavedView + parameters: + - description: Saved View ID + in: path + name: id + required: true + schema: + format: int64 + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SavedView" + description: OK + headers: + ETag: + description: Strong Saved View revision tag for optimistic concurrency + schema: + type: string + "404": content: application/json: schema: @@ -4668,25 +8227,73 @@ paths: description: Error security: - apiKey: [] - summary: Get an inline MIME part + summary: Get a shared analytical Saved View tags: - API - /api/v1/query: - post: - operationId: runQuery + patch: + operationId: patchSavedView + parameters: + - description: Saved View ID + in: path + name: id + required: true + schema: + format: int64 + type: integer + - description: Strong ETag returned by the latest Saved View read + in: header + name: If-Match + required: true + schema: + type: string requestBody: content: application/json: schema: - $ref: "#/components/schemas/QueryRequest" + $ref: "#/components/schemas/PatchSavedViewRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/QueryResult" + $ref: "#/components/schemas/SavedView" description: OK + headers: + ETag: + description: Strong Saved View revision tag for optimistic concurrency + schema: + type: string + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "428": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error default: content: application/json: @@ -4695,7 +8302,7 @@ paths: description: Error security: - apiKey: [] - summary: Run an aggregate query + summary: Update a shared analytical Saved View tags: - API /api/v1/scheduler/status: @@ -4802,6 +8409,45 @@ paths: summary: Search messages tags: - API + /api/v1/search/coverage: + post: + operationId: getSearchCoverage + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SearchCoverageRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SearchCoverageResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get semantic index coverage for an analytical context + tags: + - Exploration /api/v1/search/deep: get: operationId: deepSearch @@ -5194,6 +8840,100 @@ paths: summary: Find messages similar to a seed message tags: - API + /api/v1/settings: + get: + operationId: getSettings + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SettingsResponse" + description: OK + headers: + ETag: + description: Strong content hash for optimistic concurrency + schema: + type: string + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get browser-managed settings + tags: + - API + patch: + operationId: patchSettings + parameters: + - description: Strong ETag returned by the latest settings read + in: header + name: If-Match + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SettingsPatchRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SettingsResponse" + description: OK + headers: + ETag: + description: Strong content hash for optimistic concurrency + schema: + type: string + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "412": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "428": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Update browser-managed settings + tags: + - API /api/v1/sources/status: get: operationId: listSourceStatus diff --git a/cmd/msgvault/cmd/build_cache.go b/cmd/msgvault/cmd/build_cache.go index ac029490b..4572653ea 100644 --- a/cmd/msgvault/cmd/build_cache.go +++ b/cmd/msgvault/cmd/build_cache.go @@ -124,21 +124,17 @@ var buildCacheWriteStateFile = os.WriteFile // columns are added/removed/renamed in the COPY queries below so that // incremental builds automatically trigger a full rebuild instead of // producing Parquet files with mismatched schemas. -const cacheSchemaVersion = 7 // v7: include meeting transcripts in the searchable Parquet cache +const cacheSchemaVersion = query.CacheSchemaVersion -// sentCacheExportMessageWhere identifies rows eligible for the searchable -// Parquet cache. Calendar events are the sole archived message type excluded -// from this dataset; analytics eligibility is enforced separately by the -// query engine. +// sentCacheExportMessageWhere identifies every timestamped durable row eligible +// for the modality-neutral analytical cache. Modality scoping belongs in query +// code, never in publication. func sentCacheExportMessageWhere(alias string) string { qualifier := "" if alias != "" { qualifier = alias + "." } - return fmt.Sprintf( - "%ssent_at IS NOT NULL AND COALESCE(%smessage_type, '') <> 'calendar_event'", - qualifier, qualifier, - ) + return qualifier + "sent_at IS NOT NULL" } func exportableMessageWhere(alias string) string { @@ -189,8 +185,7 @@ func readCacheSyncCounters(db sqlRowQuerier) (cacheSyncCounters, error) { JOIN sources src ON src.id = sr.source_id WHERE sr.status IN ('completed', 'failed') AND sr.completed_at IS NOT NULL - AND src.source_type <> ? - `, sourceTypeCalendar).Scan( + `).Scan( &counters.additions, &counters.updates, &counters.failedRunCount, @@ -212,6 +207,7 @@ during export (especially for incremental updates). The cache files are stored in ~/.msgvault/analytics/: - messages/year=*/ Core message data, partitioned by year - participants/ Email addresses and domains + - participant_identifiers/ Explicit participant identity evidence - message_recipients/ Links messages to participants (from/to/cc/bcc) - labels/ Label definitions - message_labels/ Links messages to labels @@ -290,9 +286,12 @@ func runBuildCacheLocal(fullRebuild, auto bool) error { return err } - if result.Skipped { + switch { + case result.Skipped: fmt.Println("No new messages to export.") - } else { + case result.IdentityOnly: + fmt.Println("Refreshed identity datasets (no message changes).") + default: fmt.Printf("Exported %d messages to %s\n", result.ExportedCount, result.OutputDir) } fmt.Println("\nCache build complete! The TUI will now use fast cached queries.") @@ -315,6 +314,7 @@ type buildResult struct { MaxMessageID int64 OutputDir string Skipped bool + IdentityOnly bool // true when only owner_participants/participant_clusters were refreshed } // buildCache honors an explicit full rebuild unconditionally. Default builds @@ -361,6 +361,43 @@ func acquireCacheBuildLock(analyticsDir string) (*flock.Flock, error) { return buildLock, nil } +// identityDriftOnly reports whether HasIdentityDrift is the only staleness +// signal set: no new, deleted, or updated messages, and no account-identity +// drift — just participant links changing which participants are owners. +// In that case a full message rebuild is unnecessary — refreshIdentityDatasetsOnly +// re-exports only the two identity datasets. A full rebuild triggered by +// any other signal refreshes identity data naturally, so this check only +// matters when HasIdentityDrift fires alone. +// +// HasAccountIdentityDrift is excluded even though it also bumps +// identity_revision (and therefore HasIdentityDrift): confirming or +// removing an account identity changes the is_from_me flag baked into +// message Parquet shards, which the lightweight identity-only refresh does +// not re-derive. Account-identity drift must always take the full-rebuild +// path. +func identityDriftOnly(staleness cacheStaleness) bool { + return staleness.HasIdentityDrift && !staleness.HasNew && !staleness.HasDeleted && + !staleness.HasUpdated && !staleness.HasAccountIdentityDrift +} + +// refreshIdentityDatasetsOnly re-exports owner_participants and +// participant_clusters via cacheops.RefreshIdentityDatasets, leaving every +// message-derived dataset untouched. The caller already holds the +// exclusive cross-process cache build lock (buildCacheLocked's precondition), +// satisfying RefreshIdentityDatasets' own locking precondition. +func refreshIdentityDatasetsOnly(dbPath, analyticsDir string) (*buildResult, error) { + st, err := store.Open(dbPath) + if err != nil { + return nil, fmt.Errorf("open store for identity refresh: %w", err) + } + defer func() { _ = st.Close() }() + + if _, err := cacheops.RefreshIdentityDatasets(context.Background(), st, analyticsDir); err != nil { + return nil, fmt.Errorf("refresh identity datasets: %w", err) + } + return &buildResult{OutputDir: analyticsDir, IdentityOnly: true}, nil +} + // buildCacheLocked exports and publishes a cache while the caller holds the // exclusive cross-process cache lock. func buildCacheLocked(dbPath, analyticsDir string, fullRebuild, recheckStaleness bool) (*buildResult, error) { @@ -372,6 +409,9 @@ func buildCacheLocked(dbPath, analyticsDir string, fullRebuild, recheckStaleness if !staleness.NeedsBuild { return &buildResult{Skipped: true, OutputDir: analyticsDir}, nil } + if identityDriftOnly(staleness) { + return refreshIdentityDatasetsOnly(dbPath, analyticsDir) + } fullRebuild = staleness.FullRebuild } @@ -399,6 +439,44 @@ func buildCacheLocked(dbPath, analyticsDir string, fullRebuild, recheckStaleness } } + // Read the identity revision and computed participant clusters through + // their own short-lived connection, before the SQLite snapshot below pins + // a read transaction. ParticipantClusters does its own graph traversal in + // Go (internal/store), so it cannot be expressed as a COPY query the way + // owner_participants can. + // + // Invariant: the identity revision is read before (or atomically with) the + // cluster snapshot and before the message-export snapshot is pinned. A + // concurrent identity mutation therefore makes the stamped revision LAG + // the store, which HasIdentityDrift detects on the next staleness check — + // the cache self-heals. Never move this read after the export. The same + // invariant applies to the account-identity revision read alongside it: + // this full build derives is_from_me fresh from the current store state, + // so stamping a lagging account-identity revision here is likewise + // self-healing — HasAccountIdentityDrift catches it on the next check. + identityStore, err := store.Open(dbPath) + if err != nil { + return nil, fmt.Errorf("open store for identity export: %w", err) + } + identityRevision, err := identityStore.IdentityRevision() + if err != nil { + _ = identityStore.Close() + return nil, fmt.Errorf("read identity revision: %w", err) + } + accountIdentityRevision, err := identityStore.AccountIdentityRevision() + if err != nil { + _ = identityStore.Close() + return nil, fmt.Errorf("read account identity revision: %w", err) + } + participantClusters, err := identityStore.ParticipantClusters() + if err != nil { + _ = identityStore.Close() + return nil, fmt.Errorf("read participant clusters: %w", err) + } + if err := identityStore.Close(); err != nil { + return nil, fmt.Errorf("close store after identity export: %w", err) + } + // Keep metadata reads and every source-table export on one SQLite snapshot. // On platforms with sqlite_scanner, sqlite_query preserves native SQLite // indexes while the surrounding DuckDB transaction pins the same snapshot @@ -489,6 +567,13 @@ func buildCacheLocked(dbPath, analyticsDir string, fullRebuild, recheckStaleness if buildCacheAfterSnapshotHook != nil { buildCacheAfterSnapshotHook() } + var attachmentMIMEColumnCount int + if err := sourceSnapshot.QueryRow(` + SELECT COUNT(*) FROM pragma_table_info('attachments') WHERE name = 'mime_type' + `).Scan(&attachmentMIMEColumnCount); err != nil { + return nil, fmt.Errorf("inspect attachment MIME schema: %w", err) + } + sourceSnapshot.hasAttachmentMIME = attachmentMIMEColumnCount > 0 if err := sourceSnapshot.Prepare(); err != nil { return nil, err } @@ -522,8 +607,8 @@ func buildCacheLocked(dbPath, analyticsDir string, fullRebuild, recheckStaleness } // Junction rows are searchable exactly when their parent message is - // exportable. This includes meeting attendees and excludes calendar - // invitees, hidden rows, and messages without a timestamp. + // exportable. This includes calendar invitees and meeting attendees while + // excluding hidden rows and messages without a timestamp. exportableJunctionWhere := fmt.Sprintf( "TRY_CAST(message_id AS BIGINT) IN (SELECT CAST(m.id AS BIGINT) FROM sqlite_db.messages m WHERE %s AND TRY_CAST(m.id AS BIGINT) <= %d)", exportableMessageWhere("m"), maxID, @@ -606,18 +691,24 @@ func buildCacheLocked(dbPath, analyticsDir string, fullRebuild, recheckStaleness attachmentsFilter = fmt.Sprintf(" WHERE message_id > %d", lastMessageID) } attachmentsFilter = junctionFilter(attachmentsFilter) + attachmentMIMEExpression := "'' AS mime_type" + if sourceSnapshot.hasAttachmentMIME { + attachmentMIMEExpression = "COALESCE(TRY_CAST(mime_type AS VARCHAR), '') AS mime_type" + } if err := runExport(tableAttachments, fmt.Sprintf(` COPY ( SELECT + id AS attachment_id, message_id, size, - COALESCE(TRY_CAST(filename AS VARCHAR), '') as filename + COALESCE(TRY_CAST(filename AS VARCHAR), '') as filename, + %s FROM sqlite_db.attachments%s ) TO '%s/%s' ( FORMAT PARQUET, COMPRESSION 'zstd' ) - `, attachmentsFilter, escapedAttachmentsDir, junctionFile)); err != nil { + `, attachmentMIMEExpression, attachmentsFilter, escapedAttachmentsDir, junctionFile)); err != nil { return nil, fmt.Errorf("export attachments: %w", err) } @@ -641,6 +732,115 @@ func buildCacheLocked(dbPath, analyticsDir string, fullRebuild, recheckStaleness return nil, fmt.Errorf("export participants: %w", err) } + participantIdentifiersDir := filepath.Join(staging.root, tableParticipantIdentifiers) + escapedParticipantIdentifiersDir := strings.ReplaceAll(participantIdentifiersDir, "'", "''") + if err := runExport(tableParticipantIdentifiers, fmt.Sprintf(` + COPY ( + SELECT participant_id, + COALESCE(TRY_CAST(identifier_type AS VARCHAR), '') AS identifier_type, + COALESCE(TRY_CAST(identifier_value AS VARCHAR), '') AS identifier_value, + COALESCE(TRY_CAST(display_value AS VARCHAR), '') AS display_value, + COALESCE(TRY_CAST(is_primary AS BOOLEAN), false) AS is_primary + FROM sqlite_db.participant_identifiers + ) TO '%s/participant_identifiers.parquet' ( + FORMAT PARQUET, + COMPRESSION 'zstd' + ) + `, escapedParticipantIdentifiersDir)); err != nil { + return nil, fmt.Errorf("export participant identifiers: %w", err) + } + + // Owner participants: every participant row that a confirmed + // account_identities address resolves to for its source, matched + // case-insensitively against either the durable participant email or an + // explicit participant_identifiers email, or verbatim against any + // non-email participant_identifiers row (phone, chat handle, etc. — see + // the identity CLI contract: identifiers are stored verbatim, and only + // email comparisons fold case). Always fully replaced (not filtered by + // lastMessageID) since identities are cheap to recompute and independent + // of the message ID watermark. + ownerParticipantsDir := filepath.Join(staging.root, tableOwnerParticipants) + escapedOwnerParticipantsDir := strings.ReplaceAll(ownerParticipantsDir, "'", "''") + if err := runExport(tableOwnerParticipants, fmt.Sprintf(` + COPY ( + SELECT DISTINCT ai.source_id, p.id AS participant_id + FROM sqlite_db.account_identities ai + JOIN sqlite_db.participants p + ON p.email_address IS NOT NULL AND lower(p.email_address) = lower(ai.address) + UNION + SELECT DISTINCT ai.source_id, pi.participant_id + FROM sqlite_db.account_identities ai + JOIN sqlite_db.participant_identifiers pi + ON (pi.identifier_type = 'email' AND lower(pi.identifier_value) = lower(ai.address)) + OR (pi.identifier_type != 'email' AND pi.identifier_value = ai.address) + ) TO '%s/owner_participants.parquet' ( + FORMAT PARQUET, + COMPRESSION 'zstd' + ) + `, escapedOwnerParticipantsDir)); err != nil { + return nil, fmt.Errorf("export owner participants: %w", err) + } + + // Participant clusters: participant_id -> canonical (smallest member) + // cluster ID, computed in Go (ParticipantClusters does graph traversal + // that SQL cannot express directly) and staged through a DuckDB temp + // table so it can be COPYed to Parquet like every other dataset. Always + // written, even when there are no linked participants, so the dataset + // directory required by RequiredParquetDirs always exists. + participantClustersDir := filepath.Join(staging.root, tableParticipantClusters) + escapedParticipantClustersDir := strings.ReplaceAll(participantClustersDir, "'", "''") + if _, err := exportDB.Exec( + `CREATE TEMP TABLE tmp_participant_clusters (participant_id BIGINT, canonical_id BIGINT)`, + ); err != nil { + return nil, fmt.Errorf("create participant clusters temp table: %w", err) + } + if len(participantClusters) > 0 { + values := make([]string, 0, len(participantClusters)) + for participantID, canonicalID := range participantClusters { + values = append(values, fmt.Sprintf("(%d, %d)", participantID, canonicalID)) + } + insertSQL := "INSERT INTO tmp_participant_clusters (participant_id, canonical_id) VALUES " + + strings.Join(values, ", ") + if _, err := exportDB.Exec(insertSQL); err != nil { + return nil, fmt.Errorf("populate participant clusters temp table: %w", err) + } + } + if err := runExport(tableParticipantClusters, fmt.Sprintf(` + COPY ( + SELECT participant_id, canonical_id FROM tmp_participant_clusters + ) TO '%s/participant_clusters.parquet' ( + FORMAT PARQUET, + COMPRESSION 'zstd' + ) + `, escapedParticipantClustersDir)); err != nil { + return nil, fmt.Errorf("export participant clusters: %w", err) + } + if _, err := exportDB.Exec(`DROP TABLE tmp_participant_clusters`); err != nil { + return nil, fmt.Errorf("drop participant clusters temp table: %w", err) + } + + conversationParticipantsDir := filepath.Join(staging.root, tableConversationParticipants) + escapedConversationParticipantsDir := strings.ReplaceAll(conversationParticipantsDir, "'", "''") + if err := runExport(tableConversationParticipants, fmt.Sprintf(` + COPY ( + SELECT + cp.conversation_id, + cp.participant_id + FROM sqlite_db.conversation_participants cp + WHERE EXISTS ( + SELECT 1 FROM sqlite_db.messages m + WHERE m.conversation_id = cp.conversation_id + AND `+exportableMessageWhere("m")+` + AND TRY_CAST(m.id AS BIGINT) <= %d + ) + ) TO '%s/conversation_participants.parquet' ( + FORMAT PARQUET, + COMPRESSION 'zstd' + ) + `, maxID, escapedConversationParticipantsDir)); err != nil { + return nil, fmt.Errorf("export conversation participants: %w", err) + } + // 5. Export labels labelsDir := filepath.Join(staging.root, tableLabels) escapedLabelsDir := strings.ReplaceAll(labelsDir, "'", "''") @@ -727,6 +927,19 @@ func buildCacheLocked(dbPath, analyticsDir string, fullRebuild, recheckStaleness m.deleted_from_source_at, m.sender_id, COALESCE(TRY_CAST(m.message_type AS VARCHAR), '') as message_type, + (COALESCE(m.is_from_me, FALSE) OR EXISTS ( + SELECT 1 FROM sqlite_db.account_identities ai + JOIN sqlite_db.participants sp ON sp.id = m.sender_id + WHERE ai.source_id = m.source_id + AND sp.email_address IS NOT NULL + AND lower(sp.email_address) = lower(ai.address) + ) OR EXISTS ( + SELECT 1 FROM sqlite_db.account_identities ai + JOIN sqlite_db.participant_identifiers spi ON spi.participant_id = m.sender_id + WHERE ai.source_id = m.source_id + AND ((spi.identifier_type = 'email' AND lower(spi.identifier_value) = lower(ai.address)) + OR (spi.identifier_type != 'email' AND spi.identifier_value = ai.address)) + )) AS is_from_me, CAST(EXTRACT(YEAR FROM m.sent_at) AS INTEGER) as year, CAST(EXTRACT(MONTH FROM m.sent_at) AS INTEGER) as month FROM sqlite_db.messages m @@ -773,6 +986,19 @@ func buildCacheLocked(dbPath, analyticsDir string, fullRebuild, recheckStaleness m.deleted_from_source_at, m.sender_id, COALESCE(TRY_CAST(m.message_type AS VARCHAR), '') as message_type, + (COALESCE(m.is_from_me, FALSE) OR EXISTS ( + SELECT 1 FROM sqlite_db.account_identities ai + JOIN sqlite_db.participants sp ON sp.id = m.sender_id + WHERE ai.source_id = m.source_id + AND sp.email_address IS NOT NULL + AND lower(sp.email_address) = lower(ai.address) + ) OR EXISTS ( + SELECT 1 FROM sqlite_db.account_identities ai + JOIN sqlite_db.participant_identifiers spi ON spi.participant_id = m.sender_id + WHERE ai.source_id = m.source_id + AND ((spi.identifier_type = 'email' AND lower(spi.identifier_value) = lower(ai.address)) + OR (spi.identifier_type != 'email' AND spi.identifier_value = ai.address)) + )) AS is_from_me, CAST(EXTRACT(MONTH FROM m.sent_at) AS INTEGER) as month FROM sqlite_db.messages m WHERE 1 = 0 @@ -828,14 +1054,16 @@ func buildCacheLocked(dbPath, analyticsDir string, fullRebuild, recheckStaleness // Save sync state using the pre-export watermark so any deletion // that occurs during or after the build is detected as stale. state := syncState{ - LastMessageID: maxID, - LastSyncAt: cacheWatermark, - SchemaVersion: cacheSchemaVersion, - LastCompletedSyncRunID: lastCompletedSyncRunID, - LastCacheAdditionCount: syncCounters.additions, - LastCacheUpdateCount: syncCounters.updates, - LastFailedSyncRunCount: syncCounters.failedRunCount, - LastFailedSyncRunIDSum: syncCounters.failedRunIDSum, + LastMessageID: maxID, + LastSyncAt: cacheWatermark, + SchemaVersion: cacheSchemaVersion, + LastCompletedSyncRunID: lastCompletedSyncRunID, + LastCacheAdditionCount: syncCounters.additions, + LastCacheUpdateCount: syncCounters.updates, + LastFailedSyncRunCount: syncCounters.failedRunCount, + LastFailedSyncRunIDSum: syncCounters.failedRunIDSum, + IdentityRevision: identityRevision, + AccountIdentityRevision: accountIdentityRevision, } stateData, err := json.Marshal(state) if err != nil { @@ -950,6 +1178,20 @@ func printCacheStats(out io.Writer, errOut io.Writer, stats *cacheops.CacheStats if err := writeCacheStatsLine(out, "Run 'msgvault build-cache' to repair it.\n"); err != nil { return err } + case cacheops.StatusStaleSchema: + if err := writeCacheStatsLine(out, "Analytics cache schema is stale.\n"); err != nil { + return err + } + if err := writeCacheStatsLine(out, "Run 'msgvault build-cache --full-rebuild' to upgrade it.\n"); err != nil { + return err + } + case cacheops.StatusDrifted: + if err := writeCacheStatsLine(out, "Analytics cache files do not match the committed publication.\n"); err != nil { + return err + } + if err := writeCacheStatsLine(out, "Run 'msgvault build-cache --full-rebuild' to repair them.\n"); err != nil { + return err + } case cacheops.StatusReady: if err := writeCacheStatsLine(out, "Cache Statistics:\n"); err != nil { return err @@ -1007,11 +1249,12 @@ func writeCacheStatsLine(w io.Writer, format string, args ...any) error { // transaction directly. The fallback exports every table from one go-sqlite3 // transaction before DuckDB reads the resulting static CSV files. type cacheSourceSnapshot struct { - duckDB *sql.DB - duckTx *sql.Tx - sqliteDB *sql.DB - sqliteTx *sql.Tx - tmpDir string + duckDB *sql.DB + duckTx *sql.Tx + sqliteDB *sql.DB + sqliteTx *sql.Tx + tmpDir string + hasAttachmentMIME bool } func openCacheSourceSnapshot(duckDB *sql.DB, dbPath string) (*cacheSourceSnapshot, error) { @@ -1095,6 +1338,10 @@ func (s *cacheSourceSnapshot) Prepare() error { // Tables and the SELECT queries to export them. // Column lists match what the COPY-to-Parquet queries expect. + attachmentQuery := "SELECT id AS attachment_id, message_id, size, filename, '' AS mime_type FROM attachments" + if s.hasAttachmentMIME { + attachmentQuery = "SELECT id AS attachment_id, message_id, size, filename, mime_type FROM attachments" + } tables := []struct { name string query string @@ -1104,15 +1351,18 @@ func (s *cacheSourceSnapshot) Prepare() error { // `deleted_at IS NULL` filter on this path the same way it does // on the sqlite_scanner path; otherwise DuckDB binds against a // CSV view that lacks the column and the export fails on Windows. - {tableMessages, "SELECT id, source_id, source_message_id, conversation_id, subject, snippet, sent_at, size_estimate, has_attachments, attachment_count, deleted_from_source_at, deleted_at, sender_id, message_type FROM messages WHERE sent_at IS NOT NULL", + {tableMessages, "SELECT id, source_id, source_message_id, conversation_id, subject, snippet, sent_at, size_estimate, has_attachments, attachment_count, deleted_from_source_at, deleted_at, sender_id, message_type, is_from_me FROM messages WHERE sent_at IS NOT NULL", "types={'sent_at': 'TIMESTAMP', 'deleted_from_source_at': 'TIMESTAMP', 'deleted_at': 'TIMESTAMP'}"}, {"message_recipients", "SELECT message_id, participant_id, recipient_type, display_name FROM message_recipients", ""}, {"message_labels", "SELECT message_id, label_id FROM message_labels", ""}, - {tableAttachments, "SELECT message_id, size, filename FROM attachments", ""}, + {tableAttachments, attachmentQuery, ""}, {tableParticipants, "SELECT id, email_address, domain, display_name, phone_number FROM participants", ""}, + {"account_identities", "SELECT source_id, address FROM account_identities", ""}, + {tableParticipantIdentifiers, "SELECT participant_id, identifier_type, identifier_value, display_value, is_primary FROM participant_identifiers", ""}, {tableLabels, "SELECT id, name FROM labels", ""}, {"sources", "SELECT id, identifier, source_type FROM sources", ""}, {tableConversations, "SELECT id, source_conversation_id, title, COALESCE(conversation_type, 'email_thread') AS conversation_type FROM conversations", ""}, + {tableConversationParticipants, "SELECT conversation_id, participant_id FROM conversation_participants", ""}, } for _, t := range tables { @@ -1259,7 +1509,11 @@ func rebuildCacheAfterWrite(dbPath string) error { if err != nil { return fmt.Errorf("refresh analytics cache: %w", err) } - if !result.Skipped { + switch { + case result.Skipped: + case result.IdentityOnly: + logger.Info("identity datasets refreshed") + default: logger.Info("cache rebuilt", "exported", result.ExportedCount) } return nil diff --git a/cmd/msgvault/cmd/build_cache_calendar_test.go b/cmd/msgvault/cmd/build_cache_calendar_test.go index 2ef8a31a7..6dda3a67b 100644 --- a/cmd/msgvault/cmd/build_cache_calendar_test.go +++ b/cmd/msgvault/cmd/build_cache_calendar_test.go @@ -17,12 +17,11 @@ import ( "go.kenn.io/msgvault/internal/store" ) -// TestBuildCache_ExcludesCalendarEvents is the Parquet-leak regression: calendar -// events (and their attendee junction rows) must be excluded from the email -// analytics Parquet, or attendees would surface in Sender/Recipient/Domain -// aggregates and per-view counts would stop reconciling with the email-gated -// stats header. -func TestBuildCache_ExcludesCalendarEvents(t *testing.T) { +// TestBuildCache_IncludesCalendarEventsInModalityNeutralCache verifies calendar +// rows and attendees are available to the common analytical read model. Legacy +// email aggregates remain email-scoped in query code rather than by dropping +// non-email rows during publication. +func TestBuildCache_IncludesCalendarEventsInModalityNeutralCache(t *testing.T) { assert := assert.New(t) require := require.New(t) tmp := t.TempDir() @@ -84,25 +83,25 @@ func TestBuildCache_ExcludesCalendarEvents(t *testing.T) { require.NoError(err) defer func() { _ = duckdb.Close() }() - // messages Parquet: only the email, never the calendar event. + // messages Parquet is modality-neutral: email and calendar event coexist. msgPattern := filepath.Join(analyticsDir, "messages", "**", "*.parquet") var msgCount int require.NoError(duckdb.QueryRow( `SELECT COUNT(*) FROM read_parquet(?, hive_partitioning=true)`, msgPattern).Scan(&msgCount)) - assert.Equal(1, msgCount, "only the email message should be exported") + assert.Equal(2, msgCount, "email and calendar event should be exported") var calCount int require.NoError(duckdb.QueryRow( `SELECT COUNT(*) FROM read_parquet(?, hive_partitioning=true) WHERE message_type = 'calendar_event'`, msgPattern).Scan(&calCount)) - assert.Equal(0, calCount, "no calendar_event rows in the messages Parquet") + assert.Equal(1, calCount, "calendar_event row in the messages Parquet") - // message_recipients Parquet: carol (email) present, bob (attendee) absent. + // Both email recipients and event attendees are analytical participants. recPattern := filepath.Join(analyticsDir, "message_recipients", "*.parquet") var bobRows int require.NoError(duckdb.QueryRow( `SELECT COUNT(*) FROM read_parquet(?) WHERE participant_id = ?`, recPattern, bobID).Scan(&bobRows)) - assert.Equal(0, bobRows, "calendar attendee must not leak into the recipients Parquet") + assert.Equal(1, bobRows, "calendar attendee should be exported") var carolRows int require.NoError(duckdb.QueryRow( @@ -113,10 +112,10 @@ func TestBuildCache_ExcludesCalendarEvents(t *testing.T) { require.NoError(duckdb.QueryRow( `SELECT COUNT(*) FROM read_parquet(?) WHERE conversation_type = 'calendar'`, filepath.Join(analyticsDir, "conversations", "*.parquet")).Scan(&calendarConversationRows)) - assert.Equal(0, calendarConversationRows, "calendar conversation must not leak into conversations Parquet") + assert.Equal(1, calendarConversationRows, "calendar conversation should be exported") } -func TestBuildCache_AllCalendarEventsWritesEmptyCacheState(t *testing.T) { +func TestBuildCache_AllCalendarEventsWritesAnalyticalCacheState(t *testing.T) { assert := assert.New(t) require := require.New(t) tmp := t.TempDir() @@ -143,9 +142,9 @@ func TestBuildCache_AllCalendarEventsWritesEmptyCacheState(t *testing.T) { require.NoError(st.Close()) result, err := buildCache(dbPath, analyticsDir, false) - require.NoError(err, "buildCache should accept an archive with no exportable email messages") + require.NoError(err, "buildCache should accept a calendar-only archive") require.False(result.Skipped, "calendar-only database still advances cache state") - assert.Equal(int64(0), result.ExportedCount, "calendar events are intentionally excluded from email analytics") + assert.Equal(int64(1), result.ExportedCount, "calendar events are part of the modality-neutral cache") data, err := os.ReadFile(filepath.Join(analyticsDir, "_last_sync.json")) require.NoError(err, "cache state should be written") diff --git a/cmd/msgvault/cmd/build_cache_identity_test.go b/cmd/msgvault/cmd/build_cache_identity_test.go new file mode 100644 index 000000000..c51a0c576 --- /dev/null +++ b/cmd/msgvault/cmd/build_cache_identity_test.go @@ -0,0 +1,407 @@ +package cmd + +import ( + "database/sql" + "os" + "path/filepath" + "testing" + "time" + + _ "github.com/duckdb/duckdb-go/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/store" +) + +// TestBuildCache_DerivesIsFromMeAndIdentityDatasets verifies that: +// - messages Parquet gains a derived is_from_me column: true when the +// sender's participant email case-insensitively matches a confirmed +// account_identities address for the message's source, even when the +// stored is_from_me is false. +// - owner_participants maps a source to every participant that resolves +// to one of its confirmed identities. +// - participant_clusters maps linked participants to their smaller-ID +// canonical member after a rebuild. +func TestBuildCache_DerivesIsFromMeAndIdentityDatasets(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + tmp := t.TempDir() + dbPath := filepath.Join(tmp, "msgvault.db") + analyticsDir := filepath.Join(tmp, "analytics") + + st, err := store.Open(dbPath) + require.NoError(err, "open store") + require.NoError(st.InitSchema(), "init schema") + + src, err := st.GetOrCreateSource("gmail", "owner@example.com") + require.NoError(err) + require.NoError(st.AddAccountIdentity(src.ID, "owner@example.com", "manual")) + + convID, err := st.EnsureConversation(src.ID, "thread-1", "Hi") + require.NoError(err) + + // The owner's participant email differs only in case from the confirmed + // identity address, exercising the case-insensitive match. + ownerParticipantID, err := st.EnsureParticipant("Owner@Example.com", "Owner", "example.com") + require.NoError(err) + otherParticipantID, err := st.EnsureParticipant("other@example.com", "Other", "example.com") + require.NoError(err) + + ownerMsgID, err := st.UpsertMessage(&store.Message{ + ConversationID: convID, + SourceID: src.ID, + SourceMessageID: "m1", + MessageType: "email", + SentAt: sql.NullTime{Time: time.Date(2024, 5, 1, 12, 0, 0, 0, time.UTC), Valid: true}, + SenderID: sql.NullInt64{Int64: ownerParticipantID, Valid: true}, + Subject: sql.NullString{String: "From owner", Valid: true}, + // IsFromMe left false: the export must derive true from the + // confirmed identity match, not the stored column alone. + }) + require.NoError(err) + require.NoError(st.ReplaceMessageRecipients(ownerMsgID, "from", []int64{ownerParticipantID}, []string{""})) + + controlMsgID, err := st.UpsertMessage(&store.Message{ + ConversationID: convID, + SourceID: src.ID, + SourceMessageID: "m2", + MessageType: "email", + SentAt: sql.NullTime{Time: time.Date(2024, 5, 1, 13, 0, 0, 0, time.UTC), Valid: true}, + SenderID: sql.NullInt64{Int64: otherParticipantID, Valid: true}, + Subject: sql.NullString{String: "From other", Valid: true}, + }) + require.NoError(err) + require.NoError(st.ReplaceMessageRecipients(controlMsgID, "from", []int64{otherParticipantID}, []string{""})) + + require.NoError(st.Close()) + + result, err := buildCache(dbPath, analyticsDir, false) + require.NoError(err, "buildCache") + require.False(result.Skipped, "buildCache unexpectedly skipped") + + duckdb, err := sql.Open("duckdb", "") + require.NoError(err) + defer func() { _ = duckdb.Close() }() + + msgPattern := filepath.Join(analyticsDir, "messages", "**", "*.parquet") + var ownerIsFromMe, controlIsFromMe bool + require.NoError(duckdb.QueryRow( + `SELECT is_from_me FROM read_parquet(?, hive_partitioning=true) WHERE id = ?`, + msgPattern, ownerMsgID).Scan(&ownerIsFromMe)) + require.NoError(duckdb.QueryRow( + `SELECT is_from_me FROM read_parquet(?, hive_partitioning=true) WHERE id = ?`, + msgPattern, controlMsgID).Scan(&controlIsFromMe)) + assert.True(ownerIsFromMe, "owner-sent message should derive is_from_me = true") + assert.False(controlIsFromMe, "control message should not derive is_from_me") + + ownerParticipantsPattern := filepath.Join(analyticsDir, "owner_participants", "*.parquet") + var ownerRows int + require.NoError(duckdb.QueryRow( + `SELECT COUNT(*) FROM read_parquet(?) WHERE source_id = ? AND participant_id = ?`, + ownerParticipantsPattern, src.ID, ownerParticipantID, + ).Scan(&ownerRows)) + assert.Equal(1, ownerRows, "owner_participants should map the source to the owner participant") + + var otherRows int + require.NoError(duckdb.QueryRow( + `SELECT COUNT(*) FROM read_parquet(?) WHERE participant_id = ?`, + ownerParticipantsPattern, otherParticipantID, + ).Scan(&otherRows)) + assert.Equal(0, otherRows, "non-owner participant should not appear in owner_participants") + + clustersPattern := filepath.Join(analyticsDir, "participant_clusters", "*.parquet") + var initialClusterRows int + require.NoError(duckdb.QueryRow( + `SELECT COUNT(*) FROM read_parquet(?)`, clustersPattern, + ).Scan(&initialClusterRows)) + assert.Equal(0, initialClusterRows, "no participants are linked yet") + + // Link the owner and control participants (Task 1 API), then rebuild: + // participant_clusters should map both to the smaller ID. + st2, err := store.Open(dbPath) + require.NoError(err, "reopen store") + _, err = st2.LinkParticipants(ownerParticipantID, otherParticipantID) + require.NoError(err, "LinkParticipants") + require.NoError(st2.Close()) + + result, err = buildCache(dbPath, analyticsDir, true) + require.NoError(err, "buildCache full rebuild") + require.False(result.Skipped) + + canonical := min(ownerParticipantID, otherParticipantID) + rows, err := duckdb.Query( + `SELECT participant_id, canonical_id FROM read_parquet(?) ORDER BY participant_id`, clustersPattern) + require.NoError(err) + defer func() { _ = rows.Close() }() + + type clusterRow struct{ participantID, canonicalID int64 } + var got []clusterRow + for rows.Next() { + var r clusterRow + require.NoError(rows.Scan(&r.participantID, &r.canonicalID)) + got = append(got, r) + } + require.NoError(rows.Err()) + require.Len(got, 2, "both linked participants should appear in participant_clusters") + for _, r := range got { + assert.Equal(canonical, r.canonicalID, "participant %d should map to the canonical (smallest) ID", r.participantID) + } +} + +// TestBuildCache_OwnerParticipantsMatchesNonEmailIdentifiersVerbatim covers +// owners known only by a non-email identifier (e.g. an iMessage/SMS "me" +// confirmed as a phone number): owner_participants and the is_from_me +// derivation must match participant_identifiers of ANY type, not just +// 'email'. Per the identity CLI contract, email comparisons fold case but +// every other identifier type (phone, chat handle, ...) matches verbatim, so +// a case-differing non-email identifier must NOT match. +func TestBuildCache_OwnerParticipantsMatchesNonEmailIdentifiersVerbatim(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + tmp := t.TempDir() + dbPath := filepath.Join(tmp, "msgvault.db") + analyticsDir := filepath.Join(tmp, "analytics") + + st, err := store.Open(dbPath) + require.NoError(err, "open store") + require.NoError(st.InitSchema(), "init schema") + + src, err := st.GetOrCreateSource("imessage", "+15550100001") + require.NoError(err) + require.NoError(st.AddAccountIdentity(src.ID, "+15550100001", "manual"), "confirm phone identity") + require.NoError(st.AddAccountIdentity(src.ID, "@user:matrix.org", "manual"), "confirm chat-handle identity") + + convID, err := st.EnsureConversation(src.ID, "thread-1", "Hi") + require.NoError(err) + + // Owner known only by a phone participant_identifiers row, no email. + // EnsureParticipantByPhone writes the participant_identifiers row itself. + phoneOwnerID, err := st.EnsureParticipantByPhone("+15550100001", "Phone Owner", "phone") + require.NoError(err) + + // Case-differing non-email identifier: must NOT match verbatim. + caseVariantID, err := st.EnsureParticipantByIdentifier("matrix", "@User:matrix.org", "Case Variant") + require.NoError(err) + + phoneMsgID, err := st.UpsertMessage(&store.Message{ + ConversationID: convID, + SourceID: src.ID, + SourceMessageID: "m1", + MessageType: "sms", + SentAt: sql.NullTime{Time: time.Date(2024, 5, 1, 12, 0, 0, 0, time.UTC), Valid: true}, + SenderID: sql.NullInt64{Int64: phoneOwnerID, Valid: true}, + // IsFromMe left false: the export must derive true from the + // confirmed phone identity match. + }) + require.NoError(err) + require.NoError(st.ReplaceMessageRecipients(phoneMsgID, "from", []int64{phoneOwnerID}, []string{""})) + + caseVariantMsgID, err := st.UpsertMessage(&store.Message{ + ConversationID: convID, + SourceID: src.ID, + SourceMessageID: "m2", + MessageType: "sms", + SentAt: sql.NullTime{Time: time.Date(2024, 5, 1, 13, 0, 0, 0, time.UTC), Valid: true}, + SenderID: sql.NullInt64{Int64: caseVariantID, Valid: true}, + }) + require.NoError(err) + require.NoError(st.ReplaceMessageRecipients(caseVariantMsgID, "from", []int64{caseVariantID}, []string{""})) + + require.NoError(st.Close()) + + result, err := buildCache(dbPath, analyticsDir, false) + require.NoError(err, "buildCache") + require.False(result.Skipped, "buildCache unexpectedly skipped") + + duckdb, err := sql.Open("duckdb", "") + require.NoError(err) + defer func() { _ = duckdb.Close() }() + + msgPattern := filepath.Join(analyticsDir, "messages", "**", "*.parquet") + var phoneIsFromMe, caseVariantIsFromMe bool + require.NoError(duckdb.QueryRow( + `SELECT is_from_me FROM read_parquet(?, hive_partitioning=true) WHERE id = ?`, + msgPattern, phoneMsgID).Scan(&phoneIsFromMe)) + require.NoError(duckdb.QueryRow( + `SELECT is_from_me FROM read_parquet(?, hive_partitioning=true) WHERE id = ?`, + msgPattern, caseVariantMsgID).Scan(&caseVariantIsFromMe)) + assert.True(phoneIsFromMe, "phone-identified owner message should derive is_from_me = true") + assert.False(caseVariantIsFromMe, "a case-differing non-email identifier must not match verbatim") + + ownerParticipantsPattern := filepath.Join(analyticsDir, "owner_participants", "*.parquet") + var phoneOwnerRows int + require.NoError(duckdb.QueryRow( + `SELECT COUNT(*) FROM read_parquet(?) WHERE source_id = ? AND participant_id = ?`, + ownerParticipantsPattern, src.ID, phoneOwnerID, + ).Scan(&phoneOwnerRows)) + assert.Equal(1, phoneOwnerRows, "owner_participants should map the source to the phone-identified owner") + + var caseVariantRows int + require.NoError(duckdb.QueryRow( + `SELECT COUNT(*) FROM read_parquet(?) WHERE participant_id = ?`, + ownerParticipantsPattern, caseVariantID, + ).Scan(&caseVariantRows)) + assert.Equal(0, caseVariantRows, "case-differing non-email identifier must not appear in owner_participants") +} + +// TestBuildCache_AccountIdentityDriftForcesFullRebuildAndRederivesIsFromMe +// covers Finding 1 of the relationships-backend review end-to-end: after a +// build has already baked is_from_me=false for a pre-existing message, +// confirming an account identity for that message's sender must force a +// full rebuild (never the lightweight identity-only refresh, even though +// the same mutation also bumps identity_revision), and the rebuilt message +// shard must re-derive is_from_me=true. +func TestBuildCache_AccountIdentityDriftForcesFullRebuildAndRederivesIsFromMe(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + tmp := t.TempDir() + dbPath := filepath.Join(tmp, "msgvault.db") + analyticsDir := filepath.Join(tmp, "analytics") + + st, err := store.Open(dbPath) + require.NoError(err, "open store") + require.NoError(st.InitSchema(), "init schema") + + src, err := st.GetOrCreateSource("gmail", "owner@example.com") + require.NoError(err) + convID, err := st.EnsureConversation(src.ID, "thread-1", "Hi") + require.NoError(err) + owner, err := st.EnsureParticipant("owner@example.com", "Owner", "example.com") + require.NoError(err) + + msgID, err := st.UpsertMessage(&store.Message{ + ConversationID: convID, + SourceID: src.ID, + SourceMessageID: "m1", + MessageType: "email", + SentAt: sql.NullTime{Time: time.Date(2024, 5, 1, 12, 0, 0, 0, time.UTC), Valid: true}, + SenderID: sql.NullInt64{Int64: owner, Valid: true}, + // IsFromMe left false, and no account identity is confirmed yet, so + // the initial build must not derive it true. + }) + require.NoError(err) + require.NoError(st.ReplaceMessageRecipients(msgID, "from", []int64{owner}, []string{""})) + require.NoError(st.Close()) + + result, err := buildCache(dbPath, analyticsDir, true) + require.NoError(err, "initial buildCache") + require.False(result.Skipped, "initial build must run") + + duckdb, err := sql.Open("duckdb", "") + require.NoError(err) + defer func() { _ = duckdb.Close() }() + msgPattern := filepath.Join(analyticsDir, "messages", "**", "*.parquet") + var isFromMe bool + require.NoError(duckdb.QueryRow( + `SELECT is_from_me FROM read_parquet(?, hive_partitioning=true) WHERE id = ?`, + msgPattern, msgID).Scan(&isFromMe)) + assert.False(isFromMe, "is_from_me must be false before the identity is confirmed") + + st2, err := store.Open(dbPath) + require.NoError(err, "reopen store") + require.NoError(st2.AddAccountIdentity(src.ID, "owner@example.com", "manual"), "AddAccountIdentity") + require.NoError(st2.Close()) + + staleness := cacheNeedsBuild(dbPath, analyticsDir) + assert.True(staleness.NeedsBuild, "account identity confirmation should request a build") + assert.True(staleness.HasAccountIdentityDrift, "account identity drift signal should be set") + assert.True(staleness.FullRebuild, "account identity drift must force a full rebuild") + assert.False(identityDriftOnly(staleness), + "account identity drift must not take the identity-only refresh path") + + result, err = buildCacheAuto(dbPath, analyticsDir) + require.NoError(err, "buildCacheAuto after account identity confirmation") + require.False(result.Skipped, "account identity drift must still trigger a build") + assert.False(result.IdentityOnly, + "account identity drift must rewrite message shards, not take the identity-only path") + + require.NoError(duckdb.QueryRow( + `SELECT is_from_me FROM read_parquet(?, hive_partitioning=true) WHERE id = ?`, + msgPattern, msgID).Scan(&isFromMe)) + assert.True(isFromMe, + "is_from_me must be re-derived true after the identity is confirmed and a full rebuild runs") +} + +// TestBuildCache_IdentityDriftOnlyRefreshesWithoutMessageRebuild verifies +// that when identity drift (a participant link) is the only staleness +// signal, the auto-build path calls cacheops.RefreshIdentityDatasets +// instead of rewriting message shards: the message Parquet file is left +// byte-for-byte untouched (same mtime) while participant_clusters and the +// stamped identity revision are refreshed. +func TestBuildCache_IdentityDriftOnlyRefreshesWithoutMessageRebuild(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + tmp := t.TempDir() + dbPath := filepath.Join(tmp, "msgvault.db") + analyticsDir := filepath.Join(tmp, "analytics") + + st, err := store.Open(dbPath) + require.NoError(err, "open store") + require.NoError(st.InitSchema(), "init schema") + + src, err := st.GetOrCreateSource("gmail", "owner@example.com") + require.NoError(err) + convID, err := st.EnsureConversation(src.ID, "thread-1", "Hi") + require.NoError(err) + a, err := st.EnsureParticipant("alice@example.com", "Alice", "example.com") + require.NoError(err) + b, err := st.EnsureParticipant("alice@personal.example", "Alice P", "personal.example") + require.NoError(err) + msgID, err := st.UpsertMessage(&store.Message{ + ConversationID: convID, + SourceID: src.ID, + SourceMessageID: "m1", + MessageType: "email", + SentAt: sql.NullTime{Time: time.Date(2024, 5, 1, 12, 0, 0, 0, time.UTC), Valid: true}, + SenderID: sql.NullInt64{Int64: a, Valid: true}, + }) + require.NoError(err) + require.NoError(st.ReplaceMessageRecipients(msgID, "from", []int64{a}, []string{""})) + require.NoError(st.Close()) + + result, err := buildCache(dbPath, analyticsDir, false) + require.NoError(err, "initial buildCache") + require.False(result.Skipped, "initial build must run") + + messageShards, err := filepath.Glob(filepath.Join(analyticsDir, "messages", "*", "*.parquet")) + require.NoError(err) + require.NotEmpty(messageShards, "expected message shards after initial build") + before, err := os.Stat(messageShards[0]) + require.NoError(err) + + st2, err := store.Open(dbPath) + require.NoError(err, "reopen store") + wantRevision, err := st2.LinkParticipants(a, b) + require.NoError(err, "LinkParticipants") + require.NoError(st2.Close()) + + result, err = buildCacheAuto(dbPath, analyticsDir) + require.NoError(err, "buildCacheAuto after identity drift") + require.False(result.Skipped, "identity drift alone must still trigger a build") + assert.True(result.IdentityOnly, "identity drift alone should take the lightweight refresh path") + + after, err := os.Stat(messageShards[0]) + require.NoError(err) + assert.Equal(before.ModTime(), after.ModTime(), "message shard must be untouched by an identity-only refresh") + assert.Equal(before.Size(), after.Size(), "message shard must be untouched by an identity-only refresh") + + state, err := query.ReadCacheSyncState(analyticsDir) + require.NoError(err, "ReadCacheSyncState") + assert.Equal(wantRevision, state.IdentityRevision, "_last_sync.json identity_revision") + + clustersPattern := filepath.Join(analyticsDir, "participant_clusters", "*.parquet") + duckdb, err := sql.Open("duckdb", "") + require.NoError(err) + defer func() { _ = duckdb.Close() }() + var canonicalA, canonicalB int64 + require.NoError(duckdb.QueryRow( + `SELECT canonical_id FROM read_parquet(?) WHERE participant_id = ?`, clustersPattern, a, + ).Scan(&canonicalA)) + require.NoError(duckdb.QueryRow( + `SELECT canonical_id FROM read_parquet(?) WHERE participant_id = ?`, clustersPattern, b, + ).Scan(&canonicalB)) + want := min(a, b) + assert.Equal(want, canonicalA, "participant a canonical id") + assert.Equal(want, canonicalB, "participant b canonical id") +} diff --git a/cmd/msgvault/cmd/build_cache_test.go b/cmd/msgvault/cmd/build_cache_test.go index d4f8ae92b..d19103f44 100644 --- a/cmd/msgvault/cmd/build_cache_test.go +++ b/cmd/msgvault/cmd/build_cache_test.go @@ -57,6 +57,7 @@ func setupTestSQLite(t *testing.T) string { deleted_from_source_at TIMESTAMP, sender_id INTEGER, message_type TEXT NOT NULL DEFAULT 'email', + is_from_me BOOLEAN DEFAULT FALSE, deleted_at DATETIME, UNIQUE(source_id, source_message_id) ); @@ -69,6 +70,16 @@ func setupTestSQLite(t *testing.T) string { phone_number TEXT ); + CREATE TABLE participant_identifiers ( + id INTEGER PRIMARY KEY, + participant_id INTEGER NOT NULL REFERENCES participants(id), + identifier_type TEXT NOT NULL, + identifier_value TEXT NOT NULL, + display_value TEXT, + is_primary BOOLEAN DEFAULT FALSE, + UNIQUE(identifier_type, identifier_value) + ); + CREATE TABLE message_recipients ( id INTEGER PRIMARY KEY, message_id INTEGER NOT NULL REFERENCES messages(id), @@ -107,6 +118,33 @@ func setupTestSQLite(t *testing.T) string { title TEXT, conversation_type TEXT NOT NULL DEFAULT 'email' ); + + CREATE TABLE conversation_participants ( + conversation_id INTEGER NOT NULL REFERENCES conversations(id), + participant_id INTEGER NOT NULL REFERENCES participants(id), + PRIMARY KEY (conversation_id, participant_id) + ); + + CREATE TABLE archive_metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ); + + CREATE TABLE account_identities ( + source_id INTEGER NOT NULL REFERENCES sources(id), + address TEXT NOT NULL, + source_signal TEXT NOT NULL DEFAULT '', + confirmed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (source_id, address) + ); + + CREATE TABLE participant_links ( + participant_a INTEGER NOT NULL REFERENCES participants(id), + participant_b INTEGER NOT NULL REFERENCES participants(id), + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (participant_a, participant_b), + CHECK (participant_a < participant_b) + ); ` if _, err := db.Exec(schema); err != nil { @@ -126,6 +164,11 @@ func setupTestSQLite(t *testing.T) string { (3, 'carol@example.com', 'example.com', 'Carol White'), (4, 'dan@other.net', 'other.net', 'Dan Brown'); + INSERT INTO participant_identifiers + (participant_id, identifier_type, identifier_value, display_value, is_primary) VALUES + (1, 'email', 'alice@example.com', 'Alice Smith ', 1), + (2, 'email', 'bob@company.org', 'bob@company.org', 1); + -- Labels INSERT INTO labels (id, source_id, name) VALUES (1, 1, 'INBOX'), @@ -184,6 +227,10 @@ func setupTestSQLite(t *testing.T) string { (102, 1, 'thread102', 'Follow up Thread'), (103, 1, 'thread103', 'Question Thread'), (104, 1, 'thread104', 'Final Thread'); + + INSERT INTO conversation_participants (conversation_id, participant_id) VALUES + (101, 1), (101, 2), (101, 3), (102, 1), (102, 2), + (103, 1), (103, 2), (104, 1), (104, 2); ` _, err = db.Exec(testData) @@ -799,6 +846,7 @@ func TestBuildCache_BasicExport(t *testing.T) { "messages", "sources", "participants", + "participant_identifiers", "message_recipients", "labels", "message_labels", @@ -824,6 +872,27 @@ func TestBuildCache_BasicExport(t *testing.T) { assert.Equal(int64(5), state.LastMessageID) } +func TestBuildCache_PublishesConversationParticipants(t *testing.T) { + require := require.New(t) + tmpDir := setupTestSQLite(t) + dbPath := filepath.Join(tmpDir, "test.db") + analyticsDir := filepath.Join(tmpDir, "analytics") + + _, err := buildCache(dbPath, analyticsDir, false) + require.NoError(err) + + duckdb, err := sql.Open("duckdb", "") + require.NoError(err) + defer func() { _ = duckdb.Close() }() + var count int64 + err = duckdb.QueryRow( + `SELECT COUNT(*) FROM read_parquet(?)`, + filepath.Join(analyticsDir, "conversation_participants", "*.parquet"), + ).Scan(&count) + require.NoError(err) + assert.Equal(t, int64(9), count) +} + // TestBuildCache_DataIntegrity verifies the exported Parquet data matches SQLite. func TestBuildCache_DataIntegrity(t *testing.T) { require := require.New(t) @@ -886,6 +955,10 @@ func TestBuildCache_DataIntegrity(t *testing.T) { attQuery := "SELECT SUM(size) FROM read_parquet('" + filepath.Join(analyticsDir, "attachments", "*.parquet") + "')" require.NoError(db.QueryRow(attQuery).Scan(&totalSize), "query attachments") assert.Equal(int64(35000), totalSize, "expected total attachment size 10000+5000+20000") + var attachmentIDs int64 + require.NoError(db.QueryRow("SELECT COUNT(DISTINCT attachment_id) FROM read_parquet('" + + filepath.Join(analyticsDir, "attachments", "*.parquet") + "')").Scan(&attachmentIDs)) + assert.Equal(int64(3), attachmentIDs, "durable attachment IDs") } // TestBuildCache_IncrementalExport tests that incremental exports only add new messages. @@ -1965,13 +2038,18 @@ func TestBuildCache_EmptyDatabase(t *testing.T) { db, _ := sql.Open("sqlite3", dbPath) _, _ = db.Exec(` CREATE TABLE sources (id INTEGER PRIMARY KEY, source_type TEXT NOT NULL DEFAULT 'gmail', identifier TEXT); - CREATE TABLE messages (id INTEGER PRIMARY KEY, source_id INTEGER, source_message_id TEXT, sent_at TIMESTAMP, size_estimate INTEGER, has_attachments BOOLEAN, subject TEXT, snippet TEXT, conversation_id INTEGER, deleted_from_source_at TIMESTAMP, attachment_count INTEGER DEFAULT 0, sender_id INTEGER, message_type TEXT NOT NULL DEFAULT 'email', deleted_at DATETIME); + CREATE TABLE messages (id INTEGER PRIMARY KEY, source_id INTEGER, source_message_id TEXT, sent_at TIMESTAMP, size_estimate INTEGER, has_attachments BOOLEAN, subject TEXT, snippet TEXT, conversation_id INTEGER, deleted_from_source_at TIMESTAMP, attachment_count INTEGER DEFAULT 0, sender_id INTEGER, message_type TEXT NOT NULL DEFAULT 'email', is_from_me BOOLEAN DEFAULT FALSE, deleted_at DATETIME); CREATE TABLE participants (id INTEGER PRIMARY KEY, email_address TEXT, domain TEXT, display_name TEXT, phone_number TEXT); + CREATE TABLE participant_identifiers (participant_id INTEGER, identifier_type TEXT, identifier_value TEXT, display_value TEXT, is_primary BOOLEAN); CREATE TABLE message_recipients (message_id INTEGER, participant_id INTEGER, recipient_type TEXT, display_name TEXT); CREATE TABLE labels (id INTEGER PRIMARY KEY, name TEXT); CREATE TABLE message_labels (message_id INTEGER, label_id INTEGER); - CREATE TABLE attachments (message_id INTEGER, size INTEGER, filename TEXT); + CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER, size INTEGER, filename TEXT); CREATE TABLE conversations (id INTEGER PRIMARY KEY, source_conversation_id TEXT, title TEXT, conversation_type TEXT NOT NULL DEFAULT 'email'); + CREATE TABLE conversation_participants (conversation_id INTEGER, participant_id INTEGER, PRIMARY KEY (conversation_id, participant_id)); + CREATE TABLE archive_metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL); + CREATE TABLE account_identities (source_id INTEGER, address TEXT, source_signal TEXT NOT NULL DEFAULT '', confirmed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (source_id, address)); + CREATE TABLE participant_links (participant_a INTEGER, participant_b INTEGER, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (participant_a, participant_b)); `) _ = db.Close() @@ -2147,6 +2225,7 @@ func BenchmarkBuildCache(b *testing.B) { CREATE TABLE sources (id INTEGER PRIMARY KEY, identifier TEXT); CREATE TABLE messages (id INTEGER PRIMARY KEY, source_id INTEGER, source_message_id TEXT, sent_at TIMESTAMP, size_estimate INTEGER, has_attachments BOOLEAN, subject TEXT, snippet TEXT, conversation_id INTEGER, deleted_from_source_at TIMESTAMP, attachment_count INTEGER DEFAULT 0, sender_id INTEGER, message_type TEXT NOT NULL DEFAULT 'email', deleted_at DATETIME); CREATE TABLE participants (id INTEGER PRIMARY KEY, email_address TEXT UNIQUE, domain TEXT, display_name TEXT, phone_number TEXT); + CREATE TABLE participant_identifiers (participant_id INTEGER, identifier_type TEXT, identifier_value TEXT, display_value TEXT, is_primary BOOLEAN); CREATE TABLE message_recipients (message_id INTEGER, participant_id INTEGER, recipient_type TEXT, display_name TEXT); CREATE TABLE labels (id INTEGER PRIMARY KEY, name TEXT); CREATE TABLE message_labels (message_id INTEGER, label_id INTEGER); @@ -2233,6 +2312,7 @@ func setupTestSQLiteEmpty(t *testing.T) string { deleted_from_source_at TIMESTAMP, sender_id INTEGER, message_type TEXT NOT NULL DEFAULT 'email', + is_from_me BOOLEAN DEFAULT FALSE, deleted_at DATETIME, UNIQUE(source_id, source_message_id) ); @@ -2243,6 +2323,10 @@ func setupTestSQLiteEmpty(t *testing.T) string { display_name TEXT, phone_number TEXT ); + CREATE TABLE participant_identifiers ( + participant_id INTEGER, identifier_type TEXT, identifier_value TEXT, + display_value TEXT, is_primary BOOLEAN + ); CREATE TABLE message_recipients ( id INTEGER PRIMARY KEY, message_id INTEGER NOT NULL REFERENCES messages(id), @@ -2277,6 +2361,32 @@ func setupTestSQLiteEmpty(t *testing.T) string { title TEXT, conversation_type TEXT NOT NULL DEFAULT 'email' ); + CREATE TABLE conversation_participants ( + conversation_id INTEGER NOT NULL REFERENCES conversations(id), + participant_id INTEGER NOT NULL REFERENCES participants(id), + PRIMARY KEY (conversation_id, participant_id) + ); + + CREATE TABLE archive_metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL + ); + + CREATE TABLE account_identities ( + source_id INTEGER NOT NULL REFERENCES sources(id), + address TEXT NOT NULL, + source_signal TEXT NOT NULL DEFAULT '', + confirmed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (source_id, address) + ); + + CREATE TABLE participant_links ( + participant_a INTEGER NOT NULL REFERENCES participants(id), + participant_b INTEGER NOT NULL REFERENCES participants(id), + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (participant_a, participant_b), + CHECK (participant_a < participant_b) + ); ` _, err = db.Exec(schema) require.NoError(t, err, "create schema") @@ -2369,11 +2479,21 @@ func createFakeParquet(t *testing.T, analyticsDir string) { require.NoError(t, os.MkdirAll(msgDir, 0755), "MkdirAll messages") require.NoError(t, os.WriteFile(filepath.Join(msgDir, "data.parquet"), []byte("fake"), 0644), "write messages parquet") // Other required tables use flat layout - for _, dir := range []string{"sources", "participants", "message_recipients", "labels", "message_labels", "attachments", "conversations"} { + for _, dir := range []string{"sources", "participants", "participant_identifiers", "message_recipients", "labels", "message_labels", "attachments", "conversations", "conversation_participants", tableOwnerParticipants, tableParticipantClusters} { d := filepath.Join(analyticsDir, dir) require.NoError(t, os.MkdirAll(d, 0755), "MkdirAll %s", dir) require.NoError(t, os.WriteFile(filepath.Join(d, "data.parquet"), []byte("fake"), 0644), "write %s parquet", dir) } + state, err := query.ReadCacheSyncState(analyticsDir) + if err == nil { + fingerprint, fingerprintErr := query.CacheDatasetFingerprint(analyticsDir) + require.NoError(t, fingerprintErr) + state.PublishedAt = time.Now().UTC() + state.DatasetFingerprint = fingerprint + data, marshalErr := json.Marshal(state) + require.NoError(t, marshalErr) + require.NoError(t, os.WriteFile(query.CacheStatePath(analyticsDir), data, 0o600)) + } } func TestCacheNeedsBuild(t *testing.T) { @@ -2578,6 +2698,111 @@ func TestCacheNeedsBuild(t *testing.T) { } } +func TestCacheNeedsBuild_DriftedPublicationForcesFullRebuild(t *testing.T) { + assert := assert.New(t) + tmpDir := setupTestSQLiteEmpty(t) + dbPath := filepath.Join(tmpDir, "test.db") + analyticsDir := filepath.Join(tmpDir, "analytics") + writeSyncState(t, analyticsDir, 0) + createFakeParquet(t, analyticsDir) + require.NoError(t, os.WriteFile( + filepath.Join(analyticsDir, "sources", "drift.parquet"), []byte("drift"), 0o600, + )) + + got := cacheNeedsBuild(dbPath, analyticsDir) + assert.True(got.NeedsBuild) + assert.True(got.FullRebuild) + assert.Contains(got.Reason, "drift") +} + +// TestCacheNeedsBuild_IdentityRevisionChangeRequiresIncrementalBuild verifies +// that identity drift (a link/unlink or confirmed identity since the last +// build) alone requests a build but does not force a full rebuild: the +// lightweight identity-dataset refresh path handles it without rewriting +// message shards. +// +// This also exercises the cache's self-healing property against a +// concurrent-mutation race in buildCacheLocked: if an identity mutation lands +// between reading the identity revision/cluster snapshot and pinning the +// message-export snapshot, the cache is stamped with a revision (N) that +// lags the store's live revision (N+1). This test simulates exactly that +// post-build state — sync state stamped at revision 0, store advanced to +// revision 1 — and confirms the next staleness check reports NeedsBuild with +// the "identity revision changed" reason rather than silently serving stale +// identity data. +func TestCacheNeedsBuild_IdentityRevisionChangeRequiresIncrementalBuild(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + tmpDir := setupTestSQLiteEmpty(t) + + dbPath := filepath.Join(tmpDir, "test.db") + analyticsDir := filepath.Join(tmpDir, "analytics") + writeSyncState(t, analyticsDir, 0) + createFakeParquet(t, analyticsDir) + + db, err := sql.Open("sqlite3", dbPath) + require.NoError(err, "open db") + _, err = db.Exec(`INSERT INTO archive_metadata (key, value) VALUES ('identity_revision', '1')`) + require.NoError(err, "bump identity revision") + require.NoError(db.Close()) + + got := cacheNeedsBuild(dbPath, analyticsDir) + assert.True(got.NeedsBuild, "identity drift alone should still request a build") + assert.True(got.HasIdentityDrift, "identity drift signal should be set") + assert.False(got.FullRebuild, "identity drift alone must not force a full rebuild") + assert.Equal("identity revision changed", got.Reason) +} + +// TestCacheNeedsBuild_AccountIdentityRevisionChangeForcesFullRebuild covers +// Finding 1 of the relationships-backend review: account-identity +// mutations (confirming/removing a "me" address) change the is_from_me +// flag baked into every message Parquet shard at export time, so unlike +// plain participant-link drift, they must force a full rebuild rather than +// being satisfied by the lightweight identity-only refresh. +func TestCacheNeedsBuild_AccountIdentityRevisionChangeForcesFullRebuild(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + tmpDir := setupTestSQLiteEmpty(t) + + dbPath := filepath.Join(tmpDir, "test.db") + analyticsDir := filepath.Join(tmpDir, "analytics") + writeSyncState(t, analyticsDir, 0) + createFakeParquet(t, analyticsDir) + + db, err := sql.Open("sqlite3", dbPath) + require.NoError(err, "open db") + _, err = db.Exec(`INSERT INTO archive_metadata (key, value) VALUES ('account_identity_revision', '1')`) + require.NoError(err, "bump account identity revision") + require.NoError(db.Close()) + + got := cacheNeedsBuild(dbPath, analyticsDir) + assert.True(got.NeedsBuild, "account identity drift should request a build") + assert.True(got.HasAccountIdentityDrift, "account identity drift signal should be set") + assert.True(got.FullRebuild, "account identity drift must force a full rebuild") + assert.Contains(got.Reason, "identity mutations that invalidate baked message data") +} + +// TestCacheNeedsBuild_AccountIdentityDriftIsNotIdentityDriftOnly verifies +// that identityDriftOnly (the gate that picks the cheap identity-only +// refresh path) rejects staleness reports with HasAccountIdentityDrift set, +// even though AddAccountIdentity/RemoveAccountIdentity also bump +// identity_revision and therefore set HasIdentityDrift on the same report. +func TestCacheNeedsBuild_AccountIdentityDriftIsNotIdentityDriftOnly(t *testing.T) { + assert := assert.New(t) + + staleness := cacheStaleness{ + HasIdentityDrift: true, + HasAccountIdentityDrift: true, + FullRebuild: true, + } + assert.False(identityDriftOnly(staleness), + "account identity drift must never be satisfied by the identity-only refresh path") + + linkOnly := cacheStaleness{HasIdentityDrift: true} + assert.True(identityDriftOnly(linkOnly), + "plain participant-link drift alone must still take the identity-only refresh path") +} + func TestCacheNeedsBuild_LabelOnlySyncRequiresFullRebuild(t *testing.T) { require := require.New(t) tmpDir := setupTestSQLiteEmpty(t) @@ -2627,7 +2852,7 @@ func TestCacheNeedsBuild_LabelOnlySyncRequiresFullRebuild(t *testing.T) { require.Contains(got.Reason, "updated", "cacheNeedsBuild() reason") } -func TestCacheNeedsBuild_CalendarOnlyUpdateDoesNotRebuild(t *testing.T) { +func TestCacheNeedsBuild_CalendarUpdateRebuildsModalityNeutralCache(t *testing.T) { require := require.New(t) tmpDir := setupTestSQLiteEmpty(t) dbPath := filepath.Join(tmpDir, "test.db") @@ -2660,7 +2885,9 @@ func TestCacheNeedsBuild_CalendarOnlyUpdateDoesNotRebuild(t *testing.T) { require.NoError(err) got := cacheNeedsBuild(dbPath, analyticsDir) - require.False(got.NeedsBuild, "calendar-only update must not invalidate searchable cache: %+v", got) + require.True(got.NeedsBuild, "calendar update must invalidate the modality-neutral cache: %+v", got) + require.True(got.FullRebuild) + require.Contains(got.Reason, "updated") } func TestCacheNeedsBuild_UpdatedSyncCompletionOrderAndFailure(t *testing.T) { @@ -2842,6 +3069,7 @@ func TestCacheNeedsBuild_IgnoresAlreadyProcessedUpdatedSyncRun(t *testing.T) { // schema version other than the current one now forces a full rebuild. func TestCacheNeedsBuild_SchemaVersionMismatch(t *testing.T) { require := require.New(t) + require.Equal(14, cacheSchemaVersion, "identifier-type-aware is_from_me derivation requires cache v14") tmpDir := setupTestSQLiteEmpty(t) dbPath := filepath.Join(tmpDir, "test.db") @@ -2859,7 +3087,7 @@ func TestCacheNeedsBuild_SchemaVersionMismatch(t *testing.T) { state := syncState{ LastMessageID: 10, LastSyncAt: time.Now(), - SchemaVersion: cacheSchemaVersion - 1, + SchemaVersion: 11, } data, err := json.Marshal(state) require.NoError(err, "marshal sync state") @@ -2870,6 +3098,23 @@ func TestCacheNeedsBuild_SchemaVersionMismatch(t *testing.T) { require.True(got.NeedsBuild, "cacheNeedsBuild() = %+v, want NeedsBuild=true on schema mismatch", got) require.True(got.FullRebuild, "cacheNeedsBuild() = %+v, want FullRebuild=true on schema mismatch", got) require.Contains(got.Reason, "schema", "cacheNeedsBuild() reason should mention schema") + + result, err := buildCache(dbPath, analyticsDir, false) + require.NoError(err, "upgrade v11 cache through normal build") + require.False(result.Skipped, "schema mismatch must execute a full rebuild") + upgraded, err := query.ReadCacheSyncState(analyticsDir) + require.NoError(err, "read upgraded cache state") + require.Equal(14, upgraded.SchemaVersion) + require.NoFileExists(filepath.Join(analyticsDir, tableParticipantIdentifiers, "data.parquet"), + "full rebuild must replace rather than extend the v11 identifier dataset") + identifierParquet := filepath.Join(analyticsDir, tableParticipantIdentifiers, "participant_identifiers.parquet") + require.FileExists(identifierParquet) + duckdb, err := sql.Open("duckdb", "") + require.NoError(err) + defer func() { require.NoError(duckdb.Close()) }() + var identifiers int64 + require.NoError(duckdb.QueryRow(`SELECT COUNT(*) FROM read_parquet(?)`, identifierParquet).Scan(&identifiers)) + require.Zero(identifiers, "synthetic archive has no explicit identifier rows") } // TestCacheNeedsBuild_DedupHidesAfterLastSync covers the regression @@ -2998,6 +3243,7 @@ func BenchmarkBuildCacheIncremental(b *testing.B) { CREATE TABLE sources (id INTEGER PRIMARY KEY, identifier TEXT); CREATE TABLE messages (id INTEGER PRIMARY KEY, source_id INTEGER, source_message_id TEXT, sent_at TIMESTAMP, size_estimate INTEGER, has_attachments BOOLEAN, subject TEXT, snippet TEXT, conversation_id INTEGER, deleted_from_source_at TIMESTAMP, attachment_count INTEGER DEFAULT 0, sender_id INTEGER, message_type TEXT NOT NULL DEFAULT 'email', deleted_at DATETIME); CREATE TABLE participants (id INTEGER PRIMARY KEY, email_address TEXT UNIQUE, domain TEXT, display_name TEXT, phone_number TEXT); + CREATE TABLE participant_identifiers (participant_id INTEGER, identifier_type TEXT, identifier_value TEXT, display_value TEXT, is_primary BOOLEAN); CREATE TABLE message_recipients (message_id INTEGER, participant_id INTEGER, recipient_type TEXT, display_name TEXT); CREATE TABLE labels (id INTEGER PRIMARY KEY, name TEXT); CREATE TABLE message_labels (message_id INTEGER, label_id INTEGER); diff --git a/cmd/msgvault/cmd/cache_publication.go b/cmd/msgvault/cmd/cache_publication.go index 487c48d8d..637595107 100644 --- a/cmd/msgvault/cmd/cache_publication.go +++ b/cmd/msgvault/cmd/cache_publication.go @@ -1,12 +1,14 @@ package cmd import ( + "encoding/json" "errors" "fmt" "io/fs" "os" "path/filepath" "strings" + "time" "go.kenn.io/msgvault/internal/query" ) @@ -79,7 +81,8 @@ func replacesCacheDataset(dataset string, replaceAll bool) bool { return true } switch dataset { - case "participants", "labels", "sources", "conversations": + case "participants", "participant_identifiers", "labels", "sources", "conversations", "conversation_participants", + tableOwnerParticipants, tableParticipantClusters: return true default: return false @@ -173,6 +176,20 @@ func publishCache(staging *cacheStaging, analyticsDir string, replaceAll bool, s return fmt.Errorf("publish cache path %s: %w", move.destination, err) } } + fingerprint, err := query.CacheDatasetFingerprint(analyticsDir) + if err != nil { + return fmt.Errorf("fingerprint published analytics cache: %w", err) + } + var state query.CacheSyncState + if err := json.Unmarshal(stateData, &state); err != nil { + return fmt.Errorf("decode cache sync state for publication: %w", err) + } + state.PublishedAt = time.Now().UTC() + state.DatasetFingerprint = fingerprint + stateData, err = json.Marshal(state) + if err != nil { + return fmt.Errorf("encode committed cache sync state: %w", err) + } if err := buildCacheWriteStateFile(statePath, stateData, 0o600); err != nil { return fmt.Errorf("save cache sync state to %s: %w", statePath, err) } diff --git a/cmd/msgvault/cmd/cache_publication_test.go b/cmd/msgvault/cmd/cache_publication_test.go index 85b7fabb5..3de170375 100644 --- a/cmd/msgvault/cmd/cache_publication_test.go +++ b/cmd/msgvault/cmd/cache_publication_test.go @@ -1,16 +1,50 @@ package cmd import ( + "encoding/json" "errors" "os" "path/filepath" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.kenn.io/msgvault/internal/query" ) +func TestCachePublicationCommitsRevisionTimestampAndDatasetFingerprint(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + parent := t.TempDir() + analyticsDir := filepath.Join(parent, "analytics") + staging, err := newCacheStaging(analyticsDir) + require.NoError(err) + t.Cleanup(func() { _ = staging.cleanup() }) + writePublicationTree(t, staging.root, "new.parquet") + input, err := json.Marshal(query.CacheSyncState{ + SchemaVersion: query.CacheSchemaVersion, + LastMessageID: 41, + LastSyncAt: time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC), + LastCacheUpdateCount: 3, + LastFailedSyncRunCount: 2, + LastFailedSyncRunIDSum: 19, + }) + require.NoError(err) + + require.NoError(publishCache(staging, analyticsDir, true, input)) + state, err := query.ReadCacheSyncState(analyticsDir) + require.NoError(err) + assert.False(state.PublishedAt.IsZero()) + fingerprint, err := query.CacheDatasetFingerprint(analyticsDir) + require.NoError(err) + assert.Equal(fingerprint, state.DatasetFingerprint) + assert.NotEmpty(state.Revision()) + readiness, err := query.InspectCacheReadiness(analyticsDir) + require.NoError(err) + assert.Equal(query.CacheReady, readiness) +} + func TestCachePublicationFullReplacesEveryDatasetAndWritesStateLast(t *testing.T) { require := require.New(t) assert := assert.New(t) @@ -34,7 +68,11 @@ func TestCachePublicationFullReplacesEveryDatasetAndWritesStateLast(t *testing.T } gotState, err := os.ReadFile(query.CacheStatePath(analyticsDir)) require.NoError(err) - assert.Equal(newState, gotState) + var committed query.CacheSyncState + require.NoError(json.Unmarshal(gotState, &committed)) + assert.Equal(time.Date(2026, 7, 15, 11, 0, 0, 0, time.UTC), committed.LastSyncAt) + assert.False(committed.PublishedAt.IsZero()) + assert.NotEmpty(committed.DatasetFingerprint) } func TestIncrementalPublicationReplacesDimensionsAndPrefixesAppends(t *testing.T) { @@ -54,7 +92,7 @@ func TestIncrementalPublicationReplacesDimensionsAndPrefixesAppends(t *testing.T require.NoError(publishCache(staging, analyticsDir, false, []byte(`{"last_sync_at":"2026-07-15T11:00:00Z"}`))) - for _, dataset := range []string{"participants", "labels", "sources", "conversations"} { + for _, dataset := range []string{"participants", "participant_identifiers", "labels", "sources", "conversations"} { assert.False(publicationFileExists(analyticsDir, dataset, "old.parquet"), dataset) assert.True(publicationFileExists(analyticsDir, dataset, "data.parquet"), dataset) } diff --git a/cmd/msgvault/cmd/cache_staleness.go b/cmd/msgvault/cmd/cache_staleness.go index 8d16ddb6a..e3c731c1f 100644 --- a/cmd/msgvault/cmd/cache_staleness.go +++ b/cmd/msgvault/cmd/cache_staleness.go @@ -10,12 +10,26 @@ import ( // cacheStaleness describes why the analytics cache needs a rebuild. type cacheStaleness struct { - NeedsBuild bool - HasNew bool // new messages since last build - HasDeleted bool // deletions since last build - HasUpdated bool // updates or additions within the cached ID boundary require repair - FullRebuild bool // must rewrite all shards (not incremental) - Reason string + NeedsBuild bool + HasNew bool // new messages since last build + HasDeleted bool // deletions since last build + HasUpdated bool // updates or additions within the cached ID boundary require repair + // HasIdentityDrift signals participant_links or account_identities + // changed since the last build. Also set whenever + // HasAccountIdentityDrift is set (AddAccountIdentity/RemoveAccountIdentity + // bump both revisions together), so callers deciding whether the cheap + // identity-only refresh applies must check HasAccountIdentityDrift too — + // see identityDriftOnly in build_cache.go. + HasIdentityDrift bool + // HasAccountIdentityDrift signals an identity mutation that invalidates + // baked message data since the last build: an account identity was + // confirmed or removed, or two participants were merged (merges repoint + // messages.sender_id). Either changes the is_from_me flag baked into + // message Parquet shards. Unlike plain participant-link drift, this can + // only be repaired by a full rebuild, so it always sets FullRebuild. + HasAccountIdentityDrift bool + FullRebuild bool // must rewrite all shards (not incremental) + Reason string } // deletedSinceBuildCountSQL counts exportable messages source-deleted since @@ -73,6 +87,16 @@ func cacheNeedsBuild(dbPath, analyticsDir string) cacheStaleness { NeedsBuild: true, FullRebuild: true, Reason: "analytics cache publication interrupted", } + case query.CacheStaleSchema: + return cacheStaleness{ + NeedsBuild: true, FullRebuild: true, + Reason: "analytics cache schema is stale", + } + case query.CacheDrifted: + return cacheStaleness{ + NeedsBuild: true, FullRebuild: true, + Reason: "analytics cache publication drifted", + } case query.CacheReady: } @@ -224,6 +248,50 @@ func cacheNeedsBuild(dbPath, analyticsDir string) cacheStaleness { } } + // Account-identity drift covers identity mutations that invalidate baked + // message data: confirming or removing a confirmed "me" address via + // AddAccountIdentity/RemoveAccountIdentity, and participant merges via + // MergeParticipants/mergeParticipant (which repoint messages.sender_id). + // Either changes the is_from_me flag baked into every message Parquet + // shard at export time. Unlike plain participant link/unlink drift, this + // cannot be repaired by the lightweight identity-only refresh — + // incremental appends can't rewrite already-exported shards — so it + // always forces a full rebuild. Checked before HasIdentityDrift, and + // independently of it, so identityDriftOnly (build_cache.go) never + // mistakes this for the cheap-refresh case even though the same + // mutation also bumps identity_revision below. + accountIdentityRevision, err := db.AccountIdentityRevision() + if err != nil { + return cacheStaleness{ + NeedsBuild: true, FullRebuild: true, + Reason: "cannot verify account identity revision", + } + } + if accountIdentityRevision != state.AccountIdentityRevision { + result.HasAccountIdentityDrift = true + result.FullRebuild = true + reasons = append(reasons, "identity mutations that invalidate baked message data (account identities, participant merges)") + } + + // Identity drift (participant link/unlink/merge mutations, or + // confirming/removing an account identity) affects only the is_from_me + // derivation and the two identity datasets, not message content, so on + // its own it never forces a full rebuild: the lightweight refresh path + // (cacheops.RefreshIdentityDatasets) handles it, and a full rebuild + // triggered by any other signal (including HasAccountIdentityDrift + // above) refreshes it naturally. + identityRevision, err := db.IdentityRevision() + if err != nil { + return cacheStaleness{ + NeedsBuild: true, FullRebuild: true, + Reason: "cannot verify identity revision", + } + } + if identityRevision != state.IdentityRevision { + result.HasIdentityDrift = true + reasons = append(reasons, "identity revision changed") + } + if len(reasons) > 0 { result.NeedsBuild = true result.Reason = strings.Join(reasons, "; ") diff --git a/cmd/msgvault/cmd/cache_staleness_test.go b/cmd/msgvault/cmd/cache_staleness_test.go index efaffdbba..06f653d05 100644 --- a/cmd/msgvault/cmd/cache_staleness_test.go +++ b/cmd/msgvault/cmd/cache_staleness_test.go @@ -34,8 +34,8 @@ func TestCacheNeedsBuild_MeetingMutation(t *testing.T) { }{ {name: "meeting source deletion", messageType: "meeting_transcript", mutationField: "deleted_from_source_at", wantStale: true}, {name: "meeting dedup hide", messageType: "meeting_transcript", mutationField: "deleted_at", wantStale: true}, - {name: "calendar source deletion", messageType: "calendar_event", mutationField: "deleted_from_source_at", wantStale: false}, - {name: "calendar dedup hide", messageType: "calendar_event", mutationField: "deleted_at", wantStale: false}, + {name: "calendar source deletion", messageType: "calendar_event", mutationField: "deleted_from_source_at", wantStale: true}, + {name: "calendar dedup hide", messageType: "calendar_event", mutationField: "deleted_at", wantStale: true}, } for _, tt := range tests { diff --git a/cmd/msgvault/cmd/cache_stats_test.go b/cmd/msgvault/cmd/cache_stats_test.go index 96fa1f5d7..ad2be9ed8 100644 --- a/cmd/msgvault/cmd/cache_stats_test.go +++ b/cmd/msgvault/cmd/cache_stats_test.go @@ -26,6 +26,26 @@ func TestPrintCacheStatsInterrupted(t *testing.T) { assert.Equal(t, "Analytics cache publication was interrupted.\nRun 'msgvault build-cache' to repair it.\n", stdout.String()) } +func TestPrintCacheStatsRequiresFullRebuildForInvalidPublication(t *testing.T) { + tests := []struct { + status string + want string + }{ + {status: cacheops.StatusStaleSchema, want: "Analytics cache schema is stale.\nRun 'msgvault build-cache --full-rebuild' to upgrade it.\n"}, + {status: cacheops.StatusDrifted, want: "Analytics cache files do not match the committed publication.\nRun 'msgvault build-cache --full-rebuild' to repair them.\n"}, + } + for _, tt := range tests { + t.Run(tt.status, func(t *testing.T) { + var stdout bytes.Buffer + var stderr bytes.Buffer + err := printCacheStats(&stdout, &stderr, &cacheops.CacheStats{Status: tt.status}) + require.NoError(t, err) + assert.Empty(t, stderr.String()) + assert.Equal(t, tt.want, stdout.String()) + }) + } +} + func TestCacheStatsUsesConfiguredRemoteHTTPAndPreservesOutput(t *testing.T) { assert := assert.New(t) diff --git a/cmd/msgvault/cmd/constants.go b/cmd/msgvault/cmd/constants.go index 28f895584..9eace2d3d 100644 --- a/cmd/msgvault/cmd/constants.go +++ b/cmd/msgvault/cmd/constants.go @@ -19,11 +19,15 @@ const ( // names that happen to share these strings use their own literals/constants, // so renaming a storage entity cannot silently change an external contract. const ( - tableMessages = "messages" - tableLabels = "labels" - tableAttachments = "attachments" - tableParticipants = "participants" - tableConversations = "conversations" + tableMessages = "messages" + tableLabels = "labels" + tableAttachments = "attachments" + tableParticipants = "participants" + tableParticipantIdentifiers = "participant_identifiers" + tableConversations = "conversations" + tableConversationParticipants = "conversation_participants" + tableOwnerParticipants = "owner_participants" + tableParticipantClusters = "participant_clusters" ) // flagJSON is the name of the boolean --json output flag. It is kept distinct diff --git a/cmd/msgvault/cmd/export_token.go b/cmd/msgvault/cmd/export_token.go index 4d5c3236e..0b99411f8 100644 --- a/cmd/msgvault/cmd/export_token.go +++ b/cmd/msgvault/cmd/export_token.go @@ -40,15 +40,15 @@ Environment variables: Examples: # Export token to NAS over HTTPS - msgvault export-token user@gmail.com --to https://nas:8080 --api-key YOUR_KEY + msgvault export-token user@example.com --to https://archive.example:8080 --api-key YOUR_KEY # Using environment variables - export MSGVAULT_REMOTE_URL=https://nas:8080 + export MSGVAULT_REMOTE_URL=https://archive.example:8080 export MSGVAULT_REMOTE_API_KEY=your-key - msgvault export-token user@gmail.com + msgvault export-token user@example.com # With Tailscale (trusted network, HTTP allowed) - msgvault export-token user@gmail.com --to http://homebase.tail49367.ts.net:8080 --api-key KEY --allow-insecure`, + msgvault export-token user@example.com --to http://archive.example:8080 --api-key KEY --allow-insecure`, Args: cobra.ExactArgs(1), RunE: runExportToken, } diff --git a/cmd/msgvault/cmd/serve.go b/cmd/msgvault/cmd/serve.go index 14301add6..8b77315ec 100644 --- a/cmd/msgvault/cmd/serve.go +++ b/cmd/msgvault/cmd/serve.go @@ -16,8 +16,10 @@ import ( "syscall" "time" + "github.com/gofrs/flock" "github.com/spf13/cobra" "go.kenn.io/msgvault/internal/api" + "go.kenn.io/msgvault/internal/cacheops" "go.kenn.io/msgvault/internal/config" "go.kenn.io/msgvault/internal/deletion" "go.kenn.io/msgvault/internal/discord" @@ -352,14 +354,15 @@ func runServe(cmd *cobra.Command, args []string) error { sched.Start() // Create adapters for the API interfaces - storeAdapter := &storeAPIAdapter{store: s, attachmentMaintenance: attachmentMaint} + storeAdapter := &storeAPIAdapter{store: s, attachmentMaintenance: attachmentMaint, analyticsDir: cfg.AnalyticsDir()} schedAdapter := &schedulerAdapter{scheduler: sched} // Create and start API server apiOpts := api.ServerOptions{ - Config: cfg, - Store: storeAdapter, - Engine: engine, + Config: cfg, + Store: storeAdapter, + SavedViewStore: s, + Engine: engine, SQLQueryRunner: func(ctx context.Context, sql string) (*query.QueryResult, error) { return runDaemonSQLQuery(ctx, cfg, s, engine, sql) }, @@ -373,6 +376,7 @@ func runServe(cmd *cobra.Command, args []string) error { OperationGate: operationGate, BlobStore: blobStore, } + applyServerRuntimeConfig(&apiOpts, cfg) if cfg.Vector.Enabled { apiOpts.VectorStatus = api.VectorStatusInitializing } @@ -458,6 +462,10 @@ func runServe(cmd *cobra.Command, args []string) error { return nil } +func applyServerRuntimeConfig(options *api.ServerOptions, cfg *config.Config) { + options.VectorCfg = cfg.Vector +} + func listenServeAPI(bindAddr string, port int) (net.Listener, error) { if bindAddr == "" { bindAddr = defaultDaemonBindAddr @@ -730,6 +738,10 @@ func newDaemonIdleTracker(c *config.Config, stop context.CancelFunc) *api.IdleTr type storeAPIAdapter struct { store *store.Store attachmentMaintenance *attachmentMaintenance + // analyticsDir is the daemon's Parquet analytics cache directory, used + // by RefreshIdentityDatasets to locate both the cache build lock and + // the identity dataset files it re-exports. + analyticsDir string } var _ api.MessageStore = (*storeAPIAdapter)(nil) @@ -750,6 +762,23 @@ var _ api.DeletionManifestCanceller = (*storeAPIAdapter)(nil) var _ api.CLIDeduplicatePlanner = (*storeAPIAdapter)(nil) var _ api.CLIEmbeddingsPlanner = (*storeAPIAdapter)(nil) var _ api.CLIDedupDeleteStore = (*storeAPIAdapter)(nil) +var _ api.IdentityLinkStore = (*storeAPIAdapter)(nil) +var _ api.IdentityCacheRefresher = (*storeAPIAdapter)(nil) +var _ api.ClusterLookupStore = (*storeAPIAdapter)(nil) +var _ api.ConversationWindowStore = (*storeAPIAdapter)(nil) + +func (a *storeAPIAdapter) ConversationExistsContext(ctx context.Context, conversationID int64) (bool, error) { + return a.store.ConversationExistsContext(ctx, conversationID) +} + +func (a *storeAPIAdapter) GetConversationWindowContext( + ctx context.Context, + conversationID, anchorID int64, + before, after int, + start, end *time.Time, +) (*store.ConversationWindow, error) { + return a.store.GetConversationWindowContext(ctx, conversationID, anchorID, before, after, start, end) +} func (a *storeAPIAdapter) GetStats() (*api.StoreStats, error) { return a.store.GetStats() @@ -775,6 +804,21 @@ func (a *storeAPIAdapter) GetMessagesSummariesByIDsContext( return a.store.GetMessagesSummariesByIDsContext(ctx, ids) } +func (a *storeAPIAdapter) GetFileMetadata(ctx context.Context, id int64) (*store.FileMetadata, error) { + return a.store.GetFileMetadata(ctx, id) +} + +func (a *storeAPIAdapter) GetFileMetadataBatch(ctx context.Context, ids []int64) (map[int64]store.FileMetadata, error) { + return a.store.GetFileMetadataBatch(ctx, ids) +} + +func (a *storeAPIAdapter) GetArchivedMessageRaw(ctx context.Context, id int64) ([]byte, error) { + if err := ctx.Err(); err != nil { + return nil, err + } + return a.store.GetMessageRaw(id) +} + func (a *storeAPIAdapter) GetStatsForScope(sourceIDs []int64) (*store.Stats, error) { return a.store.GetStatsForScope(sourceIDs) } @@ -1212,6 +1256,44 @@ func (a *storeAPIAdapter) RemoveAccountIdentity(sourceID int64, address string) return a.store.RemoveAccountIdentity(sourceID, address) } +func (a *storeAPIAdapter) LinkParticipants(participantA, participantB int64) (int64, error) { + return a.store.LinkParticipants(participantA, participantB) +} + +func (a *storeAPIAdapter) UnlinkParticipants(participantA, participantB int64) (int64, error) { + return a.store.UnlinkParticipants(participantA, participantB) +} + +func (a *storeAPIAdapter) ClusterMembers(id int64) ([]int64, error) { + return a.store.ClusterMembers(id) +} + +func (a *storeAPIAdapter) ClusterEdges(id int64) ([]store.LinkEdge, error) { + return a.store.ClusterEdges(id) +} + +// RefreshIdentityDatasets re-exports the owner_participants and +// participant_clusters Parquet datasets after an identity mutation (a +// participant link/unlink or an account identity add/remove) commits. +// cacheops.RefreshIdentityDatasets requires its caller to hold the +// cross-process analytics cache build lock exclusively; this acquires it +// non-blocking, so a build already in progress makes the refresh fail fast +// rather than stalling the HTTP request. The API layer treats any error, +// including lock contention, as cache_state "stale" — the identity mutation +// itself already committed and is not affected. +func (a *storeAPIAdapter) RefreshIdentityDatasets(ctx context.Context) (int64, error) { + lock := flock.New(query.CacheBuildLockPath(a.analyticsDir)) + locked, err := lock.TryLock() + if err != nil { + return 0, fmt.Errorf("acquire analytics cache build lock for identity refresh: %w", err) + } + if !locked { + return 0, errors.New("analytics cache build lock is held by another process") + } + defer func() { _ = lock.Unlock() }() + return cacheops.RefreshIdentityDatasets(ctx, a.store, a.analyticsDir) +} + func (a *storeAPIAdapter) GetActiveSync(sourceID int64) (*store.SyncRun, error) { return a.store.GetActiveSync(sourceID) } diff --git a/cmd/msgvault/cmd/serve_api_wiring_test.go b/cmd/msgvault/cmd/serve_api_wiring_test.go new file mode 100644 index 000000000..b96762f34 --- /dev/null +++ b/cmd/msgvault/cmd/serve_api_wiring_test.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/api" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/testutil" +) + +func TestServeRuntimeConfigCarriesVectorScopeBeforeInitialization(t *testing.T) { + vectorCfg := config.NewDefaultConfig().Vector + vectorCfg.Enabled = true + vectorCfg.Embed.Scope.MessageTypes = []string{"teams"} + opts := api.ServerOptions{} + + applyServerRuntimeConfig(&opts, &config.Config{Vector: vectorCfg}) + + assert.Equal(t, []string{"teams"}, opts.VectorCfg.Embed.Scope.MessageTypes) +} + +func TestStoreAPIAdapterExposesFileMetadataCatalog(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + st := testutil.NewTestStore(t) + adapter := &storeAPIAdapter{store: st} + + file, err := adapter.GetFileMetadata(t.Context(), 999999) + requirements.NoError(err) + assertions.Nil(file) + files, err := adapter.GetFileMetadataBatch(t.Context(), nil) + requirements.NoError(err) + assertions.Empty(files) +} diff --git a/cmd/msgvault/cmd/store_adapter_test.go b/cmd/msgvault/cmd/store_adapter_test.go index f0fd21262..3ce326234 100644 --- a/cmd/msgvault/cmd/store_adapter_test.go +++ b/cmd/msgvault/cmd/store_adapter_test.go @@ -2,10 +2,13 @@ package cmd import ( "context" + "database/sql" "testing" + "time" "github.com/stretchr/testify/require" "go.kenn.io/msgvault/internal/api" + "go.kenn.io/msgvault/internal/store" "go.kenn.io/msgvault/internal/testutil" ) @@ -50,3 +53,57 @@ func TestStoreAPIAdapterContextReadsHonorCancellation(t *testing.T) { _, err = adapter.GetMessagesSummariesByIDsContext(ctx, []int64{1}) require.ErrorIs(err, context.Canceled, "GetMessagesSummariesByIDsContext must honor a cancelled context") } + +var _ api.ConversationWindowStore = (*storeAPIAdapter)(nil) + +// TestStoreAPIAdapterImplementsConversationWindowStore is a compile-time +// guard that the production adapter wired into api.Server satisfies the +// context-aware conversation reader. Without it, GET /conversations/{id} +// falls back to an unexported interface the adapter can never satisfy and +// returns 503 conversation_unavailable in daemon mode. +func TestStoreAPIAdapterImplementsConversationWindowStore(t *testing.T) { + var adapter api.MessageStore = &storeAPIAdapter{} + _, ok := adapter.(api.ConversationWindowStore) + require.True(t, ok, "storeAPIAdapter must implement api.ConversationWindowStore") +} + +// TestStoreAPIAdapterConversationWindowContext verifies the adapter's +// conversation-window pass-throughs return a real window from the +// underlying store, both unbounded and time-bounded. +func TestStoreAPIAdapterConversationWindowContext(t *testing.T) { + require := require.New(t) + st := testutil.NewTestStore(t) + source, err := st.GetOrCreateSource("gmail", "test@example.com") + require.NoError(err, "GetOrCreateSource") + conversationID, err := st.EnsureConversation(source.ID, "thread-1", "Thread") + require.NoError(err, "EnsureConversation") + + sentAt := time.Date(2015, 1, 7, 12, 0, 0, 0, time.UTC) + messageID, err := st.UpsertMessage(&store.Message{ + ConversationID: conversationID, + SourceID: source.ID, + SourceMessageID: "msg-1", + MessageType: "email", + SentAt: sql.NullTime{Time: sentAt, Valid: true}, + }) + require.NoError(err, "UpsertMessage") + + adapter := &storeAPIAdapter{store: st} + ctx := context.Background() + + exists, err := adapter.ConversationExistsContext(ctx, conversationID) + require.NoError(err, "ConversationExistsContext") + require.True(exists, "conversation should exist") + + window, err := adapter.GetConversationWindowContext(ctx, conversationID, messageID, 5, 5, nil, nil) + require.NoError(err, "GetConversationWindowContext unbounded") + require.Len(window.Messages, 1, "unbounded window should contain the seeded message") + require.Equal(messageID, window.Messages[0].ID, "unbounded window message ID") + + start := sentAt.Add(-time.Hour) + end := sentAt.Add(time.Hour) + bounded, err := adapter.GetConversationWindowContext(ctx, conversationID, messageID, 5, 5, &start, &end) + require.NoError(err, "GetConversationWindowContext bounded") + require.Len(bounded.Messages, 1, "bounded window should contain the seeded message") + require.Equal(messageID, bounded.Messages[0].ID, "bounded window message ID") +} diff --git a/docs/api-server.md b/docs/api-server.md index dac370f61..5bc599214 100644 --- a/docs/api-server.md +++ b/docs/api-server.md @@ -1,14 +1,18 @@ --- -title: Web Server -description: REST API for programmatic access to your msgvault archive, with optional background sync scheduling. +title: Web UI & API Server +description: Daemon-served analytical Web UI and REST API for your msgvault archive, with optional background sync scheduling. --- ## Overview -`msgvault serve` starts an HTTP server that exposes your email archive over a REST API. It optionally runs a background sync scheduler to keep accounts up to date on a cron-based schedule. +`msgvault serve` starts an HTTP server that exposes your archive through the +first-party Web UI at `/` and a REST API under `/api`. It optionally runs a +background sync scheduler to keep accounts up to date on a cron-based schedule. +The complete UI is embedded in the release binary; see [Web UI](/web-ui/) for +browser login, secure remote deployment, search states, and keyboard controls. -The API is registered through Huma and exposes a generated OpenAPI document at `/openapi.json`; interactive docs are available at `/docs`. You can also run `msgvault openapi` to print the same checked-in contract without starting a daemon or opening the archive database. The OpenAPI `info.version` is the API schema version used for client/server compatibility; the running daemon binary version is exposed separately in the generated document metadata. The API queries the same archive database and attachment store as the CLI and TUI. SQLite is the default archive database; PostgreSQL is supported when `[data].database_url` is a PostgreSQL DSN. Keyword search and ordinary archive reads stay local to that database. If vector search is enabled, semantic and hybrid search also call the embedding endpoint configured in `[vector.embeddings]`. The server is designed for local integrations, dashboards, and automation scripts. +The API is registered through Huma and exposes a generated OpenAPI document at `/openapi.json`; interactive docs are available at `/docs`. You can also run `msgvault openapi` to print the same checked-in contract without starting a daemon or opening the archive database. The OpenAPI `info.version` is the API schema version used for client/server compatibility; the running daemon binary version is exposed separately in the generated document metadata. The API queries the same archive database and attachment store as the CLI, Web UI, and TUI. SQLite is the default archive database; PostgreSQL is supported when `[data].database_url` is a PostgreSQL DSN. Keyword search and ordinary archive reads stay local to that database. If vector search is enabled, semantic and hybrid search also call the embedding endpoint configured in `[vector.embeddings]`. The server is designed for interactive archive use, local integrations, dashboards, and automation scripts. Go integrations can use the generated client in `pkg/client`. The wrapper handles msgvault-specific response details such as deletion staging dry-runs @@ -45,7 +49,10 @@ curl http://localhost:8080/openapi.json ## Authentication -All endpoints except `/health` require authentication when `api_key` is set in your config. Three authentication methods are supported: +Archive API endpoints require authentication when `api_key` is set in your +config. The public application shell, `/health`, and the browser-session +bootstrap/login routes remain reachable so the UI can determine whether login +is required. Three API-key authentication methods are supported: | Method | Header | Example | |---|---|---| @@ -894,7 +901,7 @@ All server settings go in the `[server]` section of `config.toml`. Account sched | Key | Default | Description | |---|---|---| -| `engine` | `auto` | Aggregate engine for TUI and aggregate HTTP views: `auto`, `sql`, or `duckdb` | +| `engine` | `auto` | Aggregate engine for Web UI, TUI, and aggregate HTTP views: `auto`, `sql`, or `duckdb` | | `auto_build_cache` | `true` | Build stale or missing Parquet cache files before the daemon opens DuckDB | `engine = "sql"` forces live SQL for aggregate views. `engine = "duckdb"` requires a usable Parquet cache and fails daemon startup if the cache cannot be built or opened. `auto_build_cache = false` leaves cache rebuilds to explicit `msgvault build-cache` runs. These settings replace the TUI/MCP analytics flags deprecated in 0.17.0; see [Configuration: analytics](/configuration/#analytics). diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index 111ff82c3..f002ca538 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -7,7 +7,7 @@ msgvault syncs Gmail, Google Calendar, IMAP, Microsoft 365 mail, Microsoft Teams, Discord, Beeper Desktop, Granola, and Circleback into a local SQLite database by default and can import local PST/MBOX archives, Apple Mail exports, and common chat/text formats. PostgreSQL is available as an opt-in backend for new -archives. Keyword search, analytics, the TUI, and the MCP server run against +archives. Keyword search, analytics, the Web UI, the TUI, and the MCP server run against the configured archive database, Parquet metadata exports where available, and the mixed loose/packed attachment store. Optional vector search calls the embedding endpoint configured in `[vector.embeddings]` to build/query semantic @@ -20,10 +20,12 @@ on PostgreSQL. ``` msgvault/ +├── web/ # Svelte application embedded in release binaries ├── cmd/msgvault/ # CLI entrypoint │ └── cmd/ # Cobra commands ├── internal/ # Core packages │ ├── tui/ # Bubble Tea TUI +│ ├── api/ # HTTP API, browser sessions, and embedded UI server │ ├── query/ # DuckDB/SQL query engines │ ├── store/ # SQLite/PostgreSQL database access │ ├── backupapp/ # msgvault's App seam for the kit backup engine @@ -63,6 +65,8 @@ msgvault/ | Package | Responsibility | |---|---| | `cmd/` | Cobra CLI commands, config loading | +| `web/` | Svelte analytical browser application and generated API client | +| `internal/api` | HTTP routes, authentication/session middleware, and embedded UI assets | | `internal/store` | SQLite and PostgreSQL database operations, schema management | | `internal/backupapp` | msgvault's `backup.App` implementation (stats, layout, exclusions) over `go.kenn.io/kit`'s backup/pack engine | | `internal/attachmentstore` | Daemon-owned streaming reader for loose and packed attachment content | @@ -97,13 +101,13 @@ msgvault/ - **Local-first by design**: Mail, chat, calendar, and meeting providers are contacted only by explicit authorization/registration, sync, media-backfill, - and deletion workflows. Keyword search, analytics, TUI views, and ordinary + and deletion workflows. Keyword search, analytics, Web UI and TUI views, and ordinary MCP reads run against archived data without contacting those providers. Optional vector search additionally calls only the embedding endpoint you configure, so a local/self-hosted endpoint keeps semantic search on your own machine or network. - **SQLite by default, PostgreSQL opt-in**: SQLite is the default system of record. PostgreSQL can be selected with `[data].database_url` for new archives that should live in a server database. -- **DuckDB + Parquet for default analytics**: On SQLite archives, the TUI runs an embedded DuckDB engine over Parquet metadata exports, delivering aggregate queries hundreds of times faster than SQLite JOINs. The entire analytics cache for hundreds of thousands of messages fits in a few megabytes, making drill-down and re-aggregation feel instant. PostgreSQL archives currently use live SQL for aggregate views. +- **DuckDB + Parquet for default analytics**: On SQLite archives, the Web UI and TUI use an embedded DuckDB engine over Parquet metadata exports, delivering aggregate queries hundreds of times faster than SQLite JOINs. The entire analytics cache for hundreds of thousands of messages fits in a few megabytes, making drill-down and re-aggregation feel instant. PostgreSQL archives currently use live SQL for aggregate views. - **Content-addressed attachments**: Deduplicated by SHA-256 hash and stored as loose files or in sealed immutable packs; readers resolve both layouts transparently. diff --git a/docs/architecture/postgresql.md b/docs/architecture/postgresql.md index 87b7be610..5773c5925 100644 --- a/docs/architecture/postgresql.md +++ b/docs/architecture/postgresql.md @@ -8,10 +8,18 @@ new archives or fresh re-syncs. There is currently no SQLite to PostgreSQL migration command. On PostgreSQL, sync, full-text search, vector and hybrid semantic search, -deletion staging, attachment metadata, stats, and the TUI, HTTP, and MCP read -paths all run against PostgreSQL. Optional semantic search uses the -[pgvector](https://github.com/pgvector/pgvector) extension and stores embeddings -in the same database as the message archive. +deletion staging, attachment metadata, stats, and the Web UI, TUI, HTTP, and MCP read +paths all run against PostgreSQL. This covers the basic message-listing API +(`GET /api/v1/messages`), aggregate views (Senders/Domains/Labels/Time), and +search — the paths served by the dialect-aware query engine directly. The +Web UI's cache-backed analytical surfaces — Explore (its base listing as +well as the grouping/coverage/selection endpoints), Files, the People and +domains workspaces, and Relationships (ranking, timeline, and the identity +link/unlink cache refresh) — require the SQLite + DuckDB/Parquet analytics +cache and are unavailable on PostgreSQL; see Storage Differences below. +Optional semantic search uses the +[pgvector](https://github.com/pgvector/pgvector) extension and +stores embeddings in the same database as the message archive. ## Prerequisites @@ -127,17 +135,31 @@ run_after_sync = true | Analytics cache | DuckDB over Parquet | Live SQL through the query engine | PostgreSQL does not currently have a Parquet acceleration layer equivalent to -the default SQLite TUI path. Aggregate views run as live SQL, so very large -archives should be validated with realistic data before PostgreSQL becomes your -primary backend. +the default SQLite TUI path. The TUI's Senders/Domains/Labels/Time aggregate +views run as live SQL, so very large archives should be validated with +realistic data before PostgreSQL becomes your primary backend. + +`msgvault build-cache` is SQLite-only — it refuses to run against a +PostgreSQL `database_url`. This means the Web UI's cache-backed analytical +surfaces (Explore, including its base listing and its +grouping/coverage/selection endpoints; Files; the People and domains +workspaces; and Relationships' ranking, timeline, and identity link/unlink +cache refresh) have no PostgreSQL equivalent: those endpoints detect the +missing DuckDB/Parquet cache and return a named unavailable-cache state +rather than falling back to live SQL. If you see that state on a PostgreSQL +backend, it is expected — the [cache troubleshooting guidance](/web-ui/#cache-states) +applies to SQLite archives only. ## Current Scope Implemented: - PostgreSQL schema initialization and legacy column migrations. -- Store, query, TUI, HTTP, and MCP read paths through a dialect-aware query - layer. +- Store, query, Web UI, TUI, HTTP, and MCP read paths through a dialect-aware + query layer, covering the basic message-listing API, aggregate views, + search, and stats. The cache-only analytical surfaces (Explore, Files, + People and domains, Relationships) are not part of this layer — see Not + implemented below. - Full-text search with PostgreSQL `tsvector` and `ts_rank`. - Deletion staging and execution metadata updates. - Attachment metadata and cleanup paths. @@ -149,6 +171,12 @@ Not implemented: - SQLite to PostgreSQL archive migration. - PostgreSQL Parquet or materialized aggregate acceleration. +- The Web UI's cache-backed analytical surfaces (Explore, including its + base listing and its grouping/coverage/selection endpoints; Files; the + People and domains workspaces; and Relationships' ranking, timeline, and + identity link/unlink cache refresh): these require the DuckDB/Parquet + cache, which `build-cache` refuses to build against PostgreSQL, so they + return an unavailable-cache state rather than running as live SQL. - PostgreSQL corruption checks inside `msgvault verify`; use PostgreSQL operational tooling such as `pg_amcheck`. diff --git a/docs/architecture/search-ranking.md b/docs/architecture/search-ranking.md index 933a343e7..e492afb20 100644 --- a/docs/architecture/search-ranking.md +++ b/docs/architecture/search-ranking.md @@ -3,7 +3,7 @@ title: Search Ranking Across Backends description: Why SQLite and PostgreSQL can order the same matching messages differently. --- -`msgvault search`, the TUI, the HTTP API, and the MCP server can rank results +`msgvault search`, the Web UI, the TUI, the HTTP API, and the MCP server can rank results with different database engines depending on your archive backend and search mode. Result sets usually match, but result order can differ because each backend uses a different scoring model. diff --git a/docs/architecture/storage.md b/docs/architecture/storage.md index fb4fd4580..f46af8305 100644 --- a/docs/architecture/storage.md +++ b/docs/architecture/storage.md @@ -149,7 +149,12 @@ There is currently no SQLite to PostgreSQL migration command. Use PostgreSQL for ## Parquet (Analytics Cache) -The TUI needs to aggregate across your entire archive (top senders, domains, labels, time series) and return results instantly as you drill down. SQLite JOINs across normalized tables cannot do this at interactive speeds on large archives. msgvault solves this on the default SQLite backend with denormalized Parquet files queried by an embedded DuckDB engine, delivering aggregate queries hundreds of times faster than SQLite. +The Web UI and TUI need to aggregate across your entire archive and return +results instantly as you group and drill down. SQLite JOINs across normalized +tables cannot do this at interactive speeds on large archives. msgvault solves +this on the default SQLite backend with denormalized Parquet files queried by +an embedded DuckDB engine, delivering aggregate queries hundreds of times +faster than SQLite. The Parquet cache is disposable and can be rebuilt at any time. Aggregate views never trigger a build mid-session: with `auto_build_cache = true` (the default) the daemon builds a stale or missing cache synchronously at startup — incremental builds take seconds — and then serves DuckDB over it, refreshing the cache after scheduled syncs and ingest commands. Cache builds hold an exclusive cross-process lock and queries hold it shared, so a rebuild never removes Parquet files out from under a running query. `msgvault build-cache` builds the cache on demand. PostgreSQL archives currently use live SQL for aggregate views rather than this Parquet acceleration layer. diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 9d8c660ca..2a2b0dd03 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -1422,7 +1422,7 @@ msgvault daemon restart `start` launches the daemon in the background, `status` reports its recorded URL/PID/version/API schema/uptime, `stop` shuts it down, and `restart` performs a stop followed by a start. Starting a newer compatible binary replaces an older recorded daemon when `[server].daemon_auto_restart = "newer"`; incompatible running daemons are reported with a prompt to stop them first. -The lifecycle commands have no command-specific flags. All configuration (port, bind address, API key, CORS, account schedules, SyncTech SMS sources, background idle timeout, daemon restart policy, and vector embedding schedule) is read from your `config.toml`. See [Web Server](/api-server/) for endpoint documentation, run `msgvault openapi`, or fetch `/openapi.json` from a running server for the generated OpenAPI contract. See [Configuration](/configuration/#server) for config options. When vector search is enabled, the daemon can also run the embed worker on a cron and/or after every successful sync, see [Configuration: vector.embed.schedule](/configuration/#vectorembedschedule). +The lifecycle commands have no command-specific flags. All configuration (port, bind address, API key, CORS, account schedules, SyncTech SMS sources, background idle timeout, daemon restart policy, and vector embedding schedule) is read from your `config.toml`. See [Web UI & API Server](/api-server/) for endpoint documentation, run `msgvault openapi`, or fetch `/openapi.json` from a running server for the generated OpenAPI contract. See [Configuration](/configuration/#server) for config options. When vector search is enabled, the daemon can also run the embed worker on a cron and/or after every successful sync, see [Configuration: vector.embed.schedule](/configuration/#vectorembedschedule). Background daemons started by `daemon start` or auto-started by a CLI command shut down after `[server].daemon_idle_timeout` with no requests. The default is `20m`; set it to `"0s"` to disable idle shutdown. `MSGVAULT_DAEMON_IDLE_TIMEOUT` can override the value for a lifecycle-managed background daemon. @@ -1434,7 +1434,7 @@ For compatibility with existing scripts, `msgvault serve start|status|stop|resta ## serve -Start the web server with optional background sync scheduling in the foreground. +Start the Web UI and API server with optional background sync scheduling in the foreground. ```bash msgvault serve diff --git a/docs/configuration.md b/docs/configuration.md index b6f8cd0dd..382c1d9cb 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -73,7 +73,7 @@ daemon_idle_timeout = "20m" # background daemon idle timeout; "0s" disables daemon_auto_restart = "newer" # newer, never, or always [analytics] -# Daemon-side analytics engine for TUI and aggregate HTTP views: +# Daemon-side analytics engine for Web UI, TUI, and aggregate HTTP views: # "auto" uses DuckDB/Parquet when usable, otherwise live SQL. # "sql" always uses live SQL. "duckdb" requires a usable Parquet cache. engine = "auto" @@ -242,7 +242,7 @@ Use `msgvault logs` to view and tail log files from the selected local or remote ### `[server]` -Settings for the web server started by `msgvault serve`. The same HTTP server is used by remote CLI access and by the local background daemon for archive-access CLI commands. See [Web Server](/api-server/) for endpoint documentation, or fetch `/openapi.json` from a running server for the generated OpenAPI contract. +Settings for the Web UI and API server started by `msgvault serve`. The same HTTP server is used by remote CLI access and by the local background daemon for archive-access CLI commands. See [Web UI & API Server](/api-server/) for endpoint documentation, or fetch `/openapi.json` from a running server for the generated OpenAPI contract. | Key | Default | Description | |---|---|---| @@ -253,6 +253,7 @@ Settings for the web server started by `msgvault serve`. The same HTTP server is | `cors_origins` | `[]` | Allowed CORS origins | | `cors_credentials` | `false` | Allow credentials in CORS requests | | `cors_max_age` | `0` | CORS preflight cache duration in seconds | +| `trusted_proxies` | `[]` | IP addresses or CIDRs allowed to supply forwarded HTTPS/host headers | | `daemon_idle_timeout` | `20m` | Idle timeout for lifecycle-managed background daemons; set to `"0s"` to disable | | `daemon_auto_restart` | `newer` | Local daemon restart policy when the CLI finds a different daemon binary version: `newer`, `never`, or `always` | @@ -260,9 +261,48 @@ Settings for the web server started by `msgvault serve`. The same HTTP server is `daemon_auto_restart = "newer"` replaces an older compatible local daemon with the current CLI binary. Use `"never"` when another supervisor owns the daemon lifecycle, or `"always"` to restart whenever the recorded daemon version differs. Remote servers are never auto-restarted by a CLI client. +Browser sessions are additive to API-key authentication. Existing CLI and +programmatic clients continue to send the configured key. For remote browser +access, terminate TLS at a reverse proxy and list that proxy—not arbitrary +clients—in `trusted_proxies`. See [Web UI](/web-ui/) for the complete security +model and the plain-HTTP warning. + +### `[web]` + +Defaults for the daemon-served browser application. These values can also be +changed from Settings; `config.toml` remains authoritative. + +| Key | Default | Description | +|---|---|---| +| `default_search_mode` | `full_text` | Initial mode: `full_text`, `semantic`, or `hybrid` | +| `theme` | `system` | Color theme: `system`, `light`, or `dark` | +| `density` | `compact` | Table density: `compact` or `comfortable` | + +Browser-managed settings are validated and written with optimistic concurrency. +They are restart-required unless the UI explicitly says otherwise; a pending +restart banner means the file is saved but the running daemon still has its old +value. Changing `server.api_key` requires a confirmation and takes effect only +after restart, which also invalidates browser sessions. + +### `[integrations.tasks]` + +Optional provider-neutral task integration: + +| Key | Default | Description | +|---|---|---| +| `enabled` | `false` | Enable discovery and capability checks | +| `endpoint` | — | Explicit loopback HTTP, Unix socket, or HTTPS endpoint; empty requests secure local discovery | +| `api_key` | — | Server-side credential; never returned to the browser | +| `default_project` | `msgvault` | Fixed project used for create/link/search operations | + +Remote plaintext HTTP is rejected. An endpoint is usable only when it supports +the required idempotency and compare-and-swap capabilities; the UI distinguishes +disabled, authentication required, incompatible, partial, stale, unavailable, +and ready states. + ### `[analytics]` -Settings for daemon-side aggregate query behavior. The TUI, MCP server, and aggregate list commands use these settings through the local daemon or a configured remote server. +Settings for daemon-side aggregate query behavior. The Web UI, TUI, MCP server, and aggregate list commands use these settings through the local daemon or a configured remote server. | Key | Default | Description | |---|---|---| @@ -271,6 +311,8 @@ Settings for daemon-side aggregate query behavior. The TUI, MCP server, and aggr Deprecated in 0.17.0: per-command analytics flags such as `msgvault tui --force-sql`, `msgvault mcp --force-sql`, `msgvault tui --no-cache-build`, and `--no-sqlite-scanner` were replaced by this daemon-level section. Use `engine = "sql"` for live SQL, `auto_build_cache = false` to skip automatic daemon cache builds, or `msgvault build-cache` to prebuild cache files on the daemon host. If `engine = "duckdb"` and the cache cannot be built or opened, `msgvault serve` fails instead of silently falling back. +This setting governs the aggregate views (Senders/Domains/Labels/Time) and is ignored entirely when `[data].database_url` points at PostgreSQL — a PostgreSQL backend always uses live SQL for those views, and `build-cache` refuses to run against it. It does not affect the Web UI's Explore, Files, or People/domains workspaces, which require the SQLite + DuckDB/Parquet cache regardless of this setting and are unavailable on PostgreSQL; see [PostgreSQL Backend](/architecture/postgresql/) for the current scope. + ### `[backup]` Default settings for `msgvault backup`. See [Backup](/usage/backup/) for the @@ -572,4 +614,4 @@ All data lives under the msgvault home directory (`~/.msgvault` on macOS/Linux, | `attachments/` | Content-addressed attachment files | | `tokens/` | OAuth tokens per account | | `logs/` | Structured log files (when [file logging](/configuration/#log) is enabled) | -| `analytics/` | Parquet cache files for TUI | +| `analytics/` | Parquet cache files for Web UI and TUI analytical views | diff --git a/docs/development.md b/docs/development.md index 06d5a61a6..72d05ee09 100644 --- a/docs/development.md +++ b/docs/development.md @@ -5,6 +5,10 @@ description: Build, test, lint, and code conventions. ## Build +Source builds require Go 1.26+, Bun 1.3.14+, and a C/C++ compiler. The Make +targets install the pinned browser dependencies when `web/package.json` or +`web/bun.lock` changes, then embed the production UI in the Go binary. + ### macOS and Linux ```bash @@ -78,6 +82,8 @@ go vet ./... ## Code Conventions +- **Web UI**: Svelte with TypeScript, generated OpenAPI types, and components + from the shared UI toolkit - **TUI**: Bubble Tea for model/update, lipgloss for styling - **Database**: All DB operations through the `Store` struct (`internal/store`) - **Error handling**: Return `error`, wrap with context via `fmt.Errorf` @@ -113,6 +119,8 @@ WHERE EXISTS ( | Library | Purpose | |---|---| +| `svelte` | Embedded analytical Web UI | +| `openapi-fetch` | Typed browser API client | | `cobra` | CLI framework | | `charmbracelet/bubbletea` | TUI framework | | `charmbracelet/lipgloss` | TUI styling | diff --git a/docs/faq.md b/docs/faq.md index b4a534651..01ff53b4f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -17,7 +17,11 @@ The MCP server's lack of sync capability offers some protection here, since an a

What is the web server for?

-`msgvault serve` starts a web server that lets you query your archive programmatically via a REST API. It is useful for building dashboards, running automations, or integrating with other tools. The server can also run background sync on a cron schedule to keep your archive up to date. See [Web Server](/api-server/) for details. +`msgvault serve` starts the first-party analytical Web UI and the REST API it +uses. Open it to search and group across email, chat, calendar, and meeting +data, inspect people and files, monitor sources, and review staged deletions. +The same server supports automations, integrations, and scheduled background +sync. See [Web UI](/web-ui/) and [Web UI & API Server](/api-server/) for details.

Where is my email data stored?

@@ -29,10 +33,10 @@ Yes. You can sync any standard IMAP server, Microsoft 365 mail and Teams, Discord guilds, Beeper Desktop chats, Google Calendar, and supported meeting note services. You can also import email from PST, MBOX, or Apple Mail and chats/texts from WhatsApp, iMessage, Google Voice, Facebook Messenger, and SMS -Backup & Restore. All messages use the same search, TUI, MCP, web-server, and -export surfaces. See [Setup Guide](/setup/#add-an-imap-account), [Importing -Local Email](/usage/importing/), [Text Messages](/usage/text-messages/), and -[Discord](/usage/discord/). +Backup & Restore. All messages use the same Web UI, search, TUI, MCP, REST API, +and export surfaces. See [Setup Guide](/setup/#add-an-imap-account), +[Importing Local Email](/usage/importing/), [Text Messages](/usage/text-messages/), +and [Discord](/usage/discord/).

Can msgvault archive Discord direct messages?

@@ -43,7 +47,7 @@ archive accessible guild channels, threads, forum posts, and attachments; see

Does deleting email in msgvault delete it from Gmail?

-Only if you explicitly run the full deletion workflow. Staging messages for deletion in the TUI does not touch Gmail or your IMAP provider. You must run `MSGVAULT_ENABLE_REMOTE_DELETE=1 msgvault delete-staged` to execute staged deletions. Gmail messages move to trash by default; `--permanent` opts into permanent Gmail deletion. IMAP deletion removes messages from the provider. Your local archive is always preserved. See [Deleting Email](/usage/deletion/) for the complete process. +Only if you explicitly run the full deletion workflow. Staging messages for deletion in the Web UI or TUI does not touch Gmail or your IMAP provider. You must run `MSGVAULT_ENABLE_REMOTE_DELETE=1 msgvault delete-staged` to execute staged deletions. Gmail messages move to trash by default; `--permanent` opts into permanent Gmail deletion. IMAP deletion removes messages from the provider. Your local archive is always preserved. See [Deleting Email](/usage/deletion/) for the complete process. --- diff --git a/docs/guides/remote-deployment.md b/docs/guides/remote-deployment.md index e81f978cb..c739a447f 100644 --- a/docs/guides/remote-deployment.md +++ b/docs/guides/remote-deployment.md @@ -240,6 +240,16 @@ After setup, your data directory contains: └── analytics/ # Parquet cache for fast queries ``` +## Open the Remote Web UI + +Open the daemon URL in a browser and sign in with the configured API key. For +regular remote use, terminate HTTPS at a reverse proxy and add only that +proxy's address or CIDR to `server.trusted_proxies`; the daemon uses trusted +forwarding information to mark its browser session cookie `Secure`. Plain HTTP +on a private network is supported as an explicit tradeoff and produces a UI +warning because the cookie is not encrypted in transit. See [Web UI](/web-ui/) +for the complete session and proxy model. + ## Using the Local CLI Against Remote When your local machine config has: diff --git a/docs/index.md b/docs/index.md index c6167d7f3..c886ab16b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ --- title: msgvault -description: Offline email, chat, and meeting archive with full-text search, an interactive TUI, backup snapshots, and multi-account support. Sync Gmail, IMAP, Google Calendar, Microsoft Teams, Discord, Beeper, Granola, and Circleback. +description: Offline email, chat, calendar, and meeting archive with a daemon-served analytical Web UI, full-text and semantic search, a keyboard-driven TUI, and sync for Gmail, IMAP, Teams, Discord, Beeper, Granola, and Circleback. --- # msgvault @@ -54,7 +54,7 @@ of your email (from Gmail, IMAP, or local archives) and imports chats and texts from WhatsApp, iMessage, Google Voice, Facebook Messenger, and SMS Backup & Restore, and can sync Teams conversations, Discord guilds, Beeper chats, plus Granola and Circleback meeting notes. -Keyword search, analytics, the TUI, and the MCP server query your archive. +Keyword search, analytics, the Web UI, the TUI, and the MCP server query your archive. **Source services are contacted only by authorization/registration, sync, media-backfill, and deletion workflows that you run or schedule explicitly.** Optional vector search calls only the embedding endpoint you configure; use a @@ -87,7 +87,7 @@ it lives in an archive on disk that you own and control.

Meetings & Beeper

-

Sync Granola and Circleback notes and transcripts into a dedicated TUI browser, and archive chats and media from networks bridged through Beeper Desktop. Search each source separately by message type.

+

Sync Granola and Circleback notes and transcripts, and archive chats and media from networks bridged through Beeper Desktop. Explore each source in the Web UI or its dedicated TUI mode.

Backup Snapshots

@@ -103,7 +103,7 @@ it lives in an archive on disk that you own and control.

Semantic & Hybrid Search

-

Opt-in semantic search with vectors stored locally, plus hybrid ranking that fuses BM25 and vector similarity via Reciprocal Rank Fusion. Point msgvault at a local or self-hosted OpenAI-compatible embedding endpoint and query by meaning, not just keywords. Exposed through local CLI search, the HTTP API, and MCP server.

+

Opt-in semantic search with vectors stored locally, plus hybrid ranking that fuses BM25 and vector similarity via Reciprocal Rank Fusion. Point msgvault at a local or self-hosted OpenAI-compatible embedding endpoint and query by meaning, not just keywords. Search mode and index coverage stay explicit in the Web UI.

Multi-Account

@@ -122,8 +122,8 @@ it lives in an archive on disk that you own and control.

Install bundled read-only skills that teach Claude Code and Codex how to search the archive, retrieve attachments, and run analytics from the terminal.

-

Web Server

-

REST API for programmatic access to your archive. Optional cron-based background sync scheduling. Build dashboards, automations, and integrations.

+

Analytical Web UI & API

+

Open a dense, keyboard-driven archive interface served directly by the daemon. Search and group across modalities, inspect people and files, monitor sources, review deletions, or use the same server through its REST API.

Local Import

@@ -131,7 +131,7 @@ it lives in an archive on disk that you own and control.

Safe Deletion

-

Stage messages for deletion in the TUI or via AI assistant, review manifests, then permanently delete from Gmail or IMAP provider.

+

Review and stage messages in the Web UI, TUI, or via an AI assistant, then explicitly execute the manifest against Gmail or your IMAP provider while preserving the local archive.

diff --git a/docs/introduction.md b/docs/introduction.md index 0ac7920fa..350bac14b 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -15,9 +15,9 @@ systems to do useful things with it. This means: -* Search it instantaneously and categorize it the way I want. With a - beautiful fast terminal UI rather than a slow, increasingly less - useful web application. +* Search it instantaneously and categorize it the way I want, with both a + fast terminal UI and a dense, keyboard-driven analytical Web UI instead of + being limited by a provider's interface. * Chat with it securely via MCP in Claude Desktop and using my home local LLM inference servers. * Extract and file away the over 100,000 e-mail attachments sent to me diff --git a/docs/scripts/check_built_site.py b/docs/scripts/check_built_site.py index e516476c5..17eb0c514 100755 --- a/docs/scripts/check_built_site.py +++ b/docs/scripts/check_built_site.py @@ -41,6 +41,7 @@ "/usage/text-messages/", "/usage/tui/", "/usage/vector-search/", + "/web-ui/", ] REQUIRED_SITEMAP_URLS = [ @@ -73,6 +74,7 @@ "https://msgvault.io/usage/text-messages/", "https://msgvault.io/usage/tui/", "https://msgvault.io/usage/vector-search/", + "https://msgvault.io/web-ui/", ] REQUIRED_METADATA = [ @@ -215,6 +217,7 @@ def __init__(self) -> None: super().__init__() self.ids: set[str] = set() self.links: list[str] = [] + self.nav_links: list[str] = [] self.assets: list[str] = [] self.style_attrs: list[str] = [] self.style_blocks: list[str] = [] @@ -233,6 +236,8 @@ def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None if tag == "a" and "href" in attr: self.links.append(attr["href"]) classes = set(attr.get("class", "").split()) + if "md-nav__link" in classes: + self.nav_links.append(attr["href"]) if "md-nav__link" in classes and attr["href"].startswith("#"): self._nav_label_href = attr["href"] self._nav_label_text = [] @@ -423,6 +428,14 @@ def main() -> None: fail(f"forbidden generated marker found: {pattern}") parsed_by_file = {path.resolve(): parse_html(path) for path in html_files} + index_parser = parsed_by_file[(SITE / "index.html").resolve()] + web_ui_route = route_to_file("/web-ui/").resolve() + if not any( + (target_file(SITE / "index.html", href) or pathlib.Path()).resolve() + == web_ui_route + for href in index_parser.nav_links + ): + fail("Web UI is missing from the rendered primary navigation") for current, parser in parsed_by_file.items(): for href in parser.links: parsed = urllib.parse.urlparse(href) diff --git a/docs/setup.md b/docs/setup.md index d7193efb4..bb5096baa 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -45,7 +45,10 @@ conda install -c conda-forge msgvault ## Build From Source -Requires Go 1.26+ and a C/C++ compiler (GCC or Clang). CGO is required because msgvault uses `mattn/go-sqlite3` (SQLite with FTS5) and `duckdb-go/v2` (Parquet analytics), both of which compile native extensions. +Requires Go 1.26+, Bun 1.3.14+, and a C/C++ compiler (GCC or Clang). Bun builds +the browser application embedded in the binary. CGO is required because +msgvault uses `mattn/go-sqlite3` (SQLite with FTS5) and `duckdb-go/v2` (Parquet +analytics), both of which compile native extensions. ```bash git clone https://github.com/kenn-io/msgvault.git @@ -168,7 +171,8 @@ After adding the account, sync it the same way as a Gmail account: msgvault sync-full ``` -IMAP accounts are stored in the same database as Gmail accounts. All tools (TUI, search, MCP, web server) work with IMAP messages the same way. +IMAP accounts are stored in the same database as Gmail accounts. All tools +(Web UI, TUI, search, MCP, and REST API) work with IMAP messages the same way. !!! tip "Microsoft 365 / Outlook.com" For Outlook, Hotmail, Live.com, and Microsoft 365 accounts, `add-o365` provides OAuth-based access without app passwords. It auto-detects the correct IMAP host and configures XOAUTH2 authentication. See the [OAuth Setup guide](/guides/oauth-setup/#microsoft-365-outlook-hotmail) for details. @@ -219,7 +223,7 @@ Gmail's "storage used" number includes attachments at full size. Your on-disk fo - **Message metadata + bodies** go into the SQLite database, compressed ~3-5x. - **Attachments** are extracted and stored as-is (PDFs, images, etc. are already compressed). Identical attachments across messages are deduplicated by content hash. -- **Parquet analytics cache** is a lightweight projection for the TUI — typically a few MB even for large archives. +- **Parquet analytics cache** is a lightweight projection for the Web UI and TUI — typically a few MB even for large archives. Use `--limit` or a date range for your first sync to gauge the ratio for your mailbox before committing to a full sync. After syncing, `msgvault stats` shows the actual sizes. See [Data Storage](/architecture/storage/) for details on compression and storage layers. @@ -277,6 +281,9 @@ Sync operations are **read-only**. They use only `messages.list` and `messages.g # Search your archive msgvault search from:alice@example.com +# Open the analytical Web UI +msgvault serve + # Launch the interactive TUI msgvault tui @@ -286,7 +293,8 @@ msgvault stats
msgvault TUI showing the Senders view
-See [Searching](/usage/searching/) and [Interactive TUI](/usage/tui/) for more. +See [Web UI](/web-ui/), [Searching](/usage/searching/), and [Interactive +TUI](/usage/tui/) for more. ## Optional: Sync Google Calendar @@ -302,6 +310,27 @@ Calendar sync is read-only. Events become searchable with `--message-type calendar_event`; see [Google Calendar](/usage/calendar/) for the full workflow, scheduled sync, and headless-server setup. +## Open the Web UI + +Build the analytical cache and start the daemon: + +```bash +msgvault build-cache +msgvault serve +``` + +Open the `API server` URL printed at startup. With the default loopback bind, +the browser is trusted locally. A daemon bound to another interface must use an +API key; the browser presents a login screen and stores only an in-memory +daemon session. The release binary contains the complete UI, so no frontend +runtime or separately installed web files are required. + +For a server or NAS, prefer HTTPS at a reverse proxy and configure only that +proxy in `server.trusted_proxies`. Plain HTTP is an explicit private-network +tradeoff because its session cookie cannot be marked `Secure`. See [Web UI](/web-ui/) +for URL discovery, search/index states, keyboard controls, and deployment +examples. + ## Optional: Sync Microsoft Teams To archive Teams chats and channels, add Microsoft Graph permissions to your diff --git a/docs/superpowers/plans/2026-07-20-relationships-backend.md b/docs/superpowers/plans/2026-07-20-relationships-backend.md new file mode 100644 index 000000000..5a4862096 --- /dev/null +++ b/docs/superpowers/plans/2026-07-20-relationships-backend.md @@ -0,0 +1,749 @@ +# Relationships Backend Implementation Plan (Plan 2 of 3) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Implement the backend for the relationships-centric web UI per `docs/superpowers/specs/2026-07-20-web-ui-relationships-design.md`: reversible identity-link clusters, identity-aware cache exports (`is_from_me` derivation, `owner_participants`, `participant_clusters`), the relationship ranking and day-burst timeline endpoints, identity link/unlink APIs with synchronous cache refresh, and conversation time-window bounds. + +**Architecture:** New `participant_links` table with a forest invariant and an `identity_revision` counter in `archive_metadata` (store layer); three cache changes behind a `CacheSchemaVersion` bump (derived `is_from_me`, two new identity datasets); a lightweight identity-dataset refresh in `internal/cacheops` callable from both `build-cache` and the API; DuckDB ranking/timeline queries following the `PeopleAnalyzer` pattern; Huma-registered endpoints following the explore cursor/authority conventions. + +**Tech Stack:** Go + testify; SQLite/PostgreSQL via `internal/store` dialects; DuckDB over Parquet (`internal/query`); Huma OpenAPI routes (`internal/api`); regenerated clients via `make openapi` + `web/scripts/generate-web-client.mjs`. + +## Global Constraints + +- Go tests use testify only: `require.X` halts, `assert.X` continues; argument order `(want, got)`. Never `t.Errorf`/`t.Fatalf`. +- Run Go tests with `-tags "fts5 sqlite_vec"`. After Go changes: `go fmt ./...` and `go vet ./...`; stage all resulting changes. +- Every `internal/store` schema/DDL change lands in BOTH `schema.sql` (SQLite) and `schema_pg.sql` (PostgreSQL, `DATETIME`→`TIMESTAMPTZ`). Store methods must be dialect-aware (`s.dialect.InsertOrIgnore`, `Rebind`, `Now()` — see existing patterns in `internal/store/account_identities.go`). +- Cache COPY column changes require bumping `query.CacheSchemaVersion` (currently 12 → 13, `internal/query/cache_state.go:15-17`) and updating the pinned assertion in `internal/query/cache_state_test.go:141`. +- New/changed API routes: bump `APISchemaVersion` in `internal/api/openapi.go:72` (1.18.0 → 1.19.0) with a changelog comment, run `make openapi`, and commit the regenerated `api/openapi.yaml`, `pkg/client/`, and (after `cd web && bun run generate`) `web/src/lib/api/generated/schema.d.ts`. +- Never use real PII in fixtures — synthetic names/addresses only. +- Commit after every task; pre-commit hooks must pass; never `--no-verify`. Run `make lint-ci` before finishing. +- Spec weights are binding: `sent_to_them × 2.0`, `meetings_together × 3.0`, `received_from_them × 1.0`; decay half-life 365 days; breadth boost `1 + 0.25 × (modalities − 1)`; gate `sent_to_them ≥ 1 OR meetings_together ≥ 1`; ties break by most-recent interaction then display name. + +--- + +### Task 1: `participant_links` table, identity revision, and store link API + +**Files:** +- Modify: `internal/store/schema.sql` (after `account_identities`, ~line 543) +- Modify: `internal/store/schema_pg.sql` (parallel location) +- Create: `internal/store/participant_links.go` +- Create: `internal/store/participant_links_test.go` + +**Interfaces (produces):** +```go +var ErrAlreadyLinked = errors.New("participants are already linked through other identities") + +// LinkParticipants asserts a and b are the same person. Idempotent for the +// exact existing edge; ErrAlreadyLinked for a new redundant edge between +// indirectly-connected participants. Returns the identity revision. +func (s *Store) LinkParticipants(a, b int64) (int64, error) +// UnlinkParticipants removes the edge (idempotent). Returns the revision. +func (s *Store) UnlinkParticipants(a, b int64) (int64, error) +// ParticipantClusters returns participant_id → canonical cluster ID +// (smallest member ID) for every participant that appears in a link edge. +// Unlinked participants are their own cluster and are not returned. +func (s *Store) ParticipantClusters() (map[int64]int64, error) +// ClusterMembers returns all participant IDs in the cluster containing id +// (including id itself), sorted ascending. Single-element for unlinked ids. +func (s *Store) ClusterMembers(id int64) ([]int64, error) +// IdentityRevision returns the archive_metadata identity revision (0 if unset). +func (s *Store) IdentityRevision() (int64, error) +``` + +- [ ] **Step 1: Add the schema (both dialects)** + +`internal/store/schema.sql`, after the `account_identities` index: + +```sql +-- User-asserted identity links between participants. Edges are normalized +-- (participant_a < participant_b) and the graph is kept a forest: every +-- edge joins two previously distinct clusters, so deleting an edge +-- deterministically splits one cluster in two. Connected components resolve +-- to a canonical cluster (smallest member ID) at read time. +CREATE TABLE IF NOT EXISTS participant_links ( + participant_a INTEGER NOT NULL REFERENCES participants(id) ON DELETE CASCADE, + participant_b INTEGER NOT NULL REFERENCES participants(id) ON DELETE CASCADE, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (participant_a, participant_b), + CHECK (participant_a < participant_b) +); + +CREATE INDEX IF NOT EXISTS idx_participant_links_b + ON participant_links(participant_b); +``` + +Mirror in `schema_pg.sql` with `TIMESTAMPTZ`. + +- [ ] **Step 2: Write failing store tests** + +`internal/store/participant_links_test.go` (package `store_test`, using `storetest.New(t)` like `account_identities_test.go`). Table of behaviors — each is one test: + +```go +func TestLinkParticipantsCreatesEdgeAndBumpsRevision(t *testing.T) { + f := storetest.New(t) + a := f.AddParticipant("alice@example.com", "Alice") + b := f.AddParticipant("alice@personal.example", "Alice P") + rev, err := f.Store.LinkParticipants(b, a) // reversed order: must normalize + require.NoError(t, err) + assert.Equal(t, int64(1), rev) + clusters, err := f.Store.ParticipantClusters() + require.NoError(t, err) + assert.Equal(t, map[int64]int64{a: min64(a, b), b: min64(a, b)}, clusters) +} + +func TestLinkParticipantsExactEdgeIsIdempotent(t *testing.T) // second identical Link → same revision, no error +func TestLinkParticipantsRejectsSelfAndUnknown(t *testing.T) // a==a → error; nonexistent id → error +func TestLinkParticipantsRedundantIndirectEdgeIsAlreadyLinked(t *testing.T) { + // link a-b, b-c; then Link(a, c) → ErrAlreadyLinked (they are indirectly connected) +} +func TestUnlinkParticipantsSplitsClusterDeterministically(t *testing.T) { + // link a-b, b-c; Unlink(b, c) → clusters: {a,b} and c alone; revision bumped +} +func TestUnlinkParticipantsMissingEdgeIsIdempotent(t *testing.T) // no error, revision unchanged +func TestClusterMembersForUnlinkedParticipant(t *testing.T) // returns just the id +``` + +If `storetest` has no `AddParticipant` helper, use the store's existing participant upsert (find it: `rg -n "func (s \*Store) EnsureParticipant" internal/store/`) or insert via `f.Store` exec — follow whatever `account_identities_test.go` and `collection_test.go` do to create participants. + +- [ ] **Step 3: Run tests to verify they fail** + +`go test -tags "fts5 sqlite_vec" ./internal/store/ -run TestLinkParticipants -v` — FAIL (undefined methods). + +- [ ] **Step 4: Implement `internal/store/participant_links.go`** + +```go +package store + +import ( + "database/sql" + "errors" + "fmt" + "sort" + "strconv" +) + +var ErrAlreadyLinked = errors.New("participants are already linked through other identities") + +const identityRevisionKey = "identity_revision" + +// IdentityRevision returns the current identity revision (0 if never bumped). +func (s *Store) IdentityRevision() (int64, error) { + var value string + err := s.db.QueryRow( + `SELECT value FROM archive_metadata WHERE key = ?`, identityRevisionKey, + ).Scan(&value) + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } + if err != nil { + return 0, fmt.Errorf("read identity revision: %w", err) + } + revision, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return 0, fmt.Errorf("parse identity revision %q: %w", value, err) + } + return revision, nil +} + +// bumpIdentityRevision increments the revision inside tx and returns the new value. +func (s *Store) bumpIdentityRevision(tx *loggedTx) (int64, error) { + if _, err := tx.Exec(s.dialect.InsertOrIgnore( + `INSERT OR IGNORE INTO archive_metadata (key, value) VALUES (?, '0')`), + identityRevisionKey); err != nil { + return 0, fmt.Errorf("seed identity revision: %w", err) + } + var revision int64 + if err := tx.QueryRow( + `UPDATE archive_metadata SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT) + WHERE key = ? RETURNING CAST(value AS INTEGER)`, + identityRevisionKey).Scan(&revision); err != nil { + return 0, fmt.Errorf("bump identity revision: %w", err) + } + return revision, nil +} +``` + +(If the PG dialect needs different casts, use `s.dialect.Rebind` and test under `make test-pg` when `MSGVAULT_TEST_DB` is set; `CAST(value AS INTEGER)` is valid in both.) + +Link/unlink — the graph is small (user-asserted edges), so load all edges and resolve components in Go: + +```go +type linkEdge struct{ a, b int64 } + +func (s *Store) loadLinkEdges(q interface { + Query(string, ...any) (*sql.Rows, error) +}) ([]linkEdge, error) { /* SELECT participant_a, participant_b FROM participant_links */ } + +// componentOf walks the edge list from id; returns the member set. +func componentOf(id int64, edges []linkEdge) map[int64]struct{} { /* BFS over adjacency */ } + +func normalizeEdge(a, b int64) (int64, int64) { if a > b { return b, a }; return a, b } + +func (s *Store) LinkParticipants(a, b int64) (int64, error) { + if a == b || a <= 0 || b <= 0 { + return 0, fmt.Errorf("link participants: ids must be distinct positive IDs (got %d, %d)", a, b) + } + lo, hi := normalizeEdge(a, b) + var revision int64 + err := s.withTx(func(tx *loggedTx) error { + // Verify both participants exist (clear error beats FK failure). + // SELECT COUNT(*) FROM participants WHERE id IN (?, ?) == 2 ... + edges, err := s.loadLinkEdgesTx(tx) + if err != nil { + return err + } + for _, e := range edges { + if e.a == lo && e.b == hi { // exact edge: idempotent + revision, err = s.currentIdentityRevisionTx(tx) + return err + } + } + if _, connected := componentOf(lo, edges)[hi]; connected { + return ErrAlreadyLinked + } + if _, err := tx.Exec( + `INSERT INTO participant_links (participant_a, participant_b) VALUES (?, ?)`, + lo, hi); err != nil { + return fmt.Errorf("insert participant link: %w", err) + } + revision, err = s.bumpIdentityRevision(tx) + return err + }) + return revision, err +} +``` + +`UnlinkParticipants` mirrors it: delete the normalized edge; bump only if a row was deleted, else return current revision. `ParticipantClusters` loads edges, unions components, maps every member to the component's min ID. `ClusterMembers(id)` returns the sorted component (or `[id]`). + +- [ ] **Step 5: Tests pass; fmt/vet; commit** + +`go test -tags "fts5 sqlite_vec" ./internal/store/ -run "TestLink|TestUnlink|TestCluster" -v` → PASS. +Full package: `go test -tags "fts5 sqlite_vec" ./internal/store/` → PASS. + +```bash +go fmt ./... && go vet ./... && git add -A +git commit -m "feat(store): add participant link clusters with identity revision" +``` + +--- + +### Task 2: Link-aware `MergeParticipants` with spanning-forest recompute + +**Files:** +- Modify: `internal/store/messages.go` (`MergeParticipants`, ~line 2054) +- Modify: `internal/store/migrate_phone_unique.go` (`mergeParticipant` tx variant, ~line 135) +- Test: `internal/store/participant_links_test.go` (extend) + +**Interfaces:** +- Consumes: Task 1's `bumpIdentityRevision`, `loadLinkEdgesTx`, `componentOf`, `normalizeEdge`. +- Produces: merge semantics later tasks rely on — after any merge, links form a canonical spanning star rooted at the cluster's smallest member ID and the identity revision is bumped (only when links were touched). + +- [ ] **Step 1: Write failing tests** + +```go +func TestMergeParticipantsRewritesLinkEdges(t *testing.T) { + // participants a < b < c; link b-c; merge b INTO a (a survives) + // → edge becomes a-c; b gone; ParticipantClusters maps {a,c} → a; revision bumped +} +func TestMergeParticipantsPathContractionKeepsForest(t *testing.T) { + // participants a -v`). + +- [ ] **Step 3: Implement the messages COPY change** + +In the messages COPY SELECT (build_cache.go ~line 770), add: + +```sql + (m.is_from_me OR EXISTS ( + SELECT 1 FROM sqlite_db.account_identities ai + JOIN sqlite_db.participants sp ON sp.id = m.sender_id + WHERE ai.source_id = m.source_id + AND sp.email_address IS NOT NULL + AND lower(sp.email_address) = lower(ai.address) + )) AS is_from_me, +``` + +(Match the surrounding table-qualification style — the COPY reads from the ATTACHed SQLite database; check how existing columns reference `m.` and whether the alias prefix is `sqlite_db.` or set by a `USE`/FROM clause, and mirror it exactly.) + +- [ ] **Step 4: Add the two identity-dataset exports** + +Next to the other auxiliary `runExport` calls (~line 636-751): + +```go + if err := runExport("owner_participants", ` + COPY ( + SELECT DISTINCT ai.source_id, p.id AS participant_id + FROM sqlite_db.account_identities ai + JOIN sqlite_db.participants p + ON p.email_address IS NOT NULL AND lower(p.email_address) = lower(ai.address) + UNION + SELECT DISTINCT ai.source_id, pi.participant_id + FROM sqlite_db.account_identities ai + JOIN sqlite_db.participant_identifiers pi + ON pi.identifier_type = 'email' AND lower(pi.identifier_value) = lower(ai.address) + ) TO '%s' (FORMAT PARQUET)`); err != nil { ... } +``` + +For `participant_clusters`, the mapping is computed in Go (`store.ParticipantClusters()`, Task 1) since components need graph traversal: create a DuckDB temp table, insert the pairs, `COPY (SELECT participant_id, canonical_id FROM tmp_clusters) TO ...`, drop the temp table. An empty mapping still writes an empty (schema-bearing) Parquet file — the dataset must always exist (`RequiredParquetDirs`). + +- [ ] **Step 5: Version, state, staleness, publication, reader** + +- `CacheSchemaVersion = 13`; fix the pinned test. +- `CacheSyncState`: add `IdentityRevision int64` and fold it into `Revision()`'s hash input (alongside the existing commit-marker fields). +- State assembly (build_cache.go ~879): set `IdentityRevision` from `store.IdentityRevision()`. +- `cacheStaleness`: add `HasIdentityDrift bool`; in the signal collection, compare `store.IdentityRevision()` to `state.IdentityRevision` — drift alone sets `NeedsBuild` with reason `identity revision changed` but NOT `FullRebuild` (the refresh path in Task 4 handles it; the full build path also refreshes it naturally). +- `replacesCacheDataset` (cache_publication.go:79): add `owner_participants`, `participant_clusters`. +- `RequiredParquetDirs` (duckdb.go:2360): add both datasets. +- `hasCol`/optional-column guard for `is_from_me` (default `FALSE`) so a v12 cache that somehow loads doesn't break readers — follow the existing optional-column REPLACE pattern in duckdb.go:554-589. +- Extend the test fixture builder (`internal/query/testfixtures_test.go`): `MessageOpt` gains `IsFromMe bool`; `Build()` writes the two new datasets (builder-level `AddOwnerParticipant(sourceID, participantID)` and `LinkCluster(ids ...int64)` helpers writing the parquet rows directly). Tasks 5–6 need these. + +- [ ] **Step 6: Tests pass** + +`go test -tags "fts5 sqlite_vec" ./cmd/msgvault/cmd/ ./internal/query/` → PASS. + +- [ ] **Step 7: fmt/vet; commit** + +```bash +go fmt ./... && go vet ./... && git add -A +git commit -m "feat(cache): derive is_from_me and export identity datasets (schema v13)" +``` + +--- + +### Task 4: Identity-dataset refresh in `internal/cacheops` + account-identity revision bump + +**Files:** +- Create: `internal/cacheops/identity_refresh.go` +- Create: `internal/cacheops/identity_refresh_test.go` +- Modify: `cmd/msgvault/cmd/build_cache.go` (reuse for the identity-drift-only staleness path) +- Modify: `internal/store/account_identities.go` (`AddAccountIdentity`/`RemoveAccountIdentity` bump the identity revision in the same transaction, per the spec: "confirming or removing an account identity ... changes which participants are owners for a source"; without the bump, `owner_participants` stays silently stale). Acquire `lockIdentityMutationTx` first, mirroring `LinkParticipants`. Bump only on actual mutation (adding an identity that already exists or removing one that doesn't must NOT bump — idempotency per spec). Tests: add → revision+1; duplicate add → unchanged; remove → +1; remove missing → unchanged. + +**Interfaces (produces):** +```go +// RefreshIdentityDatasets re-exports owner_participants and +// participant_clusters from the archive into the committed cache without +// touching message datasets, and stamps the new identity revision into +// _last_sync.json. Safe to call concurrently with readers: datasets are +// staged and renamed like a full publication. Returns the stamped revision. +func RefreshIdentityDatasets(ctx context.Context, st *store.Store, analyticsDir string) (int64, error) +``` + +- [ ] **Step 1: Write the failing test** + +Build a small cache from a seeded store (reuse the cmd build helpers if exported, else seed + run the same export the cmd test does — if the cmd helpers are unexported, construct the test at the `cacheops` level: write a minimal valid cache with the query fixture builder, then point `RefreshIdentityDatasets` at a real seeded store + that analyticsDir). Assert: after `LinkParticipants(a, b)` + refresh, the `participant_clusters` Parquet contains the new mapping, `_last_sync.json`'s `identity_revision` equals `store.IdentityRevision()`, and the message datasets' files are untouched (same mtimes/fingerprint). + +- [ ] **Step 2: Implement** + +The implementation moves/extracts the minimal staging pieces: create a same-parent staging dir (mirror `newCacheStaging`'s `.name.build-` convention from `cmd/msgvault/cmd/cache_publication.go` — extract shared helpers into `internal/cacheops` if the cmd package can consume them back without an import cycle; `cmd` imports `internal/cacheops`, never the reverse). Export the two datasets (same SQL as Task 4 exports — owner_participants via a short-lived DuckDB connection ATTACHing the SQLite file read-only, participant_clusters from `st.ParticipantClusters()` written via DuckDB temp table). Publish: read current `CacheSyncState`, invalidate, rename the two dataset dirs into place, re-fingerprint, write state with updated `IdentityRevision` + `PublishedAt`. If no committed cache exists (no `_last_sync.json`), return a typed error — the API maps it to the standard unavailable-cache response. + +PostgreSQL note: the DuckDB SQLite-ATTACH path only works for SQLite archives. When `st.IsPostgreSQL()`, derive `owner_participants` rows in Go via store queries (`ListAccountIdentities` + participant lookups) and write both datasets through DuckDB temp tables. (Relationships ranking itself is DuckDB-cache-only per the spec, but the refresh must not crash a PG archive.) + +- [ ] **Step 3: Wire the identity-drift staleness path** + +In the build-cache flow (where `cacheStaleness.HasIdentityDrift` is the only signal), call `cacheops.RefreshIdentityDatasets` instead of a message rebuild. + +- [ ] **Step 4: Tests pass; fmt/vet; commit** + +```bash +go test -tags "fts5 sqlite_vec" ./internal/cacheops/ ./cmd/msgvault/cmd/ +go fmt ./... && go vet ./... && git add -A +git commit -m "feat(cacheops): refresh identity datasets without message rebuild" +``` + +--- + +### Task 5: Relationship ranking — score function, DuckDB query, endpoint + +**Files:** +- Create: `internal/query/relationships.go` +- Create: `internal/query/relationships_test.go` +- Create: `internal/api/relationships.go` +- Create: `internal/api/relationships_test.go` +- Modify: `internal/api/routes.go` (~line 209: `s.registerRelationshipRoutes(apiV1)`) +- Modify: `internal/api/openapi.go` (version 1.19.0 + changelog note) + +**Interfaces (produces):** +```go +// internal/query +type RelationshipSignals struct { + SentToThem float64 // decayed sum + ReceivedFromThem float64 + MeetingsTogether float64 + SentCount int64 // raw counts (for the gate) + MeetingCount int64 + Modalities int // distinct of email/chat/meeting, 1..3 + LastInteractionAt time.Time +} +// RelationshipScore applies the spec's weights, decay having been applied in +// SQL: score = (2.0*sent + 3.0*meetings + 1.0*received) * (1 + 0.25*(modalities-1)). +func RelationshipScore(s RelationshipSignals) float64 + +type RelationshipRow struct { + CanonicalID int64 `json:"canonical_id"` + DisplayLabel string `json:"display_label"` + MemberIDs []int64 `json:"member_ids"` + Score float64 `json:"score"` + Signals RelationshipSignals `json:"signals"` + LastAt time.Time `json:"last_at"` +} +type RelationshipsRequest struct { + Context Context + ShowAll bool // lifts the reciprocity gate + Limit int + Offset int + Now time.Time // injected for deterministic decay in tests +} +type RelationshipsResponse struct { + Rows []RelationshipRow + TotalCount int64 + CacheRevision string + IdentityRevision int64 +} +type RelationshipAnalyzer interface { + Relationships(ctx context.Context, request RelationshipsRequest) (*RelationshipsResponse, error) +} +``` +- HTTP: `POST /api/v1/relationships` — body `{show_all?, limit?, cursor?, filters?}`; there is no `query`/`search_mode` (ranking is over reciprocity signals, not lexical/semantic search candidates — the shipped `RelationshipsHTTPRequest` rejects unknown fields); 503 unavailable-cache when the engine is not a `RelationshipAnalyzer` (exactly the `people.go:106-108` pattern); cursor/revision authority per the explore conventions. + +- [ ] **Step 1: Score function TDD** + +Table-driven `TestRelationshipScore` covering: weights (a single decayed sent unit scores 2.0; meeting 3.0; received 1.0), breadth boost (same signals with 3 modalities = 1.5×), and zero signals → 0. Then implement `RelationshipScore` — one documented function, exactly the spec constants: + +```go +const ( + relationshipWeightSent = 2.0 + relationshipWeightMeetings = 3.0 + relationshipWeightReceived = 1.0 + relationshipBreadthStep = 0.25 + relationshipHalfLifeDays = 365.0 +) +``` + +- [ ] **Step 2: Ranking query TDD (fixture builder)** + +`TestRelationshipsRanksByReciprocityAndGatesNewsletters`: builder fixture with an owner participant (`AddOwnerParticipant`), person A (owner sent 3 messages to them — `IsFromMe: true` with A as recipient — plus 1 meeting together), person B (newsletter: 50 inbound messages, zero sent/meetings), a cluster linking A with a second identity A2 (`LinkCluster(A, A2)`) where A2 has chat messages. Assert: default response ranks A first with cluster-combined signals under `canonical_id = min(A, A2)`, B is absent (gate); `ShowAll: true` includes B; deterministic order with injected `Now`. + +- [ ] **Step 3: Implement the DuckDB query** + +`(e *DuckDBEngine) Relationships(...)` in `internal/query/relationships.go`, following `searchPeople`'s structure (query slot, `ReadCacheSyncState` for revision, `buildExploreLogicalSQL(conditions)` for context scoping). Core SQL shape (decay computed in SQL, score in Go): + +```sql +WITH clusters AS ( + SELECT participant_id, canonical_id FROM read_parquet('/participant_clusters/*.parquet') +), owners AS ( + SELECT DISTINCT participant_id FROM read_parquet('/owner_participants/*.parquet') +), canon AS ( -- every participant resolves to itself unless clustered + SELECT p.id AS participant_id, COALESCE(c.canonical_id, p.id) AS canonical_id + FROM participants_view p LEFT JOIN clusters c ON c.participant_id = p.id +), owner_canon AS ( + SELECT DISTINCT cn.canonical_id FROM owners o JOIN canon cn ON cn.participant_id = o.participant_id +), interactions AS ( + SELECT + cn.canonical_id, + le.entry_kind, + le.occurred_at, + le.is_from_me, + exp(-0.6931471805599453 * date_diff('day', le.occurred_at, ?) / 365.0) AS decay + FROM logical_entries le + CROSS JOIN UNNEST(le.participant_ids) AS pid(participant_id) + JOIN canon cn ON cn.participant_id = pid.participant_id + WHERE cn.canonical_id NOT IN (SELECT canonical_id FROM owner_canon) +) +SELECT + canonical_id, + SUM(CASE WHEN is_from_me AND entry_kind IN ('email','conversation','item') THEN decay ELSE 0 END) AS sent_decayed, + COUNT(CASE WHEN is_from_me AND entry_kind IN ('email','conversation','item') THEN 1 END) AS sent_count, + SUM(CASE WHEN NOT is_from_me AND entry_kind IN ('email','conversation','item') THEN decay ELSE 0 END) AS received_decayed, + SUM(CASE WHEN entry_kind IN ('event','meeting') THEN decay ELSE 0 END) AS meetings_decayed, + COUNT(CASE WHEN entry_kind IN ('event','meeting') THEN 1 END) AS meeting_count, + COUNT(DISTINCT CASE + WHEN entry_kind IN ('event','meeting') THEN 'meeting' + WHEN entry_kind = 'conversation' THEN 'chat' + ELSE 'email' END) AS modalities, + MAX(occurred_at) AS last_at +FROM interactions +GROUP BY canonical_id +``` + +Notes for the implementer: `logical_entries` must carry `is_from_me` — extend `buildExploreLogicalSQL` (explore.go:365-448) to project it (anchor-message value via `arg_max` for conversation rows), guarded by the `hasCol` optional-column mechanism; a meeting "together" is any event/meeting entry whose participants include both an owner and the person — since `interactions` already excludes owner clusters and events carry all attendees in `participant_ids`, add `AND EXISTS`-style owner check for the meeting rows (`list_has_any` of the entry's participant_ids against the owners list) — implement as an additional boolean column `with_owner` computed before the UNNEST, and require it in the meeting CASE arms. Display label + member IDs come from a second small query joining `canon` back to `participants_view` (min-ID display name, `list(participant_id)`). Gate + score + sort (score DESC, last_at DESC, label ASC) in Go via `RelationshipScore`; apply `ShowAll` by skipping the gate filter. Paginate with limit/offset like `searchPeople`. + +- [ ] **Step 4: API endpoint** + +`internal/api/relationships.go`: `registerRelationshipRoutes` with `POST /relationships` (Huma, mirroring `registerPeopleRoutes`' helper usage); handler: decode via `decodeExploreJSON`, clamp limit, resolve search/snapshot via `prepareIdentitySearch`-equivalent (reuse it if its shape fits — it lives in people.go and is server-scoped), type-assert `query.RelationshipAnalyzer` else `writeExploreUnavailable(w, query.CacheAbsent)`, cursor binds `{Request, Revision, IdentityRevision, Offset}` in a signed cursor (extend `exploreCursor` with `IdentityRevision int64 \`json:"identity_revision,omitempty"\`` rather than a new type), 409 `archive_revision_changed` on cache-revision drift and `identity_revision_changed` on identity drift. Response echoes `CacheRevision` and `IdentityRevision`. API test: seeded engine fixture (builder) through `newTestServerWithEngine`, asserting ranking order, the gate, `show_all`, 409 on doctored cursor, and 503 under a non-analyzer engine. + +- [ ] **Step 5: OpenAPI + clients** + +Bump `APISchemaVersion` to 1.19.0 with a changelog comment; `make openapi`; `cd web && bun run generate`; commit generated files. + +- [ ] **Step 6: Tests, fmt/vet, commit** + +```bash +go test -tags "fts5 sqlite_vec" ./internal/query/ ./internal/api/ +go fmt ./... && go vet ./... && git add -A +git commit -m "feat(api): relationship ranking endpoint over identity clusters" +``` + +--- + +### Task 6: Relationship timeline endpoint with day bursts + +**Files:** +- Create: `internal/query/relationship_timeline.go` (+ `_test.go`) +- Modify: `internal/api/relationships.go` (+ tests) + +**Interfaces (produces):** +```go +// internal/query +type RelationshipTimelineRequest struct { + CanonicalID int64 + Timezone string // validated IANA name; "" = UTC + Context Context + Limit int + Offset int +} +type TimelineRow struct { + Key string `json:"key"` // message: | burst::: + Kind string `json:"kind"` // email | event | meeting | chat_burst + Title string `json:"title"` + Preview string `json:"preview"` + OccurredAt time.Time `json:"occurred_at"` // burst: latest message time + FirstAt time.Time `json:"first_at,omitempty"` + MessageCount int64 `json:"message_count"` + SourceID int64 `json:"source_id"` + ConversationID *int64 `json:"conversation_id,omitempty"` + AnchorMessageID *int64 `json:"anchor_message_id,omitempty"` + HasAttachments bool `json:"has_attachments"` +} +// added to RelationshipAnalyzer: +RelationshipTimeline(ctx context.Context, request RelationshipTimelineRequest) (*RelationshipTimelineResponse, error) +``` +- HTTP: `POST /api/v1/relationships/{id}/timeline` — `{timezone?, filters?, limit?, cursor?}`; no `query`/`search_mode` (same rationale as the ranking endpoint above — the shipped `RelationshipTimelineHTTPRequest` rejects unknown fields). `{id}` accepts any member participant ID and resolves to the canonical cluster, echoing `canonical_id` in the response. Cursor binds canonical ID + timezone + request hash + cache revision + identity revision; any mismatch → 409 `cursor_invalidated`. + +- [ ] **Step 1: Query TDD** + +`TestRelationshipTimelineBurstsChatByLocalDay`: builder fixture — cluster person with 3 chat messages in one conversation on 2026-07-13 (two at 23:00/23:30 UTC, one at 00:30 UTC next day) plus an email and a meeting. With `Timezone: "America/Chicago"` all three chat messages fall on the same local day → assert ONE `chat_burst` row with `MessageCount: 3`, correct first/last times, and the latest snippet as preview; with UTC they split into two bursts. Emails/meetings stay one row each. Ordering `(occurred_at DESC, key)`. + +- [ ] **Step 2: Implement the query** + +Validate the timezone in Go first (`time.LoadLocation`); reject invalid names with `ErrInvalidExploreRequest` context. In DuckDB, named-timezone conversion needs the ICU extension — load it in `NewDuckDBEngine` next to the sqlite extension (`INSTALL icu; LOAD icu;`, mirroring duckdb.go:116-153; verify with a probe query in the engine test; if the bundled build already auto-loads ICU, the probe documents it). SQL shape: start from `logical_entries` scoped to the cluster's member IDs (resolve members in Go via the clusters dataset or store), but replace the chat grouping key: instead of the existing per-conversation-lifetime grouping, group chat by `(source_id, conversation_id, strftime(timezone(?, occurred_at), '%Y-%m-%d'))`, `arg_max` for snippet/anchor, `COUNT(*)`, `MIN/MAX(occurred_at)`; non-chat rows pass through one-per-message. Reuse `FlattenSnippet` on previews. Order `(occurred_at DESC, entry_key ASC)`, limit/offset. + +Everything's existing conversation-row semantics are untouched — this is a separate query path, not a change to `buildExploreLogicalSQL`'s chat branch (extract shared CTE-text helpers rather than forking string constants wholesale). + +- [ ] **Step 3: API handler + cursor** + +Route `POST /relationships/{id}/timeline`. Resolve `{id}` → canonical + members (via the engine's clusters dataset so the API needs no store round-trip; a miss = single-member cluster). Cursor: the extended `exploreCursor` (Task 5) plus `Timezone string` and `CanonicalID int64` fields; on any mismatch (request hash, revision, identity revision, timezone, canonical) → 409 `cursor_invalidated` with the message "The timeline context changed; restart pagination". Tests: burst pagination across two pages with a stable cursor; 409 after a simulated identity-revision change (rebuild fixture with a new link); 400 on a bad timezone. + +- [ ] **Step 4: OpenAPI regen; tests; fmt/vet; commit** + +```bash +make openapi && (cd web && bun run generate) +go test -tags "fts5 sqlite_vec" ./internal/query/ ./internal/api/ +go fmt ./... && go vet ./... && git add -A +git commit -m "feat(api): relationship timeline with local-day chat bursts" +``` + +--- + +### Task 7: Identity link/unlink endpoints with synchronous refresh + +**Files:** +- Create: `internal/api/identity_links.go` (+ `identity_links_test.go`) +- Modify: `internal/api/routes.go`; `cmd/msgvault/cmd/serve.go` (adapter wiring) + +**Interfaces (produces):** +```go +// internal/api capability interfaces (implemented by storeAPIAdapter in serve.go): +type IdentityLinkStore interface { + LinkParticipants(a, b int64) (int64, error) + UnlinkParticipants(a, b int64) (int64, error) +} +type IdentityCacheRefresher interface { + RefreshIdentityDatasets(ctx context.Context) (int64, error) // wraps cacheops with the daemon's analyticsDir +} + +type IdentityLinkRequest struct { + ParticipantA int64 `json:"participant_a"` + ParticipantB int64 `json:"participant_b"` +} +type IdentityLinkResponse struct { + IdentityRevision int64 `json:"identity_revision"` + CacheState string `json:"cache_state" enum:"ready,stale"` +} +``` +- HTTP: `POST /api/v1/identity/links` (link) and `POST /api/v1/identity/unlinks` (unlink — Huma DELETE-with-body is awkward; a verb-named POST matches the existing `/explore/preflight` style). Semantics per spec: idempotent; mutation commits first; refresh attempted synchronously; `200 {identity_revision, cache_state: ready|stale}`; `ErrAlreadyLinked` → `409 already_linked` with a message explaining the participants are already connected through other links; invalid IDs → 400. + +- [ ] **Step 1: API tests first** + +Through `newTestServerWithMockStore`-style setup with a real `testutil.NewTestStore` behind the capability interfaces (follow `conversations_test.go`'s real-store pattern): link two seeded participants → 200 with revision 1 and `cache_state: "ready"` (stub refresher returns success); repeat the same link → 200 same revision (idempotent); link two indirectly-connected → 409 `already_linked`; refresher fails → still 200, `cache_state: "stale"`; unlink → 200 revision bumped; unlink again → 200 unchanged. + +- [ ] **Step 2: Implement handlers + wiring** + +Handlers: validate IDs, call the store method, on `ErrAlreadyLinked` → `writeError(w, http.StatusConflict, "already_linked", ...)`; then call the refresher with the request context — on error, log and set `cache_state: "stale"` (never fail the response; the mutation is durable). Wire `storeAPIAdapter` pass-throughs in serve.go plus a small refresher struct closing over the analytics dir (`cacheops.RefreshIdentityDatasets(ctx, a.store, analyticsDir)`); register the interface assertions alongside the existing `var _ api.X = (*storeAPIAdapter)(nil)` block. + +- [ ] **Step 3: OpenAPI regen; tests; fmt/vet; commit** + +```bash +make openapi && (cd web && bun run generate) +go test -tags "fts5 sqlite_vec" ./internal/api/ +go fmt ./... && go vet ./... && git add -A +git commit -m "feat(api): identity link endpoints with synchronous cache refresh" +``` + +--- + +### Task 8: Conversation endpoint UTC time-window bounds + +**Files:** +- Modify: `internal/api/conversations.go` (+ `conversations_test.go`) +- Modify: `internal/store/conversations.go` (`GetConversationWindowContext`) + +**Interfaces (produces):** +- `GET /api/v1/conversations/{id}?anchor=N&before=B&after=A&start=RFC3339&end=RFC3339` — optional `start`/`end` (UTC, half-open `[start, end)`). When present, the window and the before/after counts are computed only over messages inside the range; `HasBefore`/`HasAfter` are relative to the range. Invalid timestamps or `start >= end` → 400. The bounded-counts contract (default 25, max 50) is unchanged within the range. +- Store: `GetConversationWindowContext` gains optional `start, end *time.Time` parameters (extend the existing signature or add a variant per the file's conventions — check call sites first with `rg -n "GetConversationWindowContext" internal/`). + +- [ ] **Step 1: Failing tests** + +Extend `conversations_test.go` using `seedConversation`: seed 6 messages across two days; request with `start`/`end` covering day 2 and an anchor inside it → only day-2 messages, `HasBefore=false` even though earlier messages exist outside the range; anchor outside the range → 400 with a named error; malformed `start` → 400. + +- [ ] **Step 2: Implement** + +Store: thread the bounds into the window CTE — the `ROW_NUMBER()` ordering CTE gains `AND COALESCE(sent_at, received_at, internal_date) >= ? AND ... < ?` when bounds are set (dialect `Rebind`, conditional SQL fragments matching the file's `LiveMessagesWhere` templating style). API: parse RFC3339 `start`/`end` query params (reject non-UTC-parseable values with the existing `newParamError` helper), validate ordering, pass through. + +- [ ] **Step 3: OpenAPI regen (route description/params changed); tests; fmt/vet; commit** + +```bash +make openapi && (cd web && bun run generate) +go test -tags "fts5 sqlite_vec" ./internal/api/ ./internal/store/ +go fmt ./... && go vet ./... && git add -A +git commit -m "feat(api): optional UTC time-window bounds on conversation windows" +``` + +--- + +### Task 9: Final verification + +- [ ] **Step 1: Full suites + lint** + +```bash +make test +make lint-ci +cd web && bun run check && bun run test # generated schema.d.ts must not break the web build +``` + +If `MSGVAULT_TEST_DB` is configured in the environment, also run `make test-pg` (participant_links + revision live in both schemas). + +- [ ] **Step 2: End-to-end smoke on a synthetic archive** + +Build a fake vault (`./msgvault fake-vault -o --messages 2000`), add a confirmed identity for its source (`sqlite3` insert or the CLI if one exists — check `./msgvault --help` for an identities command), `MSGVAULT_HOME= ./msgvault build-cache`, `serve`, then curl: +- `POST /api/v1/relationships` → ranked rows, gate visibly excluding no-reply senders. +- `POST /api/v1/identity/links` on two participant IDs → 200 ready; re-run relationships → combined cluster. +- `POST /api/v1/relationships/{id}/timeline` with a timezone → burst rows. +- `GET /api/v1/conversations/{id}?...&start=&end=` → bounded window. +Stop the daemon (`msgvault daemon stop`) when done. + +- [ ] **Step 3: Commit any stragglers** + +```bash +git status && git add -A && git commit -m "chore: relationships backend follow-ups" +``` +(Skip if clean.) diff --git a/docs/superpowers/plans/2026-07-20-shell-stabilization.md b/docs/superpowers/plans/2026-07-20-shell-stabilization.md new file mode 100644 index 000000000..0b2ab3c08 --- /dev/null +++ b/docs/superpowers/plans/2026-07-20-shell-stabilization.md @@ -0,0 +1,744 @@ +# Shell Stabilization Implementation Plan (Plan 1 of 3) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Fix the user-visible quality and performance defects in the existing web UI — dark-theme washout, raw `###` markup in excerpts, per-keystroke DuckDB queries, unbounded walk-to-end, fixed-interval polling, and reactive-dependency hacks — per the approved spec `docs/superpowers/specs/2026-07-20-web-ui-relationships-design.md`. + +**Architecture:** Backend excerpt flattening in the explore row path (Go, `internal/query`), then frontend changes in the Svelte 5 SPA under `web/src`: the inspector becomes pinned-only (removing the modal `DetailDrawer` scrim that collapses dark-mode contrast), search typing debounces before hitting state, walk-to-end is capped, coverage polling backs off, and `JSON.stringify` dependency hacks become `$derived` fingerprints. + +**Tech Stack:** Go + testify + DuckDB test fixtures (`internal/query`); Svelte 5 runes + vitest + @testing-library/svelte (`web/`); bun as the package runner. + +**Scope note:** This is Plan 1 of 3 for the spec. Plan 2 (relationships backend) and Plan 3 (three-pane Relationships workspace) follow. Full AppShell component decomposition is deliberately deferred to Plan 3, where the new workspace forces the restructuring; this plan only removes the reactive-dependency hacks that make AppShell's effects fire redundantly. + +## Global Constraints + +- Go tests use testify only: `require.X` halts, `assert.X` continues; argument order is `(want, got)`. Never introduce `t.Errorf`/`t.Fatalf`. +- After Go changes: `go fmt ./...` and `go vet ./...`; stage all resulting changes. +- Web commands run from `web/`: `bun run test` (vitest), `bun run check` (svelte-check). Both must pass before each commit. +- Never use real PII in fixtures — synthetic names/addresses only. +- Commit after every task; pre-commit hooks must pass without `--no-verify`. +- Run `make lint-ci` before finishing (CI has checks not in the pre-commit hook). + +--- + +### Task 1: Flatten snippet markup in explore excerpts (Go) + +Meeting importers (Granola/Circleback) persist raw body prefixes as snippets — `internal/granola/format.go:132`, `internal/circleback/format.go:194` — so `### Heading` markdown surfaces verbatim in Everything rows. Flatten at query time so existing stored snippets are covered without migration. + +**Files:** +- Create: `internal/query/snippet.go` +- Create: `internal/query/snippet_test.go` +- Modify: `internal/query/explore.go` (scan loop, ~line 81-90) +- Modify: `internal/api/explore.go` (semantic excerpt fill, ~line 1091-1100) + +**Interfaces:** +- Produces: `query.FlattenSnippet(snippet string) string` — pure function, exported so `internal/api` can reuse it for `match.strongest_excerpt`. + +- [ ] **Step 1: Write the failing test** + +Create `internal/query/snippet_test.go`: + +```go +package query + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFlattenSnippet(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + {"empty", "", ""}, + {"plain text unchanged", "Quarterly planning notes", "Quarterly planning notes"}, + {"leading heading", "### Architecture\nMonorepo vs. two repos", "Architecture Monorepo vs. two repos"}, + {"mid-string heading after join", "sync When: 09:00 ### Architecture: Monorepo", "sync When: 09:00 Architecture: Monorepo"}, + {"issue reference preserved", "raulcd opened a new pull request, #50362", "raulcd opened a new pull request, #50362"}, + {"hashtag preserved", "tagged #launch in the notes", "tagged #launch in the notes"}, + {"bullet list", "- keep Core separate\n- Chinese wall", "keep Core separate Chinese wall"}, + {"numbered list", "1. kickoff talk\n2) judging", "kickoff talk judging"}, + {"bold and code stripped", "**Consensus:** keep `Kenn Core` separate", "Consensus: keep Kenn Core separate"}, + {"whitespace collapsed", "line one\n\n\tline two", "line one line two"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, FlattenSnippet(tt.in)) + }) + } +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `go test -tags "fts5 sqlite_vec" ./internal/query/ -run TestFlattenSnippet -v` +Expected: FAIL — `undefined: FlattenSnippet` + +- [ ] **Step 3: Write the implementation** + +Create `internal/query/snippet.go`: + +```go +package query + +import ( + "regexp" + "strings" +) + +var ( + // Heading markers require trailing whitespace so issue refs (#50362) + // and hashtags (#launch) survive. + snippetHeading = regexp.MustCompile(`(^|\s)#{1,6}\s+`) + snippetListMarker = regexp.MustCompile(`(?m)^\s{0,3}(?:[-*+]|\d{1,3}[.)])\s+`) + snippetEmphasis = regexp.MustCompile("(?:\\*\\*|__|`)") + snippetWhitespace = regexp.MustCompile(`\s+`) +) + +// FlattenSnippet renders stored snippet markup (markdown headings, list +// markers, bold/code emphasis) as plain single-line text. Meeting importers +// persist raw body prefixes as snippets, so structural markdown otherwise +// surfaces verbatim in explore excerpts. +func FlattenSnippet(snippet string) string { + if snippet == "" { + return snippet + } + flattened := snippetHeading.ReplaceAllString(snippet, "$1") + flattened = snippetListMarker.ReplaceAllString(flattened, "") + flattened = snippetEmphasis.ReplaceAllString(flattened, "") + flattened = snippetWhitespace.ReplaceAllString(flattened, " ") + return strings.TrimSpace(flattened) +} +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `go test -tags "fts5 sqlite_vec" ./internal/query/ -run TestFlattenSnippet -v` +Expected: PASS (all subtests) + +- [ ] **Step 5: Apply at the explore scan site** + +In `internal/query/explore.go`, the row scan loop ends around line 88 (`rows.Scan(... &row.Title, &row.Preview, ...)`). Immediately after the successful `Scan` (before the row is appended), add: + +```go +row.Title = FlattenSnippet(row.Title) +row.Preview = FlattenSnippet(row.Preview) +``` + +- [ ] **Step 6: Apply at the semantic-excerpt site** + +In `internal/api/explore.go` (~line 1095), the semantic excerpt map is built from message summaries: + +```go +byID[summary.ID] = summary.Snippet +``` + +Change to: + +```go +byID[summary.ID] = query.FlattenSnippet(summary.Snippet) +``` + +(`internal/api` already imports `go.kenn.io/msgvault/internal/query`.) + +- [ ] **Step 7: Run the affected packages' tests** + +Run: `go test -tags "fts5 sqlite_vec" ./internal/query/ ./internal/api/` +Expected: PASS. If any existing test asserts a raw markup snippet in a preview, update its expectation to the flattened form — the flattening is the new intended behavior. + +- [ ] **Step 8: Format, vet, commit** + +```bash +go fmt ./... && go vet ./... +git add -A +git commit -m "fix(query): flatten snippet markup in explore excerpts" +``` + +--- + +### Task 2: Make the inspector pinned-only + +The unpinned inspector renders as kit-ui `DetailDrawer` with a full-viewport 62% black scrim (`--overlay-bg`, `theme-dark.css:39`) — this is the dark-theme washout. The pinned side panel becomes the only mode; the dead `relationships`/`summary` extension-slot scaffolding goes too. + +**Files:** +- Modify: `web/src/lib/explore/state.svelte.ts` (default line 88, normalize line 255) +- Modify: `web/src/lib/components/inspector/Inspector.svelte` (props ~39-77, extension slots 300-303, render branch 307-325, CSS 441-443) +- Modify: `web/src/lib/components/shell/AppShell.svelte` (lines 1544, 1651-1696) +- Modify: `web/src/lib/explore/state.test.ts`, `web/src/lib/components/inspector/Inspector.test.ts`, `web/src/lib/components/shell/AppShell.test.ts` (whatever references pinning/drawer) + +**Interfaces:** +- Consumes: nothing from other tasks. +- Produces: `Inspector` no longer accepts `pinned`/`onPinnedChange` props; `ExploreURLState.inspectorPinned` remains in the URL envelope but always normalizes to `true`. + +- [ ] **Step 1: Write the failing normalization test** + +In `web/src/lib/explore/state.test.ts`, add alongside the existing round-trip tests: + +```ts +it('normalizes legacy unpinned inspector states to pinned', () => { + const parsed = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + inspectorPinned: false + })); + expect(parsed.inspectorPinned).toBe(true); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd web && bun run test -- src/lib/explore/state.test.ts` +Expected: FAIL — `inspectorPinned` parses back as `false`. + +- [ ] **Step 3: Force pinned in state** + +In `web/src/lib/explore/state.svelte.ts`: + +- Line 88 (`defaultExploreURLState`): `inspectorPinned: false,` → `inspectorPinned: true,` +- Line 255 (`normalize`): `inspectorPinned: value.inspectorPinned === true,` → `inspectorPinned: true,` + +Keep the field in the envelope so old URLs and saved views (which persist inspector preference) parse cleanly; they simply normalize to pinned. + +If the existing "round-trips every durable field" test asserts `inspectorPinned: false` round-trips, update it to expect `true` — the normalization is now intentional. + +- [ ] **Step 4: Run test to verify it passes** + +Run: `cd web && bun run test -- src/lib/explore/state.test.ts` +Expected: PASS. + +- [ ] **Step 5: Remove the DetailDrawer branch from Inspector.svelte** + +- Import (line 26): drop `DetailDrawer` → `import { Button, SplitResizeHandle, type SplitResizeEvent } from '@kenn-io/kit-ui';` +- Props: remove `pinned` and `onPinnedChange` from the destructuring (lines 47, 50) and the type (lines 66, 69). +- Locate any header pin/unpin control with `rg -n "onPinnedChange" web/src/lib/components/inspector/Inspector.svelte` and remove it. +- Replace the render branch (lines 307-325) with the unconditional pinned markup: + +```svelte +
+ + +
+``` + +- Delete the dead extension-slot scaffolding: the `
` block (lines 300-303) and its `.extension-slots { display: none; }` rule (lines 441-443). + +- [ ] **Step 6: Remove the unpinned render path from AppShell.svelte** + +- Delete the whole unpinned block, lines 1675-1696 (`{#if inspectorTargetKey && !exploreState.current.inspectorPinned}` … `{/if}`). +- Line 1651: `{#if inspectorTargetKey && exploreState.current.inspectorPinned}` → `{#if inspectorTargetKey}`. +- Line 1544: `class:results-and-inspector--pinned={Boolean(inspectorTargetKey && exploreState.current.inspectorPinned)}` → `class:results-and-inspector--pinned={Boolean(inspectorTargetKey)}`. +- In the remaining `` instance, remove the `pinned` and `onPinnedChange={…}` props. +- Sweep for stragglers: `rg -n "inspectorPinned|onPinnedChange" web/src/lib/components/shell/AppShell.svelte` — any remaining `commitNavigation({ inspectorPinned })` call sites go with the removed prop. + +- [ ] **Step 7: Fix affected tests and type-check** + +Run: `cd web && bun run check && bun run test` +Expected: svelte-check flags any leftover `pinned` prop usage — fix all. Inspector/AppShell tests that exercised drawer mode or the pin toggle should be updated to assert the pinned panel renders (and that no `.kit-detail-drawer-overlay` exists after selecting a row). Delete tests that only covered the removed drawer behavior. + +- [ ] **Step 8: Verify no scrim remains, then commit** + +Run: `rg -n "DetailDrawer" web/src` — expected: no matches. + +```bash +git add -A +git commit -m "fix(web): make the inspector a pinned side panel only" +``` + +--- + +### Task 3: Guard theme-token parity between light and dark + +`theme-light.css:1` uses `:root, :root[data-theme='light']` — light is the unconditional base layer, so any token added to light but forgotten in dark silently renders its light value in dark mode. Both files currently define the same 38 tokens; lock that in with a test. + +**Files:** +- Create: `web/src/lib/a11y/theme-parity.test.ts` + +**Interfaces:** +- Consumes/Produces: nothing — standalone guard test (mirrors the file-reading pattern of `web/src/lib/a11y/contrast.test.ts`). + +- [ ] **Step 1: Write the test** + +```ts +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; + +const root = process.cwd(); + +function definedTokenNames(cssPath: string): string[] { + const css = readFileSync(join(root, cssPath), 'utf8'); + return [...new Set([...css.matchAll(/--[\w-]+(?=\s*:)/g)].map((match) => match[0]))].sort(); +} + +describe('theme token parity', () => { + it('defines the identical custom-property set in light and dark themes', () => { + expect(definedTokenNames('src/styles/theme-dark.css')) + .toEqual(definedTokenNames('src/styles/theme-light.css')); + }); + + it('overrides color-scheme for dark mode', () => { + const tokens = readFileSync(join(root, 'src/styles/tokens.css'), 'utf8'); + expect(tokens).toMatch(/:root\[data-theme='dark'\]\s*\{[^}]*color-scheme:\s*dark/); + }); +}); +``` + +- [ ] **Step 2: Run and verify it passes today** + +Run: `cd web && bun run test -- src/lib/a11y/theme-parity.test.ts` +Expected: PASS (token sets are currently identical). + +- [ ] **Step 3: Verify the test catches a leak** + +Temporarily add `--parity-canary: red;` to `theme-light.css`, re-run, confirm FAIL, then revert the canary. + +- [ ] **Step 4: Commit** + +```bash +git add web/src/lib/a11y/theme-parity.test.ts +git commit -m "test(web): guard light/dark theme token parity" +``` + +--- + +### Task 4: Debounce identity and filename search typing + +`SearchInput` `oninput` fires per keystroke into `exploreState.replaceCommittedDraft(...)`, which refires the workspace search effects — a `limit: 500` DuckDB query per character (People/Domains identity search, Files filename filter). Debounce the state write 250 ms; Everything's submit-only search is untouched. + +**Files:** +- Create: `web/src/lib/util/debounce.ts` +- Create: `web/src/lib/util/debounce.test.ts` +- Modify: `web/src/lib/components/shell/AppShell.svelte` (handler wirings at lines 1360, 1365, 1381, 1385, 1456-1458; `onDestroy`) + +**Interfaces:** +- Produces: `debounce(fn: (...args: T) => void, delayMs: number): Debounced` where `Debounced` is callable and has `.cancel()`. Task 5+ and Plan 3 may reuse it. + +- [ ] **Step 1: Write the failing utility test** + +Create `web/src/lib/util/debounce.test.ts`: + +```ts +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { debounce } from './debounce'; + +describe('debounce', () => { + beforeEach(() => vi.useFakeTimers()); + afterEach(() => vi.useRealTimers()); + + it('fires once with the latest arguments after the delay', () => { + const fn = vi.fn(); + const debounced = debounce(fn, 250); + debounced('a'); + debounced('ab'); + debounced('abc'); + expect(fn).not.toHaveBeenCalled(); + vi.advanceTimersByTime(250); + expect(fn).toHaveBeenCalledExactlyOnceWith('abc'); + }); + + it('restarts the delay on each call', () => { + const fn = vi.fn(); + const debounced = debounce(fn, 250); + debounced('a'); + vi.advanceTimersByTime(200); + debounced('ab'); + vi.advanceTimersByTime(200); + expect(fn).not.toHaveBeenCalled(); + vi.advanceTimersByTime(50); + expect(fn).toHaveBeenCalledExactlyOnceWith('ab'); + }); + + it('cancel discards the pending call', () => { + const fn = vi.fn(); + const debounced = debounce(fn, 250); + debounced('a'); + debounced.cancel(); + vi.advanceTimersByTime(1000); + expect(fn).not.toHaveBeenCalled(); + }); +}); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `cd web && bun run test -- src/lib/util/debounce.test.ts` +Expected: FAIL — module not found. + +- [ ] **Step 3: Implement the utility** + +Create `web/src/lib/util/debounce.ts`: + +```ts +export interface Debounced { + (...args: T): void; + cancel(): void; +} + +export function debounce( + fn: (...args: T) => void, + delayMs: number +): Debounced { + let timer: ReturnType | undefined; + const debounced = (...args: T): void => { + if (timer !== undefined) clearTimeout(timer); + timer = setTimeout(() => { + timer = undefined; + fn(...args); + }, delayMs); + }; + debounced.cancel = (): void => { + if (timer !== undefined) clearTimeout(timer); + timer = undefined; + }; + return debounced; +} +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `cd web && bun run test -- src/lib/util/debounce.test.ts` +Expected: PASS. + +- [ ] **Step 5: Wire it into AppShell** + +Add near the other module-scope constants in `AppShell.svelte` (after line ~95): + +```svelte +import { debounce } from '../../util/debounce'; + +const SEARCH_TYPING_DEBOUNCE_MS = 250; +const debouncedSearchPatch = debounce( + (patch: Partial) => exploreState.replaceCommittedDraft(patch), + SEARCH_TYPING_DEBOUNCE_MS +); +``` + +Replace the five per-keystroke handlers: + +```svelte + +onIdentityQueryChange={(identityQuery) => debouncedSearchPatch({ identityQuery, analysisTarget: null, selectedIdentifier: null })} + +onFileFilenameQueryChange={(fileFilenameQuery) => debouncedSearchPatch({ fileFilenameQuery })} + +onIdentityQueryChange={(identityQuery) => debouncedSearchPatch({ identityQuery, analysisTarget: null })} + +onFileFilenameQueryChange={(fileFilenameQuery) => debouncedSearchPatch({ fileFilenameQuery })} + +onFilenameQueryChange={(fileFilenameQuery) => debouncedSearchPatch({ + fileFilenameQuery, activeRow: null, selectedRow: null, scrollAnchor: null +})} +``` + +In the existing `onDestroy` callback (locate with `rg -n "onDestroy" web/src/lib/components/shell/AppShell.svelte`), add `debouncedSearchPatch.cancel();`. + +Note the accepted tradeoff: the `SearchInput` `value` prop lags typed text by ≤250 ms. The input's DOM value already holds the typed text, so no visible clobbering occurs unless an unrelated re-render lands mid-typing — acceptable, and this whole surface is rebuilt in Plan 3. + +- [ ] **Step 6: Write the shell-level debounce test** + +In `web/src/lib/components/shell/AppShell.test.ts` (follow the file's existing render/client-stub helpers and the fake-timer precedent of the "debounces rapid visible conversation churn" test at ~line 516): + +```ts +it('debounces identity search typing into one committed state write', async () => { + vi.useFakeTimers(); + try { + const state = new ExploreState(); + state.commitWorkspace('people'); + // render AppShell with the file's standard client stub and `state` + const input = screen.getByLabelText('Search people'); + await fireEvent.input(input, { target: { value: 'al' } }); + await fireEvent.input(input, { target: { value: 'alice' } }); + expect(state.current.identityQuery).toBe(''); + vi.advanceTimersByTime(250); + expect(state.current.identityQuery).toBe('alice'); + } finally { + vi.useRealTimers(); + } +}); +``` + +- [ ] **Step 7: Run the full web suite and type-check** + +Run: `cd web && bun run check && bun run test` +Expected: PASS. + +- [ ] **Step 8: Commit** + +```bash +git add -A +git commit -m "perf(web): debounce identity and filename search typing" +``` + +--- + +### Task 5: Cap walk-to-end pagination + +`End` triggers `loadThroughEnd`, which loops 500-row pages until the cursor is exhausted — on an unfiltered 2.5M-row archive that is thousands of sequential round-trips. Cap each invocation at 20 pages; the cursor is preserved, so pressing `End` again continues from where it paused. + +**Files:** +- Create: `web/src/lib/explore/paging.ts` +- Modify: `web/src/lib/components/shell/AppShell.svelte:834-839` (`loadThroughEnd`) +- Modify: `web/src/lib/components/people/PeopleWorkspace.svelte:226-233` (`loadTimelineThroughEnd`) +- Modify: `web/src/lib/components/people/DomainWorkspace.svelte:215-222` (`loadThroughEnd`) + +**Interfaces:** +- Produces: `LOAD_THROUGH_END_MAX_PAGES = 20` exported from `web/src/lib/explore/paging.ts`. + +- [ ] **Step 1: Create the shared constant** + +Create `web/src/lib/explore/paging.ts`: + +```ts +// Each walk-to-end invocation loads at most this many pages so `End` on a +// multi-million-row view cannot spiral into thousands of sequential +// round-trips. The cursor is preserved: pressing End again continues. +export const LOAD_THROUGH_END_MAX_PAGES = 20; +``` + +- [ ] **Step 2: Cap AppShell's loadThroughEnd** + +Replace lines 834-839 of `AppShell.svelte`: + +```svelte +async function loadThroughEnd(): Promise { + let pages = 0; + while (nextCursor && !requestController?.signal.aborted) { + if (pages >= LOAD_THROUGH_END_MAX_PAGES) { + sortNotice = 'End paused loading to keep the table responsive; press End again to continue or refine the filters.'; + return; + } + const outcome = await loadMore(); + if (outcome.status !== 'advanced') break; + pages += 1; + } +} +``` + +Add `import { LOAD_THROUGH_END_MAX_PAGES } from '../../explore/paging';` to the imports block. (`sortNotice` feeds the `role="status"` live region at line 1540, so the pause is announced to screen readers.) + +- [ ] **Step 3: Cap the People timeline walk** + +Replace `loadTimelineThroughEnd` in `PeopleWorkspace.svelte` (lines 226-233): + +```svelte +async function loadTimelineThroughEnd(): Promise { + let pages = 0; + while (timelineNextCursor && !detailController?.signal.aborted) { + if (pages >= LOAD_THROUGH_END_MAX_PAGES) return; + const before = timelineNextCursor; + await loadMoreTimeline(); + if (timelineNextCursor === before) break; + pages += 1; + } +} +``` + +Add the same import. Apply the identical transformation to `DomainWorkspace.svelte`'s `loadThroughEnd` (line 215; same shape, `timelineNextCursor`/`loadMoreTimeline` names may differ slightly — mirror whatever that function's loop uses). + +- [ ] **Step 4: Write the cap test** + +In `web/src/lib/components/shell/AppShell.test.ts`, using the file's existing client stub: make the explore endpoint always return a fresh, never-repeating `nextCursor` and one new row per page, render, trigger `End` (the table's `onLoadThroughEnd`), and assert the explore endpoint was called exactly `1 + LOAD_THROUGH_END_MAX_PAGES` times (initial load + 20 pages) rather than unbounded: + +```ts +it('caps End at LOAD_THROUGH_END_MAX_PAGES pages per press', async () => { + // client stub: POST /api/v1/explore returns { rows: [row(n)], nextCursor: `cursor-${n}` } with n incrementing + // render AppShell, wait for initial load + // fire keydown End on the grid + // await settle + expect(explorePostCount).toBe(1 + LOAD_THROUGH_END_MAX_PAGES); +}); +``` + +- [ ] **Step 5: Run the web suite and type-check** + +Run: `cd web && bun run check && bun run test` +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add -A +git commit -m "perf(web): cap walk-to-end pagination at 20 pages per press" +``` + +--- + +### Task 6: Back off the coverage poll + +The semantic-coverage effect re-polls every fixed 1 s while coverage is `initializing` (`AppShell.svelte:441-483`). Add exponential backoff: 1 s, 2 s, 4 s, 8 s, capped at 10 s; reset when the coverage context (workspace/mode/filters) changes or coverage leaves `initializing`. + +**Files:** +- Modify: `web/src/lib/components/shell/AppShell.svelte:441-483` + +**Interfaces:** +- Consumes: nothing new (Task 7 later converts this effect's `JSON.stringify(filters)` dependency; keep that line intact here to avoid conflicts). + +- [ ] **Step 1: Add attempt tracking and backoff** + +Near the other coverage state (line ~143), add plain (non-`$state`) variables: + +```svelte +let coveragePollAttempts = 0; +let coveragePollKey = ''; +``` + +Inside the effect, after the dependency reads (lines 442-446), add: + +```svelte +const pollKey = `${workspace}|${mode}|${JSON.stringify(filters)}`; +if (pollKey !== coveragePollKey) { + coveragePollKey = pollKey; + coveragePollAttempts = 0; +} +``` + +Replace the fixed re-poll (lines 463-468): + +```svelte +if (loaded.status === 'initializing') { + const delay = Math.min(10_000, 1_000 * 2 ** coveragePollAttempts); + coveragePollAttempts += 1; + coveragePollTimer = setTimeout(() => { + coveragePollTimer = undefined; + if (generation === coverageRequestGeneration) coverageRetryRevision += 1; + }, delay); +} else { + coveragePollAttempts = 0; +} +``` + +- [ ] **Step 2: Write the backoff test** + +In `AppShell.test.ts` with fake timers and a client stub whose coverage endpoint always returns `status: 'initializing'`: assert the second poll fires only after 1 s, the third after a further 2 s, the fourth after a further 4 s (count coverage POSTs at each `vi.advanceTimersByTime` checkpoint). Follow the existing coverage-test setup in that file. + +- [ ] **Step 3: Run the web suite** + +Run: `cd web && bun run check && bun run test` +Expected: PASS. + +- [ ] **Step 4: Commit** + +```bash +git add -A +git commit -m "perf(web): exponential backoff for semantic coverage polling" +``` + +--- + +### Task 7: Replace JSON.stringify dependency hacks with $derived fingerprints + +Ten effects use a bare `JSON.stringify(x);` statement to force deep reactive tracking. Each re-serializes on every run and refires the effect even when the serialized value is unchanged. Hoisting to a `$derived` string means dependents only re-run when the fingerprint value actually changes. + +**Files:** +- Modify: `web/src/lib/components/shell/AppShell.svelte` (~lines 221, 376, 446, 603) +- Modify: `web/src/lib/components/people/PeopleWorkspace.svelte` (~lines 80, 111) +- Modify: `web/src/lib/components/people/DomainWorkspace.svelte` (~lines 73, 104) +- Any further sites found by the sweep in Step 1. + +**Interfaces:** +- Consumes: Task 6's `pollKey` (coordinate: the coverage effect keeps computing `pollKey` from the untracked value; only the *dependency* line changes). + +- [ ] **Step 1: Enumerate the hack sites** + +Run: `rg -n "JSON.stringify" web/src/lib/components web/src/lib/explore` +A "hack site" is a bare `JSON.stringify(x);` expression statement (value discarded) inside an `$effect`. Legitimate value uses (serialization for URLs, fingerprint keys that are then *used*) stay. + +- [ ] **Step 2: Convert each site — the pattern** + +For the coverage effect in `AppShell.svelte` (lines 444-446), hoist a derived fingerprint above the effect: + +```svelte +const coverageFiltersFingerprint = $derived(JSON.stringify(exploreState.current.filters)); +``` + +and inside the effect replace: + +```svelte +const filters = exploreState.current.filters; +void coverageRetryRevision; +JSON.stringify(filters); +``` + +with: + +```svelte +void coverageRetryRevision; +void coverageFiltersFingerprint; +const filters = untrack(() => exploreState.current.filters); +``` + +(`untrack` is already imported at line 12.) The effect now depends on the *string* — identical filter content no longer refires it. + +For `PeopleWorkspace.svelte`'s search effect (line 80 `JSON.stringify(context);`): + +```svelte +const searchContextFingerprint = $derived(JSON.stringify(contextPredicate(predicate))); +``` + +and in the effect: + +```svelte +void searchContextFingerprint; +const context = untrack(() => contextPredicate(predicate)); +``` + +(import `untrack` from `'svelte'` if the file doesn't already). Apply the same mechanical transformation to every site from Step 1: one `$derived` fingerprint per distinct tracked object, `void ;` as the dependency, `untrack(() => …)` for the value read. + +- [ ] **Step 3: Verify no bare-stringify statements remain** + +Run: `rg -n "^\s*JSON.stringify\(.*\);\s*$" web/src/lib` +Expected: no matches. + +- [ ] **Step 4: Run the full web suite** + +Run: `cd web && bun run check && bun run test` +Expected: PASS — behavior is unchanged; only refire granularity improves. Pay attention to any test that asserted effect refire counts. + +- [ ] **Step 5: Commit** + +```bash +git add -A +git commit -m "perf(web): replace stringify dependency hacks with derived fingerprints" +``` + +--- + +### Task 8: Final verification pass + +**Files:** none new. + +- [ ] **Step 1: Full test suites** + +```bash +make test +cd web && bun run check && bun run test +``` + +Expected: all pass. + +- [ ] **Step 2: Lint (CI parity)** + +```bash +make lint-ci +``` + +Expected: clean (CI's testify-helper-check is not in the pre-commit hook). + +- [ ] **Step 3: Visual smoke test** + +Build and serve (`make build && ./msgvault serve`), open the printed URL in dark mode, and confirm: selecting an Everything row opens a side panel with no full-screen scrim and no contrast collapse; meeting rows no longer show `###` in excerpts; typing in People search feels smooth. Capture a screenshot for comparison against `~/Desktop/Screenshot 2026-07-20 at 11.43.01.png`. + +- [ ] **Step 4: Commit any stragglers** + +```bash +git status +git add -A && git commit -m "chore: shell stabilization follow-ups" +``` + +(Skip if clean.) diff --git a/docs/superpowers/plans/2026-07-21-relationships-workspace.md b/docs/superpowers/plans/2026-07-21-relationships-workspace.md new file mode 100644 index 000000000..b52c73034 --- /dev/null +++ b/docs/superpowers/plans/2026-07-21-relationships-workspace.md @@ -0,0 +1,374 @@ +# Relationships Workspace Implementation Plan (Plan 3 of 3) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build the three-pane Relationships workspace as the web UI's landing view (per `docs/superpowers/specs/2026-07-20-web-ui-relationships-design.md`), merge People/Domains into it as facets, add the identity-link UI, decompose AppShell, fix the accumulated carry-over bugs, and land the kit-ui/theme visual pass. + +**Architecture:** A new `RelationshipsWorkspace` hub (left ranked list / center person header + unified timeline / right reading pane) driven by a dedicated state controller over the Plan 2 endpoints (`listRelationships`, `getRelationshipTimeline`, `linkIdentityParticipants`, `unlinkIdentityParticipants`) plus the existing people/domains search + detail endpoints. URL state gains `workspace: 'relationships'` with a facet field; old `people`/`domains` states normalize to it. AppShell sheds the Everything data-loading into an extracted controller module. + +**Tech Stack:** Svelte 5 runes, @kenn-io/kit-ui, openapi-fetch (generated `schema.d.ts` already has all four new operations — no codegen needed), vitest + @testing-library/svelte (colocated), Playwright. + +## Global Constraints + +- All web work under `web/`; run with `bun`. Quality gates per task: `bun run check` (svelte-check, 0 errors), `bun run test` (vitest), `bun run lint` if present. +- Component tests use the established pattern: real `createAPIClient(fetchFn)` with a `vi.fn` fetch routed on `pathname` returning `Response.json(...)` bodies (see `PeopleWorkspace.test.ts:1-30`). No MSW, no client mocking. +- Svelte 5 runes only (`$state`/`$derived`/`$effect`/`untrack`); no stores for new code. Prop-drilling is the established pattern (no context) — follow it. +- Spec bindings: three-pane layout regions exactly as the spec diagram; chat bursts render as single "N messages in " rows; **nothing in the workspace is modal; no scrim, ever**; `j`/`k` act on the focused pane; Esc walks back one layer; below the three-pane breakpoint the reading pane stacks over the timeline (Esc returns); below the two-pane breakpoint the list becomes a slide-in drawer; virtualization via kit-ui `virtualSlice`; cursor pagination, **no unbounded walk-to-end**; old `workspace=people|domains` URLs normalize to Relationships facets; Relationships is the default landing, falling back to Everything when the analytical engine is unavailable; degraded states are named, never silent. +- Identity-link contract: `200 {identity_revision, cache_state: "ready"|"stale"}`; `stale` → named `identity_cache_stale` UI state with rebuild guidance; `409 already_linked` message surfaced; retry safe. +- Never name private downstream projects; no real PII in fixtures (synthetic names only). +- Commit after every task; hooks must pass; never `--no-verify`; imperative subject ≤72 chars. Any Go change: `go fmt ./...`, `go vet ./...`, tests with `-tags "fts5 sqlite_vec"`. + +--- + +### Task 1: Carry-over bug fixes (paging wedge, sortNotice, FileViewer fallback, dead saved-view field) + +**Files:** +- Modify: `web/src/lib/components/people/PeopleWorkspace.svelte:228-234` +- Modify: `web/src/lib/components/people/DomainWorkspace.svelte:217-223` +- Modify: `web/src/lib/components/shell/AppShell.svelte` (sortNotice lifecycle, ~909/1504 region and the End-paging notice) +- Modify: `web/src/lib/components/files/FileViewer.svelte:150,260,265,280` +- Modify: `web/src/lib/components/saved-views/SavedViewsWorkspace.svelte:68,176` +- Tests: colocated `.test.ts` files for each + +**Interfaces:** no new interfaces — behavior fixes only. These land first so the bugs don't survive into code Task 4 copies from. + +- [ ] **Step 1: Fix the loadMore wedge (TDD)** + +Failing test first (PeopleWorkspace.test.ts): render with a fetch mock whose detail/timeline responses are delayed so `identityAuthority` is still `undefined`; click load-more; then let detail resolve; click load-more again and assert the second click issues a timeline request (today it never does — the flag wedges). Then fix both copies by resetting the flag on the early return: + +```ts +timelineLoadingMore = true; +if (!identityAuthority) { + timelineLoadingMore = false; + return; +} +``` + +(Identical two-line fix in `PeopleWorkspace.svelte` `loadMoreTimeline` and `DomainWorkspace.svelte` `loadMore`.) + +- [ ] **Step 2: sortNotice lifecycle fixes (TDD)** + +Two behaviors, tests in `AppShell.test.ts`: +1. The Files workspace must not display a stale Everything sortNotice: clear `sortNotice` (set to its neutral/default value) whenever `exploreState.current.workspace` changes — add it to the existing workspace-change handling (find where workspace commits happen in AppShell; the `commitWorkspace` wrapper at ~AppShell.svelte:108-146 is the chokepoint). +2. The "End paused loading…" pause message must reset to neutral when a later End press exhausts the cursor (walk reaches the true end): in `loadThroughEnd`, when the walk completes because `nextCursor` is gone (not because the page cap hit), reset the notice. + +- [ ] **Step 3: FileViewer filename fallback (TDD)** + +`FileViewer.svelte` applies a filename fallback only to the modal heading; the download action and labels render blank when the authoritative filename is missing. Extract one `displayFilename` derived (fallback chain: authoritative filename → `file.filename` → `attachment-`) and use it in all four sites (~150, 260, 265, 280). Test: file fact with empty filename renders the fallback in heading, download label, and download attribute. + +- [ ] **Step 4: Remove the dead saved-view inspector_pinned round-trip** + +`SavedViewsWorkspace.svelte:68` stops writing `inspector_pinned` into the create payload; `:176` stops reading it back (drop the field from the patch — `normalize()` force-overrides it anyway). Keep the server-side OpenAPI field untouched (compat: old saved views still deserialize; the client just ignores the field). Update any fixtures in `SavedViewsWorkspace`/`AppShell` tests that assert on it. + +- [ ] **Step 5: Verify and commit** + +```bash +cd web && bun run check && bun run test +git add -A && git commit -m "fix(web): paging wedge, sortNotice lifecycle, file viewer fallback" +``` + +--- + +### Task 2: URL state — `relationships` workspace, facet, normalization, landing default + +**Files:** +- Modify: `web/src/lib/explore/models.ts` (ExploreURLState fields) +- Modify: `web/src/lib/explore/state.svelte.ts` (defaults, `normalize()`, workspace whitelist ~212-217) +- Test: `web/src/lib/explore/state.svelte.test.ts` (or the file's existing test sibling — check `ls web/src/lib/explore/*.test.ts`) + +**Interfaces (produces):** +```ts +// ExploreURLState gains: +relationshipFacet: 'people' | 'domains'; // default 'people' +relationshipTarget: string | null; // 'cluster:' | 'domain:' | null +relationshipShowAll: boolean; // default false — lifts the reciprocity gate +relationshipFiles: boolean; // default false — center-pane Files N toggle +// workspace union gains 'relationships'; DEFAULT workspace becomes 'relationships' +``` +Consumed by Tasks 3-6. `analysisTarget`/`selectedIdentifier` stay (Everything's person-timeline path still uses them) — the hub uses the new fields only. + +- [ ] **Step 1: Failing tests** + +In the explore state test file: +```ts +it('normalizes legacy people/domains workspaces to relationships facets', () => { + // workspace 'people' + analysisTarget 'person:12' → workspace 'relationships', + // relationshipFacet 'people', relationshipTarget null (person IDs are participant + // IDs, which map 1:1 onto cluster member IDs — carry it: relationshipTarget 'cluster:12') + // workspace 'domains' + analysisTarget 'domain:example.com' → facet 'domains', + // relationshipTarget 'domain:example.com' +}); +it('defaults workspace to relationships', () => { /* parse '{}' → workspace 'relationships' */ }); +it('round-trips relationship fields through serialize/parse', () => { ... }); +it('rejects invalid facet/target shapes', () => { /* facet 'x' → 'people'; target 'garbage' → null */ }); +``` + +- [ ] **Step 2: Implement** + +In `normalize()` (the single validation chokepoint): +- Add the three field validations (facet whitelist, target regex `^cluster:\d+$` or `^domain:[^\s]+$`, booleans). +- Legacy mapping BEFORE workspace whitelisting: `people` → `{workspace:'relationships', relationshipFacet:'people', relationshipTarget: analysisTarget?.startsWith('person:') ? 'cluster:'+id : null}`; `domains` analogous with `domain:` targets. +- Change the fallback/default workspace from `'everything'` to `'relationships'` (both `defaultExploreURLState` and the whitelist's else-branch). +- `RESTORATION_INVALIDATING_FIELDS`: add `relationshipFacet`, `relationshipTarget` (target changes invalidate row restoration), NOT the toggles. + +- [ ] **Step 3: Fix the fallout** + +`bun run test` will surface every test that assumed `'everything'` is the default workspace (AppShell.test.ts fixtures etc.). Update fixtures to request `workspace: 'everything'` explicitly where the test is about Everything. Do not weaken assertions. + +- [ ] **Step 4: Verify and commit** + +```bash +cd web && bun run check && bun run test +git add -A && git commit -m "feat(web): relationships URL state with legacy people/domains normalization" +``` + +--- + +### Task 3: Relationships data controller + +**Files:** +- Create: `web/src/lib/relationships/controller.svelte.ts` +- Create: `web/src/lib/relationships/controller.svelte.test.ts` +- Create: `web/src/lib/relationships/models.ts` + +**Interfaces (produces):** +```ts +// models.ts — re-export/narrow the generated types: +import type { components } from '../api/generated/schema'; +export type RelationshipRow = components['schemas']['RelationshipRow']; +export type RelationshipSignals = components['schemas']['RelationshipSignals']; +export type RelationshipTimelineRow = components['schemas']['TimelineRow']; +export type RelationshipFacet = 'people' | 'domains'; + +// controller.svelte.ts +export class RelationshipsController { + constructor(client: APIClient, timezone: () => string) {} // timezone: () => Intl.DateTimeFormat().resolvedOptions().timeZone + // Left list state + facet: RelationshipFacet; query: string; showAll: boolean; + listRows: RelationshipRow[] | PersonSummary[] | DomainSummary[]; // ranked | searched + listLoading: boolean; listError: string | null; + degraded: 'cache_unavailable' | null; // 503 from listRelationships → named degraded state + // Center state + target: string | null; // 'cluster:' | 'domain:' + detail: PersonSummary | DomainSummary | null; + timelineRows: RelationshipTimelineRow[]; timelineCursor: string | null; + timelineLoading: boolean; timelineLoadingMore: boolean; timelineError: string | null; + canonicalID: number | null; identityRevision: number | null; + // Actions + loadList(predicate: ExplorePredicate): Promise; + openTarget(target: string, predicate: ExplorePredicate): Promise; // detail + first timeline page + loadMoreTimeline(): Promise; // cursor pagination; 409 cursor_invalidated → restart from page 1 + linkParticipants(a: number, b: number): Promise; + unlinkParticipants(a: number, b: number): Promise; +} +export type LinkOutcome = + | { ok: true; identityRevision: number; cacheState: 'ready' | 'stale' } + | { ok: false; code: 'already_linked' | 'invalid' | 'error'; message: string }; +``` + +Behavior contracts (each is a test): +- Ranked mode: empty `query` on the people facet → `POST /api/v1/relationships {show_all, limit: 200, filters}`; rows are `RelationshipRow`s ordered as returned (server sorts). +- Search mode: non-empty `query` → the EXISTING `POST /api/v1/people/search` (or `/domains/search`) with `identity_query` — "search finds any person, ranked or not". +- Domains facet always uses `POST /api/v1/domains/search` (activity-ranked, no gate — spec). +- 503 with the cache-unavailable body → `degraded = 'cache_unavailable'`, no throw. +- `openTarget('cluster:12', …)` → `GET /api/v1/people/12` for the header detail AND `POST /api/v1/relationships/12/timeline {timezone, filters, limit}`; stores `canonical_id` + `identity_revision` from the response. `openTarget('domain:x.com', …)` → existing domain detail + domain timeline endpoints (domains have no cluster timeline). +- `loadMoreTimeline`: passes `cursor`; on 409 (`cursor_invalidated`) clears rows + cursor and reloads page 1 (assert exactly one restart request); the wedge-bug shape from Task 1 must not be reproducible (flag reset on EVERY early return — write the test). +- `linkParticipants`: maps 200 → ok with cacheState; 409 body `already_linked` → `{ok:false, code:'already_linked'}`; 400 → `'invalid'`; else `'error'`. After `ok` with a changed `identityRevision`, the controller re-runs `openTarget` (cluster membership changed). +- Generation guard: a stale `openTarget` response arriving after a newer call is discarded (follow the `detailGeneration` counter pattern from `PeopleWorkspace.svelte`). +- AbortController per load; `destroy()` aborts all. + +- [ ] **Step 1: Write the failing controller tests** (fetch-mock pattern, one `it` per contract above) +- [ ] **Step 2: Implement the controller** (port the proven mechanics — generation counters, seenCursors cycle guard, `analyticalAuthority` — from `PeopleWorkspace.svelte`, as functions of this class rather than component-locals) +- [ ] **Step 3: Verify and commit** + +```bash +cd web && bun run check && bun run test +git add -A && git commit -m "feat(web): relationships data controller" +``` + +--- + +### Task 4: RelationshipsWorkspace — three-pane hub + +**Files:** +- Create: `web/src/lib/components/relationships/RelationshipsWorkspace.svelte` +- Create: `web/src/lib/components/relationships/RelationshipList.svelte` (left pane) +- Create: `web/src/lib/components/relationships/RelationshipTimeline.svelte` (center timeline) +- Create: `web/src/lib/components/relationships/RelationshipHeader.svelte` (center header) +- Create: colocated `.test.ts` for each +- Reuse (do not fork): `Inspector.svelte` (right pane, `InspectorSelection {kind:'entry'}`), `FilesWorkspace.svelte` (Files N toggle, via its existing `identityScope`/`expectedAuthority` props), `ContentFrame` (via Inspector), kit-ui `virtualSlice`, `SearchInput`, `Button`, `SegmentedControl`. + +**Interfaces:** +- Consumes: `RelationshipsController` (Task 3), URL fields (Task 2), `APIClient`. +- Produces (props contract AppShell wires in Task 6): +```ts +interface Props { + client: APIClient; + controller: RelationshipsController; + facet: RelationshipFacet; target: string | null; showAll: boolean; filesOpen: boolean; + predicate: ExplorePredicate; + onFacetChange(f: RelationshipFacet): void; // → commitNavigation patch + onTargetChange(t: string | null): void; + onShowAllChange(v: boolean): void; + onFilesToggle(v: boolean): void; +} +``` + +Layout (binding, from the spec diagram): left `aside` (relationship search, facet toggle People|Domains, ranked list, "show all senders" control) / center `section` (header: display name, identity chips, counts, `Link identity` and `Files N` actions; below: unified timeline with month headers OR the files table when `filesOpen`) / right reading pane rendering the selected timeline row via `Inspector` with `{kind:'entry'}`. **No modal, no scrim.** Row kinds: `email`/`event`/`meeting` rows render one item; `chat_burst` rows render "N messages in " and on selection open the conversation window bounded to the burst's local day (the conversation endpoint's `start`/`end` params — compute the UTC bounds of the burst's local day from `first_at`/`occurred_at`). + +Keyboard (binding): `j`/`k` move within the focused pane (list or timeline — two `role="grid"`/listbox regions with their own focus); Enter opens (list → target; timeline → reading pane); Esc walks back one layer (reading pane → timeline → list → nothing), consistent with `handleEscape` in AppShell. Reuse the app's shortcut registration only through props/events — the hub handles its own keys locally via `onkeydown` on its panes (matching how `EverythingTable` does it — read it first). + +Responsive (binding): CSS container/media queries — below ~1100px the reading pane stacks over the timeline (Esc returns to timeline); below ~720px the left list becomes a slide-in drawer (still not modal — a `position: absolute` panel with focus trap via existing patterns, no kit-ui Modal/DetailDrawer, no scrim; a translucent-free backdrop is NOT added). + +Degraded states (binding): `controller.degraded === 'cache_unavailable'` → named full-pane state: "Relationship ranking needs the analytical cache/engine" with the standard rebuild guidance and an "Open Everything" button (calls the workspace-change callback). Timeline 409-restart shows a one-line notice ("Timeline restarted: the archive changed"). `identity_cache_stale` handled in Task 5. + +Month headers: group timeline rows by `occurred_at` month (user's local timezone — same `timezone()` the controller sends); virtualized list must account for header rows (follow `PersonTimeline.svelte`'s existing virtualization with `virtualSlice` — read it and reuse its row-height approach). + +- [ ] **Step 1: RelationshipList TDD** — render ranked rows (label, modality badges from `signals.modalities`, last-interaction date, score-proportional activity indicator), facet toggle fires `onFacetChange`, show-all checkbox fires `onShowAllChange`, typing in search switches to search results, `j`/`k`/Enter keyboard, degraded state renders the named message. +- [ ] **Step 2: RelationshipHeader TDD** — display name, identity chips (from the person detail's identifier evidence — same data `PeopleWorkspace` renders as identifier chips today; read its markup), item counts, `Files N` toggle button, `Link identity` button (opens Task 5's dialog; in this task it renders disabled with a title). +- [ ] **Step 3: RelationshipTimeline TDD** — month headers, one row per item, `chat_burst` renders "N messages in ", selection ring, `j`/`k`, Enter fires row-open, load-more via cursor with the capped walk (no unbounded End), 409 restart notice. +- [ ] **Step 4: RelationshipsWorkspace composition TDD** — three panes render; selecting a list row calls `onTargetChange` + controller.openTarget; selecting a timeline row shows the Inspector reading pane; `chat_burst` selection requests the conversation window with the day's UTC `start`/`end`; `filesOpen` swaps center to `FilesWorkspace`; Esc layering; responsive classes applied at the breakpoints (assert class switching with a resized container, not real media queries, if jsdom can't do them — use the container-query class hook pattern and test the class logic). +- [ ] **Step 5: Verify and commit** + +```bash +cd web && bun run check && bun run test +git add -A && git commit -m "feat(web): three-pane relationships workspace" +``` + +--- + +### Task 5: Identity-link UI + +**Files:** +- Create: `web/src/lib/components/relationships/LinkIdentityDialog.svelte` (+ test) +- Modify: `web/src/lib/components/relationships/RelationshipHeader.svelte` (chips get unlink affordance; Link identity opens the dialog; `identity_cache_stale` banner) + +**Interfaces:** +- Consumes: `controller.linkParticipants`/`unlinkParticipants` (Task 3), `POST /api/v1/people/search` for the participant search. +- Dialog contract: search input → people search (`identity_query`), results list rows show display name + identifiers + participant IDs; confirming calls `linkParticipants(currentClusterMemberID, chosenParticipantID)`. This IS a true modal flow (an action dialog, not an analytical surface) — kit-ui `Modal` is allowed here per the spec's "`DetailDrawer` remains for true modal flows only"; use `Modal` like `SavedViewsWorkspace` does. +- Outcomes (binding): `ok/ready` → toast-free silent success; header refreshes (controller re-opens target — Task 3 behavior). `ok/stale` → persistent named `identity_cache_stale` banner on the header: "Identity saved; the cache refresh failed — groupings may be stale until a rebuild. Retrying is safe." with a Retry button (re-invokes the same link call — idempotent per the API contract). `already_linked` → inline dialog error: "These identities are already linked through another identifier." `invalid`/`error` → inline message. +- Chips: each identity chip annotated by source; chips representing linked cluster members (member_ids beyond the primary) get an unlink `×` affordance with an inline confirm (not a browser confirm) → `unlinkParticipants`; same outcome handling. + +- [ ] **Step 1: Dialog TDD** — search issues people-search calls (debounced 250ms via `web/src/lib/util/debounce.ts`), Enter/click selects, confirm fires link with the right IDs, each outcome renders its named state, Esc closes. +- [ ] **Step 2: Header integration TDD** — Link identity opens dialog; stale banner persists across dialog close and clears after a later `ready` outcome; unlink flow (confirm → call → refresh). +- [ ] **Step 3: Verify and commit** + +```bash +cd web && bun run check && bun run test +git add -A && git commit -m "feat(web): identity link and unlink UI" +``` + +--- + +### Task 6: AppShell integration — tabs, rendering, landing fallback, People/Domains removal + +**Files:** +- Modify: `web/src/lib/components/shell/AppShell.svelte` (tabs ~153-162, workspace chain ~1420-1480, PeopleWorkspace/DomainWorkspace wiring removal) +- Delete: `web/src/lib/components/people/PeopleWorkspace.svelte` + test, `web/src/lib/components/people/DomainWorkspace.svelte` + test (keep `PersonTimeline.svelte` if the Everything person-timeline path still renders it — check `rg -n "PersonTimeline" web/src` first; Everything's timeline presentation still uses it, so KEEP it) +- Modify: `web/src/lib/components/shell/AppShell.test.ts` (fixtures/assertions) + +**Interfaces:** +- Tabs become (binding order): `relationships, everything, files, saved_views, sources, deletions, settings` — labels "Relationships · Everything · Files · Saved Views · Sources · Deletions · Settings". `people`/`domains` tabs removed. +- Workspace chain renders `<RelationshipsWorkspace>` for `'relationships'`, wiring the Task 4 props to `exploreState` patches: facet/target/showAll/filesOpen ↔ the Task 2 URL fields, via the existing flush-before-commit navigation wrappers. +- The controller is constructed once in AppShell (like `api`), destroyed in `onDestroy`. +- Landing fallback (binding): when the archive-state the shell already loads reports the analytical engine unavailable (read how the archive-state indicator gets its data — find it with `rg -n "archive-state\|archiveState" web/src/lib/components/shell`) AND the current workspace is `'relationships'` without an explicit user navigation (i.e. the state came from defaults, not a URL that named relationships — detect: the parsed URL had no `explore` param), `replaceTransient({workspace:'everything'})`. When the URL explicitly says relationships, keep it and let the hub show its degraded state. +- `analysisTarget`-based person/domain drill-ins from Everything (e.g. "Open person" affordances): retarget them to commit `{workspace:'relationships', relationshipFacet, relationshipTarget}` — find all call sites with `rg -n "analysisTarget" web/src/lib/components` and update each; Everything's own inline person-timeline presentation stays unchanged. +- Inspector gains an "Open relationship" action for entry selections (spec): a button in the Inspector header that commits to the relationships workspace targeting the entry's primary counterpart participant (the Inspector already knows the entry's participants — read `Inspector.svelte`'s entry model first; if the participant ID isn't available on the entry row, wire it through from `EntryRow` — check `rg -n "participant" web/src/lib/explore/models.ts`). + +- [ ] **Step 1: Failing AppShell tests** — tab list content/order; `'relationships'` renders the hub; legacy `workspace=people` URL lands in the hub with the facet set; explicit-URL degraded case keeps relationships; default-landing fallback switches to everything when archive-state says unavailable. +- [ ] **Step 2: Implement + delete the two workspaces** (and their prop-wiring blocks in AppShell; run `rg -n "PeopleWorkspace\|DomainWorkspace" web/src` to catch stragglers). +- [ ] **Step 3: Full web suite; fix fallout; commit** + +```bash +cd web && bun run check && bun run test +git add -A && git commit -m "feat(web): relationships hub as landing workspace, retire people/domains" +``` + +--- + +### Task 7: AppShell decomposition + +**Files:** +- Create: `web/src/lib/components/shell/EverythingWorkspace.svelte` (the current inline Everything `<main>` block: search bar, coverage, context bar, results+inspector split, footer) +- Create: `web/src/lib/explore/loader.svelte.ts` (the extracted Everything data controller) +- Modify: `web/src/lib/components/shell/AppShell.svelte` (shrinks to: shell chrome, tabs, workspace switch, palette/help/viewer, shortcut registration) +- Tests: move/extend colocated tests accordingly (`EverythingWorkspace.test.ts`; loader tests in `web/src/lib/explore/loader.svelte.test.ts`) + +**Interfaces (produces):** +```ts +// loader.svelte.ts — owns what AppShell.svelte:670-981 does today: +export class ExploreLoader { + constructor(client: APIClient, state: ExploreState) {} + rows: EntryRow[]; groupRows: GroupRow[]; fileFacts: FileFact[]; + result: ExploreResult | null; loading: boolean; error: string | null; + nextCursor: string | null; pagingNotice: string | null; + loadMore(): Promise<void>; loadThroughEnd(): Promise<void>; // capped, Task 1 notice semantics + destroy(): void; +} +``` + +Binding refactor rules (from the spec): +- Replace every `JSON.stringify(...)`-as-reactive-dependency in the moved code with explicit `$derived` fingerprint values read via `untrack` in effects (the pattern already used for `coverageFiltersFingerprint`) — after the move, `rg -n "JSON.stringify" web/src/lib/components/shell/AppShell.svelte web/src/lib/components/shell/EverythingWorkspace.svelte web/src/lib/explore/loader.svelte.ts` must show ZERO occurrences used as effect dependencies (serialization for request bodies is fine). +- Collapse the overlapping per-predicate effects: the main load effect, the inspector group-detail load, and the lexical count loader must be driven off ONE fingerprint change each — one state change triggers one coordinated load (no double-fetch; write a test that counts fetches for a single filter commit). +- This is a MOVE, not a rewrite: behavior-preserving. The existing `AppShell.test.ts` assertions keep passing (split the test file along the component split; do not delete assertions — relocate them). +- AppShell.svelte target: under 700 lines after the split. If a piece doesn't fit cleanly (e.g. the Esc-layering spans workspaces), leave it in AppShell and note it in the report rather than forcing an awkward seam. + +- [ ] **Step 1: Extract `ExploreLoader`** (move the load/paging/restoration blocks; keep function bodies byte-identical where possible), colocated tests for: one commit → one fetch; paging cap notice; restoration walk. +- [ ] **Step 2: Extract `EverythingWorkspace.svelte`** (markup + its handlers), splitting `AppShell.test.ts`. +- [ ] **Step 3: Fingerprint sweep** (the `rg` above → zero), double-fetch regression test. +- [ ] **Step 4: Full suite; commit** + +```bash +cd web && bun run check && bun run test +git add -A && git commit -m "refactor(web): extract everything workspace and explore loader from AppShell" +``` + +--- + +### Task 8: Visual pass — theme base layer, type scale, table chrome + +**Files:** +- Modify: `web/src/styles/theme-light.css`, `web/src/styles/theme-dark.css` +- Modify: `web/src/lib/a11y/theme-parity.test.ts` +- Modify: table/grid styles in `EverythingTable.svelte`, `GroupTable.svelte`, `RelationshipTimeline.svelte`, `RelationshipList.svelte`, `FilesPresentation.svelte` (chrome only) +- Possibly modify: `web/src/styles/*.css` global type scale + +**Binding requirements (spec):** +1. Fix the fragile base-layer pattern: `theme-light.css` currently declares tokens on `:root, :root[data-theme='light']`, so any token missing from `theme-dark.css` silently leaks a light value into dark mode. Restructure: a `:root` block holds ONLY theme-neutral tokens (spacing/type/radii if any); ALL colour tokens live in `:root[data-theme='light']` and `:root[data-theme='dark']` blocks, with `:root:not([data-theme])` defaulting to the light block via a shared selector (`:root:not([data-theme='dark'])`, keeping no-attribute = light). Strengthen `theme-parity.test.ts`: besides name-set equality, assert NO colour token is declared in an unscoped `:root` block. +2. One type scale: audit `font-size` declarations across `web/src/lib/components` (`rg -n "font-size" web/src/lib/components | sort`), collapse to a token scale (`--text-xs/-sm/-base/-lg/-xl`) defined in the neutral block; components reference tokens. Keep visual sizes ~as-is (this is consolidation, not redesign). +3. Quieter table chrome: row borders → `--border-muted`; header backgrounds → `--bg-surface` (not raised); hover → `--bg-surface-hover`; selection keeps `--selected-bg`/`--selected-border`. Density comes from the existing density preference — don't touch it. +4. No modal scrims over analytical surfaces: `rg -n "overlay-bg\|scrim" web/src` — every remaining consumer must be a true modal flow (CommandPalette, KeyboardHelp, dialogs, FileViewer); list them in the commit message body. + +- [ ] **Step 1: Theme restructure + parity test (TDD: extend the test first, watch it fail on the current files)** +- [ ] **Step 2: Type-scale tokens + component sweep** +- [ ] **Step 3: Table chrome + scrim audit** +- [ ] **Step 4: Visual smoke** — build + serve a fake-vault daemon, screenshot light/dark of Relationships + Everything + open reading pane (Playwright script per the webapp-testing pattern), verify no full-viewport translucent overlay exists with the reading pane open (reuse the scrim-detection check from the earlier smoke script). Keep the screenshots in the scratchpad for the final report. +- [ ] **Step 5: Full suite; commit** + +```bash +cd web && bun run check && bun run test +git add -A && git commit -m "style(web): theme base-layer fix, type scale, quieter table chrome" +``` + +--- + +### Task 9: Playwright, full verification, final review + +- [ ] **Step 1: Playwright updates** + +- Rewrite `web/tests/people-domains.spec.ts` → `web/tests/relationships.spec.ts`: legacy `workspace=people` URL lands on the relationships hub; ranked list renders; opening a person shows the timeline; a chat-burst row opens the reading pane; facet toggle to Domains; browser back returns through the states. +- Extend `web/tests/e2e/accessibility.spec.ts` + `keyboard.spec.ts` to cover the hub (axe pass on the three-pane layout; `j`/`k`/Enter/Esc walk). +- Run whatever Playwright projects the repo's config wires to a fixture server (check `web/playwright.config.ts` for how existing specs get data — follow it). + +- [ ] **Step 2: Full verification** + +```bash +cd web && bun run check && bun run test && bun run build +cd .. && make test && make lint-ci +``` + +Plus the fake-vault UI smoke: build, `msgvault serve` on the synthetic vault, headless Playwright — hub renders ranked list, link identity flow (link two participants → combined cluster in the list), timeline bursts, reading pane, no scrim, dark theme clean. Stop the daemon after. + +- [ ] **Step 3: Final whole-branch review** (superpowers final review flow), fix wave if needed, ledger, commit. + +- [ ] **Step 4: roborev-fix** for the reviews this plan's commits generate. diff --git a/docs/superpowers/specs/2026-07-20-web-ui-relationships-design.md b/docs/superpowers/specs/2026-07-20-web-ui-relationships-design.md new file mode 100644 index 000000000..459f14682 --- /dev/null +++ b/docs/superpowers/specs/2026-07-20-web-ui-relationships-design.md @@ -0,0 +1,420 @@ +# Web UI: Relationships-Centric Redesign + +Date: 2026-07-20 +Status: Approved design, pending implementation plan + +## Problem + +The current web UI is a first cut with quality, performance, and product +problems: + +- The product foregrounds the haystack: the landing view is an "Everything" + table of 2.5M rows, dominated by mailing lists, bots, and notifications. + There is no way to see the important relationships in one's life, or the + full story of one relationship across email, chat, and meetings. +- The dark theme visibly breaks when the inspector opens: the unpinned + inspector renders as a kit-ui `DetailDrawer` with a full-viewport 62% black + modal scrim (`--overlay-bg`, theme-dark.css), collapsing contrast behind it. +- Meeting-transcript excerpts show raw `###` markdown in table rows. +- Performance: slow initial load, slow search/filter, sluggish table + interaction (root causes identified below). +- `web/src/lib/components/shell/AppShell.svelte` is a 1,907-line god + component with ~10 overlapping `$effect`s; `PeopleWorkspace.svelte` and + `DomainWorkspace.svelte` duplicate ~300 lines. + +## Goals + +1. Reshape the UI around relationships: a ranked view of the most important + people, and for each person a unified timeline of email, chat, and + meetings plus associated files. +2. Visual clarity on par with Fastmail/Gmail: crisp, clean, professional, + office-application feel. Buttery-smooth interaction. Improvements land at + the token/typography level in kit-ui where appropriate (kit-ui work is + in-bounds). +3. Fix the identified performance and quality defects in the same pass + (redesign-led sequencing: don't polish screens the redesign replaces). + +Non-goals for this cycle: relationship-score configuration UI, automatic +identity-merge suggestions, org inference from domains, any change to +deletion execution semantics. + +## Information architecture + +Navigation becomes: + +**Relationships** (home) · Everything · Files · Saved Views · Sources · +Deletions · Settings + +- The People and Domains workspaces merge into Relationships as two facets + (People | Domains toggle) of one shared hub component, eliminating the + duplicated workspace pair. +- The Domains facet keeps its existing activity/latest/label ranking; the + relationship score, reciprocity gate, and "show all senders" control are + people-facet only this cycle. Domains remain exact facts, not inferred + organizations. +- Relationships is the default landing view. Everything remains one click + away and keeps its slice-and-dice role (filters, grouping, selection, + deletion staging). +- Existing `workspace=people` and `workspace=domains` URL states are + normalized at parse time to the corresponding Relationships facet, so + old bookmarks and browser history keep working. + +## Relationships workspace: three-pane mail layout + +Fastmail's interaction shape — no page navigation; all three panes update in +place: + +``` +┌──────────────┬──────────────────────────────┬─────────────────────┐ +│ RELATIONSHIPS│ Person header: name, │ READING PANE │ +│ ⌕ search │ identity chips, sparkline, │ selected item │ +│ ranked list │ [Link identity] [Files N] │ rendered in place │ +│ of people ├──────────────────────────────┤ │ +│ (or domains) │ Unified timeline: month │ email → ContentFrame│ +│ │ headers; email / chat-burst /│ chat → message run │ +│ [show all] │ meeting rows, virtualized │ meeting → formatted │ +└──────────────┴──────────────────────────────┴─────────────────────┘ +``` + +**Left pane — ranked people list.** Display name, modality/channel badges, +last-interaction date, small activity indicator. Search finds any person, +ranked or not. Newsletters/bots are excluded by the reciprocity gate; an +explicit "show all senders" control lifts the gate. + +**Center pane — person header + unified timeline.** + +- Header: display name, identity chips from `participant_identifiers`, + activity sparkline over the relationship lifetime, item counts, `Link + identity` action, `Files N` toggle. +- Timeline: one row per logical item under month headers. Emails and meeting + notes are individual rows. Chat rolls up into bursts: consecutive messages + in one conversation on one day render as a single "N messages in + <conversation>" row. Kind is differentiated by icon plus a restrained color + accent. Virtualized (kit-ui `virtualSlice`), `j`/`k` navigation, cursor + pagination; no unbounded walk-to-end. +- `Files N` switches the center pane to a files table scoped to the person + (chronological; same reading-pane preview on select). Timeline and files + are two views of the same relationship, one keystroke apart. + +**Right pane — reading pane.** Selecting a timeline row renders the item in +place: emails through the existing sanitized `ContentFrame`, chat bursts as a +message run, meeting notes with transcript markdown rendered properly. +Attachments listed at the bottom, opening in the existing viewers. Nothing in +the workspace is modal; no scrim, ever. + +**Context carry.** Arriving from Everything with an active filter shows the +filter as a removable chip scoping both timeline and files (the same +context-scoping semantics docs/web-ui.md already defines for People). + +**Entry from Everything.** Person/domain rows and the inspector gain an +"Open relationship" affordance that opens the hub with that person selected +and the current filter carried along. + +**Narrow screens.** Panes collapse right-to-left: below the three-pane +breakpoint the reading pane stacks over the timeline (Esc returns); +below the two-pane breakpoint the people list becomes a slide-in drawer. +No new keyboard chords; `j`/`k` act on the pane that has focus and Esc +walks back one layer, consistent with the existing shell semantics. + +## Relationship ranking (backend) + +New endpoint `POST /api/v1/relationships`, computed server-side over the +DuckDB/Parquet analytical cache. + +**Data contract.** Two cache additions, both covered by a cache schema +version bump and a one-time full rebuild: + +- The messages Parquet dataset gains an `is_from_me` column, but it cannot + simply re-export `messages.is_from_me` unchanged. That column is reliable + for native chat/calendar importers, which set it from platform direction + flags (iMessage `is_from_me`, Beeper `IsSender`, calendar organizer). It + is *not* reliable for email: the Gmail/IMAP sync path (`internal/sync`) + never sets it — rows keep the default `false` — and the MIME importer + (`internal/importer/ingest.go`) sets it only by comparing the From + address against a single source identifier string, not against every + confirmed `account_identities` row for that source. Exporting the column + as-is would make `sent_to_them` zero on a typical Gmail/IMAP archive, + and the reciprocity gate would then hide legitimate relationships. The + export instead derives `is_from_me` at cache-build time as + `messages.is_from_me OR (sender's address matches, case-insensitively, a + confirmed account_identities row for that message's source)` — the `OR` + means correctly-set chat/calendar rows are unaffected. This derivation + runs entirely at export time against data already in the archive, so it + works for existing archives without any re-sync or re-import. Regression + tests cover Gmail and IMAP fixture archives where `messages.is_from_me` + is false but the sender is a confirmed account identity, asserting the + exported column is `true`. +- A small `owner_participants` Parquet dataset maps each source to its + owner participant IDs, derived from `account_identities` joined to + `participant_identifiers` at export time. This is what makes + `meetings_together` computable: for calendar events `is_from_me` only + marks events the owner organized, while attendance is stored in + participant junctions — an event organized by someone else with the + owner attending is still "together." The dataset is regenerated on every + cache build and additionally whenever the identity revision (below) + changes, under the same refresh contract as the cluster mapping. + +No other owner-identity injection into DuckDB at query time, and no +reply-graph signal in this cycle (`reply_to_message_id` is not exported; +"replies exchanged" is dropped as a distinct signal in favor of the +sent/received counts below). + +**Per-person inputs** (person = canonical identity cluster, see below): + +- `sent_to_them` — count of messages with `is_from_me` where the person is + among the recipients or direct-conversation participants. +- `received_from_them` — count of messages authored by the person. +- `meetings_together` — calendar/meeting items whose participants include + both an owner participant (via `owner_participants`) and the person. + +**Gate.** `sent_to_them >= 1 OR meetings_together >= 1`; otherwise the +person is excluded from the default ranking (this filters newsletters, +bots, and lists). "Show all senders" lifts the gate. + +**Score.** Each input is summed with exponential time decay +(half-life 365 days), combined with per-signal weights — default +`sent_to_them × 2.0`, `meetings_together × 3.0`, +`received_from_them × 1.0` (initiated contact and shared meetings signal a +relationship more strongly than inbound volume) — then multiplied by a +modality-breadth boost of `1 + 0.25 × (modalities − 1)` for +email/chat/meetings presence. Ties break by most-recent interaction, then +display name. Weights and half-life live in one documented Go function +with table-driven tests; no configuration surface initially. + +**Engine policy.** Relationship ranking and the hub require the DuckDB +analytical engine (`query.PeopleAnalyzer` is implemented only there, by +design). Under PostgreSQL or the live-SQL fallback engine, the +Relationships workspace renders a named degraded state — consistent with +the cache-state philosophy of naming states rather than silently +substituting — and the default landing view falls back to Everything. +No PG/SQL ranking implementation this cycle. + +## Identity linking + +Builds on the existing identity tables (`participants`, +`participant_identifiers`). Collections group *sources*, not participants, +and are out of scope here. + +**Model: reversible canonical clusters.** UI-asserted links do not mutate +participants, identifiers, or message junction rows. A new table +`participant_links(participant_a, participant_b, created_at)` stores +user-asserted edges between participant rows; connected components resolve +to a canonical cluster at read time. All hub queries (ranking, timeline, +files) group by cluster. `Unlink` deletes the edge — fully reversible +because nothing else moved. + +**Graph invariants.** Edges are normalized `participant_a < +participant_b`; self-edges are rejected by constraint. The link graph is +kept a forest: a link request between two participants already in the +same cluster is rejected with a named `already_linked` (409) error, +*unless* it exactly re-submits an existing edge (same normalized pair), +which is an idempotent 200 no-op instead — see the response contract +below. `already_linked` is reserved for a genuinely new, redundant edge +between two distinct participants that are already indirectly connected +through the cluster. This split keeps the forest property intact: every +edge that is actually inserted joins two previously distinct components, +and every unlink deterministically splits one cluster in two. + +**Cluster identity.** The canonical cluster ID is the smallest participant +ID in the cluster. Relationship APIs accept any member participant ID and +resolve it to the canonical cluster, echoing the canonical ID in the +response; the UI rewrites its URL to the canonical ID. After a link, +unlink, or importer merge changes cluster membership, an open relationship +URL re-resolves on next load — it never 404s while the referenced +participant exists; it simply lands on that participant's current cluster. + +**Importer merges.** `MergeParticipants` (irreversible, evidence-based) +remains importer-only, but must become link-aware: in the same +transaction it rewrites `participant_links` endpoints from the absorbed +participant to the survivor, drops resulting self-edges, deduplicates +re-normalized edges, and bumps the identity revision. Endpoint rewriting +alone does not preserve the forest invariant: when the merged pair are +themselves the two ends of an existing multi-edge path, contraction turns +that path into a cycle. For example, links A–X, X–Y, Y–B form a path from +A to B; merging A and B (B absorbed into A) rewrites the Y–B edge to +Y–A, leaving A–X, X–Y, Y–A — a cycle, not a tree. After that, `Unlink` +can no longer deterministically split the cluster in two (removing any +one edge of the cycle still leaves the other two connecting every +member). So after endpoint rewriting and dedup, the merge recomputes a +deterministic spanning forest for every cluster touched by the merge: +rebuild that cluster's edges from scratch as a canonical spanning tree +over its current members, ordered by participant ID (e.g. a path linking +each member to the next-lowest-ID member already in the tree), discarding +whatever cyclic edge set the rewrite produced. Tests must cover an importer +merge involving user-linked participants connected via paths longer than +two edges, asserting the forest invariant holds afterward (edge count is +exactly member count minus one, no cycles) and that `Unlink` on any +surviving edge still splits the cluster. The UI never re-parents +identifiers via `SetParticipantIdentifier`, which would strand historical +message references on the old participant. + +- The person header shows all identifiers across the cluster as chips, + annotated by source participant. +- `Link identity` opens a search over participants/identifiers; confirming + inserts a link edge via a new API. `Unlink` removes it. +- Automatic merging stays conservative (explicit archive evidence only, per + docs/web-ui.md); this feature is purely user-asserted linking. + +**Cache consistency.** Message Parquet never changes on plain link/unlink +(message participant IDs are untouched by the cluster model). The +participant→canonical-cluster mapping is exported as its own small Parquet +dataset (alongside `owner_participants`), versioned by an identity +revision counter (`identity_revision` in `archive_metadata`). Link, unlink, +and a link-aware importer merge that touches no link edges all bump only +this counter, and the API attempts the cheap identity-dataset refresh +(`owner_participants` + the cluster mapping) synchronously, without +touching message Parquet. + +`owner_participants` is also derived from `account_identities`, and +`account_identities` is what backs the `is_from_me` flag *baked into every +message Parquet shard at export time* — not just the identity datasets. +Two mutation classes therefore reach further than a plain link/unlink: +confirming or removing an account identity (`AddAccountIdentity` / +`RemoveAccountIdentity`, exposed via the identity CLI/API) changes which +participants are owners for a source, and a participant merge +(`MergeParticipants` / the phone-dedupe merge path) repoints +`messages.sender_id`, which can leave a stale `is_from_me` on the merged +sender's messages. Both bump a second counter, `account_identity_revision`, +in the same transaction as `identity_revision`; the mutation's own +synchronous refresh is still the cheap identity-dataset one (it cannot +rewrite already-exported message shards). Cache staleness compares both +counters: `identity_revision` drift alone is satisfied by that cheap +refresh, but any drift in `account_identity_revision` forces the *next* +scheduled or on-demand cache build to be a full rebuild rather than a +skip or the lightweight identity-only path — that is the only path that +re-derives `is_from_me`. Without the second counter, an identity +addition/removal or a participant merge would leave `is_from_me` (and +everything downstream of it — `sent_to_them`, `meetings_together`) +silently stale until an unrelated full rebuild happened to refresh it. + +**Link/unlink response contract.** Link/unlink and account-identity +add/remove are all idempotent: +re-submitting the exact same edge that already exists (same normalized +`participant_a < participant_b` pair) and re-deleting an edge that is +already missing are both no-ops returning current state, not errors — this +is what makes retries after a `stale` cache response or a dropped +connection safe. A *new* edge between two participants already connected +through the cluster (not the same pair) still hits `already_linked` +per the graph invariants above. The archive mutation commits first, then the API +attempts the identity-dataset refresh synchronously. The response is +`200` whenever the mutation is durable, with body +`{identity_revision, cache_state: "ready" | "stale"}`. `stale` means the +refresh failed; the UI shows the named `identity_cache_stale` state +(with the standard rebuild guidance) rather than silently serving pre-link +groupings, and retrying is safe — repeating the same link/unlink call +re-attempts the refresh without re-mutating. + +## Relationship timeline contract + +The hub timeline is a new endpoint (`POST /api/v1/relationships/{id}/timeline`), +not a reuse of the person-timeline forwarding into Explore — Everything's +one-row-per-conversation-lifetime chat semantics are unchanged. + +- **Row identity**: emails and meeting notes one row per message, as + today. Chat rows are day bursts keyed + `(source_id, conversation_id, local_day)`; the row carries message + count, first/last message time, and the latest snippet. +- **Day bucketing** uses a client-supplied IANA timezone (default UTC). +- **Cursor**: ordered by `(occurred_at DESC, entry_key)` where a burst's + `occurred_at` is its latest message time. The opaque cursor binds the + canonical cluster ID, timezone, active filters, message-cache revision, + and identity revision (mirroring the request/revision binding of the + existing explore cursors). A mismatch on any of these — e.g. a + link/unlink between pages — returns a named `cursor_invalidated` + conflict and the client restarts pagination from the top. +- **Reading pane**: selecting a burst loads its messages through the + conversation endpoint extended with optional UTC `start`/`end` bounds, + paginated within that window (the current anchor-plus-counts form caps + at 50 messages each way and has no time constraint, so it can neither + complete a large burst nor exclude adjacent-day messages). + +## Shell architecture changes + +- **Inspector**: the pinned side panel becomes the only inspector mode in + Everything. The modal `DetailDrawer` + scrim usage is removed (fixes the + dark-theme washout). `DetailDrawer` remains for true modal flows only. + The dead `relationships`/`summary` extension-slot scaffolding in + `Inspector.svelte` is removed; the inspector gains "Open relationship". +- **AppShell decomposition**: split into per-workspace container components + plus a shared data-loading controller. Replace `JSON.stringify(...)` + reactive-dependency hacks (10 occurrences) with explicit `$derived` + fingerprints. Collapse the overlapping per-predicate `$effect`s so one + state change triggers one coordinated load. + +## Performance fixes + +| Symptom | Root cause | Fix | +|---|---|---| +| Slow search/filter | People/Domains/Files search issues a `limit: 500` DuckDB query per keystroke, each written as committed URL history | Debounce ~250 ms; keep typing in draft state; commit on settle/submit, matching Everything's submit-only semantics | +| Table interaction | `End` (`loadThroughEnd`) walks the entire result set in 500-row round-trips; overlapping effects fire main query + coverage + preflight + match-counts together | Cap walk-to-end or jump to a server-computed last page; coordinate loads in the shared controller | +| Initial load | AppShell effect fan-out on first render; coverage poll at a fixed 1s interval while initializing | Consolidated first load; backoff on the coverage poll | +| Excerpt noise | Granola/Circleback importers persist raw body prefixes (including `###` markdown) as stored snippets, and Explore emits the stored snippet directly | Flatten markup at query/render time in the explore row path, which covers all existing stored snippets without a migration or re-import; cleaning at import time is an optional follow-up | + +Already sound, keep as-is: Everything-table virtualization, cursor +pagination with cycle detection, dynamic pdfjs import, bundle splitting. + +## Visual system + +A Fastmail-grade crispness pass done at the design-token level so the whole +app inherits it; kit-ui changes are made upstream in kit-ui: + +- Full dark-token coverage: fix the fragile `:root, :root[data-theme='light']` + base-layer pattern in `theme-light.css` so a token added to light cannot + silently leak its light value into dark mode; same for `color-scheme`. +- One type scale, quieter table chrome (lighter borders/row separation), + consistent density between panes, professional office-application tone. +- No modal scrims over analytical surfaces. + +## Testing + +- Colocated vitest component tests (existing convention) for the hub panes, + chat-burst rollup, and identity-link flows. +- Playwright flows: three-pane navigation (list → timeline → reading pane), + files toggle, identity linking, context carry from Everything. +- Table-driven Go tests for the ranking function and the new + relationships/identity-link endpoints. +- Tests exercise real behavior through production paths per project policy. + +## Decisions log + +- Sequencing: redesign-led (fixes land with the redesign, not before). +- Ranking signals: reciprocity-weighted + recency-weighted volume + + modality breadth, all three combined; reciprocity acts as the default gate. +- Identity: build on the existing participants/identifiers tables; expose + user-asserted linking in the UI (collections group sources and are out + of scope). +- Landing view: Relationships home (falls back to Everything when the + analytical engine cannot serve people analysis). +- Layout: three-pane mail style (Fastmail shape). +- Visual bar: crisp, clean, professional; kit-ui improvements in-bounds. +- Identity model: reversible canonical clusters via user-asserted link + edges kept as a forest; canonical ID = smallest member participant ID; + no re-parenting, no destructive merges from the UI; importer + `MergeParticipants` becomes link-aware and recomputes a canonical + spanning forest for any cluster it touches (contraction can otherwise + create a cycle). Re-submitting an existing edge is an idempotent 200 + no-op; `already_linked` (409) is reserved for a new edge between + distinct, already-connected participants. +- Ranking data: `is_from_me` is derived at cache-export time (existing + `messages.is_from_me` OR sender matches a confirmed `account_identities` + row for that source) rather than exported unchanged, because the + Gmail/IMAP sync and import paths do not populate it reliably; covered by + regression tests against Gmail/IMAP fixture archives. Plus an + `owner_participants` source→owner mapping exported to the cache + (one-time full rebuild); reply-graph signals deferred; signal weights + sent 2.0 / meetings 3.0 / received 1.0. +- Identity-revision scope: link, unlink, and link-aware importer merge all + bump `identity_revision` and synchronously attempt the cheap + identity-dataset refresh. Account-identity confirm/remove and participant + merges (which repoint `messages.sender_id`) additionally bump a second + counter, `account_identity_revision`, since `owner_participants` and the + baked `is_from_me` flag both depend on `account_identities` — a drift + there forces the next cache build to be a full rebuild instead of a skip + or the lightweight identity-only path. +- Chat bursts: new relationship-timeline endpoint keyed by + (source, conversation, local day); Everything semantics unchanged. +- Domains facet: activity-ranked, no reciprocity gate this cycle. +- Excerpt cleanup: query-time flattening (covers existing stored + snippets); import-time cleanup optional follow-up. +- Defaults recorded: decay half-life 365 days, breadth boost + `1 + 0.25 × (modalities − 1)`, tie-break recency then name. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 1097ee037..753c0f079 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -226,11 +226,25 @@ If the TUI launches but shows no data: 2. Rebuild the Parquet cache: `msgvault build-cache --full-rebuild` 3. Check the in-TUI account filter (shown in the title bar): press `A` to open the account selector and switch to "All Accounts" -## Web Server Issues +## Web UI and API Server Issues ### "api_key is required for non-loopback bind address" -You set `bind_addr` to a non-loopback address (e.g., `0.0.0.0`) without configuring an API key. Either add an `api_key` to your `[server]` config, or set `allow_insecure = true` if you understand the security implications. See [Web Server: Security Model](/api-server/#security-model). +You set `bind_addr` to a non-loopback address (e.g., `0.0.0.0`) without configuring an API key. Either add an `api_key` to your `[server]` config, or set `allow_insecure = true` if you understand the security implications. See [Web UI & API Server: Security Model](/api-server/#security-model). + +### The UI reports a missing, stale, or unavailable analytical cache + +Run `msgvault build-cache`, then restart a daemon that was already running. The +Web UI preserves the reported cache state instead of silently switching some +modalities to a different query engine. See [Web UI: Cache states](/web-ui/#cache-states) +and [Configuration: analytics](/configuration/#analytics). + +### The UI warns that the session cookie is not secure + +The browser reached a remote daemon over plain HTTP. Prefer HTTPS at a reverse +proxy and configure that proxy in `server.trusted_proxies`. Do not trust a whole +client network merely to suppress the warning. See [Web UI: Remote access and +HTTPS](/web-ui/#remote-access-and-https). ### Port already in use diff --git a/docs/usage/analytics.md b/docs/usage/analytics.md index daec98efa..11db8c392 100644 --- a/docs/usage/analytics.md +++ b/docs/usage/analytics.md @@ -39,4 +39,7 @@ msgvault list-labels <figure class="screenshot" data-lightbox> <img src="/assets/generated/list-senders.svg" alt="msgvault list-senders command output" loading="lazy"> </figure> -These commands query the configured daemon or remote server. For interactive exploration with drill-down, use the [TUI](/usage/tui/). +These commands query the configured daemon or remote server. For interactive +exploration, use the [Web UI](/web-ui/) to combine search, filters, grouping, +and modality-aware drill-down in a shareable URL-backed context. The +[TUI](/usage/tui/) provides a terminal-native analytical workflow. diff --git a/docs/usage/deduplication.md b/docs/usage/deduplication.md index 1c7ba9ee9..93bcef7fe 100644 --- a/docs/usage/deduplication.md +++ b/docs/usage/deduplication.md @@ -80,7 +80,7 @@ Every dedup-related command sits on one of five rungs (00 through 04). Rung 00 i - **Rung 04, remote delete.** This rung is two parts, stage then execute, and only the staging part is dedup-specific. To stage, run `deduplicate --delete-dups-from-source-server`; it writes pending deletion manifests only for pruned copies whose loser and survivor share a source (same-source-only), so a group spanning two sources stages nothing. To execute, run `delete-staged`, the generic executor for any staged deletion manifest (not just dedup), which acts on the source server and leaves your local archive untouched. Inspect first with `delete-staged --list`, target one batch with `delete-staged <batch-id>`, and note that execution is gated behind `MSGVAULT_ENABLE_REMOTE_DELETE=1`. The same-source restriction lives in the staging step, not in `delete-staged`. See [Deleting Email](/usage/deletion/) for how remote deletion works. !!! note "What \"hidden\" means" - A hidden copy is excluded from search, the TUI, vector and hybrid retrieval, the API, MCP responses, exports, and stats, while still living on disk. Every read path applies the same visibility rule, so a hidden duplicate cannot leak back into results through one backend. + A hidden copy is excluded from search, the Web UI, the TUI, vector and hybrid retrieval, the API, MCP responses, exports, and stats, while still living on disk. Every read path applies the same visibility rule, so a hidden duplicate cannot leak back into results through one backend. ## A Worked Walkthrough diff --git a/docs/usage/deletion.md b/docs/usage/deletion.md index 46cf547a4..c7cb9a03a 100644 --- a/docs/usage/deletion.md +++ b/docs/usage/deletion.md @@ -4,7 +4,29 @@ description: Staging messages for deletion, reviewing manifests, and executing d --- -msgvault supports a staged deletion workflow: select messages in the TUI or stage them via an AI assistant, review what will be deleted, then execute against Gmail or your IMAP provider. **Deletion only removes messages from the remote mail server. Your local archive is never modified.** This means you can always search, browse, and export deleted messages from your local copy. +msgvault supports a staged deletion workflow: select messages in the Web UI or +TUI, or stage them via an AI assistant; review what will be deleted; then +execute against Gmail or your IMAP provider. **Deletion only removes messages +from the remote mail server. Your local archive is never modified.** This means +you can always search, browse, and export deleted messages from your local copy. + +## Staging in the Web UI + +In Everything, select individual rows or all rows matching the current +canonical filter, then press `d` or `D` to open the Deletions workspace. The UI +runs a server-side preflight before it enables staging and requires a separate +confirmation to create the manifest. The Deletions workspace also lists, +inspects, and cancels staged manifests. It never executes remote deletion; +that final step remains the explicit CLI command described below. + +Preflight validates the selection itself (that it still matches, that the +cache/search revision hasn't moved) but does not check which accounts the +matched messages belong to. Each deletion manifest executes against exactly +one mailbox, so if you stage "all matching" across an unfiltered or +multi-account view, confirming the stage fails with a `multi_account_selection` +error even though preflight succeeded. Filter to one source/account +(the `a` key in the TUI, or the equivalent source filter in the Web UI) before +staging an all-matching selection that could span more than one account. ## Bulk Deletion via Aggregate Groups diff --git a/docs/usage/importing.md b/docs/usage/importing.md index 692972195..07fee8fff 100644 --- a/docs/usage/importing.md +++ b/docs/usage/importing.md @@ -5,7 +5,7 @@ description: Import PST archives, MBOX archives, and Apple Mail exports into msg msgvault can import email from local files, not just Gmail. This lets you archive Microsoft Outlook PST files, messages from any provider that supports MBOX export, or Apple Mail's on-disk storage. For live syncing from non-Gmail providers, see [IMAP account setup](/setup/#add-an-imap-account). -Imported messages are stored in the same database as Gmail messages. You can search, browse, export, and analyze them with all the same tools (TUI, CLI, MCP server, web server). Labels, threading, attachments, and full-text search all work the same way. +Imported messages are stored in the same database as Gmail messages. You can search, browse, export, and analyze them with all the same tools (Web UI, TUI, CLI, MCP server, and REST API). Labels, threading, attachments, and full-text search all work the same way. ## import-pst @@ -228,7 +228,9 @@ Individual messages that fail to parse are logged and skipped; the import contin ## After Importing -Imported messages are immediately available for search, TUI browsing, and MCP queries. To update the Parquet analytics cache (used by the TUI for fast aggregation), run: +Imported messages are immediately available for search, direct lookup, and +MCP queries. To update the Parquet analytical cache used by the Web UI and TUI +for fast aggregation, run: ```bash msgvault build-cache @@ -240,6 +242,9 @@ Then explore your imported messages: # Search imported messages msgvault search from:alice@example.com +# Open the browser interface +msgvault serve + # Launch the TUI msgvault tui diff --git a/docs/usage/meetings.md b/docs/usage/meetings.md index 8789707cf..d62e4e722 100644 --- a/docs/usage/meetings.md +++ b/docs/usage/meetings.md @@ -10,11 +10,11 @@ summary followed by the full speaker-labeled transcript, and the organizer and attendees join the same contact graph as the people you email. Meeting sync is **read-only**: msgvault never modifies anything in the source -service. Meetings are cached and fully searchable with `msgvault search` and -the TUI. An unscoped search intentionally returns every cached message type, -including meetings and chats. Ordinary analytics remain email-only unless you -explicitly filter by message type, so meeting attendees do not inflate email -sender/recipient statistics. +service. Meetings are cached and fully searchable with `msgvault search`, the +Web UI, and the TUI. An unscoped search intentionally returns every cached +message type, including meetings and chats. The Web UI's Everything workspace +uses modality-aware rows and filters; email-specific CLI/TUI aggregates remain +email-only unless you explicitly choose another message type. ## Source labels and account identity @@ -107,7 +107,12 @@ same limited sync again updates the existing meeting rows rather than creating duplicates. Once the results look correct, run `msgvault sync-granola work` without a limit to continue normal incremental operation. -### Browse in the TUI +### Browse in the Web UI or TUI + +Start `msgvault serve` and open the [Web UI](/web-ui/) to include meetings in +Everything, search their titles and transcripts, group them with other archive +modalities, or filter to meeting notes only. Open a result to read the note in +its containing context. Launch `msgvault tui` and press `m` until the title bar shows **Meetings**. The list combines Granola and Circleback meetings and shows their date, title, diff --git a/docs/usage/teams.md b/docs/usage/teams.md index 07b588062..d1c3300c9 100644 --- a/docs/usage/teams.md +++ b/docs/usage/teams.md @@ -147,6 +147,7 @@ search is enabled and you want newly synced Teams messages in semantic/hybrid results, run `msgvault embeddings build` after the sync, or configure `[vector.embed.schedule].run_after_sync = true` for scheduled daemon syncs. -In the [TUI](/usage/tui/), press `m` to switch from Email mode to Texts mode. -Teams direct chats, group chats, and channel conversations appear alongside -other text/chat sources in that view. +In the [Web UI](/web-ui/), Teams direct chats, group chats, and channel +conversations appear as conversation rows in Everything and can be combined +with the same search, filters, and grouping as other archive modalities. In +the [TUI](/usage/tui/), press `m` to switch from Email mode to Texts mode. diff --git a/docs/usage/text-messages.md b/docs/usage/text-messages.md index 42fabdcc9..517dbf390 100644 --- a/docs/usage/text-messages.md +++ b/docs/usage/text-messages.md @@ -7,8 +7,9 @@ msgvault can import chats and text messages from WhatsApp, iMessage, Google Voice, Facebook Messenger, and SMS Backup & Restore. It can also sync Microsoft Teams chats and channels through [Microsoft Teams](/usage/teams/) and Discord guild channels and threads through [Discord](/usage/discord/). These records -are stored in the same database as email, and you can browse text/chat -conversations in the [TUI](/usage/tui/) by pressing `m` to switch to text mode. +are stored in the same database as email. The [Web UI](/web-ui/) presents chats +as conversation rows in Everything, with individual messages available on +drill-down; in the [TUI](/usage/tui/), press `m` to switch to text mode. ## import-whatsapp @@ -230,9 +231,17 @@ msgvault sync-synctech-sms phone-backups | `--schedule` | `30 4 * * *` | Cron schedule used by `msgvault serve` | | `--oauth-app` | — | Named Google OAuth app to use | -## Browsing Texts in the TUI +## Browsing Texts -After importing, launch the TUI and press `m` to cycle from Email to Texts mode. Text mode shows a conversations list; select a conversation to drill down into its messages. Pressing `m` again reaches Meetings, then returns to Email. +Start `msgvault serve` and open the [Web UI](/web-ui/) to search email, chats, +calendar events, and meeting notes together. Chat results stay grouped as +conversations so short message fragments do not overwhelm Everything. Open a +conversation to inspect its matching messages in context. + +For terminal browsing, launch the TUI and press `m` to cycle from Email to +Texts mode. Text mode shows a conversations list; select a conversation to +drill down into its messages. Pressing `m` again reaches Meetings, then returns +to Email. ```bash msgvault tui @@ -253,7 +262,7 @@ Imports use checkpoint-based resumption. If interrupted (Ctrl+C, power loss), ru Most chat import and sync commands rebuild the analytics cache automatically. If a newly imported or synced source does not appear in aggregate views immediately, run `msgvault build-cache`. Your imported texts, Teams messages, -and Discord conversations are then available for TUI browsing. +and Discord conversations are then available in the Web UI and TUI. ```bash # Launch the TUI and press 'm' for text mode diff --git a/docs/usage/vector-search.md b/docs/usage/vector-search.md index 2d5df6f23..bd54136b3 100644 --- a/docs/usage/vector-search.md +++ b/docs/usage/vector-search.md @@ -375,7 +375,7 @@ curl "http://localhost:8080/api/v1/search?q=planning+offsite&mode=vector&explain ``` Response shape differs from the FTS path; see the -[Web Server](/api-server/#get-apiv1search) reference for details. +[Web UI & API Server](/api-server/#get-apiv1search) reference for details. HTTP vector/hybrid responses support only the first page; bump `page_size` (capped at `max_page_size_hybrid`) to retrieve a larger candidate page. @@ -510,6 +510,6 @@ are marked complete and not sent to the embedding endpoint. ## See Also -- [Web Server](/api-server/): HTTP API reference (search, stats). +- [Web UI & API Server](/api-server/): browser interface and HTTP API reference. - [Searching](/usage/searching/): Full-text search syntax. - [Search Ranking Across Backends](/architecture/search-ranking/): Ranking differences between SQLite, PostgreSQL, sqlite-vec, and pgvector. diff --git a/docs/web-ui.md b/docs/web-ui.md new file mode 100644 index 000000000..4826c004b --- /dev/null +++ b/docs/web-ui.md @@ -0,0 +1,203 @@ +--- +title: Web UI +description: Use and securely deploy msgvault's daemon-served analytical interface. +--- + +# Web UI + +Msgvault's first-party web UI is embedded in every release binary and served by +`msgvault serve`. It needs no Node/Bun process, hosted service, or external asset +directory at runtime. The transactional archive remains authoritative while the +Parquet/DuckDB analytical cache supplies the interactive tables. + +## Start and discover the URL + +```bash +msgvault build-cache +msgvault serve +``` + +Foreground startup prints `API server: http://HOST:PORT`. The default +`server.api_port = 0` chooses a free port. Local CLI commands discover that +address through the private daemon runtime record under the configured msgvault +home; browsers should use the printed URL. Configure a fixed port for a stable +remote bookmark: + +```toml +[server] +bind_addr = "127.0.0.1" +api_port = 8080 +``` + +The default loopback deployment is trusted. If an API key is active and the +request is not loopback-trusted, `/` still loads the public shell and the UI asks +for the key. A successful login creates an expiring, in-memory browser session. +Daemon restarts, logout, expiry, and API-key activation invalidate sessions. +Existing bearer-key API clients are unchanged. + +## Remote access and HTTPS + +For remote access, set a strong API key and bind deliberately: + +```toml +[server] +bind_addr = "0.0.0.0" +api_port = 8080 +api_key = "replace-with-a-long-random-key" +``` + +HTTPS at a reverse proxy is recommended. Forwarded scheme and host headers are +accepted only from explicitly trusted proxy addresses or CIDRs: + +```toml +[server] +trusted_proxies = ["127.0.0.1", "192.0.2.8/32"] +``` + +Do not add a whole client network merely to silence proxy warnings. When HTTPS +is known through a trusted proxy, the browser cookie uses `Secure`. Plain HTTP +on an encrypted private network is supported as an explicit tradeoff, but the UI +warns that its session cookie travels without TLS. `HttpOnly` and +`SameSite=Strict` do not encrypt that traffic. + +## Explore and search + +Everything opens as a compact, sortable table of logical entries: one row per +email, calendar event, meeting note, other durable item, or chat conversation. +Raw chat fragments appear only after drilling into a conversation. Filter, +Group by, Show as, and Search compose into one URL-backed context, so browser +Back and Forward restore the analytical slice and focused item. + +Search mode is always explicit: + +- **Full text** searches the complete lexical index. +- **Semantic** ranks only content covered by the current embedding generation. +- **Hybrid** combines complete lexical matching with semantic ranking where it + is available. + +The context strip reports semantic coverage. Disabled, building, stale, +incomplete, unavailable, and ready are different states; msgvault never silently +changes the requested mode. Semantic-only results cannot include unembedded +content. Hybrid retains full-text coverage and labels the semantic contribution. + +Search execution also has explicit terminal states. **Timed out** means the +selected search backend did not finish within the request budget; it is an +error, not an empty result, and the query and filters remain available to retry. +**Incompatible mode** means the daemon, browser contract, or current index +cannot safely honor the selected search mode. Update or rebuild the named +component, or deliberately select a supported mode. Msgvault does not quietly +substitute full-text search for either state. + +## Cache states + +The web tables use one modality-neutral analytical cache. When it is missing, +building, stale, or unavailable, the UI names that state instead of quietly +switching selected modalities to a different read path. Run `msgvault +build-cache` for an explicit rebuild, or leave `analytics.auto_build_cache = +true` for daemon startup to build a stale cache. With `analytics.engine = +"duckdb"`, startup fails if no usable cache can be produced. + +## Files and containing context + +Files is a searchable table of attachment date, filename, type, size, person or +domain, source, containing item, and content availability. Archived images and +PDFs open in application-controlled viewers. Metadata-only, missing, +unsupported, and previewable content remain distinct. From a file, navigate to +its containing item and then its email or chat conversation. + +## People and domains + +People combines identifiers backed by explicit archive identity evidence; it +does not merge records merely because their display names match. Select a +person to inspect contextual activity across email, chat, calendar events, and +meeting notes, plus the files associated with that person. The active search +and filters continue to scope both the timeline and file table. + +Domains provides the same activity-and-files analysis for an exact domain +fact. A domain is not treated as an inferred organization identity. Selecting +a grouped person or domain in Everything opens its inspector in the current +context, including chronologically ordered related files. + +## Saved Views + +Saved Views persist useful analytical contexts in the daemon, so the same +library is available from every authenticated browser connected to this +single-user archive. A view records its query, explicit search mode, filters, +grouping, presentation, sort, visible columns, and inspector preference. +Selection is intentionally not saved. + +Each record carries a schema version. An incompatible record remains visible, +but cannot be opened or edited: automatic migration is not attempted. Remove it +after confirmation and save the current context again. Updates and deletion use +the record revision as an optimistic-concurrency guard. If another browser +changes the view first, msgvault reports a conflict and requires you to reload +and review the latest revision instead of overwriting it. + +## Sources and sync status + +Sources is a status workspace. For each source it shows schedule information, +an active run's processed, added, and error counts, the latest terminal result, +and the last successful sync separately. A failed status request remains an +error rather than becoming an empty source list. Failed runs expose their +run-level and item-level errors, and a terminal result older than 24 hours is +marked `stale_last_result`. + +`Sync now` is available only when that source reports the capability. A `202 +Accepted` response means the daemon accepted the request, not that work has +finished. While the page is visible, the UI polls source status with bounded +backoff to show the run and live progress; it opens no streaming connection. If +the accepted run never appears, the UI reports `sync_start_not_observed` rather +than claiming success. Conflicting runs and unavailable capabilities retain +their explicit errors or reasons. Full resync, pause/resume, schedule editing, +and source add/remove are outside this workspace's initial scope. + +## Deletions + +Everything supports explicit row selection and select-all-matching for the +current canonical filter. `d` and `D` open the Deletions workspace, where the +daemon first preflights the selection and reports any unavailable action before +the UI offers a separate staging confirmation. The workspace lists, inspects, +and cancels manifests; it cannot execute deletion against a provider. Use the +explicit `msgvault delete-staged` CLI workflow for that final operation. + +## Keyboard controls + +Tab keeps its normal browser meaning. Outside inputs and content viewers: + +| Key | Action | +|---|---| +| `j` / `k`, arrows | Move row focus | +| `Home` / `End`, `PgUp` / `PgDn` | Navigate large tables | +| `Enter` | Open or drill into the focused row | +| `Esc` | Close the current shell layer or restore prior context | +| `/` | Focus search | +| `Space` | Toggle the focused row | +| `A` / `x` | Select visible rows / clear selection | +| `d` / `D` | Review deletion staging | +| `f`, `g`, `s`, `r` | Filter, group, sort, reverse sort | +| `?` | Searchable shortcut help | +| `Cmd/Ctrl+K` | Command palette | + +Destructive keys open a review; they never execute deletion immediately. +Shortcuts are suspended while typing and inside message/file content. + +## Settings and restart behavior + +Settings exposes the supported browser, server, search, source, and optional +integration keys. It performs targeted, comment-preserving edits to +`config.toml`, rejects a stale browser edit after a concurrent hand edit, and +never displays secret values—only whether they are configured. + +Most settings are restart-required by design. After saving, the UI shows a +pending-restart state until the daemon restarts. An API-key change requires an +extra confirmation. The current process keeps the active key until restart; +after restart, old browser sessions are gone and the login screen appears. + +## Optional integration states + +The optional task integration is server-side and provider-neutral. Msgvault +shows disabled, discovering, authentication required, reachable but +incompatible, partial, stale, unavailable, or ready instead of presenting a +failed lookup as “no links.” Credentials never enter browser types, URLs, or +error messages. The archive remains fully usable while the integration is +absent or unhealthy. diff --git a/docs/zensical.toml b/docs/zensical.toml index 668933ba6..829578dfe 100644 --- a/docs/zensical.toml +++ b/docs/zensical.toml @@ -1,7 +1,7 @@ [project] site_name = "msgvault" site_url = "https://msgvault.io" -site_description = "Offline email, chat, and meeting archive with full-text search and an interactive TUI. Sync Gmail, IMAP, Teams, Discord, Beeper, Granola, and Circleback; import PST, MBOX, Apple Mail, and common chat exports." +site_description = "Offline email, chat, calendar, and meeting archive with a daemon-served analytical Web UI, full-text and semantic search, and sync for Gmail, IMAP, Teams, Discord, Beeper, Granola, and Circleback." site_author = "Kenn Software LLC" copyright = 'Copyright © 2026 <a href="https://kenn.io">Kenn Software LLC</a>. MIT License.' docs_dir = "docs" @@ -14,11 +14,12 @@ use_directory_urls = true nav = [ {"Introduction" = "introduction.md"}, {"Setup Guide" = "setup.md"}, + {"Web UI" = "web-ui.md"}, {"Interactive TUI" = "usage/tui.md"}, {"Configuration" = "configuration.md"}, {"CLI Reference" = "cli-reference.md"}, {"MCP Server" = "usage/chat.md"}, - {"Web Server" = "api-server.md"}, + {"Web UI & API Server" = "api-server.md"}, {"Changelog" = "changelog.md"}, {"CLI Usage" = [ {"Searching" = "usage/searching.md"}, diff --git a/internal/api/cli_handlers.go b/internal/api/cli_handlers.go index cf4d5fbd8..8b1de76a3 100644 --- a/internal/api/cli_handlers.go +++ b/internal/api/cli_handlers.go @@ -1877,7 +1877,7 @@ func (s *Server) getCLIIdentities( return cliIdentitiesResponse{Rows: rows}, nil } -func (s *Server) addCLIIdentity(req identityops.AddRequest) (identityops.AddResult, error) { +func (s *Server) addCLIIdentity(ctx context.Context, req identityops.AddRequest) (identityops.AddResult, error) { cliStore, apiErr := s.cliStore() if apiErr != nil { return identityops.AddResult{}, apiErr @@ -1891,10 +1891,15 @@ func (s *Server) addCLIIdentity(req identityops.AddRequest) (identityops.AddResu "Failed to add identity", ) } + // AddAccountIdentity bumps the identity revision when it confirms a + // brand new (source_id, address) pair, changing owner_participants; the + // refresh is attempted unconditionally since it is an idempotent + // full re-export and the mutation already committed either way. + result.CacheState = s.refreshIdentityCacheState(ctx) return result, nil } -func (s *Server) removeCLIIdentity(req identityops.RemoveRequest) (identityops.RemoveResult, error) { +func (s *Server) removeCLIIdentity(ctx context.Context, req identityops.RemoveRequest) (identityops.RemoveResult, error) { cliStore, apiErr := s.cliStore() if apiErr != nil { return identityops.RemoveResult{}, apiErr @@ -1908,6 +1913,11 @@ func (s *Server) removeCLIIdentity(req identityops.RemoveRequest) (identityops.R "Failed to remove identity", ) } + // RemoveAccountIdentity only bumps the identity revision on an actual + // deletion, but the refresh is attempted unconditionally for the same + // reason as addCLIIdentity: it is an idempotent full re-export and the + // mutation already committed either way. + result.CacheState = s.refreshIdentityCacheState(ctx) return result, nil } diff --git a/internal/api/cli_identity_routes.go b/internal/api/cli_identity_routes.go index 97d893e84..6b1afb1eb 100644 --- a/internal/api/cli_identity_routes.go +++ b/internal/api/cli_identity_routes.go @@ -58,8 +58,8 @@ func (s *Server) registerCLIIdentityHumaRoutes(api huma.API) { Summary: "Add a confirmed identifier to an account identity", SkipValidateBody: true, Errors: []int{http.StatusBadRequest, http.StatusInternalServerError, http.StatusServiceUnavailable}, - }), func(_ context.Context, input *cliIdentityAddInput) (*cliIdentityAddOutput, error) { - result, err := s.addCLIIdentity(input.Body) + }), func(ctx context.Context, input *cliIdentityAddInput) (*cliIdentityAddOutput, error) { + result, err := s.addCLIIdentity(ctx, input.Body) if err != nil { return nil, err } @@ -74,8 +74,8 @@ func (s *Server) registerCLIIdentityHumaRoutes(api huma.API) { Summary: "Remove a confirmed identifier from an account identity", SkipValidateBody: true, Errors: []int{http.StatusBadRequest, http.StatusInternalServerError, http.StatusServiceUnavailable}, - }), func(_ context.Context, input *cliIdentityRemoveInput) (*cliIdentityRemoveOutput, error) { - result, err := s.removeCLIIdentity(input.Body) + }), func(ctx context.Context, input *cliIdentityRemoveInput) (*cliIdentityRemoveOutput, error) { + result, err := s.removeCLIIdentity(ctx, input.Body) if err != nil { return nil, err } diff --git a/internal/api/conversations.go b/internal/api/conversations.go new file mode 100644 index 000000000..f496047fd --- /dev/null +++ b/internal/api/conversations.go @@ -0,0 +1,203 @@ +package api + +import ( + "context" + "errors" + "fmt" + "net/http" + "strconv" + "time" + + "go.kenn.io/msgvault/internal/store" +) + +const ( + defaultConversationBound = 25 + maxConversationBound = 50 +) + +// ConversationResponse is a bounded chronological conversation window. +type ConversationResponse struct { + ID int64 `json:"id"` + AnchorID int64 `json:"anchor_id"` + Messages []MessageDetail `json:"messages"` + HasBefore bool `json:"has_before"` + HasAfter bool `json:"has_after"` + Total int64 `json:"total"` +} + +type conversationStore interface { + ConversationExists(conversationID int64) (bool, error) + GetConversationWindow(conversationID, anchorID int64, before, after int) (*store.ConversationWindow, error) +} + +// ConversationWindowStore is the context-aware conversation reader that +// production store adapters should implement so conversation endpoints work +// under a cancellable request context instead of falling back to the +// legacy background-context path. +type ConversationWindowStore interface { + ConversationExistsContext(ctx context.Context, conversationID int64) (bool, error) + GetConversationWindowContext( + ctx context.Context, + conversationID, anchorID int64, + before, after int, + start, end *time.Time, + ) (*store.ConversationWindow, error) +} + +func (s *Server) conversationExists(ctx context.Context, conversationID int64) (bool, error) { + if reader, ok := s.store.(ConversationWindowStore); ok { + return reader.ConversationExistsContext(ctx, conversationID) + } + reader, ok := s.store.(conversationStore) + if !ok { + return false, errors.New("conversation reader unavailable") + } + return reader.ConversationExists(conversationID) +} + +func (s *Server) conversationWindow( + ctx context.Context, + conversationID, anchorID int64, + before, after int, + start, end *time.Time, +) (*store.ConversationWindow, error) { + if reader, ok := s.store.(ConversationWindowStore); ok { + return reader.GetConversationWindowContext(ctx, conversationID, anchorID, before, after, start, end) + } + if start != nil || end != nil { + return nil, errors.New("conversation reader does not support time-bounded windows") + } + reader, ok := s.store.(conversationStore) + if !ok { + return nil, errors.New("conversation reader unavailable") + } + return reader.GetConversationWindow(conversationID, anchorID, before, after) +} + +func conversationBound(r *http.Request, name string) (int, error) { + value, present, err := queryInt(r, name) + if err != nil { + return 0, err + } + if !present { + return defaultConversationBound, nil + } + if value < 0 || value > maxConversationBound { + return 0, newParamError(name, + fmt.Sprintf("query parameter %q must be between 0 and %d", name, maxConversationBound)) + } + return value, nil +} + +// conversationTimeRange parses the optional "start"/"end" query params +// (RFC3339, UTC) that scope the conversation window to a half-open +// [start, end) range. Either or both may be absent. A present-but-unparseable +// value, or start >= end when both are present, is a paramError. +func conversationTimeRange(r *http.Request) (start, end *time.Time, err error) { + startValue, startPresent, err := queryDate(r, "start") + if err != nil { + return nil, nil, err + } + endValue, endPresent, err := queryDate(r, "end") + if err != nil { + return nil, nil, err + } + if startPresent { + start = &startValue + } + if endPresent { + end = &endValue + } + if start != nil && end != nil && !start.Before(*end) { + return nil, nil, newParamError("end", "query parameter \"end\" must be after \"start\"") + } + return start, end, nil +} + +func (s *Server) handleGetConversation(w http.ResponseWriter, r *http.Request) { + conversationID, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil || conversationID <= 0 { + writeError(w, http.StatusBadRequest, "invalid_id", "Conversation ID must be a positive integer") + return + } + anchorID, present, err := queryInt64(r, "anchor") + if err != nil { + s.rejectBadParam(w, err) + return + } + if !present || anchorID <= 0 { + writeError(w, http.StatusBadRequest, "missing_anchor", "Query parameter 'anchor' is required") + return + } + before, err := conversationBound(r, "before") + if err != nil { + s.rejectBadParam(w, err) + return + } + after, err := conversationBound(r, "after") + if err != nil { + s.rejectBadParam(w, err) + return + } + start, end, err := conversationTimeRange(r) + if err != nil { + s.rejectBadParam(w, err) + return + } + if s.store == nil { + writeError(w, http.StatusServiceUnavailable, "conversation_unavailable", "Conversation details are unavailable") + return + } + found, err := s.conversationExists(r.Context(), conversationID) + if err != nil { + if s.writeIfContextError(w, err) { + return + } + writeError(w, http.StatusServiceUnavailable, "conversation_unavailable", "Conversation details are unavailable") + return + } + if !found { + writeError(w, http.StatusNotFound, "conversation_not_found", "Conversation not found") + return + } + window, err := s.conversationWindow(r.Context(), conversationID, anchorID, before, after, start, end) + if err != nil { + if errors.Is(err, store.ErrConversationAnchorOutsideRange) { + writeError(w, http.StatusBadRequest, "conversation_anchor_outside_range", + "Anchor message is outside the requested time range") + return + } + if s.writeIfContextError(w, err) { + return + } + s.logger.Error("get conversation", "conversation_id", conversationID, "anchor_id", anchorID, "error", err) + writeError(w, http.StatusInternalServerError, "internal_error", "Could not retrieve conversation") + return + } + if window == nil || len(window.Messages) == 0 { + writeError(w, http.StatusNotFound, "conversation_anchor_not_found", "Anchor message is not in this conversation") + return + } + messages := make([]MessageDetail, 0, len(window.Messages)) + for _, message := range window.Messages { + detail := MessageDetail{ + MessageSummary: toMessageSummary(message), + Body: message.Body, + BodyHTML: message.BodyHTML, + Attachments: make([]AttachmentInfo, 0, len(message.Attachments)), + } + for _, attachment := range message.Attachments { + detail.Attachments = append(detail.Attachments, attachmentInfoFromStore(attachment)) + } + messages = append(messages, detail) + } + writeJSON(w, http.StatusOK, ConversationResponse{ + ID: conversationID, + AnchorID: anchorID, + Messages: messages, + HasBefore: window.AnchorPosition-int64(before) > 1, + HasAfter: window.AnchorPosition+int64(after) < window.Total, + Total: window.Total, + }) +} diff --git a/internal/api/conversations_test.go b/internal/api/conversations_test.go new file mode 100644 index 000000000..eeb49c1b5 --- /dev/null +++ b/internal/api/conversations_test.go @@ -0,0 +1,238 @@ +package api + +import ( + "database/sql" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/store" + "go.kenn.io/msgvault/internal/testutil" +) + +func seedConversation(t *testing.T, messageType string, count int) (*Server, int64, []int64) { + t.Helper() + st := testutil.NewTestStore(t) + source, err := st.GetOrCreateSource("test", "archive@example.com") + require.NoError(t, err) + conversationType := map[bool]string{true: "direct_chat", false: "email_thread"}[messageType != "email"] + conversationID, err := st.EnsureConversationWithType( + source.ID, "conversation-1", conversationType, "Synthetic conversation", + ) + require.NoError(t, err) + + ids := make([]int64, 0, count) + for i := range count { + // Pairs deliberately share timestamps. The ID tiebreaker must keep paging stable. + sentAt := time.Date(2026, time.January, i/2+1, 12, 0, 0, 0, time.UTC) + id, upsertErr := st.UpsertMessage(&store.Message{ + ConversationID: conversationID, + SourceID: source.ID, + SourceMessageID: fmt.Sprintf("source-%02d", i), + MessageType: messageType, + SentAt: sql.NullTime{Time: sentAt, Valid: true}, + Subject: sql.NullString{String: fmt.Sprintf("Message %02d", i), Valid: true}, + Snippet: sql.NullString{String: fmt.Sprintf("Preview %02d", i), Valid: true}, + }) + require.NoError(t, upsertErr) + ids = append(ids, id) + require.NoError(t, st.UpsertMessageBody(id, + sql.NullString{String: fmt.Sprintf("Body %02d", i), Valid: true}, + sql.NullString{String: fmt.Sprintf("<p>Body %02d</p>", i), Valid: true}, + )) + } + + srv := NewServer(&config.Config{Server: config.ServerConfig{APIPort: 8080}}, st, nil, testLogger()) + return srv, conversationID, ids +} + +func TestConversationWindowIsAnchoredBoundedAndChronological(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + srv, conversationID, ids := seedConversation(t, "email", 8) + req := httptest.NewRequest(http.MethodGet, + fmt.Sprintf("/api/v1/conversations/%d?anchor=%d&before=2&after=2", conversationID, ids[3]), nil) + w := httptest.NewRecorder() + + srv.Router().ServeHTTP(w, req) + + require.Equal(http.StatusOK, w.Code, "body: %s", w.Body.String()) + var response ConversationResponse + require.NoError(json.NewDecoder(w.Body).Decode(&response)) + assert.Equal(conversationID, response.ID) + assert.Equal(ids[3], response.AnchorID) + require.Len(response.Messages, 5) + assert.Equal([]int64{ids[1], ids[2], ids[3], ids[4], ids[5]}, []int64{ + response.Messages[0].ID, response.Messages[1].ID, response.Messages[2].ID, + response.Messages[3].ID, response.Messages[4].ID, + }) + assert.True(response.HasBefore) + assert.True(response.HasAfter) + assert.Equal("Body 03", response.Messages[2].Body) + assert.Equal("<p>Body 03</p>", response.Messages[2].BodyHTML) +} + +func TestConversationWindowReturnsIndividualChatMessages(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + srv, conversationID, ids := seedConversation(t, "imessage", 3) + req := httptest.NewRequest(http.MethodGet, + fmt.Sprintf("/api/v1/conversations/%d?anchor=%d&before=1&after=1", conversationID, ids[1]), nil) + w := httptest.NewRecorder() + + srv.Router().ServeHTTP(w, req) + + require.Equal(http.StatusOK, w.Code, "body: %s", w.Body.String()) + var response ConversationResponse + require.NoError(json.NewDecoder(w.Body).Decode(&response)) + require.Len(response.Messages, 3) + assert.Equal("imessage", response.Messages[0].MessageType) + assert.Equal(ids[1], response.AnchorID) +} + +func TestConversationWindowRejectsMissingOrForeignAnchor(t *testing.T) { + srv, conversationID, ids := seedConversation(t, "email", 2) + + for name, target := range map[string]struct { + path string + code string + }{ + "missing conversation": { + path: fmt.Sprintf("/api/v1/conversations/999999?anchor=%d", ids[0]), + code: "conversation_not_found", + }, + "foreign anchor": { + path: fmt.Sprintf("/api/v1/conversations/%d?anchor=999999", conversationID), + code: "conversation_anchor_not_found", + }, + } { + t.Run(name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, target.path, nil) + w := httptest.NewRecorder() + srv.Router().ServeHTTP(w, req) + assert.Equal(t, http.StatusNotFound, w.Code) + var response ErrorResponse + require.NoError(t, json.NewDecoder(w.Body).Decode(&response)) + assert.Equal(t, target.code, response.Error) + }) + } +} + +func TestConversationWindowValidatesBounds(t *testing.T) { + srv, conversationID, ids := seedConversation(t, "email", 1) + req := httptest.NewRequest(http.MethodGet, + fmt.Sprintf("/api/v1/conversations/%d", conversationID), nil) + w := httptest.NewRecorder() + srv.Router().ServeHTTP(w, req) + assert.Equal(t, http.StatusBadRequest, w.Code, "missing anchor") + + for _, query := range []string{ + "before=-1", "after=51", + "start=not-a-time", + "start=2026-01-02T00:00:00Z&end=2026-01-01T00:00:00Z", + "start=2026-01-01T00:00:00Z&end=2026-01-01T00:00:00Z", + } { + req = httptest.NewRequest(http.MethodGet, + fmt.Sprintf("/api/v1/conversations/%d?anchor=%d&%s", conversationID, ids[0], query), nil) + w = httptest.NewRecorder() + srv.Router().ServeHTTP(w, req) + assert.Equal(t, http.StatusBadRequest, w.Code, query) + } + req = httptest.NewRequest(http.MethodGet, + fmt.Sprintf("/api/v1/conversations/%d?anchor=bad", conversationID), nil) + w = httptest.NewRecorder() + srv.Router().ServeHTTP(w, req) + assert.Equal(t, http.StatusBadRequest, w.Code, "anchor=bad") +} + +// seedConversationAcrossDays creates count messages split across two UTC +// days (the first half on day one, the second half on day two) so tests can +// exercise start/end range scoping. Messages within a day get distinct +// minute offsets to keep chronological ordering stable. +func seedConversationAcrossDays(t *testing.T, count int) (*Server, int64, []int64, time.Time) { + t.Helper() + st := testutil.NewTestStore(t) + source, err := st.GetOrCreateSource("test", "archive@example.com") + require.NoError(t, err) + conversationID, err := st.EnsureConversationWithType( + source.ID, "conversation-ranged", "email_thread", "Ranged conversation", + ) + require.NoError(t, err) + + day1 := time.Date(2026, time.January, 1, 12, 0, 0, 0, time.UTC) + day2 := time.Date(2026, time.January, 2, 12, 0, 0, 0, time.UTC) + ids := make([]int64, 0, count) + for i := range count { + day := day1 + if i >= count/2 { + day = day2 + } + sentAt := day.Add(time.Duration(i) * time.Minute) + id, err := st.UpsertMessage(&store.Message{ + ConversationID: conversationID, + SourceID: source.ID, + SourceMessageID: fmt.Sprintf("ranged-%02d", i), + MessageType: "email", + SentAt: sql.NullTime{Time: sentAt, Valid: true}, + Subject: sql.NullString{String: fmt.Sprintf("Message %02d", i), Valid: true}, + Snippet: sql.NullString{String: fmt.Sprintf("Preview %02d", i), Valid: true}, + }) + require.NoError(t, err) + ids = append(ids, id) + } + + srv := NewServer(&config.Config{Server: config.ServerConfig{APIPort: 8080}}, st, nil, testLogger()) + return srv, conversationID, ids, day2 +} + +func TestConversationWindowScopesToTimeRangeExcludingOtherDays(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + srv, conversationID, ids, day2 := seedConversationAcrossDays(t, 6) + + start := day2.Truncate(24 * time.Hour) + end := start.AddDate(0, 0, 1) + req := httptest.NewRequest(http.MethodGet, fmt.Sprintf( + "/api/v1/conversations/%d?anchor=%d&start=%s&end=%s", + conversationID, ids[4], start.Format(time.RFC3339), end.Format(time.RFC3339), + ), nil) + w := httptest.NewRecorder() + + srv.Router().ServeHTTP(w, req) + + require.Equal(http.StatusOK, w.Code, "body: %s", w.Body.String()) + var response ConversationResponse + require.NoError(json.NewDecoder(w.Body).Decode(&response)) + assert.Equal(int64(3), response.Total) + require.Len(response.Messages, 3) + assert.Equal([]int64{ids[3], ids[4], ids[5]}, []int64{ + response.Messages[0].ID, response.Messages[1].ID, response.Messages[2].ID, + }) + assert.False(response.HasBefore, "day-1 messages exist but are outside the requested range") + assert.False(response.HasAfter) +} + +func TestConversationWindowRejectsAnchorOutsideTimeRange(t *testing.T) { + srv, conversationID, ids, day2 := seedConversationAcrossDays(t, 6) + + start := day2.Truncate(24 * time.Hour) + end := start.AddDate(0, 0, 1) + req := httptest.NewRequest(http.MethodGet, fmt.Sprintf( + "/api/v1/conversations/%d?anchor=%d&start=%s&end=%s", + conversationID, ids[0], start.Format(time.RFC3339), end.Format(time.RFC3339), + ), nil) + w := httptest.NewRecorder() + + srv.Router().ServeHTTP(w, req) + + require.Equal(t, http.StatusBadRequest, w.Code, "body: %s", w.Body.String()) + var response ErrorResponse + require.NoError(t, json.NewDecoder(w.Body).Decode(&response)) + assert.Equal(t, "conversation_anchor_outside_range", response.Error) +} diff --git a/internal/api/csrf.go b/internal/api/csrf.go new file mode 100644 index 000000000..d293eb762 --- /dev/null +++ b/internal/api/csrf.go @@ -0,0 +1,241 @@ +package api + +import ( + "context" + "crypto/sha256" + "crypto/subtle" + "errors" + "fmt" + "net/http" + "net/netip" + "net/url" + "strconv" + "strings" +) + +const csrfHeaderName = "X-Csrf-Token" + +type requestSecurityContextKey struct{} + +type requestSecurity struct { + auth requestAuthentication + scheme string + host string +} + +func (s *Server) requestSecurityMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + scheme, host, err := s.effectiveRequestOrigin(r) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_forwarded_headers", "Invalid forwarded scheme or host") + return + } + security := requestSecurity{ + auth: s.classifyAPIRequestDirect(r), + scheme: scheme, + host: host, + } + ctx := context.WithValue(r.Context(), requestSecurityContextKey{}, security) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func securityFromRequest(r *http.Request) (requestSecurity, bool) { + security, ok := r.Context().Value(requestSecurityContextKey{}).(requestSecurity) + return security, ok +} + +func (s *Server) csrfMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if isSafeMethod(r.Method) || s.requestAuthentication(r).Mode != AuthModeSession { + next.ServeHTTP(w, r) + return + } + + security, ok := securityFromRequest(r) + if !ok || !requestOriginMatches(r, security.scheme, security.host) { + writeError(w, http.StatusForbidden, "csrf_rejected", "Browser mutation origin or CSRF token is invalid") + return + } + if !constantTimeTokenEqual(r.Header.Get(csrfHeaderName), security.auth.Session.CSRFToken) { + writeError(w, http.StatusForbidden, "csrf_rejected", "Browser mutation origin or CSRF token is invalid") + return + } + + next.ServeHTTP(w, r) + }) +} + +func isSafeMethod(method string) bool { + switch method { + case http.MethodGet, http.MethodHead, http.MethodOptions, http.MethodTrace: + return true + default: + return false + } +} + +func constantTimeTokenEqual(got, want string) bool { + gotHash := sha256.Sum256([]byte(got)) + wantHash := sha256.Sum256([]byte(want)) + return subtle.ConstantTimeCompare(gotHash[:], wantHash[:]) == 1 +} + +func requestOriginMatches(r *http.Request, scheme, host string) bool { + origins := r.Header.Values("Origin") + if len(origins) != 1 || strings.Contains(origins[0], ",") { + return false + } + origin, err := url.Parse(origins[0]) + if err != nil || origin.User != nil || origin.Scheme == "" || origin.Host == "" || + origin.Path != "" || origin.RawQuery != "" || origin.Fragment != "" { + return false + } + return strings.EqualFold(origin.Scheme, scheme) && strings.EqualFold(origin.Host, host) +} + +func (s *Server) effectiveRequestOrigin(r *http.Request) (string, string, error) { + scheme := "http" + if r.TLS != nil { + scheme = "https" + } + host := r.Host + if !s.isTrustedProxy(r) { + return scheme, host, nil + } + + forwardedValues := r.Header.Values("Forwarded") + xProtoValues := r.Header.Values("X-Forwarded-Proto") + xHostValues := r.Header.Values("X-Forwarded-Host") + if len(forwardedValues) > 0 && (len(xProtoValues) > 0 || len(xHostValues) > 0) { + return "", "", errors.New("mixed Forwarded and X-Forwarded headers") + } + + if len(forwardedValues) > 0 { + forwardedScheme, forwardedHost, err := parseForwardedOrigin(forwardedValues) + if err != nil { + return "", "", err + } + if forwardedScheme != "" { + scheme = forwardedScheme + } + if forwardedHost != "" { + host = forwardedHost + } + } else { + forwardedScheme, err := singleForwardedValue("X-Forwarded-Proto", xProtoValues) + if err != nil { + return "", "", err + } + forwardedHost, err := singleForwardedValue("X-Forwarded-Host", xHostValues) + if err != nil { + return "", "", err + } + if forwardedScheme != "" { + scheme = strings.ToLower(forwardedScheme) + } + if forwardedHost != "" { + host = forwardedHost + } + } + + if err := validateForwardedOrigin(scheme, host); err != nil { + return "", "", err + } + return scheme, host, nil +} + +func parseForwardedOrigin(values []string) (string, string, error) { + if len(values) != 1 || strings.Contains(values[0], ",") { + return "", "", errors.New("ambiguous Forwarded header") + } + var scheme, host string + for part := range strings.SplitSeq(values[0], ";") { + name, value, ok := strings.Cut(strings.TrimSpace(part), "=") + if !ok { + return "", "", errors.New("malformed Forwarded parameter") + } + value = strings.TrimSpace(value) + if strings.HasPrefix(value, `"`) { + unquoted, err := strconv.Unquote(value) + if err != nil { + return "", "", fmt.Errorf("malformed quoted Forwarded value: %w", err) + } + value = unquoted + } + switch strings.ToLower(strings.TrimSpace(name)) { + case "proto": + if value == "" { + return "", "", errors.New("empty Forwarded proto value") + } + if scheme != "" { + return "", "", errors.New("multiple Forwarded proto values") + } + scheme = strings.ToLower(value) + case "host": + if value == "" { + return "", "", errors.New("empty Forwarded host value") + } + if host != "" { + return "", "", errors.New("multiple Forwarded host values") + } + host = value + } + } + return scheme, host, nil +} + +func singleForwardedValue(name string, values []string) (string, error) { + if len(values) == 0 { + return "", nil + } + if len(values) != 1 || strings.Contains(values[0], ",") { + return "", fmt.Errorf("ambiguous %s header", name) + } + value := strings.TrimSpace(values[0]) + if value == "" { + return "", fmt.Errorf("empty %s header", name) + } + return value, nil +} + +func validateForwardedOrigin(scheme, host string) error { + if scheme != "http" && scheme != "https" { + return fmt.Errorf("unsupported forwarded scheme %q", scheme) + } + parsed, err := url.Parse(scheme + "://" + host) + if err != nil || parsed.User != nil || parsed.Host == "" || parsed.Host != host || + parsed.Path != "" || parsed.RawQuery != "" || parsed.Fragment != "" { + return fmt.Errorf("invalid forwarded host %q", host) + } + return nil +} + +func trustedProxyPrefixes(entries []string) []netip.Prefix { + prefixes := make([]netip.Prefix, 0, len(entries)) + for _, entry := range entries { + if prefix, err := netip.ParsePrefix(entry); err == nil { + prefixes = append(prefixes, prefix.Masked()) + continue + } + if addr, err := netip.ParseAddr(entry); err == nil { + addr = addr.Unmap() + prefixes = append(prefixes, netip.PrefixFrom(addr, addr.BitLen())) + } + } + return prefixes +} + +func (s *Server) isTrustedProxy(r *http.Request) bool { + addr, err := netip.ParseAddr(clientIP(r)) + if err != nil { + return false + } + addr = addr.Unmap() + for _, prefix := range s.trustedProxies { + if prefix.Contains(addr) { + return true + } + } + return false +} diff --git a/internal/api/csrf_test.go b/internal/api/csrf_test.go new file mode 100644 index 000000000..e15dd08a3 --- /dev/null +++ b/internal/api/csrf_test.go @@ -0,0 +1,325 @@ +package api + +import ( + "bytes" + "context" + "crypto/tls" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" +) + +func TestSessionCSRFRequestMatrix(t *testing.T) { + tests := []struct { + name string + method string + origin string + csrfToken func(SessionStatus) string + apiKey bool + wantStatus int + forwarded http.Header + }{ + { + name: "safe method needs neither origin nor token", + method: http.MethodGet, + wantStatus: http.StatusOK, + }, + { + name: "session mutation accepts matching origin and token", + method: http.MethodDelete, + origin: "http://example.com", + csrfToken: func(status SessionStatus) string { return status.CSRFToken }, + wantStatus: http.StatusNoContent, + }, + { + name: "session mutation rejects missing origin", + method: http.MethodDelete, + csrfToken: func(status SessionStatus) string { return status.CSRFToken }, + wantStatus: http.StatusForbidden, + }, + { + name: "session mutation rejects cross origin", + method: http.MethodDelete, + origin: "https://attacker.example", + csrfToken: func(status SessionStatus) string { return status.CSRFToken }, + wantStatus: http.StatusForbidden, + }, + { + name: "session mutation rejects missing token", + method: http.MethodDelete, + origin: "http://example.com", + wantStatus: http.StatusForbidden, + }, + { + name: "session mutation rejects token from another session", + method: http.MethodDelete, + origin: "http://example.com", + csrfToken: func(SessionStatus) string { return "another-session-token" }, + wantStatus: http.StatusForbidden, + }, + { + name: "API key mutation bypasses browser checks without cookie", + method: http.MethodDelete, + apiKey: true, + wantStatus: http.StatusNoContent, + }, + { + name: "API key wins and bypasses browser checks with cookie", + method: http.MethodDelete, + apiKey: true, + wantStatus: http.StatusNoContent, + }, + { + name: "untrusted forwarded origin is ignored", + method: http.MethodDelete, + origin: "http://example.com", + csrfToken: func(status SessionStatus) string { return status.CSRFToken }, + forwarded: http.Header{ + "Forwarded": []string{"proto=https;host=public.example"}, + "X-Forwarded-Proto": []string{"https"}, + "X-Forwarded-Host": []string{"public.example"}, + }, + wantStatus: http.StatusNoContent, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + srv := newSessionTestServer(t, testSessionAPIKey) + login := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), nil, false) + require.Equal(t, http.StatusOK, login.Code, login.Body.String()) + status := decodeSessionStatus(t, login) + cookie := requireSessionCookie(t, login) + + headers := tt.forwarded.Clone() + if headers == nil { + headers = make(http.Header) + } + if tt.name != "API key mutation bypasses browser checks without cookie" { + headers.Set("Cookie", cookie.String()) + } + if tt.origin != "" { + headers.Set("Origin", tt.origin) + } + if tt.csrfToken != nil { + headers.Set(csrfHeaderName, tt.csrfToken(status)) + } + if tt.apiKey { + headers.Set("X-Api-Key", testSessionAPIKey) + } + + resp := performSessionRequest(t, srv, tt.method, sessionPath, nil, headers, false) + assert.Equal(t, tt.wantStatus, resp.Code, resp.Body.String()) + }) + } +} + +func TestRejectedSessionMutationDoesNotReachOperationGate(t *testing.T) { + gate := &recordingOperationGate{allow: true} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIKey: testSessionAPIKey}}, + Logger: testLogger(), + OperationGate: gate, + }) + t.Cleanup(func() { + require.NoError(t, srv.Shutdown(context.Background())) + }) + + login := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), nil, false) + require.Equal(t, http.StatusOK, login.Code, login.Body.String()) + status := decodeSessionStatus(t, login) + cookie := requireSessionCookie(t, login) + + tests := []struct { + name string + origin string + csrfToken string + }{ + { + name: "rejected origin", + origin: "https://attacker.example", + csrfToken: status.CSRFToken, + }, + { + name: "rejected token", + origin: "http://example.com", + csrfToken: "another-session-token", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + headers := http.Header{ + "Cookie": []string{cookie.String()}, + "Origin": []string{tt.origin}, + csrfHeaderName: []string{tt.csrfToken}, + } + resp := performSessionRequest(t, srv, http.MethodPost, "/api/v1/collections", nil, headers, false) + assert.Equal(t, http.StatusForbidden, resp.Code, resp.Body.String()) + }) + } + + begin, done := gate.counts() + assert.Equal(t, 0, begin, "CSRF rejection must not acquire or wait on the operation gate") + assert.Equal(t, 0, done, "CSRF rejection must not release an operation gate it never acquired") +} + +func TestTrustedProxyForwardedHTTPSDefinesBrowserOrigin(t *testing.T) { + tests := []struct { + name string + forwarded http.Header + }{ + { + name: "Forwarded", + forwarded: http.Header{ + "Forwarded": []string{`for=192.0.2.20;proto=https;host=archive.example`}, + }, + }, + { + name: "X-Forwarded", + forwarded: http.Header{ + "X-Forwarded-Proto": []string{"https"}, + "X-Forwarded-Host": []string{"archive.example"}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertions := assert.New(t) + srv := newTrustedProxySessionTestServer(t) + login := performProxyRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), tt.forwarded) + require.Equal(t, http.StatusOK, login.Code, login.Body.String()) + status := decodeSessionStatus(t, login) + assertions.True(status.HTTPS) + assertions.True(requireSessionCookie(t, login).Secure) + + headers := tt.forwarded.Clone() + headers.Set("Cookie", requireSessionCookie(t, login).String()) + headers.Set("Origin", "https://archive.example") + headers.Set(csrfHeaderName, status.CSRFToken) + logout := performProxyRequest(t, srv, http.MethodDelete, sessionPath, nil, headers) + assertions.Equal(http.StatusNoContent, logout.Code, logout.Body.String()) + }) + } +} + +func TestTrustedProxyRejectsAmbiguousForwardedSchemeOrHost(t *testing.T) { + tests := []struct { + name string + headers http.Header + }{ + { + name: "multiple Forwarded elements", + headers: http.Header{ + "Forwarded": []string{"proto=https;host=one.example, proto=https;host=two.example"}, + }, + }, + { + name: "multiple X-Forwarded schemes", + headers: http.Header{ + "X-Forwarded-Proto": []string{"https", "http"}, + }, + }, + { + name: "comma-separated X-Forwarded hosts", + headers: http.Header{ + "X-Forwarded-Host": []string{"one.example, two.example"}, + }, + }, + { + name: "mixed forwarding standards", + headers: http.Header{ + "Forwarded": []string{"proto=https;host=one.example"}, + "X-Forwarded-Proto": []string{"https"}, + "X-Forwarded-Host": []string{"one.example"}, + }, + }, + { + name: "empty Forwarded scheme", + headers: http.Header{ + "Forwarded": []string{"proto=;host=archive.example"}, + }, + }, + { + name: "empty Forwarded host", + headers: http.Header{ + "Forwarded": []string{"proto=https;host="}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + srv := newTrustedProxySessionTestServer(t) + resp := performProxyRequest(t, srv, http.MethodGet, sessionPath, nil, tt.headers) + assert.Equal(t, http.StatusBadRequest, resp.Code, resp.Body.String()) + }) + } +} + +func TestMalformedForwardedLoginRemainsNoStoreAndRateLimited(t *testing.T) { + assertions := assert.New(t) + srv := newTrustedProxySessionTestServer(t) + srv.rateLimiter.rate = 0 + srv.rateLimiter.burst = 1 + headers := http.Header{ + "Forwarded": []string{"proto=https;host=one.example, proto=https;host=two.example"}, + } + body := []byte(`{"api_key":"` + testSessionAPIKey + `"}`) + + first := performProxyRequest(t, srv, http.MethodPost, sessionLoginPath, body, headers) + assertions.Equal(http.StatusBadRequest, first.Code, first.Body.String()) + assertions.Equal("no-store", first.Header().Get("Cache-Control")) + + second := performProxyRequest(t, srv, http.MethodPost, sessionLoginPath, body, headers) + assertions.Equal(http.StatusTooManyRequests, second.Code, second.Body.String()) + assertions.Equal("no-store", second.Header().Get("Cache-Control")) +} + +func newTrustedProxySessionTestServer(t *testing.T) *Server { + t.Helper() + srv := NewServer(&config.Config{Server: config.ServerConfig{ + APIKey: testSessionAPIKey, + TrustedProxies: []string{"127.0.0.1/32"}, + }}, nil, nil, testLogger()) + t.Cleanup(func() { + require.NoError(t, srv.Shutdown(context.Background())) + }) + return srv +} + +func performProxyRequest( + t *testing.T, + srv *Server, + method string, + path string, + body []byte, + headers http.Header, +) *httptest.ResponseRecorder { + t.Helper() + req := httptest.NewRequest(method, path, bytes.NewReader(body)) + req.RemoteAddr = "127.0.0.1:4242" + req.Host = "proxy.internal:8080" + if len(body) > 0 { + req.Header.Set("Content-Type", "application/json") + } + for name, values := range headers { + for _, value := range values { + req.Header.Add(name, value) + } + } + if req.URL.Scheme == "https" { + req.TLS = &tls.ConnectionState{} + } + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + return resp +} diff --git a/internal/api/deletions.go b/internal/api/deletions.go index bbe1c5e7a..d581146cb 100644 --- a/internal/api/deletions.go +++ b/internal/api/deletions.go @@ -97,10 +97,12 @@ func (f *StageDeletionFilter) toMessageFilter() (query.MessageFilter, *apiHTTPEr // StageDeletionRequest is the POST /api/v1/deletions body. type StageDeletionRequest struct { - Filter *StageDeletionFilter `json:"filter,omitempty"` - MessageIDs []int64 `json:"message_ids,omitempty"` - Description string `json:"description,omitempty"` - DryRun bool `json:"dry_run,omitempty"` + Filter *StageDeletionFilter `json:"filter,omitempty"` + MessageIDs []int64 `json:"message_ids,omitempty"` + Selection *ExploreSelection `json:"selection,omitempty"` + OperationToken string `json:"operation_token,omitempty"` + Description string `json:"description,omitempty"` + DryRun bool `json:"dry_run,omitempty"` } // StageDeletionResponse covers both dry-run (200) and create (201). @@ -128,6 +130,18 @@ type ListDeletionsResponse struct { Manifests []DeletionManifestSummary `json:"manifests"` } +type DeletionManifestDetail struct { + ID string `json:"id"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + CreatedBy string `json:"created_by"` + Description string `json:"description"` + Account string `json:"account,omitempty"` + MessageCount int `json:"message_count"` + Summary *deletion.Summary `json:"summary,omitempty"` + Execution *deletion.Execution `json:"execution,omitempty"` +} + // CancelDeletionResponse is the DELETE /api/v1/deletions/{id} body. type CancelDeletionResponse struct { ID string `json:"id"` @@ -156,16 +170,37 @@ func (s *Server) handleStageDeletion(w http.ResponseWriter, r *http.Request) { fmt.Sprintf("invalid JSON request body: %v", err)) return } + if req.Selection != nil && (!req.Filter.isEmpty() || len(req.MessageIDs) > 0) { + writeError(w, http.StatusBadRequest, "invalid_request", "selection cannot be combined with filter or message_ids") + return + } if req.Filter.isEmpty() && len(req.MessageIDs) == 0 { - writeError(w, http.StatusBadRequest, "empty_filter", - "At least one filter criterion or message_ids entry is required; staging the entire archive is not supported") + if req.Selection == nil { + writeError(w, http.StatusBadRequest, "empty_filter", + "At least one filter criterion or message_ids entry is required; staging the entire archive is not supported") + return + } + } + if req.Selection == nil && !req.Filter.isEmpty() && !req.DryRun { + writeError(w, http.StatusPreconditionRequired, "preflight_required", + "Deletion staging requires the exact selection and operation token returned by preflight") return } - gmailIDs, httpErr := s.resolveStageDeletionIDs(r.Context(), &req) - if httpErr != nil { - writeAPIHTTPError(w, httpErr) - return + var gmailIDs []string + if req.Selection != nil { + var ok bool + gmailIDs, ok = s.resolveAuthorizedDeletionSelection(w, r, *req.Selection, req.OperationToken, req.DryRun) + if !ok { + return + } + } else { + var httpErr *apiHTTPError + gmailIDs, httpErr = s.resolveStageDeletionIDs(r.Context(), &req) + if httpErr != nil { + writeAPIHTTPError(w, httpErr) + return + } } if len(gmailIDs) == 0 { writeError(w, http.StatusBadRequest, "no_messages_matched", "No messages matched the given criteria") @@ -224,6 +259,119 @@ func (s *Server) handleStageDeletion(w http.ResponseWriter, r *http.Request) { }) } +func (s *Server) resolveAuthorizedDeletionSelection( + w http.ResponseWriter, + r *http.Request, + selection ExploreSelection, + token string, + dryRun bool, +) ([]string, bool) { + if token == "" { + writeError(w, http.StatusPreconditionRequired, "preflight_required", + "operation_token from deletion preflight is required") + return nil, false + } + if selection.Mode != "explicit" && selection.Mode != "all_matching" { + writeError(w, http.StatusBadRequest, "invalid_selection", "selection mode must be explicit or all_matching") + return nil, false + } + predicate, err := prepareExplorePredicate(selection.Predicate) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_selection_predicate", err.Error()) + return nil, false + } + if selection.CacheRevision == "" { + writeError(w, http.StatusBadRequest, "invalid_selection", "cache_revision is required") + return nil, false + } + if predicate.request.SearchMode == exploreSearchModeSemantic || predicate.request.SearchMode == exploreSearchModeHybrid { + if selection.CandidateSnapshotID == "" { + writeError(w, http.StatusBadRequest, "candidate_snapshot_required", + "Semantic and hybrid deletion staging require the server-issued candidate snapshot") + return nil, false + } + predicate.request.CandidateSnapshotID = selection.CandidateSnapshotID + } + searchSpec, _, resolved := s.resolveExploreSearch(r.Context(), w, predicate.request) + if !resolved || !requireCompleteCandidatePool(w, searchSpec) { + return nil, false + } + predicate.query.Search = searchSpec + selectionRequest := query.ExploreSelectionRequest{ + Explore: predicate.query, ExcludedKeys: selection.Exclusions, IncludeDeletableMessageIDs: true, + } + if selection.Mode == "explicit" { + if selection.RowKeys == nil { + writeError(w, http.StatusBadRequest, "invalid_selection", "explicit selection requires row_keys") + return nil, false + } + selectionRequest.IncludedKeys = selection.RowKeys + } + analyzer, ok := s.engine.(query.Explorer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return nil, false + } + stats, err := analyzer.ExploreSelectionStats(r.Context(), selectionRequest) + if err != nil { + s.writeExploreError(w, err) + return nil, false + } + if selection.CacheRevision != stats.CacheRevision { + writeError(w, http.StatusConflict, "archive_revision_changed", "The committed analytical cache changed; run preflight again") + return nil, false + } + if searchSpec.Mode != query.SearchNone && !sameSearchProvenance(selection.SearchProvenance, stats.SearchProvenance) { + writeError(w, http.StatusConflict, "search_revision_changed", "The search index revision changed; run preflight again") + return nil, false + } + selectionHash := hashCanonicalValue(&selection, false) + state := s.exploreState + if state == nil { + writeError(w, http.StatusConflict, "operation_token_invalid", "Deletion preflight expired; run preflight again") + return nil, false + } + grant, authorized := state.operation(token, selectionHash) + if !authorized || grant.Revision != stats.CacheRevision || grant.Count != stats.Count { + writeError(w, http.StatusConflict, "operation_token_invalid", "Deletion preflight expired or no longer matches; run preflight again") + return nil, false + } + if stats.Count == 0 { + writeError(w, http.StatusBadRequest, "no_messages_matched", "No messages matched the reviewed selection") + return nil, false + } + if stats.DeletableCount != stats.Count { + writeError(w, http.StatusConflict, "selection_not_deletable", + "The reviewed selection contains items that cannot be deleted from their source") + return nil, false + } + resolver, ok := s.engine.(deletionMessageIDResolver) + if !ok { + writeError(w, http.StatusServiceUnavailable, "engine_unavailable", + "selection deletion staging is not supported by this query engine") + return nil, false + } + gmailIDs, err := resolver.GetGmailIDsByMessageIDs(r.Context(), stats.DeletableMessageIDs) + if err != nil { + s.logger.Error("stage deletion selection resolution failed", "error", err) + writeError(w, http.StatusInternalServerError, "internal_error", "Gmail ID query failed") + return nil, false + } + if int64(len(gmailIDs)) != stats.Count { + writeError(w, http.StatusConflict, "selection_changed", + "The reviewed selection changed before staging; run preflight again") + return nil, false + } + if !dryRun { + consumed, taken := state.takeOperation(token, selectionHash) + if !taken || consumed.Revision != grant.Revision || consumed.Count != grant.Count { + writeError(w, http.StatusConflict, "operation_token_invalid", "Deletion preflight was already used or expired; run preflight again") + return nil, false + } + } + return gmailIDs, true +} + // resolveStageDeletionAccount maps the staged Gmail IDs to their owning // account and requires exactly one: deletion manifests execute against a // single mailbox, so selections spanning multiple Gmail accounts must be @@ -335,6 +483,35 @@ func (s *Server) handleListDeletions(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, ListDeletionsResponse{Manifests: summaries}) } +func (s *Server) handleGetDeletion(w http.ResponseWriter, r *http.Request) { + canceller, ok := s.store.(DeletionManifestCanceller) + if !ok { + writeAPIHTTPError(w, cliStoreUnavailableError()) + return + } + id := r.PathValue("id") + if err := deletion.ValidateManifestID(id); err != nil { + writeError(w, http.StatusBadRequest, "invalid_manifest_id", err.Error()) + return + } + manifest, status, err := canceller.GetDeletionManifest(r.Context(), id) + if errors.Is(err, deletion.ErrManifestNotFound) { + writeError(w, http.StatusNotFound, "not_found", fmt.Sprintf("deletion manifest %q not found", id)) + return + } + if err != nil { + s.logger.Error("failed to load deletion manifest", "id", id, "error", err) + writeError(w, http.StatusInternalServerError, "internal_error", "Failed to load deletion manifest") + return + } + writeJSON(w, http.StatusOK, DeletionManifestDetail{ + ID: manifest.ID, Status: string(status), CreatedAt: manifest.CreatedAt, + CreatedBy: manifest.CreatedBy, Description: manifest.Description, + Account: manifest.Filters.Account, MessageCount: len(manifest.GmailIDs), + Summary: manifest.Summary, Execution: manifest.Execution, + }) +} + func (s *Server) handleCancelDeletion(w http.ResponseWriter, r *http.Request) { canceller, ok := s.store.(DeletionManifestCanceller) if !ok { diff --git a/internal/api/deletions_test.go b/internal/api/deletions_test.go index b349387be..487c35964 100644 --- a/internal/api/deletions_test.go +++ b/internal/api/deletions_test.go @@ -22,12 +22,13 @@ import ( type deletionMockStore struct { mockStore - saved []*deletion.Manifest - manifests map[deletion.Status][]*deletion.Manifest - getStatus deletion.Status - getErr error - cancelled []string - cancelErr error + saved []*deletion.Manifest + manifests map[deletion.Status][]*deletion.Manifest + getStatus deletion.Status + getManifest *deletion.Manifest + getErr error + cancelled []string + cancelErr error } func (s *deletionMockStore) SaveCLIDeletionManifest(_ context.Context, m *deletion.Manifest) error { @@ -50,6 +51,9 @@ func (s *deletionMockStore) GetDeletionManifest(_ context.Context, id string) (* if s.getErr != nil { return nil, "", s.getErr } + if s.getManifest != nil { + return s.getManifest, s.getStatus, nil + } return &deletion.Manifest{ID: id, Status: s.getStatus}, s.getStatus, nil } @@ -80,7 +84,7 @@ func postDeletions(t *testing.T, srv *Server, body string) *httptest.ResponseRec return w } -func TestStageDeletionByFilter(t *testing.T) { +func TestStageDeletionLegacyFilterIsDryRunOnly(t *testing.T) { require := require.New(t) assert := assert.New(t) @@ -97,10 +101,10 @@ func TestStageDeletionByFilter(t *testing.T) { w := postDeletions(t, srv, `{ "filter": {"sender": "alice@example.com", "after": "2019-01-01", "before": "2020-01-01"}, - "description": "old alice mail" + "description": "old alice mail", "dry_run": true }`) - require.Equal(http.StatusCreated, w.Code, "status (body: %s)", w.Body.String()) + require.Equal(http.StatusOK, w.Code, "status (body: %s)", w.Body.String()) assert.Equal("alice@example.com", gotFilter.Sender) require.NotNil(gotFilter.After, "after parsed") assert.Equal(time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC), gotFilter.After.UTC()) @@ -108,33 +112,24 @@ func TestStageDeletionByFilter(t *testing.T) { var resp StageDeletionResponse require.NoError(json.NewDecoder(w.Body).Decode(&resp), "decode") - assert.False(resp.DryRun) + assert.True(resp.DryRun) assert.Equal(2, resp.MessageCount) assert.Equal("user@example.com", resp.Account, "resolved account reported") - assert.NotEmpty(resp.ID) - assert.Equal("pending", resp.Status) - assert.Empty(resp.SampleGmailIDs, "create response has no sample") - - require.Len(st.saved, 1, "manifest saved") - m := st.saved[0] - assert.Equal("api", m.CreatedBy) - assert.Equal([]string{"gm-1", "gm-2"}, m.GmailIDs) - assert.Equal([]string{"alice@example.com"}, m.Filters.Senders) - assert.Equal("2019-01-01", m.Filters.After) - assert.Equal("user@example.com", m.Filters.Account, - "manifest must carry the account delete-staged executes against") - assert.NotEmpty(m.RawFilter, "raw provenance recorded") + assert.Equal([]string{"gm-1", "gm-2"}, resp.SampleGmailIDs) + assert.Empty(resp.ID) + assert.Empty(st.saved, "legacy filter preview must not persist a manifest") + + mutation := postDeletions(t, srv, `{"filter":{"sender":"alice@example.com"}}`) + assert.Equal(http.StatusPreconditionRequired, mutation.Code, mutation.Body.String()) + assert.Contains(mutation.Body.String(), "preflight_required") } -func TestStageDeletionByMessageIDsUnionsAndDedupes(t *testing.T) { +func TestStageDeletionLegacyExplicitMessageIDsRemainCompatible(t *testing.T) { require := require.New(t) assert := assert.New(t) st := &deletionMockStore{} engine := &querytest.MockEngine{ - GetGmailIDsByFilterFunc: func(_ context.Context, _ query.MessageFilter) ([]string, error) { - return []string{"gm-1", "gm-2"}, nil - }, GetGmailIDsByMessageIDsFunc: func(_ context.Context, ids []int64) ([]string, error) { assert.Equal([]int64{7, 8}, ids) return []string{"gm-2", "gm-3"}, nil @@ -143,12 +138,26 @@ func TestStageDeletionByMessageIDsUnionsAndDedupes(t *testing.T) { } srv := newDeletionTestServer(t, st, engine) - w := postDeletions(t, srv, `{"filter": {"sender": "alice@example.com"}, "message_ids": [7, 8]}`) + w := postDeletions(t, srv, `{"message_ids": [7, 8]}`) require.Equal(http.StatusCreated, w.Code, "status (body: %s)", w.Body.String()) + var response StageDeletionResponse + require.NoError(json.Unmarshal(w.Body.Bytes(), &response)) + assert.Equal(2, response.MessageCount) + assert.Equal("user@example.com", response.Account) require.Len(st.saved, 1) - assert.Equal([]string{"gm-1", "gm-2", "gm-3"}, st.saved[0].GmailIDs, "union, deduped, order-preserving") - assert.Equal("user@example.com", st.saved[0].Filters.Account, "account stamped on message-ID staging") + assert.Equal([]string{"gm-2", "gm-3"}, st.saved[0].GmailIDs) +} + +func TestStageDeletionLegacyFilterCannotPiggybackOnExplicitMessageIDs(t *testing.T) { + st := &deletionMockStore{} + srv := newDeletionTestServer(t, st, &querytest.MockEngine{}) + + w := postDeletions(t, srv, `{"filter":{"sender":"alice@example.com"},"message_ids":[7,8]}`) + + assert.Equal(t, http.StatusPreconditionRequired, w.Code, w.Body.String()) + assert.Contains(t, w.Body.String(), "preflight_required") + assert.Empty(t, st.saved) } func TestStageDeletionRejectsMultiAccountSelection(t *testing.T) { @@ -166,7 +175,6 @@ func TestStageDeletionRejectsMultiAccountSelection(t *testing.T) { srv := newDeletionTestServer(t, st, engine) for _, body := range []string{ - `{"filter": {"domain": "example.com"}}`, `{"filter": {"domain": "example.com"}, "dry_run": true}`, } { w := postDeletions(t, srv, body) @@ -203,6 +211,52 @@ func TestStageDeletionDryRun(t *testing.T) { assert.Empty(st.saved, "dry run writes nothing") } +func TestStageDeletionSelectionRequiresAndConsumesExactPreflightAuthority(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + engine := newExploreDuckDBFixture(t) + st := &deletionMockStore{} + srv := newDeletionTestServer(t, st, engine) + + explore := postExploreJSON(t, srv, "/api/v1/explore", `{"filters":[{"dimension":"source","values":["1"]}]}`) + requirements.Equal(http.StatusOK, explore.Code, explore.Body.String()) + var explored struct { + CacheRevision string `json:"cache_revision"` + } + requirements.NoError(json.Unmarshal(explore.Body.Bytes(), &explored)) + selection := fmt.Sprintf(`{"mode":"all_matching","predicate":{"filters":[{"dimension":"source","values":["1"]}]},"exclusions":["source:1:message:m1"],"cache_revision":%q}`, explored.CacheRevision) + + withoutPreflight := postDeletions(t, srv, `{"selection":`+selection+`}`) + assertions.Equal(http.StatusPreconditionRequired, withoutPreflight.Code, withoutPreflight.Body.String()) + assertions.Contains(withoutPreflight.Body.String(), "preflight_required") + + preflight := postExploreJSON(t, srv, "/api/v1/explore/preflight", `{"selection":`+selection+`}`) + requirements.Equal(http.StatusOK, preflight.Code, preflight.Body.String()) + var reviewed ExplorePreflightResponse + requirements.NoError(json.Unmarshal(preflight.Body.Bytes(), &reviewed)) + requirements.NotEmpty(reviewed.OperationToken) + + dryRun := postDeletions(t, srv, fmt.Sprintf(`{"selection":%s,"operation_token":%q,"dry_run":true}`, selection, reviewed.OperationToken)) + requirements.Equal(http.StatusOK, dryRun.Code, dryRun.Body.String()) + var preview StageDeletionResponse + requirements.NoError(json.Unmarshal(dryRun.Body.Bytes(), &preview)) + assertions.True(preview.DryRun) + assertions.Equal(1, preview.MessageCount) + assertions.Equal([]string{"m2"}, preview.SampleGmailIDs) + assertions.Empty(st.saved, "dry-run must not persist a manifest") + + stage := postDeletions(t, srv, fmt.Sprintf(`{"selection":%s,"operation_token":%q,"description":"reviewed batch"}`, selection, reviewed.OperationToken)) + requirements.Equal(http.StatusCreated, stage.Code, stage.Body.String()) + requirements.Len(st.saved, 1) + assertions.Equal([]string{"m2"}, st.saved[0].GmailIDs) + assertions.Equal("archive-a@example.com", st.saved[0].Filters.Account) + + reused := postDeletions(t, srv, fmt.Sprintf(`{"selection":%s,"operation_token":%q}`, selection, reviewed.OperationToken)) + assertions.Equal(http.StatusConflict, reused.Code, reused.Body.String()) + assertions.Contains(reused.Body.String(), "operation_token_invalid") + assertions.Len(st.saved, 1, "one-shot authority must not create another manifest") +} + func TestStageDeletionRejectsEmptyFilter(t *testing.T) { st := &deletionMockStore{} srv := newDeletionTestServer(t, st, &querytest.MockEngine{}) @@ -224,7 +278,7 @@ func TestStageDeletionNoMatches(t *testing.T) { } srv := newDeletionTestServer(t, st, engine) - w := postDeletions(t, srv, `{"filter": {"sender": "nobody@example.com"}}`) + w := postDeletions(t, srv, `{"filter": {"sender": "nobody@example.com"}, "dry_run": true}`) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, w.Body.String(), "no_messages_matched") @@ -233,7 +287,7 @@ func TestStageDeletionNoMatches(t *testing.T) { func TestStageDeletionInvalidDate(t *testing.T) { srv := newDeletionTestServer(t, &deletionMockStore{}, &querytest.MockEngine{}) - w := postDeletions(t, srv, `{"filter": {"after": "not-a-date"}}`) + w := postDeletions(t, srv, `{"filter": {"after": "not-a-date"}, "dry_run": true}`) assert.Equal(t, http.StatusBadRequest, w.Code) assert.Contains(t, w.Body.String(), "invalid_date") } @@ -305,6 +359,36 @@ func TestListDeletions(t *testing.T) { assert.Contains(w.Body.String(), "invalid_status") } +func TestGetDeletionReturnsManifestLifecycleDetail(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + completedAt := time.Date(2026, 7, 1, 13, 0, 0, 0, time.UTC) + manifest := &deletion.Manifest{ + ID: "batch-1", Status: deletion.StatusFailed, CreatedAt: completedAt.Add(-time.Hour), + CreatedBy: "api", Description: "reviewed batch", GmailIDs: []string{"gm-1", "gm-2"}, + Filters: deletion.Filters{Account: "user@example.com"}, + Execution: &deletion.Execution{StartedAt: completedAt.Add(-time.Minute), CompletedAt: &completedAt, + Method: deletion.MethodTrash, Succeeded: 1, Failed: 1, FailedIDs: []string{"gm-2"}}, + } + st := &deletionMockStore{getStatus: deletion.StatusFailed} + st.getManifest = manifest + srv := newDeletionTestServer(t, st, &querytest.MockEngine{}) + + w := httptest.NewRecorder() + srv.Router().ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/api/v1/deletions/batch-1", nil)) + + requirements.Equal(http.StatusOK, w.Code, w.Body.String()) + var detail DeletionManifestDetail + requirements.NoError(json.Unmarshal(w.Body.Bytes(), &detail)) + assertions.Equal("batch-1", detail.ID) + assertions.Equal("failed", detail.Status) + assertions.Equal("user@example.com", detail.Account) + assertions.Equal(2, detail.MessageCount) + requirements.NotNil(detail.Execution) + assertions.Equal(1, detail.Execution.Failed) + assertions.Equal([]string{"gm-2"}, detail.Execution.FailedIDs) +} + func deleteDeletion(srv *Server, id string) *httptest.ResponseRecorder { w := httptest.NewRecorder() srv.Router().ServeHTTP(w, httptest.NewRequest(http.MethodDelete, "/api/v1/deletions/"+id, nil)) diff --git a/internal/api/explore.go b/internal/api/explore.go new file mode 100644 index 000000000..bbe047c62 --- /dev/null +++ b/internal/api/explore.go @@ -0,0 +1,1308 @@ +package api + +import ( + "context" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "net/http" + "slices" + "strconv" + "strings" + "time" + + "github.com/danielgtaylor/huma/v2" + "go.kenn.io/msgvault/internal/explorecatalog" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/search" + "go.kenn.io/msgvault/internal/vector" + "go.kenn.io/msgvault/internal/vector/hybrid" +) + +const apiSortDirectionDesc = "desc" + +const ( + exploreDefaultLimit = 100 + exploreMaxLimit = 500 + exploreSearchModeFullText = "full_text" + exploreSearchModeSemantic = "semantic" + exploreSearchModeHybrid = "hybrid" +) + +type ExploreFilter struct { + Dimension string `json:"dimension" enum:"source,participant,domain,message_type,after,before,deletion"` + Values []string `json:"values" minItems:"1"` +} + +type ExploreSort struct { + Field string `json:"field" enum:"occurred_at"` + Direction string `json:"direction" enum:"desc"` +} + +type ExploreGroupDimension string + +const ( + ExploreGroupSource ExploreGroupDimension = explorecatalog.GroupSource + ExploreGroupParticipant ExploreGroupDimension = explorecatalog.GroupParticipant + ExploreGroupDomain ExploreGroupDimension = explorecatalog.GroupDomain + ExploreGroupMessageType ExploreGroupDimension = explorecatalog.GroupMessageType + ExploreGroupKind ExploreGroupDimension = explorecatalog.GroupKind + ExploreGroupYear ExploreGroupDimension = explorecatalog.GroupYear + ExploreGroupMonth ExploreGroupDimension = explorecatalog.GroupMonth +) + +var exploreGroupDimensions = func() []ExploreGroupDimension { + values := explorecatalog.GroupingDimensions() + dimensions := make([]ExploreGroupDimension, len(values)) + for i, value := range values { + dimensions[i] = ExploreGroupDimension(value) + } + return dimensions +}() + +type ExploreHTTPRequest struct { + Filters []ExploreFilter `json:"filters,omitempty"` + Query string `json:"query,omitempty"` + SearchMode string `json:"search_mode,omitempty" enum:"full_text,semantic,hybrid"` + Grouping []ExploreGroupDimension `json:"grouping,omitempty"` + Presentation string `json:"presentation,omitempty" enum:"table,timeline,files"` + Sort []ExploreSort `json:"sort,omitempty"` + Cursor string `json:"cursor,omitempty"` + Limit int `json:"limit,omitempty" minimum:"0" maximum:"500"` + CandidateSnapshotID string `json:"candidate_snapshot_id,omitempty"` +} + +type ExploreHTTPResponse struct { + Rows []query.EntryRow `json:"rows"` + TotalCount *int64 `json:"total_count,omitempty"` + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + CandidateSnapshotID string `json:"candidate_snapshot_id,omitempty"` + NextCursor string `json:"next_cursor,omitempty"` + CandidatePoolSaturated bool `json:"candidate_pool_saturated,omitempty"` +} + +type ExploreGroupSort struct { + Field string `json:"field" enum:"key,count,estimated_bytes,latest_at"` + Direction string `json:"direction" enum:"asc,desc"` +} + +type ExploreGroupsHTTPRequest struct { + Filters []ExploreFilter `json:"filters,omitempty"` + Query string `json:"query,omitempty"` + SearchMode string `json:"search_mode,omitempty" enum:"full_text,semantic,hybrid"` + Grouping []ExploreGroupDimension `json:"grouping" minItems:"1" maxItems:"1"` + Presentation string `json:"presentation,omitempty" enum:"table"` + Sort []ExploreGroupSort `json:"sort,omitempty" maxItems:"1"` + Cursor string `json:"cursor,omitempty"` + Limit int `json:"limit,omitempty" minimum:"0" maximum:"500"` +} + +type ExploreGroupsHTTPResponse struct { + Rows []query.ExploreGroupRow `json:"rows"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + NextCursor string `json:"next_cursor,omitempty"` + CandidateSnapshotID string `json:"candidate_snapshot_id,omitempty"` +} + +type ExploreSelection struct { + Mode string `json:"mode" enum:"explicit,all_matching"` + Predicate ExploreHTTPRequest `json:"predicate"` + RowKeys []string `json:"row_keys,omitempty"` + Exclusions []string `json:"exclusions,omitempty"` + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + CandidateSnapshotID string `json:"candidate_snapshot_id,omitempty"` +} + +type ExplorePreflightRequest struct { + Selection ExploreSelection `json:"selection"` +} + +type ExploreUnavailableAction struct { + Action string `json:"action"` + Reason string `json:"reason"` +} + +type ExploreActionTarget struct { + Action string `json:"action"` + MessageID int64 `json:"message_id"` + Filename string `json:"filename"` +} + +type ExplorePreflightResponse struct { + Count int64 `json:"count"` + EstimatedBytes int64 `json:"estimated_bytes"` + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + UnavailableActions []ExploreUnavailableAction `json:"unavailable_actions"` + ActionTargets []ExploreActionTarget `json:"action_targets"` + OperationToken string `json:"operation_token"` + ExpiresAt time.Time `json:"expires_at"` +} + +type ExploreMatchCountsRequest struct { + Predicate ExploreHTTPRequest `json:"predicate"` + RowKeys []string `json:"row_keys" minItems:"1" maxItems:"500"` +} + +type ExploreRowMatchCount struct { + RowKey string `json:"row_key"` + Count int64 `json:"count"` +} + +type ExploreMatchCountsResponse struct { + Counts []ExploreRowMatchCount `json:"counts"` + CacheRevision string `json:"cache_revision"` + LexicalRevision string `json:"lexical_index_revision"` + CanonicalQueryHash string `json:"canonical_query_hash"` +} + +type exploreCursor struct { + Offset int `json:"offset"` + Request string `json:"request"` + Revision string `json:"revision"` + SearchRevision string `json:"search_revision,omitempty"` + Snapshot string `json:"snapshot,omitempty"` + IdentityRevision int64 `json:"identity_revision,omitempty"` + + // Timezone and CanonicalID pin a relationship timeline cursor (see + // handleRelationshipTimeline) to the request that produced it. Unlike + // every other explore-style cursor, a mismatch on any field here — + // including the request hash — maps uniformly to 409 cursor_invalidated + // rather than a 400/409 split, per that endpoint's contract. + Timezone string `json:"timezone,omitempty"` + CanonicalID int64 `json:"canonical_id,omitempty"` +} + +type explorePrepared struct { + request ExploreHTTPRequest + query query.ExploreRequest + requestHash string + offset int +} + +func (s *Server) registerExploreRoutes(api huma.API) { + registerExploreRoute[ExploreHTTPRequest, ExploreHTTPResponse](api, "explore", "/explore", "Explore canonical archive entries", s.handleExplore) + registerExploreRoute[ExploreGroupsHTTPRequest, ExploreGroupsHTTPResponse](api, "exploreGroups", "/explore/groups", "Group canonical archive entries", s.handleExploreGroups) + registerExploreRoute[ExplorePreflightRequest, ExplorePreflightResponse](api, "preflightExploreSelection", "/explore/preflight", "Preflight a revision-pinned analytical selection", s.handleExplorePreflight) + registerExploreRoute[ExploreMatchCountsRequest, ExploreMatchCountsResponse](api, "countExploreMatches", "/explore/match-counts", "Count exact lexical matches in visible rows", s.handleExploreMatchCounts) + s.registerExploreFilesRoute(api) +} + +func registerExploreRoute[Req any, Resp any](api huma.API, operationID, path, summary string, handler http.HandlerFunc) { + op := rawAPIV1Operation(operationID, http.MethodPost, path, summary) + op.Tags = []string{"Exploration"} + op.RequestBody = jsonRequestBodyFor[Req](api) + op.Responses = jsonResponsesFor[Resp](api) + addErrorResponses(api, op.Responses, http.StatusBadRequest, http.StatusConflict, http.StatusServiceUnavailable) + op.Responses[httpStatusKey(http.StatusServiceUnavailable)] = &huma.Response{ + Description: http.StatusText(http.StatusServiceUnavailable), + Content: map[string]*huma.MediaType{ + "application/json": {Schema: &huma.Schema{AnyOf: []*huma.Schema{ + schemaFor[ExploreCacheUnavailableResponse](api), + schemaFor[ErrorResponse](api), + }}}, + }, + } + registerRawHumaRoute(api, op, handler) +} + +func (s *Server) handleExplore(w http.ResponseWriter, r *http.Request) { + s.handleExploreWithScope(w, r, nil) +} + +func (s *Server) handleExploreWithScope(w http.ResponseWriter, r *http.Request, scope *ExploreFilter) { + prepared, ok := s.prepareExploreRequest(w, r, scope) + if !ok { + return + } + if prepared.request.Cursor != "" && (prepared.request.SearchMode == exploreSearchModeSemantic || prepared.request.SearchMode == exploreSearchModeHybrid) { + cursor, err := s.decodeExploreCursor(prepared.request.Cursor) + if err != nil || cursor.Snapshot == "" { + writeError(w, http.StatusBadRequest, "invalid_cursor", "semantic cursor is missing its candidate snapshot") + return + } + prepared.request.CandidateSnapshotID = cursor.Snapshot + } + searchSpec, snapshotID, ok := s.resolveExploreSearch(r.Context(), w, prepared.request) + if !ok { + return + } + if prepared.request.Cursor != "" { + cursor, _ := s.decodeExploreCursor(prepared.request.Cursor) + if cursor.SearchRevision != exploreResolvedSearchRevision(searchSpec) { + writeError(w, http.StatusConflict, "search_revision_changed", "The resolved search index revision changed; restart pagination") + return + } + } + prepared.query.Search = searchSpec + if scope != nil { + if err := applyIdentityScope(&prepared.query.Context, *scope); err != nil { + writeError(w, http.StatusConflict, "identity_scope_conflict", err.Error()) + return + } + } + semanticPage := searchSpec.Mode == query.SearchSemantic || searchSpec.Mode == query.SearchHybrid + if semanticPage { + prepared.query.Page = query.PageSpec{Limit: exploreMaxLimit} + } + explorer, ok := s.engine.(query.Explorer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + result, err := explorer.Explore(r.Context(), prepared.query) + if err != nil { + s.writeExploreError(w, err) + return + } + if snapshotID != "" { + s.enrichExploreSemanticRows(result.Rows, prepared.request, snapshotID) + } + semanticTotal := len(result.Rows) + if semanticPage { + start := min(prepared.offset, len(result.Rows)) + end := min(start+prepared.request.Limit, len(result.Rows)) + result.Rows = result.Rows[start:end] + } + if prepared.offset > 0 { + cursor, err := s.decodeExploreCursor(prepared.request.Cursor) + if err != nil || cursor.Revision != result.CacheRevision { + writeError(w, http.StatusConflict, "archive_revision_changed", "The committed analytical cache changed; restart pagination") + return + } + } + response := ExploreHTTPResponse{ + Rows: result.Rows, CacheRevision: result.CacheRevision, + SearchProvenance: result.SearchProvenance, CandidateSnapshotID: snapshotID, + } + response.CandidatePoolSaturated = searchSpec.CandidatePoolSaturated + if snapshotID != "" && s.exploreState != nil { + if snapshot, found := s.exploreState.snapshot(snapshotID, exploreSnapshotRequestHash(prepared.request)); found { + response.CandidatePoolSaturated = response.CandidatePoolSaturated || snapshot.PoolSaturated + } + } + if (prepared.query.Search.Mode == query.SearchNone || prepared.query.Search.Mode == query.SearchFullText) && !response.CandidatePoolSaturated { + response.TotalCount = &result.TotalCount + } + totalForPagination := int(result.TotalCount) + if semanticPage { + totalForPagination = semanticTotal + } + if nextOffset := prepared.offset + len(result.Rows); nextOffset < totalForPagination { + response.NextCursor = s.encodeExploreCursor(exploreCursor{ + Offset: nextOffset, Request: prepared.requestHash, Revision: result.CacheRevision, + SearchRevision: exploreResolvedSearchRevision(searchSpec), Snapshot: snapshotID, + }) + } + writeJSON(w, http.StatusOK, response) +} + +func (s *Server) handleExploreGroups(w http.ResponseWriter, r *http.Request) { + var request ExploreGroupsHTTPRequest + if !decodeExploreJSON(w, r, &request) { + return + } + for i := range request.Filters { + request.Filters[i].Dimension = strings.ToLower(strings.TrimSpace(request.Filters[i].Dimension)) + for j := range request.Filters[i].Values { + request.Filters[i].Values[j] = strings.TrimSpace(request.Filters[i].Values[j]) + } + slices.Sort(request.Filters[i].Values) + request.Filters[i].Values = slices.Compact(request.Filters[i].Values) + } + slices.SortFunc(request.Filters, func(a, b ExploreFilter) int { return strings.Compare(a.Dimension, b.Dimension) }) + request.Query = strings.TrimSpace(request.Query) + request.SearchMode = strings.ToLower(strings.TrimSpace(request.SearchMode)) + request.Presentation = strings.ToLower(strings.TrimSpace(request.Presentation)) + if err := validateExploreSearchPair(request.Query, request.SearchMode); err != nil { + writeError(w, http.StatusBadRequest, "invalid_search", err.Error()) + return + } + if request.Presentation == "" { + request.Presentation = "table" + } + if request.Presentation != "table" { + writeError(w, http.StatusBadRequest, "invalid_presentation", "Grouped exploration supports presentation=table") + return + } + if len(request.Grouping) != 1 { + writeError(w, http.StatusBadRequest, "invalid_grouping", "exactly one grouping dimension is required") + return + } + dimension := ExploreGroupDimension(strings.ToLower(strings.TrimSpace(string(request.Grouping[0])))) + if !slices.Contains(exploreGroupDimensions, dimension) { + writeError(w, http.StatusBadRequest, "invalid_grouping", fmt.Sprintf("unknown grouping dimension %q", dimension)) + return + } + ctx, err := exploreContext(request.Filters) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_filter", err.Error()) + return + } + if request.Limit == 0 { + request.Limit = exploreDefaultLimit + } + if request.Limit < 1 || request.Limit > exploreMaxLimit { + writeError(w, http.StatusBadRequest, "invalid_limit", fmt.Sprintf("limit must be between 1 and %d", exploreMaxLimit)) + return + } + sortSpec := query.SortSpec{Field: "count", Direction: apiSortDirectionDesc} + if len(request.Sort) > 1 { + writeError(w, http.StatusBadRequest, "invalid_sort", "at most one group sort is supported") + return + } + if len(request.Sort) == 1 { + sortSpec = query.SortSpec{Field: strings.ToLower(request.Sort[0].Field), Direction: strings.ToLower(request.Sort[0].Direction)} + if !slices.Contains([]string{"key", "count", "estimated_bytes", "latest_at"}, sortSpec.Field) || + !slices.Contains([]string{"asc", apiSortDirectionDesc}, sortSpec.Direction) { + writeError(w, http.StatusBadRequest, "invalid_sort", "unknown group sort field or direction") + return + } + } + request.Grouping = []ExploreGroupDimension{dimension} + requestHash := hashCanonicalValue(request, true) + offset, ok := s.parseExploreCursor(w, request.Cursor, requestHash) + if !ok { + return + } + searchRequest := ExploreHTTPRequest{Filters: request.Filters, Query: request.Query, SearchMode: request.SearchMode} + canonicalizeExploreRequest(&searchRequest) + var cursor exploreCursor + if request.Cursor != "" { + cursor, _ = s.decodeExploreCursor(request.Cursor) + if request.SearchMode == exploreSearchModeSemantic || request.SearchMode == exploreSearchModeHybrid { + if cursor.Snapshot == "" { + writeError(w, http.StatusBadRequest, "invalid_cursor", "semantic cursor is missing its candidate snapshot") + return + } + searchRequest.CandidateSnapshotID = cursor.Snapshot + } + } + searchSpec, snapshotID, ok := s.resolveExploreSearch(r.Context(), w, searchRequest) + if !ok || !requireCompleteCandidatePool(w, searchSpec) { + return + } + if request.Cursor != "" && cursor.SearchRevision != exploreResolvedSearchRevision(searchSpec) { + writeError(w, http.StatusConflict, "search_revision_changed", "The resolved search index revision changed; restart pagination") + return + } + analyzer, ok := s.engine.(query.Explorer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + result, err := analyzer.ExploreGroups(r.Context(), query.ExploreGroupRequest{ + Explore: query.ExploreRequest{Context: ctx, Search: searchSpec}, Dimension: string(dimension), Sort: sortSpec, + Page: query.PageSpec{Limit: request.Limit, Offset: offset}, + }) + if err != nil { + s.writeExploreError(w, err) + return + } + if request.Cursor != "" { + cursor, _ := s.decodeExploreCursor(request.Cursor) + if cursor.Revision != result.CacheRevision { + writeError(w, http.StatusConflict, "archive_revision_changed", "The committed analytical cache changed; restart pagination") + return + } + } + response := ExploreGroupsHTTPResponse{Rows: result.Rows, TotalCount: result.TotalCount, CacheRevision: result.CacheRevision, SearchProvenance: result.SearchProvenance, CandidateSnapshotID: snapshotID} + if next := offset + len(result.Rows); next < int(result.TotalCount) { + response.NextCursor = s.encodeExploreCursor(exploreCursor{ + Offset: next, Request: requestHash, Revision: result.CacheRevision, + SearchRevision: exploreResolvedSearchRevision(searchSpec), Snapshot: snapshotID, + }) + } + writeJSON(w, http.StatusOK, response) +} + +func (s *Server) handleExplorePreflight(w http.ResponseWriter, r *http.Request) { + var request ExplorePreflightRequest + if !decodeExploreJSON(w, r, &request) { + return + } + selection := &request.Selection + if selection.Mode != "explicit" && selection.Mode != "all_matching" { + writeError(w, http.StatusBadRequest, "invalid_selection", "selection mode must be explicit or all_matching") + return + } + predicate, err := prepareExplorePredicate(selection.Predicate) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_selection_predicate", err.Error()) + return + } + if selection.CacheRevision == "" { + writeError(w, http.StatusBadRequest, "invalid_selection", "cache_revision is required") + return + } + if predicate.request.SearchMode == exploreSearchModeSemantic || predicate.request.SearchMode == exploreSearchModeHybrid { + if selection.CandidateSnapshotID == "" { + writeError(w, http.StatusBadRequest, "candidate_snapshot_required", "Semantic and hybrid preflight require the server-issued candidate snapshot") + return + } + predicate.request.CandidateSnapshotID = selection.CandidateSnapshotID + } + searchSpec, _, ok := s.resolveExploreSearch(r.Context(), w, predicate.request) + if !ok || !requireCompleteCandidatePool(w, searchSpec) { + return + } + predicate.query.Search = searchSpec + selectionRequest := query.ExploreSelectionRequest{Explore: predicate.query, ExcludedKeys: selection.Exclusions} + if selection.Mode == "explicit" { + if selection.RowKeys == nil { + writeError(w, http.StatusBadRequest, "invalid_selection", "explicit selection requires row_keys") + return + } + selectionRequest.IncludedKeys = selection.RowKeys + } + analyzer, ok := s.engine.(query.Explorer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + stats, err := analyzer.ExploreSelectionStats(r.Context(), selectionRequest) + if err != nil { + s.writeExploreError(w, err) + return + } + if selection.CacheRevision != stats.CacheRevision { + writeError(w, http.StatusConflict, "archive_revision_changed", "The committed analytical cache changed; run preflight again") + return + } + if searchSpec.Mode != query.SearchNone && !sameSearchProvenance(selection.SearchProvenance, stats.SearchProvenance) { + writeError(w, http.StatusConflict, "search_revision_changed", "The search index revision changed; run preflight again") + return + } + selectionHash := hashCanonicalValue(selection, false) + state := s.exploreState + if state == nil { + state = newExploreServerState(time.Now) + s.exploreState = state + } + token := state.issueOperation(selectionHash, stats.Count, stats.CacheRevision) + unavailableActions := make([]ExploreUnavailableAction, 0, 4) + actionTargets := make([]ExploreActionTarget, 0, 1) + if stats.DeletableCount != stats.Count { + unavailableActions = append(unavailableActions, ExploreUnavailableAction{ + Action: "stage_deletion", Reason: "selection_contains_items_that_cannot_be_deleted_from_source", + }) + } + if stats.FileCount == 0 { + unavailableActions = append(unavailableActions, ExploreUnavailableAction{ + Action: "export_files", Reason: "selection_contains_no_files", + }) + } + if stats.Count != 1 { + unavailableActions = append(unavailableActions, ExploreUnavailableAction{ + Action: "export", Reason: "browser_export_requires_single_message", + }) + } else if stats.RawExportMessageID == nil { + unavailableActions = append(unavailableActions, ExploreUnavailableAction{ + Action: "export", Reason: "selection_has_no_exportable_raw_message", + }) + } else { + messageID := *stats.RawExportMessageID + raw, rawErr := s.engine.GetMessageRaw(r.Context(), messageID) + if rawErr != nil { + s.logger.Warn("raw export preflight failed", "message_id", messageID, "error", rawErr) + unavailableActions = append(unavailableActions, ExploreUnavailableAction{ + Action: "export", Reason: "raw_message_unavailable", + }) + } else if len(raw) == 0 { + unavailableActions = append(unavailableActions, ExploreUnavailableAction{ + Action: "export", Reason: "selection_has_no_exportable_raw_message", + }) + } else { + actionTargets = append(actionTargets, ExploreActionTarget{ + Action: "export", MessageID: messageID, Filename: fmt.Sprintf("message-%d.eml", messageID), + }) + } + } + unavailableActions = append(unavailableActions, ExploreUnavailableAction{ + Action: "open_in_source", Reason: "trusted_source_link_unavailable", + }) + writeJSON(w, http.StatusOK, ExplorePreflightResponse{ + Count: stats.Count, EstimatedBytes: stats.EstimatedBytes, CacheRevision: stats.CacheRevision, + SearchProvenance: stats.SearchProvenance, UnavailableActions: unavailableActions, + ActionTargets: actionTargets, + OperationToken: token, ExpiresAt: state.now().Add(exploreOperationTokenTTL), + }) +} + +func sameSearchProvenance(left, right query.SearchProvenance) bool { + if left.LexicalIndexRevision != right.LexicalIndexRevision { + return false + } + if left.VectorGeneration == nil || right.VectorGeneration == nil { + return left.VectorGeneration == nil && right.VectorGeneration == nil + } + return *left.VectorGeneration == *right.VectorGeneration +} + +func (s *Server) handleExploreMatchCounts(w http.ResponseWriter, r *http.Request) { + var request ExploreMatchCountsRequest + if !decodeExploreJSON(w, r, &request) { + return + } + if (request.Predicate.SearchMode != exploreSearchModeFullText && request.Predicate.SearchMode != exploreSearchModeHybrid) || request.Predicate.Query == "" { + writeError(w, http.StatusBadRequest, "lexical_search_required", "match counts require a full_text or hybrid predicate") + return + } + if len(request.RowKeys) == 0 || len(request.RowKeys) > exploreMaxLimit { + writeError(w, http.StatusBadRequest, "invalid_row_keys", fmt.Sprintf("row_keys must contain between 1 and %d entries", exploreMaxLimit)) + return + } + predicate, err := prepareExplorePredicate(request.Predicate) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_match_predicate", err.Error()) + return + } + searchSpec, _, ok := s.resolveExploreSearch(r.Context(), w, predicate.request) + if !ok || !requireCompleteCandidatePool(w, searchSpec) { + return + } + predicate.query.Search = searchSpec + slices.Sort(request.RowKeys) + request.RowKeys = slices.Compact(request.RowKeys) + analyzer, ok := s.engine.(query.Explorer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + state := s.exploreState + if state == nil { + state = newExploreServerState(time.Now) + s.exploreState = state + } + revisionProbe, err := analyzer.ExploreSelectionStats(r.Context(), query.ExploreSelectionRequest{ + Explore: predicate.query, IncludedKeys: []string{}, + }) + if err != nil { + s.writeExploreError(w, err) + return + } + cacheRequest := predicate.request + cacheRequest.Query = "" + canonicalQueryHash := exploreCanonicalQueryHash(predicate.request.Query) + cacheKey := canonicalExploreHash(cacheRequest) + "|" + canonicalQueryHash + "|" + revisionProbe.CacheRevision + "|" + searchSpec.LexicalIndexRevision + "|" + hashCanonicalValue(request.RowKeys, false) + if cached, found := state.getMatchCounts(cacheKey); found { + counts := make([]ExploreRowMatchCount, len(request.RowKeys)) + for i, key := range request.RowKeys { + counts[i] = ExploreRowMatchCount{RowKey: key, Count: cached[key]} + } + writeJSON(w, http.StatusOK, ExploreMatchCountsResponse{Counts: counts, CacheRevision: revisionProbe.CacheRevision, LexicalRevision: searchSpec.LexicalIndexRevision, CanonicalQueryHash: canonicalQueryHash}) + return + } + matchRequest := predicate.query + if searchSpec.Mode == query.SearchHybrid { + matchRequest.Search.Mode = query.SearchFullText + matchRequest.Search.CandidateMessageIDs = searchSpec.LexicalCandidateMessageIDs + matchRequest.Search.LexicalCandidateMessageIDs = nil + matchRequest.Search.VectorGeneration = nil + } + result, err := analyzer.ExploreMatchCounts(r.Context(), query.ExploreMatchCountsRequest{ + Explore: matchRequest, RowKeys: request.RowKeys, + }) + if err != nil { + s.writeExploreError(w, err) + return + } + state.putMatchCounts(cacheKey, result.Counts) + counts := make([]ExploreRowMatchCount, len(request.RowKeys)) + for i, key := range request.RowKeys { + counts[i] = ExploreRowMatchCount{RowKey: key, Count: result.Counts[key]} + } + writeJSON(w, http.StatusOK, ExploreMatchCountsResponse{ + Counts: counts, CacheRevision: result.CacheRevision, + LexicalRevision: result.SearchProvenance.LexicalIndexRevision, CanonicalQueryHash: canonicalQueryHash, + }) +} + +func exploreCanonicalQueryHash(queryText string) string { + parsed := search.Parse(queryText) + if parsed.Err() != nil { + return "" + } + return hashCanonicalValue(canonicalParsedExploreQuery(parsed), false) +} + +func (s *Server) prepareExploreRequest(w http.ResponseWriter, r *http.Request, scope *ExploreFilter) (explorePrepared, bool) { + var request ExploreHTTPRequest + if !decodeExploreJSON(w, r, &request) { + return explorePrepared{}, false + } + canonicalizeExploreRequest(&request) + if request.Limit == 0 { + request.Limit = exploreDefaultLimit + } + if request.Limit < 1 || request.Limit > exploreMaxLimit { + writeError(w, http.StatusBadRequest, "invalid_limit", fmt.Sprintf("limit must be between 1 and %d", exploreMaxLimit)) + return explorePrepared{}, false + } + if err := validateExploreSearchPair(request.Query, request.SearchMode); err != nil { + writeError(w, http.StatusBadRequest, "invalid_search", err.Error()) + return explorePrepared{}, false + } + if len(request.Grouping) != 0 { + writeError(w, http.StatusBadRequest, "invalid_grouping", "Grouping is available through /api/v1/explore/groups") + return explorePrepared{}, false + } + if request.Presentation == "" { + request.Presentation = "table" + } + if request.Presentation != "table" { + writeError(w, http.StatusBadRequest, "invalid_presentation", "Entry exploration supports presentation=table") + return explorePrepared{}, false + } + if len(request.Sort) == 0 { + request.Sort = []ExploreSort{{Field: "occurred_at", Direction: apiSortDirectionDesc}} + } + if len(request.Sort) != 1 || request.Sort[0].Field != "occurred_at" || request.Sort[0].Direction != apiSortDirectionDesc { + writeError(w, http.StatusBadRequest, "invalid_sort", "Entry exploration supports only occurred_at descending") + return explorePrepared{}, false + } + ctx, err := exploreContext(request.Filters) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_filter", err.Error()) + return explorePrepared{}, false + } + requestHash := canonicalScopedExploreHash(request, scope) + offset := 0 + if request.Cursor != "" { + cursor, err := s.decodeExploreCursor(request.Cursor) + if err != nil || cursor.Offset < 0 || cursor.Request != requestHash { + writeError(w, http.StatusBadRequest, "invalid_cursor", "cursor does not match the canonical request") + return explorePrepared{}, false + } + offset = cursor.Offset + } + return explorePrepared{ + request: request, requestHash: requestHash, offset: offset, + query: query.ExploreRequest{ + Context: ctx, Presentation: query.PresentationTable, + Sort: []query.SortSpec{{Field: "sent_at", Direction: apiSortDirectionDesc}}, + Page: query.PageSpec{Limit: request.Limit, Offset: offset}, + }, + }, true +} + +func canonicalScopedExploreHash(request ExploreHTTPRequest, scope *ExploreFilter) string { + if scope == nil { + return canonicalExploreHash(request) + } + request.Cursor = "" + return hashCanonicalValue(struct { + Request ExploreHTTPRequest `json:"request"` + Scope ExploreFilter `json:"identity_scope"` + }{Request: request, Scope: *scope}, false) +} + +func applyIdentityScope(context *query.Context, scope ExploreFilter) error { + switch scope.Dimension { + case "participant": + if len(scope.Values) != 1 { + return errors.New("participant identity scope requires one exact ID") + } + id, err := strconv.ParseInt(scope.Values[0], 10, 64) + if err != nil || id < 1 { + return errors.New("participant identity scope requires a positive integer ID") + } + if len(context.ParticipantIDs) > 0 && !slices.Contains(context.ParticipantIDs, id) { + return errors.New("the base predicate excludes the requested participant") + } + context.ParticipantIDs = []int64{id} + case "domain": + if len(scope.Values) != 1 { + return errors.New("domain identity scope requires one exact domain") + } + domain := strings.ToLower(strings.TrimSpace(scope.Values[0])) + if domain == "" { + return errors.New("domain identity scope requires an exact domain") + } + compatible := slices.ContainsFunc(context.Domains, func(candidate string) bool { + return strings.EqualFold(strings.TrimSpace(candidate), domain) + }) + if len(context.Domains) > 0 && !compatible { + return errors.New("the base predicate excludes the requested domain") + } + context.Domains = []string{domain} + default: + return fmt.Errorf("unsupported identity scope %q", scope.Dimension) + } + return nil +} + +func exploreContext(filters []ExploreFilter) (query.Context, error) { + var result query.Context + seen := map[string]struct{}{} + for _, filter := range filters { + if _, ok := seen[filter.Dimension]; ok { + return result, fmt.Errorf("filter dimension %q may appear only once", filter.Dimension) + } + seen[filter.Dimension] = struct{}{} + if len(filter.Values) == 0 { + return result, fmt.Errorf("filter dimension %q requires at least one value", filter.Dimension) + } + switch filter.Dimension { + case "source", "participant": + ids := make([]int64, len(filter.Values)) + for i, value := range filter.Values { + id, err := strconv.ParseInt(value, 10, 64) + if err != nil || id < 1 { + return result, fmt.Errorf("filter dimension %q requires positive integer IDs", filter.Dimension) + } + ids[i] = id + } + if filter.Dimension == "source" { + result.SourceIDs = ids + } else { + result.ParticipantIDs = ids + } + case "domain": + result.Domains = append([]string(nil), filter.Values...) + case "message_type": + result.MessageTypes = append([]string(nil), filter.Values...) + case "after", "before": + if len(filter.Values) != 1 { + return result, fmt.Errorf("filter dimension %q requires exactly one timestamp", filter.Dimension) + } + value, err := time.Parse(time.RFC3339, filter.Values[0]) + if err != nil { + return result, fmt.Errorf("filter dimension %q requires an RFC3339 timestamp", filter.Dimension) + } + if filter.Dimension == "after" { + result.After = &value + } else { + result.Before = &value + } + case "deletion": + if len(filter.Values) != 1 { + return result, errors.New("filter dimension \"deletion\" requires exactly one value") + } + result.Deletion = query.DeletionFilter(filter.Values[0]) + if result.Deletion != query.DeletionActive && result.Deletion != query.DeletionDeleted && result.Deletion != query.DeletionAny { + return result, errors.New("filter dimension \"deletion\" accepts active or deleted") + } + default: + return result, fmt.Errorf("unknown filter dimension %q", filter.Dimension) + } + } + return result, nil +} + +func validateExploreSearchPair(queryText, mode string) error { + if (queryText == "") != (mode == "") { + return errors.New("query and search_mode must be provided together") + } + if mode != "" && !slices.Contains([]string{exploreSearchModeFullText, exploreSearchModeSemantic, exploreSearchModeHybrid}, mode) { + return errors.New("search_mode must be full_text, semantic, or hybrid") + } + return nil +} + +func canonicalizeExploreRequest(request *ExploreHTTPRequest) { + for i := range request.Filters { + request.Filters[i].Dimension = strings.ToLower(strings.TrimSpace(request.Filters[i].Dimension)) + for j := range request.Filters[i].Values { + request.Filters[i].Values[j] = strings.TrimSpace(request.Filters[i].Values[j]) + } + slices.Sort(request.Filters[i].Values) + request.Filters[i].Values = slices.Compact(request.Filters[i].Values) + } + slices.SortFunc(request.Filters, func(a, b ExploreFilter) int { return strings.Compare(a.Dimension, b.Dimension) }) + request.Query = strings.TrimSpace(request.Query) + request.SearchMode = strings.ToLower(strings.TrimSpace(request.SearchMode)) + request.Presentation = strings.ToLower(strings.TrimSpace(request.Presentation)) + for i := range request.Grouping { + request.Grouping[i] = ExploreGroupDimension(strings.ToLower(strings.TrimSpace(string(request.Grouping[i])))) + } + for i := range request.Sort { + request.Sort[i].Field = strings.ToLower(strings.TrimSpace(request.Sort[i].Field)) + request.Sort[i].Direction = strings.ToLower(strings.TrimSpace(request.Sort[i].Direction)) + } +} + +func canonicalExploreHash(request ExploreHTTPRequest) string { + request.Cursor = "" + data, err := json.Marshal(request) + if err != nil { + panic(fmt.Sprintf("marshal canonical explore request: %v", err)) + } + return fmt.Sprintf("explore-%x", sha256.Sum256(data)) +} + +func hashCanonicalValue(value any, clearCursor bool) string { + if clearCursor { + switch request := value.(type) { + case ExploreGroupsHTTPRequest: + request.Cursor = "" + value = request + case ExploreHTTPRequest: + request.Cursor = "" + value = request + } + } + data, err := json.Marshal(value) + if err != nil { + panic(fmt.Sprintf("marshal canonical explore value: %v", err)) + } + return fmt.Sprintf("explore-%x", sha256.Sum256(data)) +} + +func exploreResolvedSearchRevision(spec query.SearchSpec) string { + if spec.LexicalIndexRevision != "" { + return spec.LexicalIndexRevision + } + if spec.VectorGeneration != nil { + return fmt.Sprintf("vector:%d", *spec.VectorGeneration) + } + return "" +} + +func (s *Server) parseExploreCursor(w http.ResponseWriter, encoded, requestHash string) (int, bool) { + if encoded == "" { + return 0, true + } + cursor, err := s.decodeExploreCursor(encoded) + if err != nil || cursor.Offset < 0 || cursor.Request != requestHash { + writeError(w, http.StatusBadRequest, "invalid_cursor", "cursor does not match the canonical request") + return 0, false + } + return cursor.Offset, true +} + +func prepareExplorePredicate(request ExploreHTTPRequest) (explorePrepared, error) { + canonicalizeExploreRequest(&request) + if err := validateExploreSearchPair(request.Query, request.SearchMode); err != nil { + return explorePrepared{}, err + } + if request.Cursor != "" { + return explorePrepared{}, errors.New("selection predicates must not contain a cursor") + } + for _, dimension := range request.Grouping { + if !slices.Contains(exploreGroupDimensions, dimension) { + return explorePrepared{}, fmt.Errorf("unknown grouping dimension %q", dimension) + } + } + if request.Presentation == "" { + request.Presentation = "table" + } + if !slices.Contains([]string{"table", "timeline", "files"}, request.Presentation) { + return explorePrepared{}, fmt.Errorf("unknown presentation %q", request.Presentation) + } + if len(request.Sort) == 0 { + request.Sort = []ExploreSort{{Field: "occurred_at", Direction: apiSortDirectionDesc}} + } + if len(request.Sort) != 1 || request.Sort[0].Field != "occurred_at" || request.Sort[0].Direction != apiSortDirectionDesc { + return explorePrepared{}, errors.New("selection predicate supports only occurred_at descending") + } + ctx, err := exploreContext(request.Filters) + if err != nil { + return explorePrepared{}, err + } + return explorePrepared{ + request: request, requestHash: canonicalExploreHash(request), + query: query.ExploreRequest{Context: ctx}, + }, nil +} + +func (s *Server) resolveExploreSearch(ctx context.Context, w http.ResponseWriter, request ExploreHTTPRequest) (query.SearchSpec, string, bool) { + if request.SearchMode == "" { + return query.SearchSpec{}, "", true + } + if request.SearchMode == exploreSearchModeSemantic || request.SearchMode == exploreSearchModeHybrid { + return s.resolveExploreVectorSearch(ctx, w, request) + } + if request.SearchMode != exploreSearchModeFullText { + writeError(w, http.StatusBadRequest, "invalid_search_mode", "search_mode must be full_text, semantic, or hybrid") + return query.SearchSpec{}, "", false + } + if s.store == nil { + writeError(w, http.StatusServiceUnavailable, "lexical_index_unavailable", "The full-text index is unavailable") + return query.SearchSpec{}, "", false + } + parsed := search.Parse(request.Query) + if err := parsed.Err(); err != nil { + writeError(w, http.StatusBadRequest, "invalid_query", err.Error()) + return query.SearchSpec{}, "", false + } + ids := make([]int64, 0) + seen := make(map[int64]struct{}) + offset := 0 + var reportedTotal int64 + for { + remainingCapacity := query.MaxExploreCandidateMessageIDs - len(ids) + if remainingCapacity <= 0 { + break + } + pageLimit := min(exploreMaxLimit, remainingCapacity) + var ( + messages []APIMessage + total int64 + err error + ) + if searcher, ok := s.store.(ctxMessageSearcher); ok { + messages, total, err = searcher.SearchMessagesQueryContext(ctx, parsed, offset, pageLimit) + } else { + messages, total, err = s.store.SearchMessagesQuery(parsed, offset, pageLimit) + } + if err != nil { + if s.writeIfContextError(w, err) { + return query.SearchSpec{}, "", false + } + writeError(w, http.StatusServiceUnavailable, "lexical_index_unavailable", "The full-text index could not resolve candidates") + return query.SearchSpec{}, "", false + } + reportedTotal = total + for _, message := range messages { + if _, ok := seen[message.ID]; ok { + continue + } + seen[message.ID] = struct{}{} + ids = append(ids, message.ID) + } + offset += len(messages) + if len(messages) == 0 || int64(offset) >= total { + break + } + } + slices.Sort(ids) + saturated := reportedTotal > int64(offset) + revision := "fts5:" + hashCanonicalValue(struct { + ParsedQuery string `json:"parsed_query"` + IDs []int64 `json:"ids"` + Total int64 `json:"total"` + Saturated bool `json:"saturated"` + }{ParsedQuery: canonicalParsedExploreQuery(parsed), IDs: ids, Total: reportedTotal, Saturated: saturated}, false) + return query.SearchSpec{ + Mode: query.SearchFullText, Query: request.Query, CandidateMessageIDs: ids, + LexicalIndexRevision: revision, CandidatePoolSaturated: saturated, + }, "", true +} + +func requireCompleteCandidatePool(w http.ResponseWriter, spec query.SearchSpec) bool { + if !spec.CandidatePoolSaturated { + return true + } + writeError(w, http.StatusConflict, "candidate_pool_saturated", "The search matched more items than the bounded analytical candidate pool; narrow the query before requesting exact aggregates or actions") + return false +} + +func (s *Server) resolveExploreVectorSearch(ctx context.Context, w http.ResponseWriter, request ExploreHTTPRequest) (query.SearchSpec, string, bool) { + requestHash := exploreSnapshotRequestHash(request) + state := s.exploreState + if state == nil { + state = newExploreServerState(time.Now) + s.exploreState = state + } + if request.CandidateSnapshotID != "" { + snapshot, ok := state.snapshot(request.CandidateSnapshotID, requestHash) + if !ok { + writeError(w, http.StatusConflict, "candidate_snapshot_expired", "The semantic candidate snapshot is missing, expired, or belongs to another predicate") + return query.SearchSpec{}, "", false + } + generation := snapshot.Generation + spec := query.SearchSpec{ + Mode: query.SearchSemantic, Query: request.Query, CandidateMessageIDs: snapshot.IDs, + VectorGeneration: &generation, CandidatePoolSaturated: snapshot.PoolSaturated, + } + if request.SearchMode == exploreSearchModeHybrid { + spec.Mode = query.SearchHybrid + spec.LexicalIndexRevision = snapshot.LexicalRevision + spec.LexicalCandidateMessageIDs = snapshot.LexicalIDs + } + return spec, request.CandidateSnapshotID, true + } + hybridEngine, _, _ := s.vectorComponents() + if hybridEngine == nil { + writeError(w, http.StatusServiceUnavailable, "vector_not_enabled", "Vector search is not configured") + return query.SearchSpec{}, "", false + } + parsed := search.Parse(request.Query) + if err := parsed.Err(); err != nil { + writeError(w, http.StatusBadRequest, "invalid_query", err.Error()) + return query.SearchSpec{}, "", false + } + var lexicalSpec query.SearchSpec + if request.SearchMode == exploreSearchModeHybrid { + var ok bool + lexicalSpec, _, ok = s.resolveExploreSearch(ctx, w, ExploreHTTPRequest{ + Query: request.Query, SearchMode: exploreSearchModeFullText, + }) + if !ok { + return query.SearchSpec{}, "", false + } + } + freeText := strings.Join(parsed.TextTerms, " ") + if freeText == "" { + writeError(w, http.StatusBadRequest, "missing_free_text", "Semantic and hybrid exploration require free text") + return query.SearchSpec{}, "", false + } + filter, err := hybridEngine.BuildFilter(ctx, parsed) + if err != nil { + writeError(w, http.StatusServiceUnavailable, "search_filter_unavailable", "The semantic search filter could not be resolved") + return query.SearchSpec{}, "", false + } + exploreCtx, err := exploreContext(request.Filters) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_filter", err.Error()) + return query.SearchSpec{}, "", false + } + if len(exploreCtx.ParticipantIDs) > 0 || len(exploreCtx.Domains) > 0 || exploreCtx.Deletion == query.DeletionDeleted { + writeError(w, http.StatusBadRequest, "semantic_filter_unavailable", "Semantic ranking cannot safely apply participant, domain, or deleted-only filters") + return query.SearchSpec{}, "", false + } + filter.SourceIDs = append(filter.SourceIDs, exploreCtx.SourceIDs...) + filter.MessageTypes = append(filter.MessageTypes, exploreCtx.MessageTypes...) + if exploreCtx.After != nil { + filter.After = exploreCtx.After + } + if exploreCtx.Before != nil { + filter.Before = exploreCtx.Before + } + mode := hybrid.ModeVector + if request.SearchMode == exploreSearchModeHybrid { + mode = hybrid.ModeHybrid + } + hits, meta, err := hybridEngine.Search(ctx, hybrid.SearchRequest{ + Mode: mode, FreeText: freeText, Filter: filter, Limit: exploreMaxLimit, + SubjectTerms: parsed.TextTerms, + }) + if err != nil { + s.writeExploreVectorError(w, err) + return query.SearchSpec{}, "", false + } + poolSaturated := meta.PoolSaturated || len(hits) >= exploreMaxLimit || lexicalSpec.CandidatePoolSaturated + if len(hits) > exploreMaxLimit { + hits = hits[:exploreMaxLimit] + poolSaturated = true + } + ids := make([]int64, len(hits)) + snapshotHits := make([]exploreCandidateHit, len(hits)) + for i, hit := range hits { + ids[i] = hit.MessageID + score := hit.VectorScore + if request.SearchMode == exploreSearchModeHybrid && !math.IsNaN(hit.RRFScore) { + score = hit.RRFScore + } + snapshotHits[i] = exploreCandidateHit{MessageID: hit.MessageID, Score: score} + } + summaries, err := s.getMessagesSummariesByIDs(ctx, ids) + if err == nil { + byID := make(map[int64]string, len(summaries)) + for _, summary := range summaries { + byID[summary.ID] = query.FlattenSnippet(summary.Snippet) + } + for i := range snapshotHits { + snapshotHits[i].Excerpt = byID[snapshotHits[i].MessageID] + } + } + lexicalRevision := "" + if request.SearchMode == exploreSearchModeHybrid { + lexicalRevision = lexicalSpec.LexicalIndexRevision + } + snapshotID := state.issueSnapshot(exploreCandidateSnapshot{ + RequestHash: requestHash, IDs: ids, LexicalIDs: lexicalSpec.CandidateMessageIDs, Hits: snapshotHits, + Generation: int64(meta.Generation.ID), LexicalRevision: lexicalRevision, PoolSaturated: poolSaturated, + }) + generation := int64(meta.Generation.ID) + spec := query.SearchSpec{Mode: query.SearchSemantic, Query: request.Query, CandidateMessageIDs: ids, VectorGeneration: &generation, CandidatePoolSaturated: poolSaturated} + if request.SearchMode == exploreSearchModeHybrid { + spec.Mode = query.SearchHybrid + spec.LexicalIndexRevision = lexicalRevision + spec.LexicalCandidateMessageIDs = lexicalSpec.CandidateMessageIDs + } + return spec, snapshotID, true +} + +func exploreSnapshotRequestHash(request ExploreHTTPRequest) string { + candidateRequest := ExploreHTTPRequest{ + Filters: request.Filters, Query: request.Query, SearchMode: request.SearchMode, + } + canonicalizeExploreRequest(&candidateRequest) + return hashCanonicalValue(candidateRequest, false) +} + +func canonicalParsedExploreQuery(parsed *search.Query) string { + canonical := *parsed + canonicalStrings := func(values []string) []string { + values = slices.Clone(values) + slices.Sort(values) + return slices.Compact(values) + } + canonical.TextTerms = canonicalStrings(parsed.TextTerms) + canonical.FromAddrs = canonicalStrings(parsed.FromAddrs) + canonical.ToAddrs = canonicalStrings(parsed.ToAddrs) + canonical.CcAddrs = canonicalStrings(parsed.CcAddrs) + canonical.BccAddrs = canonicalStrings(parsed.BccAddrs) + canonical.SubjectTerms = canonicalStrings(parsed.SubjectTerms) + canonical.Labels = canonicalStrings(parsed.Labels) + canonical.MessageTypes = canonicalStrings(parsed.MessageTypes) + canonical.AccountIDs = slices.Clone(parsed.AccountIDs) + slices.Sort(canonical.AccountIDs) + canonical.AccountIDs = slices.Compact(canonical.AccountIDs) + // Exact candidate membership already captures the resolved date boundary. + // Excluding clock-derived instants keeps an unchanged relative-date query + // stable across cursor pages. + canonical.BeforeDate = nil + canonical.AfterDate = nil + canonical.UnsupportedOperators = nil + return hashCanonicalValue(canonical, false) +} + +func (s *Server) enrichExploreSemanticRows(rows []query.EntryRow, request ExploreHTTPRequest, snapshotID string) { + if s.exploreState == nil { + return + } + snapshot, ok := s.exploreState.snapshot(snapshotID, exploreSnapshotRequestHash(request)) + if !ok { + return + } + byID := make(map[int64]exploreCandidateHit, len(snapshot.Hits)) + rank := make(map[int64]int, len(snapshot.Hits)) + for i, hit := range snapshot.Hits { + byID[hit.MessageID] = hit + rank[hit.MessageID] = i + } + rowRank := make(map[string]int, len(rows)) + for i := range rows { + bestRank := len(snapshot.Hits) + 1 + if rows[i].StrongestMatchedMessageID != nil { + messageID := *rows[i].StrongestMatchedMessageID + if candidateRank, found := rank[messageID]; found { + bestRank = candidateRank + } + if hit, found := byID[messageID]; found { + score := hit.Score + rows[i].Match.SemanticScore = &score + rows[i].Match.StrongestExcerpt = hit.Excerpt + } + } + rowRank[rows[i].Key] = bestRank + } + slices.SortStableFunc(rows, func(a, b query.EntryRow) int { + if difference := rowRank[a.Key] - rowRank[b.Key]; difference != 0 { + return difference + } + return strings.Compare(a.Key, b.Key) + }) +} + +func (s *Server) writeExploreVectorError(w http.ResponseWriter, err error) { + switch { + case errors.Is(err, vector.ErrNotEnabled): + writeError(w, http.StatusServiceUnavailable, "vector_not_enabled", "Vector search is not configured") + case errors.Is(err, vector.ErrIndexStale): + writeError(w, http.StatusServiceUnavailable, "index_stale", "The vector index does not match the configured model") + case errors.Is(err, vector.ErrIndexBuilding): + writeError(w, http.StatusServiceUnavailable, "index_building", "The vector index is still being built") + case errors.Is(err, vector.ErrEmbeddingTimeout): + writeError(w, http.StatusServiceUnavailable, "embedding_timeout", "The embedding endpoint did not respond in time") + case errors.Is(err, vector.ErrIndexScopeMismatch): + writeError(w, http.StatusBadRequest, "index_scope_mismatch", err.Error()) + default: + writeError(w, http.StatusServiceUnavailable, "semantic_search_unavailable", "Semantic search could not resolve candidates") + } +} + +func newExploreCursorKey() [32]byte { + var key [32]byte + if _, err := rand.Read(key[:]); err != nil { + panic("crypto/rand unavailable: " + err.Error()) + } + return key +} + +func (s *Server) encodeExploreCursor(cursor exploreCursor) string { + data, err := json.Marshal(cursor) + if err != nil { + panic(fmt.Sprintf("marshal explore cursor: %v", err)) + } + payload := base64.RawURLEncoding.EncodeToString(data) + mac := hmac.New(sha256.New, s.exploreCursorKey[:]) + _, _ = mac.Write([]byte(payload)) + signature := base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) + return payload + "." + signature +} + +func (s *Server) decodeExploreCursor(encoded string) (exploreCursor, error) { + payload, encodedSignature, ok := strings.Cut(encoded, ".") + if !ok || payload == "" || encodedSignature == "" || strings.Contains(encodedSignature, ".") { + return exploreCursor{}, errors.New("invalid explore cursor signature") + } + signature, err := base64.RawURLEncoding.DecodeString(encodedSignature) + if err != nil { + return exploreCursor{}, fmt.Errorf("decode explore cursor signature: %w", err) + } + mac := hmac.New(sha256.New, s.exploreCursorKey[:]) + _, _ = mac.Write([]byte(payload)) + if !hmac.Equal(signature, mac.Sum(nil)) { + return exploreCursor{}, errors.New("invalid explore cursor signature") + } + data, err := base64.RawURLEncoding.DecodeString(payload) + if err != nil { + return exploreCursor{}, fmt.Errorf("decode explore cursor: %w", err) + } + var cursor exploreCursor + err = json.Unmarshal(data, &cursor) + return cursor, err +} + +func decodeExploreJSON(w http.ResponseWriter, r *http.Request, dst any) bool { + decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(dst); err != nil { + writeError(w, http.StatusBadRequest, "bad_request", "Invalid request body: "+err.Error()) + return false + } + if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + writeError(w, http.StatusBadRequest, "bad_request", "Request body must contain one JSON object") + return false + } + return true +} + +func (s *Server) writeExploreError(w http.ResponseWriter, err error) { + var unavailable *query.CacheUnavailableError + if errors.As(err, &unavailable) || errors.Is(err, query.ErrCacheUnavailable) { + readiness := query.CacheInterrupted + if unavailable != nil { + readiness = unavailable.Readiness + } + writeExploreUnavailable(w, readiness) + return + } + if errors.Is(err, query.ErrInvalidExploreRequest) { + writeError(w, http.StatusBadRequest, "invalid_explore_request", err.Error()) + return + } + if s.writeIfContextError(w, err) { + return + } + s.logger.Error("exploration failed", "error", err) + writeError(w, http.StatusInternalServerError, "explore_failed", "Analytical exploration failed") +} + +type ExploreCacheUnavailableResponse struct { + Error string `json:"error"` + Message string `json:"message"` + Readiness query.CacheReadiness `json:"readiness" enum:"absent,interrupted,stale_schema,drifted"` + RecoveryAction string `json:"recovery_action"` +} + +func writeExploreUnavailable(w http.ResponseWriter, readiness query.CacheReadiness) { + writeJSON(w, http.StatusServiceUnavailable, ExploreCacheUnavailableResponse{ + Error: "analytical_cache_unavailable", Message: "The committed analytical cache is unavailable", + Readiness: readiness, RecoveryAction: "Run msgvault build-cache --full-rebuild and retry", + }) +} diff --git a/internal/api/explore_e2e_test.go b/internal/api/explore_e2e_test.go new file mode 100644 index 000000000..866d2b5f2 --- /dev/null +++ b/internal/api/explore_e2e_test.go @@ -0,0 +1,425 @@ +package api + +import ( + "bytes" + "context" + "database/sql" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "time" + + _ "github.com/duckdb/duckdb-go/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/vector" + "go.kenn.io/msgvault/internal/vector/hybrid" +) + +func TestExploreHTTPUsesCommittedDuckDBReadModel(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := newExploreDuckDBFixture(t) + srv := newTestServerWithEngine(t, engine) + + response := postExploreJSON(t, srv, "/api/v1/explore", `{ + "filters":[{"dimension":"source","values":["1"]}], + "presentation":"table", + "sort":[{"field":"occurred_at","direction":"desc"}], + "limit":1 + }`) + + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var body map[string]any + requirements.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assertions.InDelta(2, body["total_count"], 0) + assertions.NotEmpty(body["cache_revision"]) + assertions.NotEmpty(body["next_cursor"]) + rows, ok := body["rows"].([]any) + requirements.True(ok) + requirements.Len(rows, 1) + row, ok := rows[0].(map[string]any) + requirements.True(ok) + assertions.Equal("Newest", row["title"]) +} + +func TestExploreGroupsAndFilesUseCompleteDuckDBFacts(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := newExploreDuckDBFixture(t) + srv := newTestServerWithEngine(t, engine) + + groups := postExploreJSON(t, srv, "/api/v1/explore/groups", `{ + "grouping":["source"],"sort":[{"field":"count","direction":"desc"}],"limit":10 + }`) + requirements.Equal(http.StatusOK, groups.Code, groups.Body.String()) + var groupBody struct { + Rows []struct { + Key string `json:"key"` + Count int64 `json:"count"` + } `json:"rows"` + TotalCount int64 `json:"total_count"` + } + requirements.NoError(json.Unmarshal(groups.Body.Bytes(), &groupBody)) + requirements.Len(groupBody.Rows, 2) + assertions.Equal("1", groupBody.Rows[0].Key) + assertions.Equal(int64(2), groupBody.Rows[0].Count) + assertions.Equal(int64(2), groupBody.TotalCount) + + files := postExploreJSON(t, srv, "/api/v1/explore/files", `{"predicate":{"presentation":"table"},"limit":10}`) + requirements.Equal(http.StatusOK, files.Code, files.Body.String()) + var fileBody struct { + Files []query.ExploreFileFact `json:"files"` + } + requirements.NoError(json.Unmarshal(files.Body.Bytes(), &fileBody)) + requirements.Len(fileBody.Files, 2) + assertions.Equal("newest.pdf", fileBody.Files[0].Filename) + assertions.Equal("older.txt", fileBody.Files[1].Filename) +} + +func TestExploreParticipantGroupsResolveDurableLabelsEndToEnd(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + srv := newTestServerWithEngine(t, newExploreDuckDBFixture(t)) + + response := postExploreJSON(t, srv, "/api/v1/explore/groups", `{ + "grouping":["participant"],"sort":[{"field":"key","direction":"asc"}],"limit":10 + }`) + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var body struct { + Rows []query.ExploreGroupRow `json:"rows"` + } + requirements.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + requirements.Len(body.Rows, 1) + assertions.Equal("1", body.Rows[0].Key) + assertions.Equal("Alice", body.Rows[0].Label) + assertions.Equal(int64(3), body.Rows[0].Count) +} + +func TestExplorePreflightPinsRevisionAndExcludesCompletePredicate(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := &rawExploreEngine{DuckDBEngine: newExploreDuckDBFixture(t)} + srv := newTestServerWithEngine(t, engine) + explore := postExploreJSON(t, srv, "/api/v1/explore", `{"filters":[{"dimension":"source","values":["1"]}],"limit":10}`) + requirements.Equal(http.StatusOK, explore.Code, explore.Body.String()) + var explored struct { + CacheRevision string `json:"cache_revision"` + } + requirements.NoError(json.Unmarshal(explore.Body.Bytes(), &explored)) + + preflight := postExploreJSON(t, srv, "/api/v1/explore/preflight", fmt.Sprintf(`{ + "selection":{"mode":"all_matching","predicate":{"filters":[{"dimension":"source","values":["1"]}],"presentation":"table"}, + "exclusions":["source:1:message:m1"],"cache_revision":%q} + }`, explored.CacheRevision)) + requirements.Equal(http.StatusOK, preflight.Code, preflight.Body.String()) + var body struct { + Count int64 `json:"count"` + EstimatedBytes int64 `json:"estimated_bytes"` + OperationToken string `json:"operation_token"` + ActionTargets []ExploreActionTarget `json:"action_targets"` + UnavailableActions []ExploreUnavailableAction `json:"unavailable_actions"` + } + requirements.NoError(json.Unmarshal(preflight.Body.Bytes(), &body)) + assertions.Equal(int64(1), body.Count) + assertions.Equal(int64(220), body.EstimatedBytes) + assertions.NotEmpty(body.OperationToken) + assertions.Equal([]ExploreActionTarget{{Action: "export", MessageID: 2, Filename: "message-2.eml"}}, body.ActionTargets) + assertions.Equal([]int64{2}, engine.rawRequests) + assertions.Contains(body.UnavailableActions, ExploreUnavailableAction{ + Action: "open_in_source", Reason: "trusted_source_link_unavailable", + }) + + multi := postExploreJSON(t, srv, "/api/v1/explore/preflight", fmt.Sprintf(`{ + "selection":{"mode":"all_matching","predicate":{"filters":[{"dimension":"source","values":["1"]}]},"cache_revision":%q} + }`, explored.CacheRevision)) + requirements.Equal(http.StatusOK, multi.Code, multi.Body.String()) + var multiBody ExplorePreflightResponse + requirements.NoError(json.Unmarshal(multi.Body.Bytes(), &multiBody)) + assertions.Empty(multiBody.ActionTargets) + assertions.Equal([]int64{2}, engine.rawRequests, "multi-row preflight must not request raw message data") + assertions.Contains(multiBody.UnavailableActions, ExploreUnavailableAction{ + Action: "export", Reason: "browser_export_requires_single_message", + }) + + conflict := postExploreJSON(t, srv, "/api/v1/explore/preflight", `{ + "selection":{"mode":"all_matching","predicate":{"presentation":"table"},"cache_revision":"cache-old"} + }`) + assertions.Equal(http.StatusConflict, conflict.Code, conflict.Body.String()) + assertions.Contains(conflict.Body.String(), "archive_revision_changed") + + other := postExploreJSON(t, srv, "/api/v1/explore", `{"filters":[{"dimension":"source","values":["2"]}]}`) + requirements.Equal(http.StatusOK, other.Code, other.Body.String()) + requirements.NoError(json.Unmarshal(other.Body.Bytes(), &explored)) + unavailable := postExploreJSON(t, srv, "/api/v1/explore/preflight", fmt.Sprintf(`{ + "selection":{"mode":"all_matching","predicate":{"filters":[{"dimension":"source","values":["2"]}]},"cache_revision":%q} + }`, explored.CacheRevision)) + requirements.Equal(http.StatusOK, unavailable.Code, unavailable.Body.String()) + var unavailableBody ExplorePreflightResponse + requirements.NoError(json.Unmarshal(unavailable.Body.Bytes(), &unavailableBody)) + assertions.Contains(unavailableBody.UnavailableActions, ExploreUnavailableAction{ + Action: "stage_deletion", Reason: "selection_contains_items_that_cannot_be_deleted_from_source", + }) + assertions.Contains(unavailableBody.UnavailableActions, ExploreUnavailableAction{ + Action: "export_files", Reason: "selection_contains_no_files", + }) + assertions.Contains(unavailableBody.UnavailableActions, ExploreUnavailableAction{ + Action: "export", Reason: "selection_has_no_exportable_raw_message", + }) + assertions.Contains(unavailableBody.UnavailableActions, ExploreUnavailableAction{ + Action: "open_in_source", Reason: "trusted_source_link_unavailable", + }) +} + +func TestExploreFullTextAndVisibleMatchCountsUseExactCandidates(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := newExploreDuckDBFixture(t) + store := &mockStore{ + messages: []APIMessage{{ID: 1, Subject: "Older", Snippet: "alpha match"}, {ID: 2, Subject: "Newest", Snippet: "alpha beta"}}, + total: 2, stats: &StoreStats{}, + } + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: store, Engine: engine, Logger: testLogger(), + }) + + explore := postExploreJSON(t, srv, "/api/v1/explore", `{"query":"alpha","search_mode":"full_text","limit":10}`) + requirements.Equal(http.StatusOK, explore.Code, explore.Body.String()) + var explored struct { + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + } + requirements.NoError(json.Unmarshal(explore.Body.Bytes(), &explored)) + assertions.NotEmpty(explored.SearchProvenance.LexicalIndexRevision) + + counts := postExploreJSON(t, srv, "/api/v1/explore/match-counts", `{ + "predicate":{"query":"alpha","search_mode":"full_text"}, + "row_keys":["source:1:message:m1","source:1:message:m2"] + }`) + requirements.Equal(http.StatusOK, counts.Code, counts.Body.String()) + var body ExploreMatchCountsResponse + requirements.NoError(json.Unmarshal(counts.Body.Bytes(), &body)) + assertions.Equal([]ExploreRowMatchCount{ + {RowKey: "source:1:message:m1", Count: 1}, + {RowKey: "source:1:message:m2", Count: 1}, + }, body.Counts) + assertions.Equal(explored.CacheRevision, body.CacheRevision) + assertions.Equal(explored.SearchProvenance.LexicalIndexRevision, body.LexicalRevision) + assertions.NotEmpty(body.CanonicalQueryHash) +} + +func TestExploreFullTextPaginationRejectsChangedLexicalRevision(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := newExploreDuckDBFixture(t) + store := &mockStore{ + messages: []APIMessage{{ID: 1}, {ID: 2}}, + total: 2, stats: &StoreStats{}, + } + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: store, Engine: engine, Logger: testLogger(), + }) + + first := postExploreJSON(t, srv, "/api/v1/explore", `{"query":"alpha","search_mode":"full_text","limit":1}`) + requirements.Equal(http.StatusOK, first.Code, first.Body.String()) + var firstPage ExploreHTTPResponse + requirements.NoError(json.Unmarshal(first.Body.Bytes(), &firstPage)) + requirements.NotEmpty(firstPage.NextCursor) + + store.messages = []APIMessage{{ID: 2}} + store.total = 1 + second := postExploreJSON(t, srv, "/api/v1/explore", fmt.Sprintf( + `{"query":"alpha","search_mode":"full_text","limit":1,"cursor":%q}`, firstPage.NextCursor, + )) + assertions.Equal(http.StatusConflict, second.Code, second.Body.String()) + assertions.Contains(second.Body.String(), "search_revision_changed") +} + +func TestExploreSemanticIssuesBoundedSnapshotWithoutInventingTotal(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := newExploreDuckDBFixture(t) + backend := &fakeVectorBackend{ + active: &vector.Generation{ID: 7, Model: "test", Dimension: 2, Fingerprint: "test:2", State: vector.GenerationActive}, + searchHits: []vector.Hit{{MessageID: 1, Score: .9, Rank: 1}, {MessageID: 2, Score: .8, Rank: 2}}, + } + hybridEngine := hybrid.NewEngine(backend, nil, realEmbedder{dim: 2}, hybrid.Config{ExpectedFingerprint: "test:2"}) + store := &mockStore{ + messages: []APIMessage{{ID: 1, Subject: "Older", Snippet: "older excerpt"}, {ID: 2, Subject: "Newest", Snippet: "newer excerpt"}}, + total: 2, stats: &StoreStats{}, + } + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: store, Engine: engine, + HybridEngine: hybridEngine, Backend: backend, Logger: testLogger(), + }) + + response := postExploreJSON(t, srv, "/api/v1/explore", `{"query":"alpha","search_mode":"semantic","limit":10}`) + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var raw map[string]json.RawMessage + requirements.NoError(json.Unmarshal(response.Body.Bytes(), &raw)) + assertions.NotContains(raw, "total_count") + var body ExploreHTTPResponse + requirements.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assertions.NotEmpty(body.CandidateSnapshotID) + requirements.NotNil(body.SearchProvenance.VectorGeneration) + assertions.Equal(int64(7), *body.SearchProvenance.VectorGeneration) + requirements.Len(body.Rows, 2) + assertions.Equal(int64(1), *body.Rows[0].AnchorMessageID) + assertions.Equal("older excerpt", body.Rows[0].Match.StrongestExcerpt) + requirements.NotNil(body.Rows[0].Match.SemanticScore) + assertions.InDelta(.9, *body.Rows[0].Match.SemanticScore, 0.0001) + assertions.Equal(exploreMaxLimit, backend.searchLimit) +} + +func TestExploreSemanticPaginationFollowsSnapshotRankNotArchiveDate(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := newExploreDuckDBFixture(t) + backend := &fakeVectorBackend{ + active: &vector.Generation{ID: 7, Model: "test", Dimension: 2, Fingerprint: "test:2", State: vector.GenerationActive}, + searchHits: []vector.Hit{{MessageID: 1, Score: .9, Rank: 1}, {MessageID: 2, Score: .8, Rank: 2}}, + } + hybridEngine := hybrid.NewEngine(backend, nil, realEmbedder{dim: 2}, hybrid.Config{ExpectedFingerprint: "test:2"}) + store := &mockStore{messages: []APIMessage{{ID: 1}, {ID: 2}}, total: 2, stats: &StoreStats{}} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: store, Engine: engine, + HybridEngine: hybridEngine, Backend: backend, Logger: testLogger(), + }) + first := postExploreJSON(t, srv, "/api/v1/explore", `{"query":"alpha","search_mode":"semantic","limit":1}`) + requirements.Equal(http.StatusOK, first.Code, first.Body.String()) + var firstPage ExploreHTTPResponse + requirements.NoError(json.Unmarshal(first.Body.Bytes(), &firstPage)) + requirements.Len(firstPage.Rows, 1) + assertions.Equal(int64(1), *firstPage.Rows[0].AnchorMessageID) + requirements.NotEmpty(firstPage.NextCursor) + + second := postExploreJSON(t, srv, "/api/v1/explore", fmt.Sprintf(`{"query":"alpha","search_mode":"semantic","limit":1,"cursor":%q}`, firstPage.NextCursor)) + requirements.Equal(http.StatusOK, second.Code, second.Body.String()) + var secondPage ExploreHTTPResponse + requirements.NoError(json.Unmarshal(second.Body.Bytes(), &secondPage)) + requirements.Len(secondPage.Rows, 1) + assertions.Equal(int64(2), *secondPage.Rows[0].AnchorMessageID) +} + +func TestExploreSemanticPreflightRequiresAndReusesCandidateSnapshot(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := newExploreDuckDBFixture(t) + backend := &fakeVectorBackend{ + active: &vector.Generation{ID: 7, Model: "test", Dimension: 2, Fingerprint: "test:2", State: vector.GenerationActive}, + searchHits: []vector.Hit{{MessageID: 1, Score: .9, Rank: 1}, {MessageID: 2, Score: .8, Rank: 2}}, + } + hybridEngine := hybrid.NewEngine(backend, nil, realEmbedder{dim: 2}, hybrid.Config{ExpectedFingerprint: "test:2"}) + store := &mockStore{messages: []APIMessage{{ID: 1}, {ID: 2}}, total: 2, stats: &StoreStats{}} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: store, Engine: engine, + HybridEngine: hybridEngine, Backend: backend, Logger: testLogger(), + }) + explore := postExploreJSON(t, srv, "/api/v1/explore", `{"query":"alpha","search_mode":"semantic","limit":10}`) + requirements.Equal(http.StatusOK, explore.Code, explore.Body.String()) + var explored ExploreHTTPResponse + requirements.NoError(json.Unmarshal(explore.Body.Bytes(), &explored)) + + preflight := postExploreJSON(t, srv, "/api/v1/explore/preflight", fmt.Sprintf(`{ + "selection":{"mode":"all_matching","predicate":{"query":"alpha","search_mode":"semantic","grouping":["source"]}, + "cache_revision":%q,"search_provenance":{"vector_generation":7},"candidate_snapshot_id":%q} + }`, explored.CacheRevision, explored.CandidateSnapshotID)) + requirements.Equal(http.StatusOK, preflight.Code, preflight.Body.String()) + var body ExplorePreflightResponse + requirements.NoError(json.Unmarshal(preflight.Body.Bytes(), &body)) + assertions.Equal(int64(2), body.Count) + assertions.Equal(int64(330), body.EstimatedBytes) + assertions.Equal(int64(7), *body.SearchProvenance.VectorGeneration) +} + +func postExploreJSON(t *testing.T, srv *Server, path, body string) *httptest.ResponseRecorder { + t.Helper() + req := httptest.NewRequest(http.MethodPost, path, bytes.NewBufferString(body)) + req.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, req) + return response +} + +func newExploreDuckDBFixture(t *testing.T) *query.DuckDBEngine { + t.Helper() + engine, _ := newExploreDuckDBFixtureWithDir(t) + return engine +} + +type rawExploreEngine struct { + *query.DuckDBEngine + + rawRequests []int64 +} + +func (e *rawExploreEngine) GetMessageRaw(_ context.Context, id int64) ([]byte, error) { + e.rawRequests = append(e.rawRequests, id) + if id == 1 || id == 2 { + return []byte("From: archive-a@example.com\r\n\r\nraw"), nil + } + return nil, nil +} + +func newExploreDuckDBFixtureWithDir(t *testing.T) (*query.DuckDBEngine, string) { + t.Helper() + analyticsDir := t.TempDir() + db, err := sql.Open("duckdb", "") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, db.Close()) }) + + tables := []struct { + dir, file, columns, values string + empty bool + }{ + { + dir: "messages/year=2026", file: "messages.parquet", + columns: "id, source_id, source_message_id, conversation_id, subject, snippet, sent_at, size_estimate, has_attachments, attachment_count, deleted_from_source_at, sender_id, message_type, year, month", + values: `(1::BIGINT, 1::BIGINT, 'm1', 101::BIGINT, 'Older', 'alpha match', TIMESTAMP '2026-07-18 10:00:00', 100::BIGINT, true, 1::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2026, 7), + (2::BIGINT, 1::BIGINT, 'm2', 102::BIGINT, 'Newest', 'alpha beta', TIMESTAMP '2026-07-18 11:00:00', 200::BIGINT, true, 1::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2026, 7), + (3::BIGINT, 2::BIGINT, 'm3', 103::BIGINT, 'Other source', 'beta', TIMESTAMP '2026-07-18 09:00:00', 300::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2026, 7)`, + }, + {dir: "sources", file: "sources.parquet", columns: "id, account_email, source_type", values: `(1::BIGINT, 'archive-a@example.com', 'gmail'), (2::BIGINT, 'archive-b@example.com', 'imap')`}, + {dir: "participants", file: "participants.parquet", columns: "id, email_address, domain, display_name, phone_number", values: `(1::BIGINT, 'alice@example.com', 'example.com', 'Alice', '')`}, + {dir: "participant_identifiers", file: "participant_identifiers.parquet", columns: "participant_id, identifier_type, identifier_value, display_value, is_primary", values: `(1::BIGINT, 'email', 'alice@example.com', 'alice@example.com', true)`}, + {dir: "message_recipients", file: "message_recipients.parquet", columns: "message_id, participant_id, recipient_type, display_name", values: `(1::BIGINT, 1::BIGINT, 'from', 'Alice'), (2::BIGINT, 1::BIGINT, 'from', 'Alice'), (3::BIGINT, 1::BIGINT, 'from', 'Alice')`}, + {dir: "labels", file: "labels.parquet", columns: "id, name", values: `(0::BIGINT, '')`, empty: true}, + {dir: "message_labels", file: "message_labels.parquet", columns: "message_id, label_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "attachments", file: "attachments.parquet", columns: "attachment_id, message_id, size, filename", values: `(11::BIGINT, 1::BIGINT, 10::BIGINT, 'older.txt'), (12::BIGINT, 2::BIGINT, 20::BIGINT, 'newest.pdf')`}, + {dir: "conversations", file: "conversations.parquet", columns: "id, source_conversation_id, title, conversation_type", values: `(101::BIGINT, 'c1', '', 'email'), (102::BIGINT, 'c2', '', 'email'), (103::BIGINT, 'c3', '', 'email')`}, + {dir: "conversation_participants", file: "conversation_participants.parquet", columns: "conversation_id, participant_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "owner_participants", file: "owner_participants.parquet", columns: "source_id, participant_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "participant_clusters", file: "participant_clusters.parquet", columns: "participant_id, canonical_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + } + for _, table := range tables { + dir := filepath.Join(analyticsDir, table.dir) + require.NoError(t, os.MkdirAll(dir, 0o755)) + where := "" + if table.empty { + where = " WHERE false" + } + path := filepath.ToSlash(filepath.Join(dir, table.file)) + _, err := db.Exec(fmt.Sprintf("COPY (SELECT * FROM (VALUES %s) AS t(%s)%s) TO '%s' (FORMAT PARQUET)", table.values, table.columns, where, path)) + require.NoError(t, err, "write %s", table.dir) + } + fingerprint, err := query.CacheDatasetFingerprint(analyticsDir) + require.NoError(t, err) + state, err := json.Marshal(query.CacheSyncState{ + LastMessageID: 3, LastSyncAt: time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC), + SchemaVersion: query.CacheSchemaVersion, PublishedAt: time.Date(2026, 7, 18, 12, 1, 0, 0, time.UTC), + DatasetFingerprint: fingerprint, + }) + require.NoError(t, err) + require.NoError(t, os.WriteFile(query.CacheStatePath(analyticsDir), state, 0o600)) + + engine, err := query.NewDuckDBEngine(analyticsDir, "", nil) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, engine.Close()) }) + return engine, analyticsDir +} diff --git a/internal/api/explore_files.go b/internal/api/explore_files.go new file mode 100644 index 000000000..d19df81e1 --- /dev/null +++ b/internal/api/explore_files.go @@ -0,0 +1,108 @@ +package api + +import ( + "fmt" + "net/http" + + "github.com/danielgtaylor/huma/v2" + "go.kenn.io/msgvault/internal/query" +) + +const exploreFilesMaxLimit = 100 + +type ExploreFilesHTTPRequest struct { + Predicate ExploreHTTPRequest `json:"predicate"` + Cursor string `json:"cursor,omitempty"` + Limit int `json:"limit,omitempty" minimum:"0" maximum:"100"` +} + +type ExploreFilesHTTPResponse struct { + Files []query.ExploreFileFact `json:"files"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + NextCursor string `json:"next_cursor,omitempty"` + CandidateSnapshotID string `json:"candidate_snapshot_id,omitempty"` +} + +func (s *Server) registerExploreFilesRoute(api huma.API) { + registerExploreRoute[ExploreFilesHTTPRequest, ExploreFilesHTTPResponse]( + api, "listExploreFiles", "/explore/files", "List bounded chronological attachment facts", s.handleExploreFiles, + ) +} + +func (s *Server) handleExploreFiles(w http.ResponseWriter, r *http.Request) { + var request ExploreFilesHTTPRequest + if !decodeExploreJSON(w, r, &request) { + return + } + predicate, err := prepareExplorePredicate(request.Predicate) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_files_predicate", err.Error()) + return + } + if request.Limit == 0 { + request.Limit = exploreFilesMaxLimit + } + if request.Limit < 1 || request.Limit > exploreFilesMaxLimit { + writeError(w, http.StatusBadRequest, "invalid_limit", fmt.Sprintf("limit must be between 1 and %d", exploreFilesMaxLimit)) + return + } + canonical := request + canonical.Cursor = "" + requestHash := hashCanonicalValue(canonical, false) + offset, ok := s.parseExploreCursor(w, request.Cursor, requestHash) + if !ok { + return + } + var cursor exploreCursor + if request.Cursor != "" { + cursor, _ = s.decodeExploreCursor(request.Cursor) + if predicate.request.SearchMode == exploreSearchModeSemantic || predicate.request.SearchMode == exploreSearchModeHybrid { + if cursor.Snapshot == "" { + writeError(w, http.StatusBadRequest, "invalid_cursor", "semantic cursor is missing its candidate snapshot") + return + } + predicate.request.CandidateSnapshotID = cursor.Snapshot + } + } + searchSpec, snapshotID, ok := s.resolveExploreSearch(r.Context(), w, predicate.request) + if !ok || !requireCompleteCandidatePool(w, searchSpec) { + return + } + if request.Cursor != "" && cursor.SearchRevision != exploreResolvedSearchRevision(searchSpec) { + writeError(w, http.StatusConflict, "search_revision_changed", "The resolved search index revision changed; restart pagination") + return + } + predicate.query.Search = searchSpec + analyzer, ok := s.engine.(query.Explorer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + result, err := analyzer.ExploreFiles(r.Context(), query.ExploreFilesRequest{ + Explore: predicate.query, Page: query.PageSpec{Limit: request.Limit, Offset: offset}, + }) + if err != nil { + s.writeExploreError(w, err) + return + } + if request.Cursor != "" { + cursor, _ := s.decodeExploreCursor(request.Cursor) + if cursor.Revision != result.CacheRevision { + writeError(w, http.StatusConflict, "archive_revision_changed", "The committed analytical cache changed; restart pagination") + return + } + } + response := ExploreFilesHTTPResponse{ + Files: result.Files, TotalCount: result.TotalCount, CacheRevision: result.CacheRevision, + SearchProvenance: result.SearchProvenance, CandidateSnapshotID: snapshotID, + } + if next := offset + len(result.Files); next < int(result.TotalCount) { + response.NextCursor = s.encodeExploreCursor(exploreCursor{ + Offset: next, Request: requestHash, Revision: result.CacheRevision, + SearchRevision: exploreResolvedSearchRevision(searchSpec), Snapshot: snapshotID, + }) + } + writeJSON(w, http.StatusOK, response) +} diff --git a/internal/api/explore_files_test.go b/internal/api/explore_files_test.go new file mode 100644 index 000000000..d3f6d7699 --- /dev/null +++ b/internal/api/explore_files_test.go @@ -0,0 +1,15 @@ +package api + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExploreFilesRejectsUnboundedLimit(t *testing.T) { + srv := newTestServerWithEngine(t, newExploreDuckDBFixture(t)) + response := postExploreJSON(t, srv, "/api/v1/explore/files", `{"predicate":{},"limit":101}`) + assert.Equal(t, http.StatusBadRequest, response.Code) + assert.Contains(t, response.Body.String(), "limit must be between") +} diff --git a/internal/api/explore_review_test.go b/internal/api/explore_review_test.go new file mode 100644 index 000000000..e263ee6ce --- /dev/null +++ b/internal/api/explore_review_test.go @@ -0,0 +1,612 @@ +package api + +import ( + "context" + "database/sql" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/search" + "go.kenn.io/msgvault/internal/store" + "go.kenn.io/msgvault/internal/testutil" + "go.kenn.io/msgvault/internal/vector" + "go.kenn.io/msgvault/internal/vector/hybrid" +) + +func TestExploreRelativeDateRevisionIgnoresParseClock(t *testing.T) { + firstNow := time.Date(2026, 7, 19, 12, 0, 0, 0, time.UTC) + secondNow := firstNow.Add(time.Minute) + first := (&search.Parser{Now: func() time.Time { return firstNow }}).Parse("alpha newer_than:1d") + second := (&search.Parser{Now: func() time.Time { return secondNow }}).Parse("alpha newer_than:1d") + + assert.Equal(t, canonicalParsedExploreQuery(first), canonicalParsedExploreQuery(second)) +} + +func TestExploreFullTextResolverBoundsCandidateTransfer(t *testing.T) { + assertions := assert.New(t) + require := require.New(t) + require.Equal(10_000, query.MaxExploreCandidateMessageIDs) + store := &mockStore{stats: &StoreStats{}} + store.searchMessagesQueryFunc = func(_ *search.Query, offset, limit int) ([]APIMessage, int64, error) { + const resolverResultCount = 10_001 + remaining := resolverResultCount - offset + if remaining <= 0 { + return nil, resolverResultCount, nil + } + count := min(limit, remaining) + messages := make([]APIMessage, count) + for index := range messages { + messages[index].ID = int64(offset + index + 1) + } + return messages, resolverResultCount, nil + } + base := newExploreDuckDBFixture(t) + engine := &recordingExploreEngine{Engine: base, Explorer: base} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: store, Engine: engine, Logger: testLogger(), + }) + + response := postExploreJSON(t, srv, "/api/v1/explore", `{"query":"alpha","search_mode":"full_text","limit":50}`) + require.Equal(http.StatusOK, response.Code, response.Body.String()) + var body ExploreHTTPResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assertions.True(body.CandidatePoolSaturated) + assertions.Nil(body.TotalCount, "a bounded lexical candidate set must not publish an exact total") + require.NotEmpty(store.searchMessagesQueryLimits) + for _, limit := range store.searchMessagesQueryLimits { + require.LessOrEqual(limit, exploreMaxLimit) + } + require.Equal(10_000, store.searchMessagesQueryTransferred) + require.Len(engine.request.Search.CandidateMessageIDs, 10_000) + preflight := postExploreJSON(t, srv, "/api/v1/explore/preflight", fmt.Sprintf(`{ + "selection":{"mode":"all_matching","predicate":{"query":"alpha","search_mode":"full_text"}, + "exclusions":[],"cache_revision":%q,"search_provenance":{"lexical_index_revision":%q}} + }`, body.CacheRevision, body.SearchProvenance.LexicalIndexRevision)) + assertions.Equal(http.StatusConflict, preflight.Code, preflight.Body.String()) + assertions.Contains(preflight.Body.String(), "candidate_pool_saturated") +} + +type recordingExploreEngine struct { + query.Engine + query.Explorer + + request query.ExploreRequest +} + +func (e *recordingExploreEngine) Explore(ctx context.Context, request query.ExploreRequest) (*query.ExploreResponse, error) { + e.request = request + return e.Explorer.Explore(ctx, request) +} + +func TestExploreHybridMatchCountsUseCompleteLexicalMembership(t *testing.T) { + requirements := require.New(t) + srv := newReviewSemanticServerWithHits(t, []vector.Hit{{MessageID: 1, Score: .9, Rank: 1}}) + request := ExploreHTTPRequest{Query: "alpha", SearchMode: exploreSearchModeHybrid} + snapshotID := srv.exploreState.issueSnapshot(exploreCandidateSnapshot{ + RequestHash: exploreSnapshotRequestHash(request), IDs: []int64{1}, LexicalIDs: []int64{1, 2}, + Generation: 7, LexicalRevision: "fts5:exact-membership", + }) + response := postExploreJSON(t, srv, "/api/v1/explore/match-counts", fmt.Sprintf(`{ + "predicate":{"query":"alpha","search_mode":"hybrid","candidate_snapshot_id":%q}, + "row_keys":["source:1:message:m2"] + }`, snapshotID)) + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var body ExploreMatchCountsResponse + requirements.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + requirements.Len(body.Counts, 1) + assert.Equal(t, int64(1), body.Counts[0].Count) +} + +func TestExploreSnapshotPreservesResolvedEmptyLexicalMembership(t *testing.T) { + state := newExploreServerState(time.Now) + request := ExploreHTTPRequest{Query: "alpha", SearchMode: exploreSearchModeHybrid} + token := state.issueSnapshot(exploreCandidateSnapshot{ + RequestHash: exploreSnapshotRequestHash(request), + IDs: []int64{1}, + LexicalIDs: make([]int64, 0), + }) + + snapshot, ok := state.snapshot(token, exploreSnapshotRequestHash(request)) + require.True(t, ok) + assert.NotNil(t, snapshot.LexicalIDs, "resolved empty membership must remain distinct from unresolved nil membership") + assert.Empty(t, snapshot.LexicalIDs) +} + +func TestApplyIdentityScopeAcceptsCaseEquivalentDomain(t *testing.T) { + context := query.Context{Domains: []string{"EXAMPLE.COM"}} + err := applyIdentityScope(&context, ExploreFilter{Dimension: "domain", Values: []string{"example.com"}}) + require.NoError(t, err) + assert.Equal(t, []string{"example.com"}, context.Domains) +} + +func TestExploreRejectsTamperedCursorsOnEveryPaginatedSurface(t *testing.T) { + tests := []struct { + name string + path string + firstBody string + field string + value any + server func(*testing.T) *Server + }{ + { + name: "explore offset", path: "/api/v1/explore", firstBody: `{"limit":1}`, + field: "offset", value: float64(499), server: func(t *testing.T) *Server { + t.Helper() + return newTestServerWithEngine(t, newExploreDuckDBFixture(t)) + }, + }, + { + name: "groups cache revision", path: "/api/v1/explore/groups", firstBody: `{"grouping":["source"],"limit":1}`, + field: "revision", value: "cache-forged", server: func(t *testing.T) *Server { + t.Helper() + return newTestServerWithEngine(t, newExploreDuckDBFixture(t)) + }, + }, + { + name: "files search revision", path: "/api/v1/explore/files", firstBody: `{"predicate":{},"limit":1}`, + field: "search_revision", value: "fts5:forged", server: func(t *testing.T) *Server { + t.Helper() + return newTestServerWithEngine(t, newExploreDuckDBFixture(t)) + }, + }, + { + name: "semantic snapshot token", path: "/api/v1/explore", firstBody: `{"query":"alpha","search_mode":"semantic","limit":1}`, + field: "snapshot", value: "forged-snapshot", server: newReviewSemanticServer, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := tt.server(t) + first := postExploreJSON(t, srv, tt.path, tt.firstBody) + requirements.Equal(http.StatusOK, first.Code, first.Body.String()) + cursor := nextExploreCursor(t, first) + tampered := tamperExploreCursor(t, cursor, tt.field, tt.value) + secondBody := addExploreCursor(t, tt.firstBody, tampered) + second := postExploreJSON(t, srv, tt.path, secondBody) + assertions.Equal(http.StatusBadRequest, second.Code, second.Body.String()) + assertions.Contains(second.Body.String(), "invalid_cursor") + }) + } +} + +func TestExploreGroupsValidatesCompletePredicateBeforeQuerying(t *testing.T) { + srv := newTestServerWithEngine(t, newExploreDuckDBFixture(t)) + tests := []struct { + name string + body string + }{ + {name: "query without mode", body: `{"grouping":["source"],"query":"alpha"}`}, + {name: "mode without query", body: `{"grouping":["source"],"search_mode":"full_text"}`}, + {name: "unsupported presentation", body: `{"grouping":["source"],"presentation":"timeline"}`}, + {name: "unsupported sort", body: `{"grouping":["source"],"sort":[{"field":"key","direction":"sideways"}]}`}, + {name: "unknown group", body: `{"grouping":["sql"]}`}, + {name: "null group", body: `{"grouping":null}`}, + {name: "unknown field", body: `{"grouping":["source"],"sql":"select 1"}`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + response := postExploreJSON(t, srv, "/api/v1/explore/groups", tt.body) + assert.Equal(t, http.StatusBadRequest, response.Code, response.Body.String()) + }) + } +} + +func TestExploreGroupsPaginatesCompletePopulation(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + srv := newTestServerWithEngine(t, newExploreDuckDBFixture(t)) + first := postExploreJSON(t, srv, "/api/v1/explore/groups", `{"grouping":["source"],"limit":1}`) + requirements.Equal(http.StatusOK, first.Code, first.Body.String()) + var firstPage ExploreGroupsHTTPResponse + requirements.NoError(json.Unmarshal(first.Body.Bytes(), &firstPage)) + requirements.Len(firstPage.Rows, 1) + requirements.NotEmpty(firstPage.NextCursor) + + second := postExploreJSON(t, srv, "/api/v1/explore/groups", fmt.Sprintf( + `{"grouping":["source"],"limit":1,"cursor":%q}`, firstPage.NextCursor, + )) + requirements.Equal(http.StatusOK, second.Code, second.Body.String()) + var secondPage ExploreGroupsHTTPResponse + requirements.NoError(json.Unmarshal(second.Body.Bytes(), &secondPage)) + requirements.Len(secondPage.Rows, 1) + assertions.NotEqual(firstPage.Rows[0].Key, secondPage.Rows[0].Key) + assertions.Equal(firstPage.TotalCount, secondPage.TotalCount) +} + +func TestExploreSemanticUsesStrongestConversationHitInsteadOfChronologicalAnchor(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := newExploreSemanticChatDuckDBFixture(t) + const strongest = int64(1) + const anchor = int64(2) + const email = int64(3) + backend := &fakeVectorBackend{ + active: &vector.Generation{ID: 7, Model: "test", Dimension: 2, Fingerprint: "test:2", State: vector.GenerationActive}, + searchHits: []vector.Hit{ + {MessageID: strongest, Score: .95, Rank: 1}, + {MessageID: email, Score: .8, Rank: 2}, + {MessageID: anchor, Score: .4, Rank: 3}, + }, + } + hybridEngine := hybrid.NewEngine(backend, nil, realEmbedder{dim: 2}, hybrid.Config{ExpectedFingerprint: "test:2"}) + store := &mockStore{messages: []APIMessage{ + {ID: strongest, Snippet: "strongest conversation excerpt"}, + {ID: email, Snippet: "email excerpt"}, + {ID: anchor, Snippet: "newest conversation excerpt"}, + }, total: 3, stats: &StoreStats{}} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: store, Engine: engine, + HybridEngine: hybridEngine, Backend: backend, Logger: testLogger(), + }) + + response := postExploreJSON(t, srv, "/api/v1/explore", `{"query":"alpha","search_mode":"semantic","limit":10}`) + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var body ExploreHTTPResponse + requirements.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + requirements.Len(body.Rows, 2) + assertions.Equal(query.EntryConversation, body.Rows[0].Kind) + requirements.NotNil(body.Rows[0].AnchorMessageID) + assertions.Equal(anchor, *body.Rows[0].AnchorMessageID, "row identity remains chronological") + assertions.Equal("strongest conversation excerpt", body.Rows[0].Match.StrongestExcerpt) + requirements.NotNil(body.Rows[0].Match.SemanticScore) + assertions.InDelta(.95, *body.Rows[0].Match.SemanticScore, .0001) + assertions.Equal(query.EntryEmail, body.Rows[1].Kind) +} + +func TestExploreMapsEveryCommittedCacheFailureToStructuredUnavailable(t *testing.T) { + tests := []struct { + name string + readiness query.CacheReadiness + mutate func(*testing.T, string) + }{ + { + name: "missing root", readiness: query.CacheAbsent, + mutate: func(t *testing.T, dir string) { + t.Helper() + require.NoError(t, os.Rename(dir, dir+".missing")) + }, + }, + { + name: "missing state", readiness: query.CacheInterrupted, + mutate: func(t *testing.T, dir string) { + t.Helper() + require.NoError(t, os.Remove(query.CacheStatePath(dir))) + }, + }, + { + name: "malformed state", readiness: query.CacheInterrupted, + mutate: func(t *testing.T, dir string) { + t.Helper() + require.NoError(t, os.WriteFile(query.CacheStatePath(dir), []byte("{"), 0o600)) + }, + }, + { + name: "interrupted state", readiness: query.CacheInterrupted, + mutate: func(t *testing.T, dir string) { + t.Helper() + updateExploreCacheState(t, dir, func(state *query.CacheSyncState) { state.LastSyncAt = time.Time{} }) + }, + }, + { + name: "stale state", readiness: query.CacheStaleSchema, + mutate: func(t *testing.T, dir string) { + t.Helper() + updateExploreCacheState(t, dir, func(state *query.CacheSyncState) { state.SchemaVersion-- }) + }, + }, + { + name: "drifted state", readiness: query.CacheDrifted, + mutate: func(t *testing.T, dir string) { + t.Helper() + path := filepath.Join(dir, "messages", "year=2026", "messages.parquet") + info, err := os.Stat(path) + require.NoError(t, err) + require.NoError(t, os.Chtimes(path, info.ModTime().Add(time.Hour), info.ModTime().Add(time.Hour))) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine, analyticsDir := newExploreDuckDBFixtureWithDir(t) + srv := newTestServerWithEngine(t, engine) + tt.mutate(t, analyticsDir) + response := postExploreJSON(t, srv, "/api/v1/explore", `{}`) + requirements.Equal(http.StatusServiceUnavailable, response.Code, response.Body.String()) + var body struct { + Error string `json:"error"` + Readiness query.CacheReadiness `json:"readiness"` + RecoveryAction string `json:"recovery_action"` + } + requirements.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assertions.Equal("analytical_cache_unavailable", body.Error) + assertions.Equal(tt.readiness, body.Readiness) + assertions.NotEmpty(body.RecoveryAction) + }) + } +} + +func TestExploreCanonicalizesParsedFullTextQueryForRevisionAndCountCache(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := newExploreDuckDBFixture(t) + store := &mockStore{ + messages: []APIMessage{{ID: 1}, {ID: 2}}, total: 2, stats: &StoreStats{}, + } + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: store, Engine: engine, Logger: testLogger(), + }) + pairs := [][2]string{ + {"alpha beta", "alpha beta"}, + {"from:alice@example.com subject:invoice", "subject:invoice from:alice@example.com"}, + {"from:alice@example.com from:bob@example.com", "from:bob@example.com from:alice@example.com"}, + } + for _, pair := range pairs { + first := postExploreJSON(t, srv, "/api/v1/explore", fmt.Sprintf( + `{"query":%q,"search_mode":"full_text"}`, pair[0], + )) + requirements.Equal(http.StatusOK, first.Code, first.Body.String()) + second := postExploreJSON(t, srv, "/api/v1/explore", fmt.Sprintf( + `{"query":%q,"search_mode":"full_text"}`, pair[1], + )) + requirements.Equal(http.StatusOK, second.Code, second.Body.String()) + var firstBody, secondBody ExploreHTTPResponse + requirements.NoError(json.Unmarshal(first.Body.Bytes(), &firstBody)) + requirements.NoError(json.Unmarshal(second.Body.Bytes(), &secondBody)) + assertions.Equal(firstBody.SearchProvenance.LexicalIndexRevision, secondBody.SearchProvenance.LexicalIndexRevision, pair) + } + + bareQueries := []string{"alpha beta", "beta alpha", "alpha alpha beta", "alpha beta beta"} + var bareRevision string + for _, queryText := range bareQueries { + response := postExploreJSON(t, srv, "/api/v1/explore", fmt.Sprintf( + `{"query":%q,"search_mode":"full_text"}`, queryText, + )) + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var body ExploreHTTPResponse + requirements.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + if bareRevision == "" { + bareRevision = body.SearchProvenance.LexicalIndexRevision + } + assertions.Equal(bareRevision, body.SearchProvenance.LexicalIndexRevision, queryText) + } + + for _, queryText := range bareQueries { + response := postExploreJSON(t, srv, "/api/v1/explore/match-counts", fmt.Sprintf(`{ + "predicate":{"query":%q,"search_mode":"full_text"}, + "row_keys":["source:1:message:m1"] + }`, queryText)) + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + } + srv.exploreState.mu.Lock() + assertions.Len(srv.exploreState.matchCounts, 1, "equivalent parsed queries share one count-cache entry") + srv.exploreState.mu.Unlock() +} + +func TestExploreSemanticSnapshotExpiresAndReportsCandidatePoolSaturation(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + hits := make([]vector.Hit, exploreMaxLimit) + for i := range hits { + hits[i] = vector.Hit{MessageID: int64(i%2 + 1), Score: .9, Rank: i + 1} + } + srv := newReviewSemanticServerWithHits(t, hits) + now := time.Date(2026, 7, 19, 12, 0, 0, 0, time.UTC) + srv.exploreState.now = func() time.Time { return now } + + first := postExploreJSON(t, srv, "/api/v1/explore", `{"query":"alpha","search_mode":"semantic","limit":1}`) + requirements.Equal(http.StatusOK, first.Code, first.Body.String()) + var firstBody ExploreHTTPResponse + requirements.NoError(json.Unmarshal(first.Body.Bytes(), &firstBody)) + assertions.True(firstBody.CandidatePoolSaturated) + requirements.NotEmpty(firstBody.NextCursor) + + now = now.Add(exploreCandidateSnapshotTTL + time.Second) + second := postExploreJSON(t, srv, "/api/v1/explore", addExploreCursor(t, + `{"query":"alpha","search_mode":"semantic","limit":1}`, firstBody.NextCursor, + )) + assertions.Equal(http.StatusConflict, second.Code, second.Body.String()) + assertions.Contains(second.Body.String(), "candidate_snapshot_expired") +} + +func TestExploreRequiresAuthenticatedCSRFSafeRemoteMutation(t *testing.T) { + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIKey: testSessionAPIKey}}, + Store: &mockStore{stats: &StoreStats{}}, Engine: newExploreDuckDBFixture(t), Logger: testLogger(), + }) + + unauthenticated := performSavedViewRequest(t, srv, http.MethodPost, "/api/v1/explore", []byte(`{}`), nil) + assert.Equal(t, http.StatusUnauthorized, unauthenticated.Code, unauthenticated.Body.String()) + + session := loginSavedViewSession(t, srv) + missingCSRF := performSavedViewRequest(t, srv, http.MethodPost, "/api/v1/explore", []byte(`{}`), session.headers()) + assert.Equal(t, http.StatusForbidden, missingCSRF.Code, missingCSRF.Body.String()) + + authorized := performSavedViewRequest(t, srv, http.MethodPost, "/api/v1/explore", []byte(`{}`), session.mutationHeaders()) + assert.Equal(t, http.StatusOK, authorized.Code, authorized.Body.String()) +} + +func TestExploreFullTextUsesRealSQLiteFTS5Candidates(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + st := testutil.NewSQLiteTestStore(t) + source, err := st.GetOrCreateSource("gmail", "archive@example.com") + requirements.NoError(err) + conversationID, err := st.EnsureConversation(source.ID, "thread", "Thread") + requirements.NoError(err) + messages := []struct { + sourceID string + body string + }{ + {sourceID: "m1", body: "alpha uniquely matches this message"}, + {sourceID: "m2", body: "beta belongs to the other message"}, + } + for i, message := range messages { + messageID, err := st.UpsertMessage(&store.Message{ + ConversationID: conversationID, SourceID: source.ID, SourceMessageID: message.sourceID, + MessageType: "email", Subject: sql.NullString{String: message.body, Valid: true}, SizeEstimate: 100, + }) + requirements.NoError(err) + requirements.Equal(int64(i+1), messageID, "fixture IDs must align with committed analytical facts") + requirements.NoError(st.UpsertMessageBody(messageID, sql.NullString{String: message.body, Valid: true}, sql.NullString{})) + } + _, err = st.BackfillFTS(nil) + requirements.NoError(err) + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: st, + Engine: newExploreDuckDBFixture(t), Logger: testLogger(), + }) + + response := postExploreJSON(t, srv, "/api/v1/explore", `{"query":"alpha","search_mode":"full_text"}`) + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var body ExploreHTTPResponse + requirements.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + requirements.Len(body.Rows, 1) + requirements.NotNil(body.Rows[0].AnchorMessageID) + assertions.Equal(int64(1), *body.Rows[0].AnchorMessageID) + assertions.NotEmpty(body.SearchProvenance.LexicalIndexRevision) +} + +func updateExploreCacheState(t *testing.T, analyticsDir string, update func(*query.CacheSyncState)) { + t.Helper() + data, err := os.ReadFile(query.CacheStatePath(analyticsDir)) + require.NoError(t, err) + var state query.CacheSyncState + require.NoError(t, json.Unmarshal(data, &state)) + update(&state) + data, err = json.Marshal(state) + require.NoError(t, err) + require.NoError(t, os.WriteFile(query.CacheStatePath(analyticsDir), data, 0o600)) +} + +func newExploreSemanticChatDuckDBFixture(t *testing.T) *query.DuckDBEngine { + t.Helper() + analyticsDir := t.TempDir() + db, err := sql.Open("duckdb", "") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, db.Close()) }) + tables := []struct { + dir, file, columns, values string + empty bool + }{ + { + dir: "messages/year=2026", file: "messages.parquet", + columns: "id, source_id, source_message_id, conversation_id, subject, snippet, sent_at, size_estimate, has_attachments, attachment_count, deleted_from_source_at, sender_id, message_type, year, month", + values: `(1::BIGINT, 1::BIGINT, 'chat-1', 700::BIGINT, '', 'strongest conversation excerpt', TIMESTAMP '2026-07-18 10:00:00', 100::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'imessage', 2026, 7), + (2::BIGINT, 1::BIGINT, 'chat-2', 700::BIGINT, '', 'newest conversation excerpt', TIMESTAMP '2026-07-18 12:00:00', 100::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'imessage', 2026, 7), + (3::BIGINT, 2::BIGINT, 'mail-1', 701::BIGINT, 'middle-ranked email', 'email excerpt', TIMESTAMP '2026-07-18 11:00:00', 100::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2026, 7)`, + }, + {dir: "sources", file: "sources.parquet", columns: "id, account_email, source_type", values: `(1::BIGINT, '+15550000000', 'imessage'), (2::BIGINT, 'archive@example.com', 'gmail')`}, + {dir: "participants", file: "participants.parquet", columns: "id, email_address, domain, display_name, phone_number", values: `(1::BIGINT, 'alice@example.com', 'example.com', 'Alice', '')`, empty: true}, + {dir: "participant_identifiers", file: "participant_identifiers.parquet", columns: "participant_id, identifier_type, identifier_value, display_value, is_primary", values: `(1::BIGINT, 'email', 'alice@example.com', 'alice@example.com', true)`, empty: true}, + {dir: "message_recipients", file: "message_recipients.parquet", columns: "message_id, participant_id, recipient_type, display_name", values: `(1::BIGINT, 1::BIGINT, 'from', 'Alice')`, empty: true}, + {dir: "labels", file: "labels.parquet", columns: "id, name", values: `(0::BIGINT, '')`, empty: true}, + {dir: "message_labels", file: "message_labels.parquet", columns: "message_id, label_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "attachments", file: "attachments.parquet", columns: "attachment_id, message_id, size, filename", values: `(0::BIGINT, 1::BIGINT, 0::BIGINT, '')`, empty: true}, + {dir: "conversations", file: "conversations.parquet", columns: "id, source_conversation_id, title, conversation_type", values: `(700::BIGINT, 'chat', 'Conversation', 'direct_chat'), (701::BIGINT, 'mail', '', 'email')`}, + {dir: "conversation_participants", file: "conversation_participants.parquet", columns: "conversation_id, participant_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "owner_participants", file: "owner_participants.parquet", columns: "source_id, participant_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "participant_clusters", file: "participant_clusters.parquet", columns: "participant_id, canonical_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + } + for _, table := range tables { + dir := filepath.Join(analyticsDir, table.dir) + require.NoError(t, os.MkdirAll(dir, 0o755)) + where := "" + if table.empty { + where = " WHERE false" + } + path := filepath.ToSlash(filepath.Join(dir, table.file)) + _, err := db.Exec(fmt.Sprintf("COPY (SELECT * FROM (VALUES %s) AS t(%s)%s) TO '%s' (FORMAT PARQUET)", table.values, table.columns, where, path)) + require.NoError(t, err, "write %s", table.dir) + } + fingerprint, err := query.CacheDatasetFingerprint(analyticsDir) + require.NoError(t, err) + state, err := json.Marshal(query.CacheSyncState{ + LastMessageID: 3, LastSyncAt: time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC), + SchemaVersion: query.CacheSchemaVersion, PublishedAt: time.Date(2026, 7, 18, 12, 1, 0, 0, time.UTC), + DatasetFingerprint: fingerprint, + }) + require.NoError(t, err) + require.NoError(t, os.WriteFile(query.CacheStatePath(analyticsDir), state, 0o600)) + engine, err := query.NewDuckDBEngine(analyticsDir, "", nil) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, engine.Close()) }) + return engine +} + +func newReviewSemanticServer(t *testing.T) *Server { + t.Helper() + return newReviewSemanticServerWithHits(t, []vector.Hit{ + {MessageID: 1, Score: .9, Rank: 1}, {MessageID: 2, Score: .8, Rank: 2}, + }) +} + +func newReviewSemanticServerWithHits(t *testing.T, hits []vector.Hit) *Server { + t.Helper() + engine := newExploreDuckDBFixture(t) + backend := &fakeVectorBackend{ + active: &vector.Generation{ID: 7, Model: "test", Dimension: 2, Fingerprint: "test:2", State: vector.GenerationActive}, + searchHits: hits, + } + hybridEngine := hybrid.NewEngine(backend, nil, realEmbedder{dim: 2}, hybrid.Config{ExpectedFingerprint: "test:2"}) + store := &mockStore{messages: []APIMessage{{ID: 1}, {ID: 2}}, total: 2, stats: &StoreStats{}} + return NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: store, Engine: engine, + HybridEngine: hybridEngine, Backend: backend, Logger: testLogger(), + }) +} + +func nextExploreCursor(t *testing.T, response *httptest.ResponseRecorder) string { + t.Helper() + var body struct { + NextCursor string `json:"next_cursor"` + } + require.NoError(t, json.Unmarshal(response.Body.Bytes(), &body)) + require.NotEmpty(t, body.NextCursor) + return body.NextCursor +} + +func tamperExploreCursor(t *testing.T, encoded, field string, value any) string { + t.Helper() + payload := strings.SplitN(encoded, ".", 2)[0] + data, err := base64.RawURLEncoding.DecodeString(payload) + require.NoError(t, err) + var cursor map[string]any + require.NoError(t, json.Unmarshal(data, &cursor)) + cursor[field] = value + data, err = json.Marshal(cursor) + require.NoError(t, err) + tamperedPayload := base64.RawURLEncoding.EncodeToString(data) + if _, signature, found := strings.Cut(encoded, "."); found { + return tamperedPayload + "." + signature + } + return tamperedPayload +} + +func addExploreCursor(t *testing.T, body, cursor string) string { + t.Helper() + var object map[string]any + require.NoError(t, json.Unmarshal([]byte(body), &object)) + object["cursor"] = cursor + data, err := json.Marshal(object) + require.NoError(t, err) + return string(data) +} diff --git a/internal/api/explore_state.go b/internal/api/explore_state.go new file mode 100644 index 000000000..2cfee663d --- /dev/null +++ b/internal/api/explore_state.go @@ -0,0 +1,224 @@ +package api + +import ( + "crypto/rand" + "encoding/base64" + "maps" + "sync" + "time" +) + +const ( + exploreOperationTokenTTL = 5 * time.Minute + exploreCandidateSnapshotTTL = 5 * time.Minute + exploreMatchCountTTL = 2 * time.Minute + exploreStateMaxEntries = 128 +) + +type exploreCandidateHit struct { + MessageID int64 + Score float64 + Excerpt string +} + +type exploreCandidateSnapshot struct { + RequestHash string + IDs []int64 + LexicalIDs []int64 + Hits []exploreCandidateHit + Generation int64 + LexicalRevision string + PoolSaturated bool + ExpiresAt time.Time +} + +type exploreMatchCountEntry struct { + Counts map[string]int64 + ExpiresAt time.Time +} + +type exploreOperationGrant struct { + SelectionHash string + Count int64 + Revision string + ExpiresAt time.Time +} + +type exploreServerState struct { + mu sync.Mutex + now func() time.Time + operations map[string]exploreOperationGrant + snapshots map[string]exploreCandidateSnapshot + matchCounts map[string]exploreMatchCountEntry +} + +func newExploreServerState(now func() time.Time) *exploreServerState { + if now == nil { + now = time.Now + } + return &exploreServerState{ + now: now, operations: make(map[string]exploreOperationGrant), + snapshots: make(map[string]exploreCandidateSnapshot), matchCounts: make(map[string]exploreMatchCountEntry), + } +} + +func cloneExploreIDs(ids []int64) []int64 { + if ids == nil { + return nil + } + return append(make([]int64, 0, len(ids)), ids...) +} + +func (s *exploreServerState) issueSnapshot(snapshot exploreCandidateSnapshot) string { + s.mu.Lock() + defer s.mu.Unlock() + now := s.now() + s.pruneLocked(now) + s.evictSnapshotsLocked() + token := randomExploreToken() + snapshot.IDs = cloneExploreIDs(snapshot.IDs) + snapshot.LexicalIDs = cloneExploreIDs(snapshot.LexicalIDs) + snapshot.Hits = append([]exploreCandidateHit(nil), snapshot.Hits...) + snapshot.ExpiresAt = now.Add(exploreCandidateSnapshotTTL) + s.snapshots[token] = snapshot + return token +} + +func (s *exploreServerState) snapshot(token, requestHash string) (exploreCandidateSnapshot, bool) { + s.mu.Lock() + defer s.mu.Unlock() + now := s.now() + s.pruneLocked(now) + snapshot, ok := s.snapshots[token] + if !ok || snapshot.RequestHash != requestHash { + return exploreCandidateSnapshot{}, false + } + snapshot.IDs = cloneExploreIDs(snapshot.IDs) + snapshot.LexicalIDs = cloneExploreIDs(snapshot.LexicalIDs) + snapshot.Hits = append([]exploreCandidateHit(nil), snapshot.Hits...) + return snapshot, true +} + +func (s *exploreServerState) getMatchCounts(key string) (map[string]int64, bool) { + s.mu.Lock() + defer s.mu.Unlock() + s.pruneLocked(s.now()) + entry, ok := s.matchCounts[key] + if !ok { + return nil, false + } + return cloneExploreCounts(entry.Counts), true +} + +func (s *exploreServerState) putMatchCounts(key string, counts map[string]int64) { + s.mu.Lock() + defer s.mu.Unlock() + now := s.now() + s.pruneLocked(now) + for len(s.matchCounts) >= exploreStateMaxEntries { + var oldestKey string + var oldest time.Time + for candidate, entry := range s.matchCounts { + if oldestKey == "" || entry.ExpiresAt.Before(oldest) { + oldestKey, oldest = candidate, entry.ExpiresAt + } + } + delete(s.matchCounts, oldestKey) + } + s.matchCounts[key] = exploreMatchCountEntry{Counts: cloneExploreCounts(counts), ExpiresAt: now.Add(exploreMatchCountTTL)} +} + +func (s *exploreServerState) pruneLocked(now time.Time) { + for token, grant := range s.operations { + if !grant.ExpiresAt.After(now) { + delete(s.operations, token) + } + } + for token, snapshot := range s.snapshots { + if !snapshot.ExpiresAt.After(now) { + delete(s.snapshots, token) + } + } + for key, entry := range s.matchCounts { + if !entry.ExpiresAt.After(now) { + delete(s.matchCounts, key) + } + } +} + +func (s *exploreServerState) evictSnapshotsLocked() { + for len(s.snapshots) >= exploreStateMaxEntries { + var oldestToken string + var oldest time.Time + for token, snapshot := range s.snapshots { + if oldestToken == "" || snapshot.ExpiresAt.Before(oldest) { + oldestToken, oldest = token, snapshot.ExpiresAt + } + } + delete(s.snapshots, oldestToken) + } +} + +func cloneExploreCounts(input map[string]int64) map[string]int64 { + result := make(map[string]int64, len(input)) + maps.Copy(result, input) + return result +} + +func randomExploreToken() string { + var raw [24]byte + if _, err := rand.Read(raw[:]); err != nil { + panic("crypto/rand unavailable: " + err.Error()) + } + return base64.RawURLEncoding.EncodeToString(raw[:]) +} + +func (s *exploreServerState) issueOperation(selectionHash string, count int64, revision string) string { + s.mu.Lock() + defer s.mu.Unlock() + now := s.now() + for token, grant := range s.operations { + if !grant.ExpiresAt.After(now) { + delete(s.operations, token) + } + } + for len(s.operations) >= exploreStateMaxEntries { + var oldestToken string + var oldest time.Time + for token, grant := range s.operations { + if oldestToken == "" || grant.ExpiresAt.Before(oldest) { + oldestToken, oldest = token, grant.ExpiresAt + } + } + delete(s.operations, oldestToken) + } + token := randomExploreToken() + s.operations[token] = exploreOperationGrant{ + SelectionHash: selectionHash, Count: count, Revision: revision, + ExpiresAt: now.Add(exploreOperationTokenTTL), + } + return token +} + +func (s *exploreServerState) operation(token, selectionHash string) (exploreOperationGrant, bool) { + s.mu.Lock() + defer s.mu.Unlock() + s.pruneLocked(s.now()) + grant, ok := s.operations[token] + if !ok || grant.SelectionHash != selectionHash { + return exploreOperationGrant{}, false + } + return grant, true +} + +func (s *exploreServerState) takeOperation(token, selectionHash string) (exploreOperationGrant, bool) { + s.mu.Lock() + defer s.mu.Unlock() + s.pruneLocked(s.now()) + grant, ok := s.operations[token] + if !ok || grant.SelectionHash != selectionHash { + return exploreOperationGrant{}, false + } + delete(s.operations, token) + return grant, true +} diff --git a/internal/api/explore_test.go b/internal/api/explore_test.go new file mode 100644 index 000000000..5e34488d9 --- /dev/null +++ b/internal/api/explore_test.go @@ -0,0 +1,91 @@ +package api + +import ( + "encoding/json" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/query" +) + +func TestExploreRejectsUnknownFilterDimension(t *testing.T) { + srv := newTestServerWithEngine(t, newExploreDuckDBFixture(t)) + response := postExploreJSON(t, srv, "/api/v1/explore", `{"filters":[{"dimension":"sql","values":["select *"]}]}`) + assert.Equal(t, http.StatusBadRequest, response.Code) + assert.Contains(t, response.Body.String(), "unknown filter dimension") +} + +func TestExploreCursorAcceptsCanonicalFilterValueOrdering(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := newTestServerWithEngine(t, newExploreDuckDBFixture(t)) + first := postExploreJSON(t, srv, "/api/v1/explore", `{ + "filters":[{"dimension":"source","values":["2","1"]}],"limit":1 + }`) + requirements.Equal(http.StatusOK, first.Code, first.Body.String()) + var page ExploreHTTPResponse + requirements.NoError(json.Unmarshal(first.Body.Bytes(), &page)) + requirements.NotEmpty(page.NextCursor) + + second := postExploreJSON(t, srv, "/api/v1/explore", `{ + "filters":[{"dimension":"source","values":["1","2"]}],"limit":1,"cursor":"`+page.NextCursor+`" + }`) + requirements.Equal(http.StatusOK, second.Code, second.Body.String()) + var secondPage ExploreHTTPResponse + requirements.NoError(json.Unmarshal(second.Body.Bytes(), &secondPage)) + requirements.Len(secondPage.Rows, 1) + assertions.Equal("Older", secondPage.Rows[0].Title) + assertions.Equal(page.CacheRevision, secondPage.CacheRevision) +} + +func TestExploreUnavailableReturnsNamedReadinessAndRecovery(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv, _ := newTestServerWithMockStore(t) + response := postExploreJSON(t, srv, "/api/v1/explore", `{}`) + requirements.Equal(http.StatusServiceUnavailable, response.Code, response.Body.String()) + var body ExploreCacheUnavailableResponse + requirements.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assertions.Equal("analytical_cache_unavailable", body.Error) + assertions.Equal(query.CacheAbsent, body.Readiness) + assertions.NotEmpty(body.RecoveryAction) +} + +func TestExploreServerStateBoundsAndExpiresTransientCapabilities(t *testing.T) { + assertions := assert.New(t) + now := time.Date(2026, 7, 19, 12, 0, 0, 0, time.UTC) + state := newExploreServerState(func() time.Time { return now }) + oldestOperationToken := state.issueOperation("oldest-selection", 1, "cache") + var operationToken string + for range exploreStateMaxEntries + 10 { + now = now.Add(time.Millisecond) + state.issueSnapshot(exploreCandidateSnapshot{RequestHash: "request", IDs: []int64{1}}) + operationToken = state.issueOperation("selection", 1, "cache") + state.putMatchCounts(randomExploreToken(), map[string]int64{"row": 1}) + } + state.mu.Lock() + assertions.LessOrEqual(len(state.snapshots), exploreStateMaxEntries) + assertions.LessOrEqual(len(state.operations), exploreStateMaxEntries) + assertions.LessOrEqual(len(state.matchCounts), exploreStateMaxEntries) + state.mu.Unlock() + _, oldestOperationExists := state.operation(oldestOperationToken, "oldest-selection") + assertions.False(oldestOperationExists, "the oldest operation grant is evicted at capacity") + _, wrongSelectionExists := state.operation(operationToken, "other-selection") + assertions.False(wrongSelectionExists, "operation grants stay bound to their preflight selection") + grant, operationExists := state.operation(operationToken, "selection") + assertions.True(operationExists) + assertions.Equal(int64(1), grant.Count) + assertions.Equal("cache", grant.Revision) + + now = now.Add(exploreCandidateSnapshotTTL + time.Second) + state.issueSnapshot(exploreCandidateSnapshot{RequestHash: "fresh"}) + _, operationExists = state.operation(operationToken, "selection") + assertions.False(operationExists) + state.mu.Lock() + assertions.Len(state.snapshots, 1) + assertions.Empty(state.matchCounts) + state.mu.Unlock() +} diff --git a/internal/api/files.go b/internal/api/files.go new file mode 100644 index 000000000..d948936f2 --- /dev/null +++ b/internal/api/files.go @@ -0,0 +1,491 @@ +package api + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "os" + "slices" + "strconv" + "strings" + "time" + + "github.com/danielgtaylor/huma/v2" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/store" +) + +const filesMaxLimit = 500 + +type FileContentState string + +const ( + FileContentMetadataOnly FileContentState = "metadata_only" + FileContentURLOnly FileContentState = "url_only" + FileContentMissingBlob FileContentState = "missing_blob" + FileContentLocal FileContentState = "local_content" +) + +type FileSearchSort struct { + Field string `json:"field"` + Direction string `json:"direction"` +} + +type FileSearchHTTPRequest struct { + Predicate ExploreHTTPRequest `json:"predicate"` + FilenameQuery string `json:"filename_query,omitempty"` + MIMEFamilies []query.FileMIMEFamily `json:"mime_families,omitempty"` + Sort FileSearchSort `json:"sort"` + Cursor string `json:"cursor,omitempty"` + Limit int `json:"limit,omitempty" minimum:"0" maximum:"500"` +} + +type FileSearchRow struct { + ID int64 `json:"id"` + Key string `json:"key"` + EntryKey string `json:"entry_key"` + MessageID int64 `json:"message_id"` + ConversationID int64 `json:"conversation_id"` + OccurredAt time.Time `json:"occurred_at"` + SourceID int64 `json:"source_id"` + SourceType string `json:"source_type"` + SourceIdentifier string `json:"source_identifier"` + ContainingTitle string `json:"containing_title"` + Filename string `json:"filename"` + MimeType string `json:"mime_type"` + MIMEFamily query.FileMIMEFamily `json:"mime_family"` + Size int64 `json:"size_bytes"` + ParticipantIDs []int64 `json:"participant_ids,omitempty"` + ParticipantLabels []string `json:"participant_labels,omitempty"` + ParticipantDomains []string `json:"participant_domains,omitempty"` + ContentState FileContentState `json:"content_state" enum:"metadata_only,url_only,missing_blob,local_content"` + ContentAvailable bool `json:"content_available"` +} + +type FileSearchHTTPResponse struct { + Files []FileSearchRow `json:"files"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + NextCursor string `json:"next_cursor,omitempty"` + CandidateSnapshotID string `json:"candidate_snapshot_id,omitempty"` +} + +type FileGroupsHTTPRequest struct { + Predicate ExploreHTTPRequest `json:"predicate"` + FilenameQuery string `json:"filename_query,omitempty"` + MIMEFamilies []query.FileMIMEFamily `json:"mime_families,omitempty"` + Grouping []ExploreGroupDimension `json:"grouping" minItems:"1" maxItems:"1"` + Sort []ExploreGroupSort `json:"sort,omitempty" maxItems:"1"` + Cursor string `json:"cursor,omitempty"` + Limit int `json:"limit,omitempty" minimum:"0" maximum:"500"` +} + +type FileGroupsHTTPResponse struct { + Rows []query.ExploreGroupRow `json:"rows"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + NextCursor string `json:"next_cursor,omitempty"` + CandidateSnapshotID string `json:"candidate_snapshot_id,omitempty"` +} + +type FileMetadataResponse struct { + ID int64 `json:"id"` + MessageID int64 `json:"message_id"` + ConversationID int64 `json:"conversation_id"` + Filename string `json:"filename"` + MimeType string `json:"mime_type"` + Size int64 `json:"size_bytes"` + ContentHash string `json:"content_hash,omitempty"` + URL string `json:"url,omitempty"` + ContentState FileContentState `json:"content_state" enum:"metadata_only,url_only,missing_blob,local_content"` + ContentAvailable bool `json:"content_available"` +} + +type fileMetadataCatalog interface { + GetFileMetadata(ctx context.Context, id int64) (*store.FileMetadata, error) + GetFileMetadataBatch(ctx context.Context, ids []int64) (map[int64]store.FileMetadata, error) +} + +func (s *Server) registerFilesRoutes(api huma.API) { + registerExploreRoute[FileSearchHTTPRequest, FileSearchHTTPResponse]( + api, "searchFiles", "/files/search", "Search analytical files", s.handleSearchFiles, + ) + registerExploreRoute[FileSearchHTTPRequest, FileSearchHTTPResponse]( + api, "searchPersonFiles", "/people/{id}/files/search", "Search one person's analytical files", s.handleSearchPersonFiles, + ) + registerExploreRoute[FileSearchHTTPRequest, FileSearchHTTPResponse]( + api, "searchDomainFiles", "/domains/{domain}/files/search", "Search one domain's analytical files", s.handleSearchDomainFiles, + ) + registerExploreRoute[FileGroupsHTTPRequest, FileGroupsHTTPResponse]( + api, "groupFiles", "/files/groups", "Group analytical files", s.handleGroupFiles, + ) + registerAPIV1RawHumaJSONRoute[FileMetadataResponse]( + api, "getFile", http.MethodGet, "/files/{id}", "Get authoritative file metadata", s.handleGetFile, + ) +} + +func (s *Server) handleGroupFiles(w http.ResponseWriter, r *http.Request) { + var request FileGroupsHTTPRequest + if !decodeExploreJSON(w, r, &request) { + return + } + predicate, err := prepareExplorePredicate(request.Predicate) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_files_predicate", err.Error()) + return + } + if len(request.Grouping) != 1 { + writeError(w, http.StatusBadRequest, "invalid_grouping", "exactly one grouping dimension is required") + return + } + dimension := ExploreGroupDimension(strings.ToLower(strings.TrimSpace(string(request.Grouping[0])))) + if !slices.Contains(exploreGroupDimensions, dimension) { + writeError(w, http.StatusBadRequest, "invalid_grouping", fmt.Sprintf("unknown grouping dimension %q", dimension)) + return + } + request.FilenameQuery = strings.TrimSpace(request.FilenameQuery) + for i := range request.MIMEFamilies { + request.MIMEFamilies[i] = query.FileMIMEFamily(strings.ToLower(strings.TrimSpace(string(request.MIMEFamilies[i])))) + } + slices.Sort(request.MIMEFamilies) + request.MIMEFamilies = slices.Compact(request.MIMEFamilies) + if request.Limit == 0 { + request.Limit = exploreDefaultLimit + } + if request.Limit < 1 || request.Limit > filesMaxLimit { + writeError(w, http.StatusBadRequest, "invalid_limit", fmt.Sprintf("limit must be between 1 and %d", filesMaxLimit)) + return + } + sortSpec := query.SortSpec{Field: "count", Direction: apiSortDirectionDesc} + if len(request.Sort) > 1 { + writeError(w, http.StatusBadRequest, "invalid_sort", "at most one group sort is supported") + return + } + if len(request.Sort) == 1 { + sortSpec = query.SortSpec{ + Field: strings.ToLower(strings.TrimSpace(request.Sort[0].Field)), + Direction: strings.ToLower(strings.TrimSpace(request.Sort[0].Direction)), + } + if !slices.Contains([]string{"key", "count", "estimated_bytes", "latest_at"}, sortSpec.Field) || + !slices.Contains([]string{"asc", apiSortDirectionDesc}, sortSpec.Direction) { + writeError(w, http.StatusBadRequest, "invalid_sort", "unknown group sort field or direction") + return + } + request.Sort = []ExploreGroupSort{{Field: sortSpec.Field, Direction: sortSpec.Direction}} + } + request.Predicate = predicate.request + request.Grouping = []ExploreGroupDimension{dimension} + canonical := request + canonical.Cursor = "" + requestHash := hashCanonicalValue(canonical, false) + offset, ok := s.parseExploreCursor(w, request.Cursor, requestHash) + if !ok { + return + } + searchRequest := predicate.request + var cursor exploreCursor + if request.Cursor != "" { + cursor, _ = s.decodeExploreCursor(request.Cursor) + if searchRequest.SearchMode == exploreSearchModeSemantic || searchRequest.SearchMode == exploreSearchModeHybrid { + if cursor.Snapshot == "" { + writeError(w, http.StatusBadRequest, "invalid_cursor", "semantic cursor is missing its candidate snapshot") + return + } + searchRequest.CandidateSnapshotID = cursor.Snapshot + } + } + searchSpec, snapshotID, ok := s.resolveExploreSearch(r.Context(), w, searchRequest) + if !ok || !requireCompleteCandidatePool(w, searchSpec) { + return + } + if request.Cursor != "" && cursor.SearchRevision != exploreResolvedSearchRevision(searchSpec) { + writeError(w, http.StatusConflict, "search_revision_changed", "The resolved search index revision changed; restart pagination") + return + } + grouper, ok := s.engine.(query.FileGrouper) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + result, err := grouper.GroupFiles(r.Context(), query.FileGroupRequest{ + Explore: query.ExploreRequest{Context: predicate.query.Context, Search: searchSpec}, + FilenameQuery: request.FilenameQuery, MIMEFamilies: request.MIMEFamilies, + Dimension: string(dimension), Sort: sortSpec, + Page: query.PageSpec{Limit: request.Limit, Offset: offset}, + }) + if err != nil { + s.writeExploreError(w, err) + return + } + if request.Cursor != "" && cursor.Revision != result.CacheRevision { + writeError(w, http.StatusConflict, "archive_revision_changed", "The committed analytical cache changed; restart pagination") + return + } + response := FileGroupsHTTPResponse{ + Rows: result.Rows, TotalCount: result.TotalCount, CacheRevision: result.CacheRevision, + SearchProvenance: result.SearchProvenance, CandidateSnapshotID: snapshotID, + } + if next := offset + len(result.Rows); next < int(result.TotalCount) { + response.NextCursor = s.encodeExploreCursor(exploreCursor{ + Offset: next, Request: requestHash, Revision: result.CacheRevision, + SearchRevision: exploreResolvedSearchRevision(searchSpec), Snapshot: snapshotID, + }) + } + writeJSON(w, http.StatusOK, response) +} + +func (s *Server) handleSearchFiles(w http.ResponseWriter, r *http.Request) { + s.handleSearchFilesWithScope(w, r, nil) +} + +func (s *Server) handleSearchPersonFiles(w http.ResponseWriter, r *http.Request) { + id, ok := positivePersonPathID(w, r) + if !ok { + return + } + scope := ExploreFilter{Dimension: "participant", Values: []string{strconv.FormatInt(id, 10)}} + s.handleSearchFilesWithScope(w, r, &scope) +} + +func (s *Server) handleSearchDomainFiles(w http.ResponseWriter, r *http.Request) { + domain, ok := domainPathFactSuffix(w, r, "/files/search") + if !ok { + return + } + scope := ExploreFilter{Dimension: "domain", Values: []string{domain}} + s.handleSearchFilesWithScope(w, r, &scope) +} + +func (s *Server) handleSearchFilesWithScope(w http.ResponseWriter, r *http.Request, scope *ExploreFilter) { + var request FileSearchHTTPRequest + if !decodeExploreJSON(w, r, &request) { + return + } + predicate, err := prepareExplorePredicate(request.Predicate) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_files_predicate", err.Error()) + return + } + if request.Limit == 0 { + request.Limit = 100 + } + if request.Limit < 1 || request.Limit > filesMaxLimit { + writeError(w, http.StatusBadRequest, "invalid_limit", fmt.Sprintf("limit must be between 1 and %d", filesMaxLimit)) + return + } + if request.Sort.Field == "" { + request.Sort = FileSearchSort{Field: "occurred_at", Direction: apiSortDirectionDesc} + } + request.Predicate = predicate.request + requestHash := canonicalScopedFileSearchHash(request, scope) + offset, ok := s.parseExploreCursor(w, request.Cursor, requestHash) + if !ok { + return + } + var cursor exploreCursor + if request.Cursor != "" { + cursor, _ = s.decodeExploreCursor(request.Cursor) + if predicate.request.SearchMode == exploreSearchModeSemantic || predicate.request.SearchMode == exploreSearchModeHybrid { + if cursor.Snapshot == "" { + writeError(w, http.StatusBadRequest, "invalid_cursor", "semantic cursor is missing its candidate snapshot") + return + } + predicate.request.CandidateSnapshotID = cursor.Snapshot + } + } + searchSpec, snapshotID, ok := s.resolveExploreSearch(r.Context(), w, predicate.request) + if !ok || !requireCompleteCandidatePool(w, searchSpec) { + return + } + if request.Cursor != "" && cursor.SearchRevision != exploreResolvedSearchRevision(searchSpec) { + writeError(w, http.StatusConflict, "search_revision_changed", "The resolved search index revision changed; restart pagination") + return + } + predicate.query.Search = searchSpec + if scope != nil { + if err := applyIdentityScope(&predicate.query.Context, *scope); err != nil { + writeError(w, http.StatusConflict, "identity_scope_conflict", err.Error()) + return + } + } + searcher, ok := s.engine.(query.FileSearcher) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + result, err := searcher.SearchFiles(r.Context(), query.FileSearchRequest{ + Explore: predicate.query, FilenameQuery: strings.TrimSpace(request.FilenameQuery), + MIMEFamilies: request.MIMEFamilies, + Sort: query.SortSpec{Field: request.Sort.Field, Direction: request.Sort.Direction}, + Page: query.PageSpec{Limit: request.Limit, Offset: offset}, + }) + if err != nil { + s.writeExploreError(w, err) + return + } + if request.Cursor != "" { + if cursor.Revision != result.CacheRevision { + writeError(w, http.StatusConflict, "archive_revision_changed", "The committed analytical cache changed; restart pagination") + return + } + } + catalog, ok := s.store.(fileMetadataCatalog) + if !ok { + writeError(w, http.StatusServiceUnavailable, "file_metadata_unavailable", "Authoritative file metadata is unavailable") + return + } + ids := make([]int64, len(result.Files)) + for i, file := range result.Files { + ids[i] = file.ID + } + metadata, err := catalog.GetFileMetadataBatch(r.Context(), ids) + if err != nil { + if s.writeIfContextError(w, err) { + return + } + writeError(w, http.StatusServiceUnavailable, "file_metadata_unavailable", "Authoritative file metadata is unavailable") + return + } + response := FileSearchHTTPResponse{ + Files: make([]FileSearchRow, 0, len(result.Files)), TotalCount: result.TotalCount, + CacheRevision: result.CacheRevision, SearchProvenance: result.SearchProvenance, + CandidateSnapshotID: snapshotID, + } + for _, file := range result.Files { + authority, found := metadata[file.ID] + if !found { + writeError(w, http.StatusConflict, "file_metadata_changed", "The authoritative file metadata changed; refresh the Files workspace") + return + } + state, available := fileContentState(authority) + response.Files = append(response.Files, FileSearchRow{ + ID: file.ID, Key: file.Key, EntryKey: file.EntryKey, MessageID: file.MessageID, + ConversationID: file.ConversationID, OccurredAt: file.OccurredAt, + SourceID: file.SourceID, SourceType: file.SourceType, SourceIdentifier: file.SourceIdentifier, + ContainingTitle: file.ContainingTitle, Filename: file.Filename, MimeType: file.MimeType, + MIMEFamily: file.MIMEFamily, Size: file.Size, ParticipantIDs: file.ParticipantIDs, + ParticipantLabels: file.ParticipantLabels, ParticipantDomains: file.ParticipantDomains, + ContentState: state, ContentAvailable: available, + }) + } + if next := offset + len(result.Files); next < int(result.TotalCount) { + response.NextCursor = s.encodeExploreCursor(exploreCursor{ + Offset: next, Request: requestHash, Revision: result.CacheRevision, + SearchRevision: exploreResolvedSearchRevision(searchSpec), Snapshot: snapshotID, + }) + } + writeJSON(w, http.StatusOK, response) +} + +func canonicalScopedFileSearchHash(request FileSearchHTTPRequest, scope *ExploreFilter) string { + request.Cursor = "" + if scope == nil { + return hashCanonicalValue(request, false) + } + return hashCanonicalValue(struct { + Request FileSearchHTTPRequest `json:"request"` + Scope ExploreFilter `json:"identity_scope"` + }{Request: request, Scope: *scope}, false) +} + +func (s *Server) handleGetFile(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil || id <= 0 { + writeError(w, http.StatusBadRequest, "invalid_id", "File ID must be a positive integer") + return + } + catalog, ok := s.store.(fileMetadataCatalog) + if !ok { + writeError(w, http.StatusServiceUnavailable, "file_metadata_unavailable", "Authoritative file metadata is unavailable") + return + } + file, err := catalog.GetFileMetadata(r.Context(), id) + if err != nil { + if s.writeIfContextError(w, err) { + return + } + writeError(w, http.StatusServiceUnavailable, "file_metadata_unavailable", "Authoritative file metadata is unavailable") + return + } + if file == nil { + writeError(w, http.StatusNotFound, "file_not_found", "File not found") + return + } + state, available := fileContentState(*file) + writeJSON(w, http.StatusOK, FileMetadataResponse{ + ID: file.ID, MessageID: file.MessageID, ConversationID: file.ConversationID, + Filename: file.Filename, MimeType: file.MimeType, Size: file.Size, + ContentHash: file.ContentHash, URL: file.URL, ContentState: state, ContentAvailable: available, + }) +} + +func (s *Server) handleGetFileContent(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil || id <= 0 { + writeError(w, http.StatusBadRequest, "invalid_id", "File ID must be a positive integer") + return + } + catalog, ok := s.store.(fileMetadataCatalog) + if !ok { + writeError(w, http.StatusServiceUnavailable, "file_metadata_unavailable", "Authoritative file metadata is unavailable") + return + } + file, err := catalog.GetFileMetadata(r.Context(), id) + if err != nil { + writeError(w, http.StatusServiceUnavailable, "file_metadata_unavailable", "Authoritative file metadata is unavailable") + return + } + if file == nil { + writeError(w, http.StatusNotFound, "file_not_found", "File not found") + return + } + if state, available := fileContentState(*file); state != FileContentLocal || !available { + writeError(w, http.StatusNotFound, "file_content_unavailable", "File content is not available") + return + } + var content io.ReadCloser + var length int64 + if s.blobStore != nil { + content, length, err = s.blobStore.OpenStream(r.Context(), file.ContentHash) + } + if s.blobStore == nil || errors.Is(err, os.ErrNotExist) { + content, length, err = openLooseAttachmentContent(s.cfg.AttachmentsDir(), file.ContentHash, file.StoragePath) + } + if err != nil { + if errors.Is(err, os.ErrNotExist) { + writeError(w, http.StatusNotFound, "file_content_unavailable", "File content is not available") + return + } + writeError(w, http.StatusInternalServerError, "internal_error", "Failed to open file content") + return + } + contentType := file.MimeType + if contentType == "" { + contentType = "application/octet-stream" + } + w.Header().Set("Content-Type", contentType) + w.Header().Set("Content-Disposition", contentDisposition(file.Filename)) + w.Header().Set("Content-Length", strconv.FormatInt(length, 10)) + w.Header().Set("X-Content-Type-Options", "nosniff") + _, copyErr := io.Copy(w, content) + if err := errors.Join(copyErr, content.Close()); err != nil { + s.logger.Error("failed to stream file", "error", err, "file_id", id) + } +} + +func fileContentState(file store.FileMetadata) (FileContentState, bool) { + if file.URL != "" { + return FileContentURLOnly, false + } + if file.ContentHash == "" { + return FileContentMetadataOnly, false + } + if file.StoragePath == "" { + return FileContentMissingBlob, false + } + return FileContentLocal, true +} diff --git a/internal/api/files_test.go b/internal/api/files_test.go new file mode 100644 index 000000000..e45bbdd0f --- /dev/null +++ b/internal/api/files_test.go @@ -0,0 +1,320 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/query/querytest" + "go.kenn.io/msgvault/internal/store" +) + +type fixedFileBlobStore struct{ content []byte } + +func (s fixedFileBlobStore) OpenStream(context.Context, string) (io.ReadCloser, int64, error) { + return io.NopCloser(bytes.NewReader(s.content)), int64(len(s.content)), nil +} + +type fileSearchEngine struct { + *querytest.MockEngine + + request query.FileSearchRequest + result *query.FileSearchResponse + groupRequest query.FileGroupRequest + groupResult *query.ExploreGroupResponse + groupErr error +} + +func (e *fileSearchEngine) SearchFiles(_ context.Context, request query.FileSearchRequest) (*query.FileSearchResponse, error) { + e.request = request + return e.result, nil +} + +func (e *fileSearchEngine) GroupFiles(_ context.Context, request query.FileGroupRequest) (*query.ExploreGroupResponse, error) { + e.groupRequest = request + return e.groupResult, e.groupErr +} + +type fileCatalogStore struct { + *mockStore + + files map[int64]store.FileMetadata + batchCalls int + batchIDs []int64 +} + +func (s *fileCatalogStore) GetFileMetadata(_ context.Context, id int64) (*store.FileMetadata, error) { + file, ok := s.files[id] + if !ok { + return nil, nil //nolint:nilnil // not-found is the expected catalog contract + } + return &file, nil +} + +func (s *fileCatalogStore) GetFileMetadataBatch(_ context.Context, ids []int64) (map[int64]store.FileMetadata, error) { + s.batchCalls++ + s.batchIDs = append([]int64(nil), ids...) + result := make(map[int64]store.FileMetadata, len(ids)) + for _, id := range ids { + if file, ok := s.files[id]; ok { + result[id] = file + } + } + return result, nil +} + +func TestFilesSearchUsesAnalyticalQueryAndOneCatalogBatch(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + now := time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC) + engine := &fileSearchEngine{MockEngine: &querytest.MockEngine{}, result: &query.FileSearchResponse{ + Files: []query.FileRow{ + {ID: 1, Key: "file:1", EntryKey: "message:1", MessageID: 11, ConversationID: 21, OccurredAt: now, Filename: "local.png", MimeType: "image/png", MIMEFamily: query.FileMIMEImage}, + {ID: 2, Key: "file:2", EntryKey: "message:2", MessageID: 12, ConversationID: 22, OccurredAt: now, Filename: "remote.pdf", MimeType: "application/pdf", MIMEFamily: query.FileMIMEPDF}, + {ID: 3, Key: "file:3", EntryKey: "message:3", MessageID: 13, ConversationID: 23, OccurredAt: now, Filename: "missing.bin"}, + {ID: 4, Key: "file:4", EntryKey: "message:4", MessageID: 14, ConversationID: 24, OccurredAt: now, Filename: "metadata.txt"}, + }, + TotalCount: 4, CacheRevision: "cache-files", SearchProvenance: query.SearchProvenance{}, + }} + hash := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + catalog := &fileCatalogStore{mockStore: &mockStore{}, files: map[int64]store.FileMetadata{ + 1: {ID: 1, MessageID: 11, ConversationID: 21, Filename: "local.png", MimeType: "image/png", ContentHash: hash, StoragePath: "aa/" + hash}, + 2: {ID: 2, MessageID: 12, ConversationID: 22, Filename: "remote.pdf", MimeType: "application/pdf", URL: "https://files.example/remote.pdf"}, + 3: {ID: 3, MessageID: 13, ConversationID: 23, Filename: "missing.bin", ContentHash: hash}, + 4: {ID: 4, MessageID: 14, ConversationID: 24, Filename: "metadata.txt"}, + }} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: catalog, Engine: engine, Logger: testLogger(), + }) + + body := bytes.NewBufferString(`{"predicate":{"filters":[{"dimension":"source","values":["7"]}]},"filename_query":"report","mime_families":["image","pdf"],"sort":{"field":"size","direction":"asc"},"limit":25}`) + req := httptest.NewRequest(http.MethodPost, "/api/v1/files/search", body) + req.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, req) + + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var result FileSearchHTTPResponse + requirements.NoError(json.NewDecoder(response.Body).Decode(&result)) + requirements.Len(result.Files, 4) + assertions.Equal([]int64{1, 2, 3, 4}, catalog.batchIDs) + assertions.Equal(1, catalog.batchCalls) + assertions.Equal(FileContentLocal, result.Files[0].ContentState) + assertions.True(result.Files[0].ContentAvailable) + assertions.Equal(FileContentURLOnly, result.Files[1].ContentState) + assertions.False(result.Files[1].ContentAvailable) + assertions.Equal(FileContentMissingBlob, result.Files[2].ContentState) + assertions.Equal(FileContentMetadataOnly, result.Files[3].ContentState) + assertions.Equal([]int64{7}, engine.request.Explore.Context.SourceIDs) + assertions.Equal("report", engine.request.FilenameQuery) + assertions.Equal([]query.FileMIMEFamily{query.FileMIMEImage, query.FileMIMEPDF}, engine.request.MIMEFamilies) + assertions.Equal(query.SortSpec{Field: "size", Direction: "asc"}, engine.request.Sort) +} + +func TestFileMetadataNamesEveryContentStateAndContainingAuthorities(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + hash := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + catalog := &fileCatalogStore{mockStore: &mockStore{}, files: map[int64]store.FileMetadata{ + 1: {ID: 1, MessageID: 11, ConversationID: 21, Filename: "metadata.txt"}, + 2: {ID: 2, MessageID: 12, ConversationID: 22, Filename: "remote.txt", URL: "https://files.example/remote.txt"}, + 3: {ID: 3, MessageID: 13, ConversationID: 23, Filename: "missing.pdf", MimeType: "application/pdf", ContentHash: hash}, + 4: {ID: 4, MessageID: 14, ConversationID: 24, Filename: "local.png", MimeType: "image/png", ContentHash: hash, StoragePath: "bb/" + hash}, + }} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: catalog, + Engine: &querytest.MockEngine{}, Logger: testLogger(), + }) + + wants := []struct { + id int + state FileContentState + available bool + }{ + {id: 1, state: FileContentMetadataOnly}, + {id: 2, state: FileContentURLOnly}, + {id: 3, state: FileContentMissingBlob}, + {id: 4, state: FileContentLocal, available: true}, + } + for _, want := range wants { + request := httptest.NewRequest(http.MethodGet, "/api/v1/files/"+string(rune('0'+want.id)), nil) + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, request) + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var metadata FileMetadataResponse + requirements.NoError(json.NewDecoder(response.Body).Decode(&metadata)) + assertions.Equal(want.state, metadata.ContentState) + assertions.Equal(want.available, metadata.ContentAvailable) + assertions.Equal(int64(10+want.id), metadata.MessageID) + assertions.Equal(int64(20+want.id), metadata.ConversationID) + } +} + +func TestFileContentUsesSelectedAttachmentMetadata(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + hash := "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + catalog := &fileCatalogStore{mockStore: &mockStore{}, files: map[int64]store.FileMetadata{ + 7: {ID: 7, MessageID: 11, ConversationID: 21, Filename: "selected.png", MimeType: "image/png", ContentHash: hash, StoragePath: "bb/blob"}, + }} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: catalog, + Engine: &querytest.MockEngine{}, BlobStore: fixedFileBlobStore{content: []byte("png")}, Logger: testLogger(), + }) + + request := httptest.NewRequest(http.MethodGet, "/api/v1/files/7/content", nil) + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, request) + + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + assertions.Equal("image/png", response.Header().Get("Content-Type")) + assertions.Contains(response.Header().Get("Content-Disposition"), "selected.png") + assertions.Equal("png", response.Body.String()) +} + +func TestFilesSearchNamesUnavailableCache(t *testing.T) { + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: &mockStore{}, Engine: &querytest.MockEngine{}, Logger: testLogger(), + }) + request := httptest.NewRequest(http.MethodPost, "/api/v1/files/search", bytes.NewBufferString(`{"predicate":{}}`)) + request.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, request) + assert.Equal(t, http.StatusServiceUnavailable, response.Code) + assert.Contains(t, response.Body.String(), "cache") +} + +func TestFilesSearchPreservesLegitimateEmptyFilenameAndMIME(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := &fileSearchEngine{MockEngine: &querytest.MockEngine{}, result: &query.FileSearchResponse{ + Files: []query.FileRow{{ + ID: 9, Key: "file:9", EntryKey: "message:9", MessageID: 9, + ConversationID: 9, OccurredAt: time.Date(2026, 7, 19, 12, 0, 0, 0, time.UTC), + MIMEFamily: query.FileMIMEOther, + }}, + TotalCount: 1, CacheRevision: "cache-empty", SearchProvenance: query.SearchProvenance{}, + }} + catalog := &fileCatalogStore{mockStore: &mockStore{}, files: map[int64]store.FileMetadata{ + 9: {ID: 9, MessageID: 9, ConversationID: 9}, + }} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: catalog, Engine: engine, Logger: testLogger(), + }) + request := httptest.NewRequest(http.MethodPost, "/api/v1/files/search", bytes.NewBufferString(`{"predicate":{}}`)) + request.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, request) + + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var result FileSearchHTTPResponse + requirements.NoError(json.NewDecoder(response.Body).Decode(&result)) + requirements.Len(result.Files, 1) + assertions.Empty(result.Files[0].Filename) + assertions.Empty(result.Files[0].MimeType) +} + +func TestFileGroupsUsesFilePopulationAndForwardsEveryConstraint(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + now := time.Date(2026, 7, 19, 12, 0, 0, 0, time.UTC) + engine := &fileSearchEngine{MockEngine: &querytest.MockEngine{}, groupResult: &query.ExploreGroupResponse{ + Rows: []query.ExploreGroupRow{{Key: "7", Label: "Example source", Count: 2, EstimatedBytes: 300, LatestAt: now}}, + TotalCount: 1, CacheRevision: "cache-files", SearchProvenance: query.SearchProvenance{}, + }} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: &mockStore{}, Engine: engine, Logger: testLogger(), + }) + body := bytes.NewBufferString(`{ + "predicate":{"filters":[{"dimension":"source","values":["7"]}]}, + "filename_query":" invoice ","mime_families":["pdf"],"grouping":["participant"], + "sort":[{"field":"estimated_bytes","direction":"desc"}],"limit":25 + }`) + request := httptest.NewRequest(http.MethodPost, "/api/v1/files/groups", body) + request.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, request) + + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var result FileGroupsHTTPResponse + requirements.NoError(json.NewDecoder(response.Body).Decode(&result)) + requirements.Len(result.Rows, 1) + assertions.Equal(int64(2), result.Rows[0].Count) + assertions.Equal([]int64{7}, engine.groupRequest.Explore.Context.SourceIDs) + assertions.Equal("invoice", engine.groupRequest.FilenameQuery) + assertions.Equal([]query.FileMIMEFamily{query.FileMIMEPDF}, engine.groupRequest.MIMEFamilies) + assertions.Equal("participant", engine.groupRequest.Dimension) + assertions.Equal(query.SortSpec{Field: "estimated_bytes", Direction: "desc"}, engine.groupRequest.Sort) + assertions.Equal(query.PageSpec{Limit: 25}, engine.groupRequest.Page) +} + +func TestFileGroupsNamesUnavailableCacheWithoutGenericFallback(t *testing.T) { + engine := &fileSearchEngine{ + MockEngine: &querytest.MockEngine{}, + groupErr: &query.CacheUnavailableError{Readiness: query.CacheStaleSchema}, + } + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: &mockStore{}, Engine: engine, Logger: testLogger(), + }) + request := httptest.NewRequest(http.MethodPost, "/api/v1/files/groups", bytes.NewBufferString(`{ + "predicate":{},"grouping":["source"] + }`)) + request.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, request) + + assert.Equal(t, http.StatusServiceUnavailable, response.Code) + assert.Contains(t, response.Body.String(), "stale_schema") +} + +func TestFileGroupsCursorBindsRequestAndCacheRevision(t *testing.T) { + requirements := require.New(t) + engine := &fileSearchEngine{MockEngine: &querytest.MockEngine{}, groupResult: &query.ExploreGroupResponse{ + Rows: []query.ExploreGroupRow{{Key: "1", Label: "First", Count: 1}}, + TotalCount: 2, CacheRevision: "cache-one", SearchProvenance: query.SearchProvenance{}, + }} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: &mockStore{}, Engine: engine, Logger: testLogger(), + }) + requestBody := `{"predicate":{},"filename_query":"invoice","mime_families":["pdf"],"grouping":["source"],"limit":1}` + firstRequest := httptest.NewRequest(http.MethodPost, "/api/v1/files/groups", bytes.NewBufferString(requestBody)) + firstRequest.Header.Set("Content-Type", "application/json") + firstResponse := httptest.NewRecorder() + srv.Router().ServeHTTP(firstResponse, firstRequest) + requirements.Equal(http.StatusOK, firstResponse.Code, firstResponse.Body.String()) + var first FileGroupsHTTPResponse + requirements.NoError(json.NewDecoder(firstResponse.Body).Decode(&first)) + requirements.NotEmpty(first.NextCursor) + + secondBody := fmt.Sprintf(`{"predicate":{},"filename_query":"invoice","mime_families":["pdf"],"grouping":["source"],"limit":1,"cursor":%q}`, first.NextCursor) + secondRequest := httptest.NewRequest(http.MethodPost, "/api/v1/files/groups", bytes.NewBufferString(secondBody)) + secondRequest.Header.Set("Content-Type", "application/json") + secondResponse := httptest.NewRecorder() + srv.Router().ServeHTTP(secondResponse, secondRequest) + requirements.Equal(http.StatusOK, secondResponse.Code, secondResponse.Body.String()) + assert.Equal(t, query.PageSpec{Limit: 1, Offset: 1}, engine.groupRequest.Page) + + engine.groupResult.CacheRevision = "cache-two" + staleRequest := httptest.NewRequest(http.MethodPost, "/api/v1/files/groups", bytes.NewBufferString(secondBody)) + staleRequest.Header.Set("Content-Type", "application/json") + staleResponse := httptest.NewRecorder() + srv.Router().ServeHTTP(staleResponse, staleRequest) + assert.Equal(t, http.StatusConflict, staleResponse.Code) + assert.Contains(t, staleResponse.Body.String(), "archive_revision_changed") +} diff --git a/internal/api/handlers.go b/internal/api/handlers.go index b3d2f4fb8..2b82122d9 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -84,15 +84,21 @@ type SourceStatusResponse struct { // SourceStatus represents one source and its read-only sync status. type SourceStatus struct { - ID int64 `json:"id"` - SourceType string `json:"source_type"` - Identifier string `json:"identifier"` - DisplayName *string `json:"display_name"` - LastSyncAt *string `json:"last_sync_at"` - UpdatedAt string `json:"updated_at"` - ActiveSync *SyncRunStatus `json:"active_sync"` - LatestSync *SyncRunStatus `json:"latest_sync"` - LastSuccessfulSync *SyncRunStatus `json:"last_successful_sync"` + ID int64 `json:"id"` + SourceType string `json:"source_type"` + Identifier string `json:"identifier"` + DisplayName *string `json:"display_name"` + LastSyncAt *string `json:"last_sync_at"` + UpdatedAt string `json:"updated_at"` + ActiveSync *SyncRunStatus `json:"active_sync"` + LatestSync *SyncRunStatus `json:"latest_sync"` + LastSuccessfulSync *SyncRunStatus `json:"last_successful_sync"` + CanSync bool `json:"can_sync"` + SyncUnavailableReason string `json:"sync_unavailable_reason,omitempty"` + Scheduled bool `json:"scheduled"` + Schedule string `json:"schedule,omitempty"` + NextSyncAt *string `json:"next_sync_at"` + SchedulerLastError string `json:"scheduler_last_error,omitempty"` } // SyncRunStatus represents the API-visible details for a sync run. @@ -713,7 +719,7 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) { parsedQuery.AccountIDs = append(parsedQuery.AccountIDs, sourceIDs...) } - if mode == "vector" || mode == "hybrid" { + if mode == "vector" || mode == exploreSearchModeHybrid { page, _, err := queryInt(r, "page") if err != nil { s.rejectBadParam(w, err) @@ -1353,6 +1359,32 @@ func (s *Server) sourceStatus(statusStore SourceStatusStore, source *store.Sourc if err := s.hydrateSyncRunStatus(statusStore, status.ActiveSync); err != nil { return SourceStatus{}, err } + schedulerRunning := false + if s.scheduler != nil { + status.Scheduled = s.scheduler.IsScheduled(source.Identifier) + for _, scheduled := range s.scheduler.Status() { + if scheduled.Email != source.Identifier { + continue + } + status.Schedule = scheduled.Schedule + status.SchedulerLastError = scheduled.LastError + schedulerRunning = scheduled.Running + if !scheduled.NextRun.IsZero() { + status.NextSyncAt = nullableTimePtr(scheduled.NextRun) + } + break + } + } + switch { + case status.ActiveSync != nil || schedulerRunning: + status.SyncUnavailableReason = "sync_already_running" + case s.scheduler == nil: + status.SyncUnavailableReason = "scheduler_unavailable" + case !status.Scheduled: + status.SyncUnavailableReason = "sync_not_configured" + default: + status.CanSync = true + } latest, err := statusStore.GetLatestSync(source.ID) if err != nil && !errors.Is(err, store.ErrSyncRunNotFound) { @@ -1855,7 +1887,7 @@ type TextMessagesResponse struct { // aggregateViewTypes are the accepted view_type values, surfaced in 400 messages. var aggregateViewTypes = []string{ "senders", "sender_names", "recipients", "recipient_names", - "domains", "labels", "time", + "domains", "labels", "time", //nolint:goconst // "labels" here names a view_type enum value, not the Parquet dataset test fixtures also spell "labels"; a shared constant would blur two unrelated concepts } // parseViewType parses a view type string into query.ViewType. @@ -1907,7 +1939,7 @@ var ( aggregateSortFields = []string{"count", "size", "attachment_size", "name"} messageSortFields = []string{"date", "size", "subject"} textSortFields = []string{"last_message", "count", "name"} - sortDirections = []string{"asc", "desc"} + sortDirections = []string{"asc", apiSortDirectionDesc} timeGranularities = []string{"year", "month", "day"} ) @@ -1932,7 +1964,7 @@ func parseSortDirection(s string) (query.SortDirection, bool) { switch strings.ToLower(s) { case "asc": return query.SortAsc, true - case "desc": + case apiSortDirectionDesc: return query.SortDesc, true default: return query.SortDesc, false @@ -3447,6 +3479,10 @@ func isEngineUnsupported(err error) bool { return errors.Is(err, query.ErrNotImplemented) || errors.Is(err, daemonclient.ErrNotSupported) } +type archivedMessageRawReader interface { + GetArchivedMessageRaw(ctx context.Context, id int64) ([]byte, error) +} + // handleMessageInline serves a CID-referenced inline MIME part (e.g. an // embedded image) from the raw message data. The CID is passed as a `cid` // query parameter so values containing `/` (legal per RFC 5322) round-trip @@ -3470,7 +3506,12 @@ func (s *Server) handleMessageInline(w http.ResponseWriter, r *http.Request) { return } - raw, err := s.engine.GetMessageRaw(r.Context(), id) + var raw []byte + if reader, ok := s.store.(archivedMessageRawReader); ok { + raw, err = reader.GetArchivedMessageRaw(r.Context(), id) + } else { + raw, err = s.engine.GetMessageRaw(r.Context(), id) + } if err != nil { if isEngineUnsupported(err) { writeError(w, http.StatusNotImplemented, "not_supported", "Inline MIME parts are not available on this engine") diff --git a/internal/api/handlers_test.go b/internal/api/handlers_test.go index e09f05f56..185bb7e4b 100644 --- a/internal/api/handlers_test.go +++ b/internal/api/handlers_test.go @@ -2991,6 +2991,7 @@ func TestHandleSourceStatus(t *testing.T) { assert := assert.New(t) st := testutil.NewTestStore(t) sched := newMockScheduler() + sched.scheduled["alice@example.com"] = true srv := NewServer(&config.Config{Server: config.ServerConfig{APIPort: 8080}}, st, sched, testLogger()) gmail, err := st.GetOrCreateSource("gmail", "alice@example.com") @@ -3052,6 +3053,8 @@ func TestHandleSourceStatus(t *testing.T) { require.NotNil(got.LastSyncAt, "LastSyncAt") assert.NotEmpty(*got.LastSyncAt, "LastSyncAt") assert.NotEmpty(got.UpdatedAt, "UpdatedAt") + assert.False(got.CanSync, "active source cannot start a conflicting sync") + assert.Equal("sync_already_running", got.SyncUnavailableReason) require.NotNil(got.ActiveSync, "ActiveSync") assert.Equal(runningID, got.ActiveSync.ID, "ActiveSync.ID") @@ -3075,6 +3078,62 @@ func TestHandleSourceStatus(t *testing.T) { assert.Equal("history-2", *got.LastSuccessfulSync.CursorAfter, "LastSuccessfulSync.CursorAfter") } +func TestHandleSourceStatusExposesServerAuthorizedSyncCapability(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + st := testutil.NewTestStore(t) + sched := newMockScheduler() + sched.scheduled["ready@example.com"] = true + sched.statuses = []AccountStatus{{ + Email: "ready@example.com", Schedule: "0 */6 * * *", + NextRun: time.Date(2026, 7, 20, 0, 0, 0, 0, time.UTC), LastError: "previous timeout", + }} + _, err := st.GetOrCreateSource("gmail", "ready@example.com") + requirements.NoError(err) + srv := NewServer(&config.Config{Server: config.ServerConfig{APIPort: 8080}}, st, sched, testLogger()) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/sources/status", nil) + w := httptest.NewRecorder() + srv.Router().ServeHTTP(w, req) + + requirements.Equal(http.StatusOK, w.Code, w.Body.String()) + var response SourceStatusResponse + requirements.NoError(json.Unmarshal(w.Body.Bytes(), &response)) + requirements.Len(response.Sources, 1) + assertions.True(response.Sources[0].CanSync) + assertions.Empty(response.Sources[0].SyncUnavailableReason) + assertions.True(response.Sources[0].Scheduled) + assertions.Equal("0 */6 * * *", response.Sources[0].Schedule) + requirements.NotNil(response.Sources[0].NextSyncAt) + assertions.Equal("2026-07-20T00:00:00Z", *response.Sources[0].NextSyncAt) + assertions.Equal("previous timeout", response.Sources[0].SchedulerLastError) +} + +func TestHandleSourceStatusDisablesSyncWhileSchedulerReportsAccountRunning(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + st := testutil.NewTestStore(t) + sched := newMockScheduler() + sched.scheduled["running@example.com"] = true + sched.statuses = []AccountStatus{{ + Email: "running@example.com", Running: true, Schedule: "0 */6 * * *", + }} + _, err := st.GetOrCreateSource("gmail", "running@example.com") + requirements.NoError(err) + srv := NewServer(&config.Config{Server: config.ServerConfig{APIPort: 8080}}, st, sched, testLogger()) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/sources/status", nil) + w := httptest.NewRecorder() + srv.Router().ServeHTTP(w, req) + + requirements.Equal(http.StatusOK, w.Code, w.Body.String()) + var response SourceStatusResponse + requirements.NoError(json.Unmarshal(w.Body.Bytes(), &response)) + requirements.Len(response.Sources, 1) + assertions.False(response.Sources[0].CanSync) + assertions.Equal("sync_already_running", response.Sources[0].SyncUnavailableReason) +} + func TestHandleSourceStatusNoSyncRuns(t *testing.T) { require := require.New(t) assert := assert.New(t) @@ -3097,6 +3156,8 @@ func TestHandleSourceStatusNoSyncRuns(t *testing.T) { assert.Nil(resp.Sources[0].ActiveSync, "ActiveSync") assert.Nil(resp.Sources[0].LatestSync, "LatestSync") assert.Nil(resp.Sources[0].LastSuccessfulSync, "LastSuccessfulSync") + assert.False(resp.Sources[0].CanSync) + assert.Equal("scheduler_unavailable", resp.Sources[0].SyncUnavailableReason) } func TestHandleGetMessage(t *testing.T) { @@ -6148,6 +6209,30 @@ func rawMIMEWithInlineImage(cid, contentType string, body []byte) []byte { return rawMIMEWithImagePart(cid, contentType, "inline", body) } +type archiveRawMessageStore struct { + *mockStore + + raw []byte +} + +func (s *archiveRawMessageStore) GetArchivedMessageRaw(context.Context, int64) ([]byte, error) { + return append([]byte(nil), s.raw...), nil +} + +func TestHandleMessageInlineUsesArchiveAwareRawAuthority(t *testing.T) { + raw := rawMIMEWithInlineImage("retained@example", "image/png", []byte("retained")) + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: &archiveRawMessageStore{mockStore: &mockStore{stats: &StoreStats{}}, raw: raw}, + Engine: &querytest.MockEngine{}, Logger: testLogger(), + }) + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, httptest.NewRequest(http.MethodGet, inlineURL(7, "retained@example"), nil)) + + require.Equal(t, http.StatusOK, response.Code, response.Body.String()) + assert.Equal(t, "retained", response.Body.String()) +} + func TestHandleMessageInline_ImagePNG(t *testing.T) { assert := assert.New(t) imgData := []byte{0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A} diff --git a/internal/api/identity_links.go b/internal/api/identity_links.go new file mode 100644 index 000000000..1bb19562a --- /dev/null +++ b/internal/api/identity_links.go @@ -0,0 +1,162 @@ +package api + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/danielgtaylor/huma/v2" + "go.kenn.io/msgvault/internal/store" +) + +// IdentityLinkStore mutates the participant link graph. Implemented by the +// serve daemon's store adapter as a direct pass-through to *store.Store. +type IdentityLinkStore interface { + LinkParticipants(a, b int64) (int64, error) + UnlinkParticipants(a, b int64) (int64, error) +} + +// ClusterLookupStore resolves a participant's cluster membership and edges, +// read-only. The person-detail handler (see handleGetPerson in people.go) +// uses it to enrich a cluster-aware query.PersonSummary with a PersonCluster +// block; the query layer itself stays free of a store dependency, so this +// capability is composed in at the HTTP handler instead. Implemented by the +// serve daemon's store adapter as a direct pass-through to *store.Store. +type ClusterLookupStore interface { + ClusterMembers(id int64) ([]int64, error) + ClusterEdges(id int64) ([]store.LinkEdge, error) +} + +// IdentityCacheRefresher re-exports the identity-derived Parquet datasets +// (owner_participants, participant_clusters) after a link/unlink mutation +// commits, so cached analytics reflect the new participant clusters without +// waiting for the next scheduled cache build. Implemented by the serve +// daemon's store adapter, which wraps cacheops.RefreshIdentityDatasets with +// the daemon's analytics directory and the cross-process cache build lock. +type IdentityCacheRefresher interface { + RefreshIdentityDatasets(ctx context.Context) (int64, error) +} + +const ( + identityCacheStateReady = "ready" + identityCacheStateStale = "stale" +) + +// IdentityLinkRequest is the POST /api/v1/identity/links and +// /api/v1/identity/unlinks request body. +type IdentityLinkRequest struct { + ParticipantA int64 `json:"participant_a"` + ParticipantB int64 `json:"participant_b"` +} + +// IdentityLinkResponse reports the identity revision after the mutation +// committed and whether the synchronous cache refresh that followed it +// succeeded. The mutation is durable regardless of cache_state: "stale" +// only means the Parquet identity datasets have not caught up yet. +type IdentityLinkResponse struct { + IdentityRevision int64 `json:"identity_revision"` + CacheState string `json:"cache_state" enum:"ready,stale"` +} + +func (s *Server) registerIdentityLinkRoutes(api huma.API) { + registerAPIV1RawHumaJSONRouteWithRequest[IdentityLinkRequest, IdentityLinkResponse]( + api, "linkIdentityParticipants", http.MethodPost, "/identity/links", + "Assert two participants are the same person", s.handleLinkIdentity, + ) + registerAPIV1RawHumaJSONRouteWithRequest[IdentityLinkRequest, IdentityLinkResponse]( + api, "unlinkIdentityParticipants", http.MethodPost, "/identity/unlinks", + "Remove a link edge between two participants", s.handleUnlinkIdentity, + ) +} + +func (s *Server) handleLinkIdentity(w http.ResponseWriter, r *http.Request) { + s.handleIdentityLinkMutation(w, r, IdentityLinkStore.LinkParticipants) +} + +func (s *Server) handleUnlinkIdentity(w http.ResponseWriter, r *http.Request) { + s.handleIdentityLinkMutation(w, r, IdentityLinkStore.UnlinkParticipants) +} + +// handleIdentityLinkMutation is shared by the link and unlink handlers: +// decode and validate the pair, run the requested mutation, then attempt a +// synchronous identity cache refresh and report its outcome. The mutation +// commits before the refresh is attempted, so a refresh failure never rolls +// back or fails the request. +// +// Store errors are mapped by kind, not lumped into one status: ErrAlreadyLinked +// is a 409 (the edge is redundant, not invalid); ErrParticipantNotFound and +// ErrInvalidParticipantID are 400s (the request named a bad participant); +// anything else (lock contention, I/O failure, context cancellation) is an +// internal failure and must not leak driver text to the client as a 400. +func (s *Server) handleIdentityLinkMutation( + w http.ResponseWriter, + r *http.Request, + mutate func(IdentityLinkStore, int64, int64) (int64, error), +) { + linker, ok := s.store.(IdentityLinkStore) + if !ok { + writeAPIHTTPError(w, cliStoreUnavailableError()) + return + } + + var req IdentityLinkRequest + if !decodeIdentityLinkRequest(w, r, &req) { + return + } + if req.ParticipantA <= 0 || req.ParticipantB <= 0 || req.ParticipantA == req.ParticipantB { + writeError(w, http.StatusBadRequest, "invalid_participant_id", + "participant_a and participant_b must be distinct positive participant IDs") + return + } + + revision, err := mutate(linker, req.ParticipantA, req.ParticipantB) + switch { + case errors.Is(err, store.ErrAlreadyLinked): + writeError(w, http.StatusConflict, "already_linked", + "these participants are already connected through other links") + return + case errors.Is(err, store.ErrParticipantNotFound), errors.Is(err, store.ErrInvalidParticipantID): + writeError(w, http.StatusBadRequest, "invalid_participant_id", err.Error()) + return + case err != nil: + s.logger.Error("identity link mutation failed", "error", err) + writeError(w, http.StatusInternalServerError, "internal_error", "failed to update participant links") + return + } + + writeJSON(w, http.StatusOK, IdentityLinkResponse{ + IdentityRevision: revision, + CacheState: s.refreshIdentityCacheState(r.Context()), + }) +} + +// decodeIdentityLinkRequest decodes the request body, rejecting unknown +// fields so a typo in participant_a/participant_b is not silently ignored. +func decodeIdentityLinkRequest(w http.ResponseWriter, r *http.Request, req *IdentityLinkRequest) bool { + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + if err := dec.Decode(req); err != nil { + writeError(w, http.StatusBadRequest, "invalid_request", + fmt.Sprintf("invalid JSON request body: %v", err)) + return false + } + return true +} + +// refreshIdentityCacheState attempts the synchronous identity-dataset cache +// refresh and reports "ready" or "stale". A missing refresher capability or +// a refresh error both degrade to "stale" rather than failing the caller's +// request: the mutation that triggered the refresh already committed. +func (s *Server) refreshIdentityCacheState(ctx context.Context) string { + refresher, ok := s.store.(IdentityCacheRefresher) + if !ok { + return identityCacheStateStale + } + if _, err := refresher.RefreshIdentityDatasets(ctx); err != nil { + s.logger.Error("identity cache refresh failed", "error", err) + return identityCacheStateStale + } + return identityCacheStateReady +} diff --git a/internal/api/identity_links_test.go b/internal/api/identity_links_test.go new file mode 100644 index 000000000..90bd3c071 --- /dev/null +++ b/internal/api/identity_links_test.go @@ -0,0 +1,283 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/store" + "go.kenn.io/msgvault/internal/testutil" +) + +// stubIdentityCacheStore wraps a real *store.Store to add a controllable +// IdentityCacheRefresher: LinkParticipants/UnlinkParticipants run for real +// against the embedded store, while RefreshIdentityDatasets is a stub whose +// success/failure the test controls, mirroring how the daemon's store +// adapter refreshes the Parquet identity datasets after a real mutation. +type stubIdentityCacheStore struct { + *store.Store + + refreshErr error + refreshCalls int +} + +func (s *stubIdentityCacheStore) RefreshIdentityDatasets(_ context.Context) (int64, error) { + s.refreshCalls++ + if s.refreshErr != nil { + return 0, s.refreshErr + } + return 1, nil +} + +// mustParticipant is a thin wrapper around EnsureParticipant for these +// table-driven tests. +func (s *stubIdentityCacheStore) mustParticipant(t *testing.T, email, name, domain string) int64 { + t.Helper() + id, err := s.EnsureParticipant(email, name, domain) + require.NoError(t, err) + return id +} + +func newIdentityLinkTestServer(t *testing.T) (*Server, *stubIdentityCacheStore) { + t.Helper() + st := testutil.NewTestStore(t) + wrapped := &stubIdentityCacheStore{Store: st} + srv := NewServer(&config.Config{Server: config.ServerConfig{APIPort: 8080}}, wrapped, nil, testLogger()) + return srv, wrapped +} + +// failingLinkMutationStore wraps a stubIdentityCacheStore to force +// Link/UnlinkParticipants to fail with an arbitrary, non-sentinel error — +// simulating a transient/internal store failure (lock contention, dropped +// connection, context cancellation) that must map to a 500, not the 400 +// used for invalid participant IDs. +type failingLinkMutationStore struct { + *stubIdentityCacheStore + + err error +} + +func (s *failingLinkMutationStore) LinkParticipants(_, _ int64) (int64, error) { + return 0, s.err +} + +func (s *failingLinkMutationStore) UnlinkParticipants(_, _ int64) (int64, error) { + return 0, s.err +} + +func newFailingIdentityLinkTestServer(t *testing.T, err error) *Server { + t.Helper() + st := testutil.NewTestStore(t) + failing := &failingLinkMutationStore{ + stubIdentityCacheStore: &stubIdentityCacheStore{Store: st}, + err: err, + } + return NewServer(&config.Config{Server: config.ServerConfig{APIPort: 8080}}, failing, nil, testLogger()) +} + +func postIdentityLink(t *testing.T, srv *Server, path string, body IdentityLinkRequest) *httptest.ResponseRecorder { + t.Helper() + raw, err := json.Marshal(body) + require.NoError(t, err) + req := httptest.NewRequest(http.MethodPost, path, bytes.NewReader(raw)) + req.Header.Set("Content-Type", "application/json") + w := httptest.NewRecorder() + srv.Router().ServeHTTP(w, req) + return w +} + +func decodeIdentityLinkResponse(t *testing.T, w *httptest.ResponseRecorder) IdentityLinkResponse { + t.Helper() + var resp IdentityLinkResponse + require.NoError(t, json.NewDecoder(w.Body).Decode(&resp), "body: %s", w.Body.String()) + return resp +} + +func TestLinkIdentity_CreatesEdgeAndReportsReadyCache(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + srv, wrapped := newIdentityLinkTestServer(t) + a := wrapped.mustParticipant(t, "alice@example.com", "Alice", "example.com") + b := wrapped.mustParticipant(t, "alice@personal.example", "Alice P", "personal.example") + + w := postIdentityLink(t, srv, "/api/v1/identity/links", IdentityLinkRequest{ParticipantA: a, ParticipantB: b}) + + require.Equal(http.StatusOK, w.Code, "body: %s", w.Body.String()) + resp := decodeIdentityLinkResponse(t, w) + assert.Equal(int64(1), resp.IdentityRevision) + assert.Equal(identityCacheStateReady, resp.CacheState) + assert.Equal(1, wrapped.refreshCalls) +} + +func TestLinkIdentity_RepeatedExactEdgeIsIdempotent(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + srv, wrapped := newIdentityLinkTestServer(t) + a := wrapped.mustParticipant(t, "alice@example.com", "Alice", "example.com") + b := wrapped.mustParticipant(t, "alice@personal.example", "Alice P", "personal.example") + + first := postIdentityLink(t, srv, "/api/v1/identity/links", IdentityLinkRequest{ParticipantA: a, ParticipantB: b}) + require.Equal(http.StatusOK, first.Code) + firstResp := decodeIdentityLinkResponse(t, first) + + second := postIdentityLink(t, srv, "/api/v1/identity/links", IdentityLinkRequest{ParticipantA: a, ParticipantB: b}) + require.Equal(http.StatusOK, second.Code) + secondResp := decodeIdentityLinkResponse(t, second) + + assert.Equal(firstResp.IdentityRevision, secondResp.IdentityRevision) + assert.Equal(2, wrapped.refreshCalls, "retry re-attempts the cache refresh without re-mutating") +} + +func TestLinkIdentity_IndirectEdgeConflict(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + srv, wrapped := newIdentityLinkTestServer(t) + a := wrapped.mustParticipant(t, "a@example.com", "A", "example.com") + b := wrapped.mustParticipant(t, "b@example.com", "B", "example.com") + c := wrapped.mustParticipant(t, "c@example.com", "C", "example.com") + + require.Equal(http.StatusOK, + postIdentityLink(t, srv, "/api/v1/identity/links", IdentityLinkRequest{ParticipantA: a, ParticipantB: b}).Code) + require.Equal(http.StatusOK, + postIdentityLink(t, srv, "/api/v1/identity/links", IdentityLinkRequest{ParticipantA: b, ParticipantB: c}).Code) + + w := postIdentityLink(t, srv, "/api/v1/identity/links", IdentityLinkRequest{ParticipantA: a, ParticipantB: c}) + + require.Equal(http.StatusConflict, w.Code, "body: %s", w.Body.String()) + var errResp ErrorResponse + require.NoError(json.NewDecoder(w.Body).Decode(&errResp)) + assert.Equal("already_linked", errResp.Error) +} + +func TestLinkIdentity_RefresherFailureReportsStaleWithoutFailingRequest(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + srv, wrapped := newIdentityLinkTestServer(t) + wrapped.refreshErr = errors.New("cache build lock held by another process") + a := wrapped.mustParticipant(t, "alice@example.com", "Alice", "example.com") + b := wrapped.mustParticipant(t, "alice@personal.example", "Alice P", "personal.example") + + w := postIdentityLink(t, srv, "/api/v1/identity/links", IdentityLinkRequest{ParticipantA: a, ParticipantB: b}) + + require.Equal(http.StatusOK, w.Code, "body: %s", w.Body.String()) + resp := decodeIdentityLinkResponse(t, w) + assert.Equal(int64(1), resp.IdentityRevision, "mutation is durable despite refresh failure") + assert.Equal(identityCacheStateStale, resp.CacheState) +} + +func TestLinkIdentity_InvalidParticipantIDs(t *testing.T) { + srv, wrapped := newIdentityLinkTestServer(t) + a := wrapped.mustParticipant(t, "alice@example.com", "Alice", "example.com") + + for name, req := range map[string]IdentityLinkRequest{ + "self link": {ParticipantA: a, ParticipantB: a}, + "zero participant": {ParticipantA: a, ParticipantB: 0}, + "negative id": {ParticipantA: a, ParticipantB: -1}, + "unknown id": {ParticipantA: a, ParticipantB: 999999}, + } { + t.Run(name, func(t *testing.T) { + w := postIdentityLink(t, srv, "/api/v1/identity/links", req) + assert.Equal(t, http.StatusBadRequest, w.Code, "body: %s", w.Body.String()) + }) + } +} + +// TestLinkIdentity_StoreErrorMapsTo500 covers Finding 1: a store error that +// is not one of the known sentinels (ErrAlreadyLinked, ErrParticipantNotFound, +// ErrInvalidParticipantID) must map to 500, not the 400 used for invalid +// participant IDs. Otherwise a transient failure like "database is locked" +// would be reported to the client as if they had sent a bad ID, leaking raw +// driver text along the way. +func TestLinkIdentity_StoreErrorMapsTo500(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + srv := newFailingIdentityLinkTestServer(t, errors.New("database is locked")) + + w := postIdentityLink(t, srv, "/api/v1/identity/links", IdentityLinkRequest{ParticipantA: 1, ParticipantB: 2}) + + assert.Equal(http.StatusInternalServerError, w.Code, "body: %s", w.Body.String()) + var errResp ErrorResponse + require.NoError(json.NewDecoder(w.Body).Decode(&errResp)) + assert.Equal("internal_error", errResp.Error) + assert.NotContains(errResp.Message, "database is locked", "raw driver text must not leak to the client") +} + +// TestUnlinkIdentity_StoreErrorMapsTo500 mirrors +// TestLinkIdentity_StoreErrorMapsTo500 for the unlink path. +func TestUnlinkIdentity_StoreErrorMapsTo500(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + srv := newFailingIdentityLinkTestServer(t, errors.New("database is locked")) + + w := postIdentityLink(t, srv, "/api/v1/identity/unlinks", IdentityLinkRequest{ParticipantA: 1, ParticipantB: 2}) + + assert.Equal(http.StatusInternalServerError, w.Code, "body: %s", w.Body.String()) + var errResp ErrorResponse + require.NoError(json.NewDecoder(w.Body).Decode(&errResp)) + assert.Equal("internal_error", errResp.Error) +} + +func TestUnlinkIdentity_RemovesEdgeAndBumpsRevision(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + srv, wrapped := newIdentityLinkTestServer(t) + a := wrapped.mustParticipant(t, "alice@example.com", "Alice", "example.com") + b := wrapped.mustParticipant(t, "alice@personal.example", "Alice P", "personal.example") + linkResp := postIdentityLink(t, srv, "/api/v1/identity/links", IdentityLinkRequest{ParticipantA: a, ParticipantB: b}) + require.Equal(http.StatusOK, linkResp.Code) + linked := decodeIdentityLinkResponse(t, linkResp) + + w := postIdentityLink(t, srv, "/api/v1/identity/unlinks", IdentityLinkRequest{ParticipantA: a, ParticipantB: b}) + + require.Equal(http.StatusOK, w.Code, "body: %s", w.Body.String()) + resp := decodeIdentityLinkResponse(t, w) + assert.Greater(resp.IdentityRevision, linked.IdentityRevision) + assert.Equal(identityCacheStateReady, resp.CacheState) +} + +func TestUnlinkIdentity_RepeatedIsUnchanged(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + srv, wrapped := newIdentityLinkTestServer(t) + a := wrapped.mustParticipant(t, "alice@example.com", "Alice", "example.com") + b := wrapped.mustParticipant(t, "alice@personal.example", "Alice P", "personal.example") + require.Equal(http.StatusOK, + postIdentityLink(t, srv, "/api/v1/identity/links", IdentityLinkRequest{ParticipantA: a, ParticipantB: b}).Code) + refreshCallsBeforeUnlinks := wrapped.refreshCalls + + first := postIdentityLink(t, srv, "/api/v1/identity/unlinks", IdentityLinkRequest{ParticipantA: a, ParticipantB: b}) + require.Equal(http.StatusOK, first.Code) + firstResp := decodeIdentityLinkResponse(t, first) + + second := postIdentityLink(t, srv, "/api/v1/identity/unlinks", IdentityLinkRequest{ParticipantA: a, ParticipantB: b}) + + require.Equal(http.StatusOK, second.Code) + secondResp := decodeIdentityLinkResponse(t, second) + assert.Equal(firstResp.IdentityRevision, secondResp.IdentityRevision) + assert.Equal(2, wrapped.refreshCalls-refreshCallsBeforeUnlinks, + "retry re-attempts the cache refresh without re-mutating") +} + +// TestUnlinkIdentity_UnknownParticipantIsBadRequest covers Finding 2: +// UnlinkParticipants must validate participant existence the same way +// LinkParticipants does. Before the fix, unlinking a nonexistent pair +// silently returned 200 (RowsAffected == 0 short-circuited to a no-op) +// instead of 400, unlike the same IDs sent to /identity/links. +func TestUnlinkIdentity_UnknownParticipantIsBadRequest(t *testing.T) { + srv, wrapped := newIdentityLinkTestServer(t) + a := wrapped.mustParticipant(t, "alice@example.com", "Alice", "example.com") + + w := postIdentityLink(t, srv, "/api/v1/identity/unlinks", IdentityLinkRequest{ParticipantA: a, ParticipantB: 999999}) + + assert.Equal(t, http.StatusBadRequest, w.Code, "body: %s", w.Body.String()) + var errResp ErrorResponse + require.NoError(t, json.NewDecoder(w.Body).Decode(&errResp)) + assert.Equal(t, "invalid_participant_id", errResp.Error) +} diff --git a/internal/api/middleware.go b/internal/api/middleware.go index 1edddf3b1..803783c66 100644 --- a/internal/api/middleware.go +++ b/internal/api/middleware.go @@ -43,7 +43,7 @@ func defaultCORSAllowedMethods() []string { } func defaultCORSAllowedHeaders() []string { - return []string{"Accept", "Authorization", "Content-Type", "X-API-Key"} + return []string{"Accept", "Authorization", "Content-Type", "X-API-Key", csrfHeaderName} } // CORSMiddleware returns a middleware that handles CORS headers. @@ -174,6 +174,54 @@ func isLoopbackRequest(r *http.Request) bool { return parsed != nil && parsed.IsLoopback() } +type requestAuthentication struct { + Mode AuthMode + SessionID string + Session browserSession +} + +func (s *Server) requestAuthentication(r *http.Request) requestAuthentication { + if security, ok := securityFromRequest(r); ok { + return security.auth + } + return s.classifyAPIRequestDirect(r) +} + +func (s *Server) classifyAPIRequestDirect(r *http.Request) requestAuthentication { + // Preserve the existing keyless mode: secure startup confines the daemon to + // loopback unless the operator explicitly opts into unauthenticated remote + // access, and every request remains authorized when no key is configured. + if s.cfg.Server.APIKey == "" { + return requestAuthentication{Mode: AuthModeLoopback} + } + + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + authHeader = r.Header.Get("X-Api-Key") + } + if len(authHeader) > 7 && authHeader[:7] == "Bearer " { + authHeader = authHeader[7:] + } + if constantTimeAPIKeyEqual(authHeader, s.cfg.Server.APIKey) { + return requestAuthentication{Mode: AuthModeAPIKey} + } + + if cookie, err := r.Cookie(sessionCookieName); err == nil && s.sessions != nil { + if session, ok := s.sessions.lookup(cookie.Value); ok { + return requestAuthentication{ + Mode: AuthModeSession, + SessionID: cookie.Value, + Session: session, + } + } + } + return requestAuthentication{Mode: AuthModeRequired} +} + +func (s *Server) apiRequestAuthorized(r *http.Request) bool { + return s.requestAuthentication(r).Mode != AuthModeRequired +} + // RateLimitMiddleware returns a middleware that rate limits requests by IP. // The exempt predicate lets trusted requests bypass the limiter: the local // TUI/CLI legitimately bursts far past the remote budget (daemon discovery diff --git a/internal/api/middleware_test.go b/internal/api/middleware_test.go index dce9fc2b7..e1c34fc19 100644 --- a/internal/api/middleware_test.go +++ b/internal/api/middleware_test.go @@ -1,6 +1,7 @@ package api import ( + "context" "net/http" "net/http/httptest" "testing" @@ -251,3 +252,57 @@ func TestRateLimitMiddlewareLimitsUntrustedLoopback(t *testing.T) { assert.Equal(t, http.StatusTooManyRequests, w2.Code, "untrusted loopback request must be rate limited") } + +func TestExistingAPIKeyAuthenticationCompatibility(t *testing.T) { + const apiKey = "compatibility-test-key" + tests := []struct { + name string + headers http.Header + }{ + { + name: "X-Api-Key", + headers: http.Header{"X-Api-Key": []string{apiKey}}, + }, + { + name: "bearer authorization", + headers: http.Header{"Authorization": []string{"Bearer " + apiKey}}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + srv := NewServer( + &config.Config{Server: config.ServerConfig{APIKey: apiKey}}, + nil, nil, testLogger(), + ) + t.Cleanup(func() { + require.NoError(t, srv.Shutdown(context.Background())) + }) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/health", nil) + for name, values := range tt.headers { + for _, value := range values { + req.Header.Add(name, value) + } + } + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + + assert.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) + }) + } +} + +func TestExistingAPIKeyAuthenticationKeepsKeylessLoopbackTrusted(t *testing.T) { + srv := NewServer(&config.Config{}, nil, nil, testLogger()) + t.Cleanup(func() { + require.NoError(t, srv.Shutdown(context.Background())) + }) + + req := httptest.NewRequest(http.MethodGet, "/api/v1/health", nil) + req.RemoteAddr = "127.0.0.1:4242" + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + + assert.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) +} diff --git a/internal/api/openapi.go b/internal/api/openapi.go index 6ccfb7281..c10040cd2 100644 --- a/internal/api/openapi.go +++ b/internal/api/openapi.go @@ -8,6 +8,7 @@ import ( "github.com/danielgtaylor/huma/v2" "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/explorecatalog" ) // APISchemaVersion is the version stamped into the OpenAPI document @@ -43,18 +44,77 @@ import ( // echoes to total statistics. The echoes let remote clients fail closed when // a released older daemon ignores an additive request filter. // Additive (minor bump): the major-version compatibility gate stays at 1. -const APISchemaVersion = "1.5.0" +// +// 1.6.0 adds the browser-session login, bootstrap, and logout routes. Existing +// API-key security remains the documented scheme for protected API routes; +// cookie authentication is an additive same-origin browser mechanism. +// +// 1.7.0 adds optimistic, secret-redacting browser settings reads and writes. +// +// 1.8.0 adds daemon-owned shared Saved View CRUD with schema-versioned +// canonical definitions and revision ETags. +// +// 1.9.0 adds finite analytical exploration, grouping, selection preflight, +// visible-row lexical match counts, and bounded attachment-fact operations. +// +// 1.10.0 adds filtered semantic-index coverage and explicit coverage states. +// +// 1.11.0 adds attachment-accurate analytical grouping for the Files workspace. +// 1.13.0 adds contextual People and Domain summaries with search authority. +// 1.14.0 adds path-scoped People and Domain file search routes. Their identity +// scope is server-owned narrowing applied after canonical search resolution. +// 1.15.0 adds deletion-manifest detail, exact reviewed-selection deletion +// staging, and server-owned source-sync and selection-action capabilities. +// 1.16.0 adds exact server-authorized browser action targets and truthful +// nullable source-run status fields. +// 1.18.0 makes task mutations retry-stable, adds configured-project task +// search and explicit outbound metadata disclosure, and expands cache states. +// 1.19.0 adds POST /api/v1/relationships: reciprocity-weighted, time-decayed +// ranking of counterparts over resolved identity clusters, with an +// identity_revision cursor authority alongside the existing cache revision. +// 1.20.0 adds POST /api/v1/relationships/{id}/timeline: one counterpart's +// modality-neutral interaction timeline, with chat messages grouped into +// local-day bursts. {id} accepts any member of the counterpart's identity +// cluster and the response echoes the resolved canonical_id. +// 1.21.0 adds POST /api/v1/identity/links and POST /api/v1/identity/unlinks: +// idempotent participant-link mutations that report the new identity +// revision and whether the synchronous Parquet identity-dataset refresh that +// follows succeeded (cache_state: ready|stale). Also adds the additive +// cache_state field to the existing CLI identity add/remove responses. +// 1.22.0 adds optional start/end (RFC3339, UTC, half-open [start, end)) query +// params to GET /api/v1/conversations/{id}. When present, the window and the +// before/after counts are scoped to the range; an anchor outside the range is +// a 400 (conversation_anchor_outside_range) rather than the default full- +// conversation window. Additive (minor bump): omitting the params preserves +// the existing full-conversation behavior. +// 1.23.0 makes GET /api/v1/people/{id} cluster-aware: PersonIdentifier adds +// participant_id, and PersonSummary adds an additive cluster field +// (canonical_id, member_ids, edges) populated only when the requested +// participant is linked to at least one other participant. Identifiers on a +// linked participant's detail span every cluster member instead of just the +// requested ID. Additive (minor bump): unlinked participants and existing +// callers that ignore the new fields see no behavior change. +// 1.24.0 adds the additive counterpart_participant_id field to EntryRow: the +// smallest participant ID on the entry that is not the archive owner, with +// owners resolved through the same cluster-aware canon Relationships ranking +// uses. It is omitted/null when the owner set is unknown (no +// owner_participants rows) or every participant on the entry is the owner. +// Additive (minor bump): existing callers that ignore the field see no +// behavior change. +const APISchemaVersion = "1.24.0" // OpenAPIDocument builds the API schema from the same Huma route registration // used by the daemon. It binds no socket and needs no database. func OpenAPIDocument() *huma.OpenAPI { doc := baseOpenAPIDocument() + hardenSourceStatusPublicSchemas(doc) relaxResponseAdditionalProperties(doc) return doc } func openAPIClientDocument() *huma.OpenAPI { doc := baseOpenAPIDocument() + hardenSourceStatusClientSchemas(doc) clearResponseAdditionalProperties(doc) applyClientCodegenExtensions(doc) return doc @@ -66,7 +126,196 @@ func baseOpenAPIDocument() *huma.OpenAPI { api := s.setupHumaAPI(mux) apiV1 := s.setupAPIV1Group(api) s.registerHumaRoutes(api, apiV1) - return api.OpenAPI() + doc := api.OpenAPI() + hardenSettingsSchemas(doc) + hardenSavedViewSchemas(doc) + hardenExploreSchemas(doc) + hardenSearchCoverageSchemas(doc) + hardenTaskLinkSchemas(doc) + return doc +} + +func hardenTaskLinkSchemas(doc *huma.OpenAPI) { + if doc == nil || doc.Components == nil || doc.Components.Schemas == nil { + return + } + if lookup := doc.Components.Schemas.Map()["TaskLinkLookupResponse"]; lookup != nil { + if tasks := lookup.Properties["tasks"]; tasks != nil { + tasks.Nullable = false + } + } +} + +func hardenSourceStatusPublicSchemas(doc *huma.OpenAPI) { + if doc == nil || doc.Components == nil || doc.Components.Schemas == nil { + return + } + schema := doc.Components.Schemas.Map()["SourceStatus"] + if schema == nil { + return + } + for _, name := range []string{"active_sync", "latest_sync", "last_successful_sync"} { + if property := schema.Properties[name]; property != nil { + ref := property.Ref + property.Ref = "" + property.Type = "" + property.Nullable = false + property.OneOf = []*huma.Schema{{Ref: ref}, {Type: "null"}} + } + } +} + +func hardenSourceStatusClientSchemas(doc *huma.OpenAPI) { + if doc == nil || doc.Components == nil || doc.Components.Schemas == nil { + return + } + schema := doc.Components.Schemas.Map()["SourceStatus"] + if schema == nil { + return + } + nullableRuns := map[string]struct{}{ + "active_sync": {}, "latest_sync": {}, "last_successful_sync": {}, + } + required := schema.Required[:0] + for _, name := range schema.Required { + if _, nullable := nullableRuns[name]; !nullable { + required = append(required, name) + } + } + schema.Required = required + for name := range nullableRuns { + if property := schema.Properties[name]; property != nil { + property.Nullable = true + } + } +} + +func hardenSearchCoverageSchemas(doc *huma.OpenAPI) { + if doc == nil || doc.Components == nil || doc.Components.Schemas == nil { + return + } + schema := doc.Components.Schemas.Map()["SearchCoverageResponse"] + if schema != nil && schema.Properties["actions"] != nil { + schema.Properties["actions"].Nullable = false + schema.Properties["actions"].Items.Enum = []any{"retry", "build_index"} + } +} + +func hardenExploreSchemas(doc *huma.OpenAPI) { + if doc == nil || doc.Components == nil || doc.Components.Schemas == nil { + return + } + schemas := doc.Components.Schemas.Map() + dimension := &huma.Schema{ + Type: huma.TypeString, + Enum: exploreGroupingEnum(), + } + schemas["ExploreGroupDimension"] = dimension + for _, schemaName := range []string{"ExploreGroupsHTTPRequest", "FileGroupsHTTPRequest", "ExploreHTTPRequest"} { + if schema := schemas[schemaName]; schema != nil && schema.Properties["grouping"] != nil { + schema.Properties["grouping"].Items = &huma.Schema{Ref: "#/components/schemas/ExploreGroupDimension"} + } + } + for _, schemaName := range []string{"ExploreGroupsHTTPRequest", "FileGroupsHTTPRequest"} { + if groups := schemas[schemaName]; groups != nil && groups.Properties["grouping"] != nil { + one := 1 + groups.Properties["grouping"].MinItems = &one + groups.Properties["grouping"].MaxItems = &one + } + } + for schemaName, properties := range map[string][]string{ + "ExploreFilter": {"values"}, + "ExploreHTTPResponse": {"rows"}, + "ExploreGroupsHTTPRequest": {"grouping"}, + "ExploreGroupsHTTPResponse": {"rows"}, + "FileGroupsHTTPRequest": {"grouping", "predicate"}, + "FileGroupsHTTPResponse": {"rows"}, + "ExploreMatchCountsRequest": {"predicate", "row_keys"}, + "ExploreFilesHTTPResponse": {"files"}, + "ExploreFilesHTTPRequest": {"predicate"}, + "ExploreMatchCountsResponse": {"counts"}, + "ExplorePreflightRequest": {"selection"}, + "ExplorePreflightResponse": {"unavailable_actions", "action_targets"}, + "ExploreSelection": {"predicate", "cache_revision"}, + } { + schema := schemas[schemaName] + if schema == nil { + continue + } + for _, property := range properties { + if schema.Properties[property] != nil { + schema.Properties[property].Nullable = false + } + } + } +} + +func exploreGroupingEnum() []any { + dimensions := explorecatalog.GroupingDimensions() + values := make([]any, len(dimensions)) + for index, dimension := range dimensions { + values[index] = dimension + } + return values +} + +func hardenSavedViewSchemas(doc *huma.OpenAPI) { + if doc == nil || doc.Components == nil || doc.Components.Schemas == nil { + return + } + schemas := doc.Components.Schemas.Map() + if filter := schemas["SavedViewFilter"]; filter != nil { + filter.Properties["values"].Nullable = false + } + if state := schemas["SavedViewStateEnvelope"]; state != nil { + for _, name := range []string{"filters", "grouping", "sort", "columns"} { + state.Properties[name].Nullable = false + } + state.Properties["presentation"].Enum = []any{"table", "timeline", "files"} + } +} + +func hardenSettingsSchemas(doc *huma.OpenAPI) { + if doc == nil || doc.Components == nil || doc.Components.Schemas == nil { + return + } + schemas := doc.Components.Schemas.Map() + value := schemas["SettingValue"] + if value != nil { + value.Type = "" + value.Properties = nil + value.Required = nil + value.AdditionalProperties = nil + value.OneOf = []*huma.Schema{ + settingsValueArm("string", &huma.Schema{Type: huma.TypeString}), + settingsValueArm("integer", &huma.Schema{Type: huma.TypeInteger, Format: "int64"}), + settingsValueArm("number", &huma.Schema{Type: huma.TypeNumber, Format: "double"}), + settingsValueArm("boolean", &huma.Schema{Type: huma.TypeBoolean}), + settingsValueArm("strings", &huma.Schema{ + Type: huma.TypeArray, + Items: &huma.Schema{Type: huma.TypeString}, + }), + } + } + if setting := schemas["Setting"]; setting != nil { + setting.Properties["group"].Enum = []any{"browser", "server", "archive", "search", "sources", "integrations"} + setting.Properties["kind"].Enum = []any{"string", "integer", "number", "boolean", "string_array", "secret"} + } + if request := schemas["SettingsPatchRequest"]; request != nil { + request.Properties["updates"].Nullable = false + } + if response := schemas["SettingsResponse"]; response != nil { + response.Properties["settings"].Nullable = false + } +} + +func settingsValueArm(name string, property *huma.Schema) *huma.Schema { + return &huma.Schema{ + Type: huma.TypeObject, + AdditionalProperties: false, + Properties: map[string]*huma.Schema{name: property}, + Required: []string{name}, + } } // OpenAPIYAML renders the OpenAPI 3.1 schema as YAML. @@ -133,7 +382,80 @@ func applyClientCodegenExtensions(doc *huma.OpenAPI) { if doc == nil || doc.Components == nil || doc.Components.Schemas == nil { return } - queryResult := doc.Components.Schemas.Map()["QueryResult"] + schemas := doc.Components.Schemas.Map() + for _, schemaName := range []string{"FileSearchRow", "FileMetadataResponse"} { + if schema := schemas[schemaName]; schema != nil { + for _, property := range []string{"filename", "mime_type"} { + if schema.Properties[property] != nil { + schema.Properties[property].Nullable = true + } + } + } + } + for _, schemaName := range []string{"ExploreGroupsHTTPRequest", "FileGroupsHTTPRequest"} { + if groups := schemas[schemaName]; groups != nil && groups.Properties["grouping"] != nil { + grouping := groups.Properties["grouping"] + if grouping.Extensions == nil { + grouping.Extensions = map[string]any{} + } + grouping.Extensions["x-oapi-codegen-extra-tags"] = map[string]any{ + "validate": "required,min=1,max=1", + } + } + } + setEnumNames := func(schema *huma.Schema, enumNames []any) { + if schema == nil { + return + } + if schema.Extensions == nil { + schema.Extensions = map[string]any{} + } + schema.Extensions["x-enum-names"] = enumNames + } + setEnumNames(schemas["ExploreGroupDimension"], []any{ + "ExploreGroupDimensionSource", "ExploreGroupDimensionParticipant", "ExploreGroupDimensionDomain", + "ExploreGroupDimensionMessageType", "ExploreGroupDimensionKind", "ExploreGroupDimensionYear", "ExploreGroupDimensionMonth", + }) + for schemaName, properties := range map[string]map[string][]any{ + "ExploreCacheUnavailableResponse": { + "readiness": {"ExploreCacheUnavailableResponseReadinessAbsent", "ExploreCacheUnavailableResponseReadinessInterrupted", "ExploreCacheUnavailableResponseReadinessStaleSchema", "ExploreCacheUnavailableResponseReadinessDrifted"}, + }, + "ExploreFilter": { + "dimension": {"ExploreFilterDimensionSource", "ExploreFilterDimensionParticipant", "ExploreFilterDimensionDomain", "ExploreFilterDimensionMessageType", "ExploreFilterDimensionAfter", "ExploreFilterDimensionBefore", "ExploreFilterDimensionDeletion"}, + }, + "ExploreGroupSort": { + "direction": {"ExploreGroupSortDirectionAsc", "ExploreGroupSortDirectionDesc"}, + "field": {"ExploreGroupSortFieldKey", "ExploreGroupSortFieldCount", "ExploreGroupSortFieldEstimatedBytes", "ExploreGroupSortFieldLatestAt"}, + }, + "ExploreGroupsHTTPRequest": { + "presentation": {"ExploreGroupsHTTPRequestPresentationTable"}, + "search_mode": {"ExploreGroupsHTTPRequestSearchModeFullText", "ExploreGroupsHTTPRequestSearchModeSemantic", "ExploreGroupsHTTPRequestSearchModeHybrid"}, + }, + "ExploreHTTPRequest": { + "presentation": {"ExploreHTTPRequestPresentationTable", "ExploreHTTPRequestPresentationTimeline", "ExploreHTTPRequestPresentationFiles"}, + "search_mode": {"ExploreHTTPRequestSearchModeFullText", "ExploreHTTPRequestSearchModeSemantic", "ExploreHTTPRequestSearchModeHybrid"}, + }, + "IdentitySearchSort": { + "direction": {"IdentitySearchSortDirectionAsc", "IdentitySearchSortDirectionDesc"}, + "field": {"IdentitySearchSortFieldActivityCount", "IdentitySearchSortFieldLatestAt", "IdentitySearchSortFieldDisplayLabel"}, + }, + "ExploreSelection": { + "mode": {"ExploreSelectionModeExplicit", "ExploreSelectionModeAllMatching"}, + }, + "ExploreSort": { + "direction": {"ExploreSortDirectionDesc"}, + "field": {"ExploreSortFieldOccurredAt"}, + }, + } { + schema := schemas[schemaName] + if schema == nil { + continue + } + for propertyName, enumNames := range properties { + setEnumNames(schema.Properties[propertyName], enumNames) + } + } + queryResult := schemas["QueryResult"] if queryResult == nil || queryResult.Properties == nil { return } diff --git a/internal/api/openapi_test.go b/internal/api/openapi_test.go index 099f08d5d..c5421af8d 100644 --- a/internal/api/openapi_test.go +++ b/internal/api/openapi_test.go @@ -12,8 +12,10 @@ import ( "strings" "testing" + "github.com/danielgtaylor/huma/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/explorecatalog" ) const ( @@ -30,6 +32,71 @@ func TestOpenAPIDocumentUsesAPISchemaVersion(t *testing.T) { assert.NotEmpty(t, doc.Paths, "paths") } +func TestSourceStatusRunReferencesAreNullable(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + doc := OpenAPIDocument() + schema := doc.Components.Schemas.Map()["SourceStatus"] + requirements.NotNil(schema) + for _, name := range []string{"active_sync", "latest_sync", "last_successful_sync"} { + property := schema.Properties[name] + requirements.NotNil(property, name) + requirements.Len(property.OneOf, 2, name) + assertions.Equal("#/components/schemas/SyncRunStatus", property.OneOf[0].Ref, name) + assertions.Equal("null", property.OneOf[1].Type, name) + } +} + +func TestExploreServiceUnavailableResponseUsesNonExclusiveAlternatives(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + doc := OpenAPIDocument() + operation := doc.Paths["/api/v1/explore"].Post + requirements.NotNil(operation) + response := operation.Responses["503"] + requirements.NotNil(response) + schema := response.Content["application/json"].Schema + requirements.NotNil(schema) + assertions.Empty(schema.OneOf) + assertions.Len(schema.AnyOf, 2) +} + +func TestOpenAPIFileNamesAndMIMETypesAreRequiredButMayBeEmpty(t *testing.T) { + doc := OpenAPIDocument() + for _, schemaName := range []string{"FileSearchRow", "FileMetadataResponse"} { + t.Run(schemaName, func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + schema := doc.Components.Schemas.Map()[schemaName] + requirements.NotNil(schema) + for _, property := range []string{"filename", "mime_type"} { + assertions.Contains(schema.Required, property) + field := schema.Properties[property] + requirements.NotNil(field) + assertions.Equal("string", field.Type) + assertions.Nil(field.MinLength, "empty %s is legitimate archive metadata", property) + } + }) + } +} + +func TestOpenAPIClientUsesPresenceAwareFileMetadataStrings(t *testing.T) { + publicSchemas := OpenAPIDocument().Components.Schemas.Map() + clientSchemas := openAPIClientDocument().Components.Schemas.Map() + for _, schemaName := range []string{"FileSearchRow", "FileMetadataResponse"} { + t.Run(schemaName, func(t *testing.T) { + assertions := assert.New(t) + for _, property := range []string{"filename", "mime_type"} { + assertions.Contains(publicSchemas[schemaName].Required, property) + assertions.False(publicSchemas[schemaName].Properties[property].Nullable) + assertions.Contains(clientSchemas[schemaName].Required, property) + assertions.True(clientSchemas[schemaName].Properties[property].Nullable, + "client generation needs a pointer to distinguish missing from present empty") + } + }) + } +} + func TestOpenAPIJSONVersionPrettyPrintsSchema(t *testing.T) { assert := assert.New(t) require := @@ -168,6 +235,149 @@ func TestOpenAPIBinaryRoutesDocumentJSONErrors(t *testing.T) { } } +func TestOpenAPISavedViewMutationsDocumentBadRequests(t *testing.T) { + doc := OpenAPIDocument() + + for name, operation := range map[string]*huma.Operation{ + "create": doc.Paths["/api/v1/saved-views"].Post, + "patch": doc.Paths["/api/v1/saved-views/{id}"].Patch, + } { + t.Run(name, func(t *testing.T) { + requirements := require.New(t) + requirements.NotNil(operation) + response := operation.Responses[strconv.Itoa(http.StatusBadRequest)] + requirements.NotNil(response) + media := response.Content["application/json"] + requirements.NotNil(media) + requirements.NotNil(media.Schema) + assert.Equal(t, "#/components/schemas/ErrorResponse", media.Schema.Ref) + }) + } +} + +func TestOpenAPIDocumentsAllExplorationOperations(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + doc := OpenAPIDocument() + operations := map[string]string{ + "/api/v1/explore": "explore", + "/api/v1/explore/groups": "exploreGroups", + "/api/v1/explore/preflight": "preflightExploreSelection", + "/api/v1/explore/match-counts": "countExploreMatches", + "/api/v1/explore/files": "listExploreFiles", + } + for path, operationID := range operations { + t.Run(operationID, func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + op := doc.Paths[path].Post + requirements.NotNil(op) + assertions.Equal(operationID, op.OperationID) + requirements.NotNil(op.RequestBody) + requirements.NotNil(op.Responses["200"]) + requirements.NotNil(op.Responses["400"]) + requirements.NotNil(op.Responses["409"]) + requirements.NotNil(op.Responses["503"]) + }) + } + filter := doc.Components.Schemas.Map()["ExploreFilter"] + requirements.NotNil(filter) + requirements.NotNil(filter.Properties["dimension"]) + assertions.ElementsMatch( + []any{"source", "participant", "domain", "message_type", "after", "before", "deletion"}, + filter.Properties["dimension"].Enum, + ) + for schemaName, properties := range map[string][]string{ + "ExploreFilter": {"values"}, + "ExploreHTTPResponse": {"rows"}, + "ExploreGroupsHTTPResponse": {"rows"}, + "ExploreFilesHTTPResponse": {"files"}, + "ExploreMatchCountsResponse": {"counts"}, + "ExplorePreflightResponse": {"unavailable_actions"}, + } { + schema := doc.Components.Schemas.Map()[schemaName] + requirements.NotNil(schema, schemaName) + for _, property := range properties { + requirements.NotNil(schema.Properties[property], "%s.%s", schemaName, property) + assertions.False(schema.Properties[property].Nullable, "%s.%s must not be nullable", schemaName, property) + } + } +} + +func TestOpenAPIExplorationUsesStructuredUnavailableUnion(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + doc := OpenAPIDocument() + for _, path := range []string{ + "/api/v1/explore", "/api/v1/explore/groups", "/api/v1/explore/preflight", + "/api/v1/explore/match-counts", "/api/v1/explore/files", + } { + response := doc.Paths[path].Post.Responses["503"] + requirements.NotNil(response, path) + media := response.Content["application/json"] + requirements.NotNil(media, path) + requirements.NotNil(media.Schema, path) + requirements.Len(media.Schema.AnyOf, 2, path) + assertions.ElementsMatch([]string{ + "#/components/schemas/ExploreCacheUnavailableResponse", + "#/components/schemas/ErrorResponse", + }, []string{media.Schema.AnyOf[0].Ref, media.Schema.AnyOf[1].Ref}, path) + } + schema := doc.Components.Schemas.Map()["ExploreCacheUnavailableResponse"] + requirements.NotNil(schema) + readiness := schema.Properties["readiness"] + requirements.NotNil(readiness) + assertions.ElementsMatch([]any{"absent", "interrupted", "stale_schema", "drifted"}, readiness.Enum) +} + +func TestOpenAPIExplorationFiniteRequiredFieldsAreNonNull(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + doc := OpenAPIDocument() + schemas := doc.Components.Schemas.Map() + dimension := schemas["ExploreGroupDimension"] + requirements.NotNil(dimension) + assertions.ElementsMatch([]any{"source", "participant", "domain", "message_type", "kind", "year", "month"}, dimension.Enum) + + for schemaName, properties := range map[string][]string{ + "ExploreGroupsHTTPRequest": {"grouping"}, + "ExploreMatchCountsRequest": {"predicate", "row_keys"}, + "ExplorePreflightRequest": {"selection"}, + "ExploreSelection": {"predicate", "cache_revision"}, + "ExploreFilesHTTPRequest": {"predicate"}, + } { + schema := schemas[schemaName] + requirements.NotNil(schema, schemaName) + for _, propertyName := range properties { + property := schema.Properties[propertyName] + requirements.NotNil(property, "%s.%s", schemaName, propertyName) + assertions.Contains(schema.Required, propertyName, "%s.%s", schemaName, propertyName) + assertions.False(property.Nullable, "%s.%s", schemaName, propertyName) + } + } + grouping := schemas["ExploreGroupsHTTPRequest"].Properties["grouping"] + requirements.NotNil(grouping.Items) + assertions.Equal("#/components/schemas/ExploreGroupDimension", grouping.Items.Ref) + assertions.Equal(1, *grouping.MinItems) + assertions.Equal(1, *grouping.MaxItems) + clientGrouping := openAPIClientDocument().Components.Schemas.Map()["ExploreGroupsHTTPRequest"].Properties["grouping"] + requirements.NotNil(clientGrouping.Extensions) + assertions.Equal(map[string]any{"validate": "required,min=1,max=1"}, clientGrouping.Extensions["x-oapi-codegen-extra-tags"]) +} + +func TestOpenAPIExploreGroupingEnumUsesServerCatalog(t *testing.T) { + dimensions := explorecatalog.GroupingDimensions() + want := make([]any, len(dimensions)) + for index, dimension := range dimensions { + want[index] = dimension + } + + assert.Equal(t, want, exploreGroupingEnum()) + dimension := OpenAPIDocument().Components.Schemas.Map()["ExploreGroupDimension"] + require.NotNil(t, dimension) + assert.Equal(t, want, dimension.Enum) +} + func TestOpenAPIArtifactUpToDate(t *testing.T) { got, err := OpenAPIYAML() require.NoError(t, err, "render OpenAPI YAML") @@ -221,6 +431,11 @@ func TestOpenAPIClientArtifactUpToDate(t *testing.T) { cmd.Env = append(os.Environ(), "GOWORK=off") out, err := cmd.CombinedOutput() require.NoError(err, "generate client:\n%s", out) + fixer, err := filepath.Abs("../codegenfix/cmd") + require.NoError(err, "resolve generated-client validator fixup") + cmd = exec.Command("go", "run", fixer, filepath.Join(tmpGenerated, "types.go")) + out, err = cmd.CombinedOutput() + require.NoError(err, "apply generated-client validator fixup:\n%s", out) gotFiles, err := generatedGoFiles(tmpGenerated) require.NoError( diff --git a/internal/api/operation_gate.go b/internal/api/operation_gate.go index ba767d9c4..ec390d1a9 100644 --- a/internal/api/operation_gate.go +++ b/internal/api/operation_gate.go @@ -293,10 +293,11 @@ func writeOperationGateBusy(w http.ResponseWriter, gate OperationGate) { writeError(w, http.StatusServiceUnavailable, "operation_in_progress", message) } -// operationGateExemptPaths are non-GET endpoints that only read: they must -// not queue behind long mutating operations, and they never mutate the -// archive themselves. Verify is NOT exempt: its subprocess opens the store -// read-write and runs schema init/migrations. +// operationGateExemptPaths are non-GET endpoints that do not mutate the +// archive: they must not queue behind long archive operations. Most only read; +// the session endpoints mutate process-local authentication state. Verify is +// NOT exempt: its subprocess opens the store read-write and runs schema +// init/migrations. // // The backup freeze endpoints are exempt for a different reason: begin // acquires the operation gate itself (see beginLabeledOperationGateWork in @@ -304,6 +305,8 @@ func writeOperationGateBusy(w http.ResponseWriter, gate OperationGate) { // gate as well would deadlock begin against its own acquisition. var operationGateExemptPaths = map[string]bool{ queryEndpointPath: true, + sessionPath: true, + sessionLoginPath: true, "/api/v1/cli/add-calendar/plan": true, "/api/v1/cli/delete-staged/plan": true, "/api/v1/cli/embeddings/plan": true, diff --git a/internal/api/people.go b/internal/api/people.go new file mode 100644 index 000000000..d2d2d323b --- /dev/null +++ b/internal/api/people.go @@ -0,0 +1,442 @@ +package api + +import ( + "fmt" + "net/http" + "regexp" + "slices" + "strconv" + "strings" + + "github.com/danielgtaylor/huma/v2" + "go.kenn.io/msgvault/internal/query" +) + +type IdentitySearchSort struct { + Field string `json:"field" enum:"activity_count,latest_at,display_label"` + Direction string `json:"direction" enum:"asc,desc"` +} + +type IdentitySearchHTTPRequest struct { + Predicate ExploreHTTPRequest `json:"predicate"` + IdentityQuery string `json:"identity_query,omitempty"` + Sort IdentitySearchSort `json:"sort"` + Cursor string `json:"cursor,omitempty"` + Limit int `json:"limit,omitempty" minimum:"0" maximum:"500"` +} + +type PersonSearchHTTPResponse struct { + Rows []query.PersonSummary `json:"rows"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + NextCursor string `json:"next_cursor,omitempty"` + CandidateSnapshotID string `json:"candidate_snapshot_id,omitempty"` +} + +type DomainSearchHTTPResponse struct { + Rows []query.DomainSummary `json:"rows"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + NextCursor string `json:"next_cursor,omitempty"` + CandidateSnapshotID string `json:"candidate_snapshot_id,omitempty"` +} + +type PersonContextSummaryHTTPResponse struct { + Summary query.PersonSummary `json:"summary"` + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + CandidateSnapshotID string `json:"candidate_snapshot_id,omitempty"` +} + +type DomainContextSummaryHTTPResponse struct { + Summary query.DomainSummary `json:"summary"` + CacheRevision string `json:"cache_revision"` + SearchProvenance query.SearchProvenance `json:"search_provenance"` + CandidateSnapshotID string `json:"candidate_snapshot_id,omitempty"` +} + +type identitySearchPrepared struct { + predicate explorePrepared + sort query.SortSpec + offset int + requestHash string + cursor exploreCursor + search query.SearchSpec + snapshotID string +} + +var domainFactPattern = regexp.MustCompile(`^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$`) + +func (s *Server) registerPeopleRoutes(api huma.API) { + registerExploreRoute[IdentitySearchHTTPRequest, PersonSearchHTTPResponse]( + api, "searchPeople", "/people/search", "Search analytical people", s.handleSearchPeople, + ) + registerAPIV1RawHumaJSONRoute[query.PersonSummary]( + api, "getPerson", http.MethodGet, "/people/{id}", "Get one analytical person", s.handleGetPerson, + ) + registerExploreRoute[ExploreHTTPRequest, PersonContextSummaryHTTPResponse]( + api, "getPersonContextSummary", "/people/{id}/summary", "Get one person's contextual analytical summary", s.handlePersonContextSummary, + ) + registerExploreRoute[ExploreHTTPRequest, ExploreHTTPResponse]( + api, "getPersonTimeline", "/people/{id}/timeline", "Get one person's canonical activity timeline", s.handlePersonTimeline, + ) + registerExploreRoute[IdentitySearchHTTPRequest, DomainSearchHTTPResponse]( + api, "searchDomains", "/domains/search", "Search analytical domains", s.handleSearchDomains, + ) + registerAPIV1RawHumaJSONRoute[query.DomainSummary]( + api, "getDomain", http.MethodGet, "/domains/{domain}", "Get one analytical domain", s.handleGetDomain, + ) + registerExploreRoute[ExploreHTTPRequest, DomainContextSummaryHTTPResponse]( + api, "getDomainContextSummary", "/domains/{domain}/summary", "Get one domain's contextual analytical summary", s.handleDomainContextSummary, + ) + registerExploreRoute[ExploreHTTPRequest, ExploreHTTPResponse]( + api, "getDomainTimeline", "/domains/{domain}/timeline", "Get one domain's canonical activity timeline", s.handleDomainTimeline, + ) +} + +func (s *Server) handleSearchPeople(w http.ResponseWriter, r *http.Request) { + var request IdentitySearchHTTPRequest + prepared, ok := s.prepareIdentitySearch(w, r, &request) + if !ok { + return + } + analyzer, ok := s.engine.(query.PeopleAnalyzer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + result, err := analyzer.SearchPeople(r.Context(), query.PersonSearchRequest{ + Explore: query.ExploreRequest{Context: prepared.predicate.query.Context, Search: prepared.search}, + Query: strings.TrimSpace(request.IdentityQuery), Sort: prepared.sort, + Page: query.PageSpec{Limit: request.Limit, Offset: prepared.offset}, + }) + if err != nil { + s.writeExploreError(w, err) + return + } + if request.Cursor != "" && prepared.cursor.Revision != result.CacheRevision { + writeError(w, http.StatusConflict, "archive_revision_changed", "The committed analytical cache changed; restart pagination") + return + } + response := PersonSearchHTTPResponse{Rows: result.Rows, TotalCount: result.TotalCount, CacheRevision: result.CacheRevision, + SearchProvenance: result.SearchProvenance, CandidateSnapshotID: prepared.snapshotID} + if next := prepared.offset + len(result.Rows); next < int(result.TotalCount) { + response.NextCursor = s.encodeExploreCursor(exploreCursor{Offset: next, Request: prepared.requestHash, Revision: result.CacheRevision, + SearchRevision: exploreResolvedSearchRevision(prepared.search), Snapshot: prepared.snapshotID}) + } + writeJSON(w, http.StatusOK, response) +} + +func (s *Server) handleSearchDomains(w http.ResponseWriter, r *http.Request) { + var request IdentitySearchHTTPRequest + prepared, ok := s.prepareIdentitySearch(w, r, &request) + if !ok { + return + } + analyzer, ok := s.engine.(query.PeopleAnalyzer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + result, err := analyzer.SearchDomains(r.Context(), query.DomainSearchRequest{ + Explore: query.ExploreRequest{Context: prepared.predicate.query.Context, Search: prepared.search}, + Query: strings.TrimSpace(request.IdentityQuery), Sort: prepared.sort, + Page: query.PageSpec{Limit: request.Limit, Offset: prepared.offset}, + }) + if err != nil { + s.writeExploreError(w, err) + return + } + if request.Cursor != "" && prepared.cursor.Revision != result.CacheRevision { + writeError(w, http.StatusConflict, "archive_revision_changed", "The committed analytical cache changed; restart pagination") + return + } + response := DomainSearchHTTPResponse{Rows: result.Rows, TotalCount: result.TotalCount, CacheRevision: result.CacheRevision, + SearchProvenance: result.SearchProvenance, CandidateSnapshotID: prepared.snapshotID} + if next := prepared.offset + len(result.Rows); next < int(result.TotalCount) { + response.NextCursor = s.encodeExploreCursor(exploreCursor{Offset: next, Request: prepared.requestHash, Revision: result.CacheRevision, + SearchRevision: exploreResolvedSearchRevision(prepared.search), Snapshot: prepared.snapshotID}) + } + writeJSON(w, http.StatusOK, response) +} + +func (s *Server) prepareIdentitySearch(w http.ResponseWriter, r *http.Request, request *IdentitySearchHTTPRequest) (identitySearchPrepared, bool) { + if !decodeExploreJSON(w, r, request) { + return identitySearchPrepared{}, false + } + if len(request.Predicate.Grouping) > 0 { + writeError(w, http.StatusBadRequest, "invalid_identity_predicate", "identity search does not accept grouping") + return identitySearchPrepared{}, false + } + request.Predicate.Cursor = "" + prepared, err := prepareExplorePredicate(request.Predicate) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_identity_predicate", err.Error()) + return identitySearchPrepared{}, false + } + if request.Limit == 0 { + request.Limit = exploreDefaultLimit + } + if request.Limit < 1 || request.Limit > exploreMaxLimit { + writeError(w, http.StatusBadRequest, "invalid_limit", fmt.Sprintf("limit must be between 1 and %d", exploreMaxLimit)) + return identitySearchPrepared{}, false + } + request.IdentityQuery = strings.TrimSpace(request.IdentityQuery) + request.Sort.Field = strings.ToLower(strings.TrimSpace(request.Sort.Field)) + request.Sort.Direction = strings.ToLower(strings.TrimSpace(request.Sort.Direction)) + if request.Sort.Field == "" { + request.Sort = IdentitySearchSort{Field: "activity_count", Direction: apiSortDirectionDesc} + } + if !slices.Contains([]string{"activity_count", "latest_at", "display_label"}, request.Sort.Field) || + !slices.Contains([]string{"asc", apiSortDirectionDesc}, request.Sort.Direction) { + writeError(w, http.StatusBadRequest, "invalid_sort", "unknown identity sort field or direction") + return identitySearchPrepared{}, false + } + request.Predicate = prepared.request + canonical := *request + canonical.Cursor = "" + requestHash := hashCanonicalValue(canonical, false) + offset, ok := s.parseExploreCursor(w, request.Cursor, requestHash) + if !ok { + return identitySearchPrepared{}, false + } + var cursor exploreCursor + searchRequest := prepared.request + if request.Cursor != "" { + cursor, _ = s.decodeExploreCursor(request.Cursor) + if searchRequest.SearchMode == exploreSearchModeSemantic || searchRequest.SearchMode == exploreSearchModeHybrid { + if cursor.Snapshot == "" { + writeError(w, http.StatusBadRequest, "invalid_cursor", "semantic cursor is missing its candidate snapshot") + return identitySearchPrepared{}, false + } + searchRequest.CandidateSnapshotID = cursor.Snapshot + } + } + searchSpec, snapshotID, ok := s.resolveExploreSearch(r.Context(), w, searchRequest) + if !ok || !requireCompleteCandidatePool(w, searchSpec) { + return identitySearchPrepared{}, false + } + if request.Cursor != "" && cursor.SearchRevision != exploreResolvedSearchRevision(searchSpec) { + writeError(w, http.StatusConflict, "search_revision_changed", "The resolved search index revision changed; restart pagination") + return identitySearchPrepared{}, false + } + return identitySearchPrepared{predicate: prepared, sort: query.SortSpec{Field: request.Sort.Field, Direction: request.Sort.Direction}, + offset: offset, requestHash: requestHash, cursor: cursor, search: searchSpec, snapshotID: snapshotID}, true +} + +func (s *Server) handleGetPerson(w http.ResponseWriter, r *http.Request) { + id, ok := positivePersonPathID(w, r) + if !ok { + return + } + analyzer, ok := s.engine.(query.PeopleAnalyzer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + members := s.clusterMemberIDs(id) + person, err := analyzer.GetPerson(r.Context(), id, query.Context{}, members) + if err != nil { + s.writeExploreError(w, err) + return + } + if person == nil { + writeError(w, http.StatusNotFound, "person_not_found", "Person not found") + return + } + s.attachPersonCluster(person, id, members) + writeJSON(w, http.StatusOK, person) +} + +// clusterMemberIDs returns id's sorted cluster member IDs, or nil if the +// store has no ClusterLookupStore capability, the lookup fails, or id is +// unlinked (fewer than two members) — in every one of those cases the +// person detail stays scoped to id alone, matching pre-cluster-aware +// behavior. Errors are swallowed here rather than failing the request: an +// unavailable cluster lookup must degrade the detail to unlinked, not break +// the whole person-detail endpoint. +func (s *Server) clusterMemberIDs(id int64) []int64 { + lookup, ok := s.store.(ClusterLookupStore) + if !ok { + return nil + } + members, err := lookup.ClusterMembers(id) + if err != nil || len(members) < 2 { + return nil + } + return members +} + +// attachPersonCluster sets person.Cluster from the store's edge graph when +// id is linked (members has ≥2 entries, as returned by clusterMemberIDs). +// Canonical is members[0]: ClusterMembers returns members sorted ascending, +// and the store's link-graph convention roots every cluster at its smallest +// member ID (see ParticipantClusters/rebuildClusterAsStar in +// internal/store/participant_links.go). +func (s *Server) attachPersonCluster(person *query.PersonSummary, id int64, members []int64) { + if len(members) < 2 { + return + } + lookup, ok := s.store.(ClusterLookupStore) + if !ok { + return + } + edges, err := lookup.ClusterEdges(id) + if err != nil { + s.logger.Error("cluster edges lookup failed", "error", err, "participant_id", id) + return + } + clusterEdges := make([]query.PersonClusterEdge, 0, len(edges)) + for _, e := range edges { + clusterEdges = append(clusterEdges, query.PersonClusterEdge{ParticipantA: e.A, ParticipantB: e.B}) + } + person.Cluster = &query.PersonCluster{CanonicalID: members[0], MemberIDs: members, Edges: clusterEdges} +} + +func (s *Server) handlePersonContextSummary(w http.ResponseWriter, r *http.Request) { + id, ok := positivePersonPathID(w, r) + if !ok { + return + } + explore, snapshotID, ok := s.prepareIdentitySummary(w, r) + if !ok { + return + } + analyzer, ok := s.engine.(query.PeopleAnalyzer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + result, err := analyzer.GetPersonSummary(r.Context(), id, explore) + if err != nil { + s.writeExploreError(w, err) + return + } + if result == nil || len(result.Rows) == 0 { + writeError(w, http.StatusNotFound, "person_not_found", "Person not found in the active analytical context") + return + } + writeJSON(w, http.StatusOK, PersonContextSummaryHTTPResponse{Summary: result.Rows[0], CacheRevision: result.CacheRevision, + SearchProvenance: result.SearchProvenance, CandidateSnapshotID: snapshotID}) +} + +func (s *Server) handleGetDomain(w http.ResponseWriter, r *http.Request) { + domain, ok := domainPathFactSuffix(w, r, "") + if !ok { + return + } + analyzer, ok := s.engine.(query.PeopleAnalyzer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + result, err := analyzer.GetDomain(r.Context(), domain, query.Context{}) + if err != nil { + s.writeExploreError(w, err) + return + } + if result == nil { + writeError(w, http.StatusNotFound, "domain_not_found", "Domain not found") + return + } + writeJSON(w, http.StatusOK, result) +} + +func (s *Server) handleDomainContextSummary(w http.ResponseWriter, r *http.Request) { + domain, ok := domainPathFactSuffix(w, r, "/summary") + if !ok { + return + } + explore, snapshotID, ok := s.prepareIdentitySummary(w, r) + if !ok { + return + } + analyzer, ok := s.engine.(query.PeopleAnalyzer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + result, err := analyzer.GetDomainSummary(r.Context(), domain, explore) + if err != nil { + s.writeExploreError(w, err) + return + } + if result == nil || len(result.Rows) == 0 { + writeError(w, http.StatusNotFound, "domain_not_found", "Domain not found in the active analytical context") + return + } + writeJSON(w, http.StatusOK, DomainContextSummaryHTTPResponse{Summary: result.Rows[0], CacheRevision: result.CacheRevision, + SearchProvenance: result.SearchProvenance, CandidateSnapshotID: snapshotID}) +} + +func (s *Server) prepareIdentitySummary(w http.ResponseWriter, r *http.Request) (query.ExploreRequest, string, bool) { + var request ExploreHTTPRequest + if !decodeExploreJSON(w, r, &request) { + return query.ExploreRequest{}, "", false + } + if len(request.Grouping) > 0 { + writeError(w, http.StatusBadRequest, "invalid_identity_predicate", "identity summary does not accept grouping") + return query.ExploreRequest{}, "", false + } + prepared, err := prepareExplorePredicate(request) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_identity_predicate", err.Error()) + return query.ExploreRequest{}, "", false + } + searchSpec, snapshotID, ok := s.resolveExploreSearch(r.Context(), w, prepared.request) + if !ok || !requireCompleteCandidatePool(w, searchSpec) { + return query.ExploreRequest{}, "", false + } + return query.ExploreRequest{Context: prepared.query.Context, Search: searchSpec}, snapshotID, true +} + +func (s *Server) handlePersonTimeline(w http.ResponseWriter, r *http.Request) { + id, ok := positivePersonPathID(w, r) + if !ok { + return + } + s.forwardIdentityTimeline(w, r, ExploreFilter{Dimension: "participant", Values: []string{strconv.FormatInt(id, 10)}}) +} + +func (s *Server) handleDomainTimeline(w http.ResponseWriter, r *http.Request) { + domain, ok := domainPathFact(w, r, true) + if !ok { + return + } + s.forwardIdentityTimeline(w, r, ExploreFilter{Dimension: "domain", Values: []string{domain}}) +} + +func (s *Server) forwardIdentityTimeline(w http.ResponseWriter, r *http.Request, exact ExploreFilter) { + s.handleExploreWithScope(w, r, &exact) +} + +func positivePersonPathID(w http.ResponseWriter, r *http.Request) (int64, bool) { + raw := strings.TrimPrefix(r.URL.Path, "/api/v1/people/") + raw = strings.TrimSuffix(strings.TrimSuffix(strings.TrimSuffix(raw, "/files/search"), "/timeline"), "/summary") + id, err := strconv.ParseInt(raw, 10, 64) + if err != nil || id < 1 { + writeError(w, http.StatusBadRequest, "invalid_person_id", "person ID must be a positive integer") + return 0, false + } + return id, true +} + +func domainPathFact(w http.ResponseWriter, r *http.Request, timeline bool) (string, bool) { + suffix := "" + if timeline { + suffix = "/timeline" + } + return domainPathFactSuffix(w, r, suffix) +} + +func domainPathFactSuffix(w http.ResponseWriter, r *http.Request, suffix string) (string, bool) { + raw := strings.TrimSuffix(strings.TrimPrefix(r.URL.Path, "/api/v1/domains/"), suffix) + domain := strings.ToLower(strings.TrimSpace(raw)) + if len(domain) == 0 || len(domain) > 253 || !domainFactPattern.MatchString(domain) { + writeError(w, http.StatusBadRequest, "invalid_domain", "domain must be an exact normalized domain fact") + return "", false + } + return domain, true +} diff --git a/internal/api/people_snapshot_e2e_test.go b/internal/api/people_snapshot_e2e_test.go new file mode 100644 index 000000000..469a98527 --- /dev/null +++ b/internal/api/people_snapshot_e2e_test.go @@ -0,0 +1,333 @@ +package api + +import ( + "database/sql" + "encoding/json" + "fmt" + "net/http" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/store" + "go.kenn.io/msgvault/internal/vector" + "go.kenn.io/msgvault/internal/vector/hybrid" +) + +func TestPersonTimelineReusesBaseSemanticSnapshotBeforeIdentityNarrowing(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := newReviewSemanticServerWithHits(t, []vector.Hit{ + {MessageID: 1, Score: .9, Rank: 1}, + {MessageID: 2, Score: .8, Rank: 2}, + }) + + snapshot := mintIdentitySnapshot(t, srv, "/api/v1/people/search", `{ + "predicate":{"query":"alpha","search_mode":"semantic"},"limit":25 + }`) + timeline := postExploreJSON(t, srv, "/api/v1/people/1/timeline", `{ + "query":"alpha","search_mode":"semantic","candidate_snapshot_id":"`+snapshot+`","limit":25 + }`) + + requirements.Equal(http.StatusOK, timeline.Code, timeline.Body.String()) + var body ExploreHTTPResponse + requirements.NoError(json.Unmarshal(timeline.Body.Bytes(), &body)) + assertions.Equal(snapshot, body.CandidateSnapshotID) + requirements.NotNil(body.SearchProvenance.VectorGeneration) + assertions.Equal(int64(7), *body.SearchProvenance.VectorGeneration) + requirements.NotEmpty(body.Rows) + for _, row := range body.Rows { + requirements.NotNil(row.AnchorMessageID) + assertions.Contains([]int64{1, 2}, *row.AnchorMessageID) + assertions.Contains(row.ParticipantIDs, int64(1)) + } +} + +func TestIdentityRelatedFilesReuseBaseSemanticSnapshotBeforeIdentityNarrowing(t *testing.T) { + tests := []struct { + name string + searchPath string + filesPath string + }{ + {name: "person", searchPath: "/api/v1/people/search", filesPath: "/api/v1/people/1/files/search"}, + {name: "domain", searchPath: "/api/v1/domains/search", filesPath: "/api/v1/domains/example.com/files/search"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := newReviewSemanticServerWithHits(t, []vector.Hit{ + {MessageID: 1, Score: .9, Rank: 1}, + {MessageID: 2, Score: .8, Rank: 2}, + }) + baseStore, ok := srv.store.(*mockStore) + requirements.True(ok) + srv.store = &fileCatalogStore{mockStore: baseStore, files: map[int64]store.FileMetadata{ + 11: {ID: 11, MessageID: 1, ConversationID: 101, Filename: "older.txt"}, + 12: {ID: 12, MessageID: 2, ConversationID: 102, Filename: "newest.pdf"}, + }} + snapshot := mintIdentitySnapshot(t, srv, test.searchPath, `{ + "predicate":{"query":"alpha","search_mode":"semantic"},"limit":25 + }`) + response := postExploreJSON(t, srv, test.filesPath, `{ + "predicate":{"query":"alpha","search_mode":"semantic","candidate_snapshot_id":"`+snapshot+`"}, + "sort":{"field":"occurred_at","direction":"desc"},"limit":25 + }`) + + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var body FileSearchHTTPResponse + requirements.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assertions.Equal(snapshot, body.CandidateSnapshotID) + requirements.NotNil(body.SearchProvenance.VectorGeneration) + assertions.Equal(int64(7), *body.SearchProvenance.VectorGeneration) + requirements.Len(body.Files, 2) + assertions.ElementsMatch([]int64{11, 12}, []int64{body.Files[0].ID, body.Files[1].ID}) + for _, file := range body.Files { + assertions.Contains(file.ParticipantIDs, int64(1)) + assertions.Contains(file.ParticipantDomains, "example.com") + } + }) + } +} + +func TestIdentityScopeIntersectsSemanticCandidatesWithoutExpansion(t *testing.T) { + srv := newIdentityScopeSemanticServer(t) + for _, test := range []struct { + name, searchPath, timelinePath, filesPath string + }{ + {name: "person", searchPath: "/api/v1/people/search", timelinePath: "/api/v1/people/1/timeline", filesPath: "/api/v1/people/1/files/search"}, + {name: "domain", searchPath: "/api/v1/domains/search", timelinePath: "/api/v1/domains/example.com/timeline", filesPath: "/api/v1/domains/example.com/files/search"}, + } { + t.Run(test.name, func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + snapshot := mintIdentitySnapshot(t, srv, test.searchPath, `{ + "predicate":{"query":"alpha","search_mode":"semantic"},"limit":25 + }`) + timeline := postExploreJSON(t, srv, test.timelinePath, `{ + "query":"alpha","search_mode":"semantic","candidate_snapshot_id":"`+snapshot+`" + }`) + requirements.Equal(http.StatusOK, timeline.Code, timeline.Body.String()) + var timelineBody ExploreHTTPResponse + requirements.NoError(json.Unmarshal(timeline.Body.Bytes(), &timelineBody)) + requirements.Len(timelineBody.Rows, 1) + requirements.NotNil(timelineBody.Rows[0].AnchorMessageID) + assertions.Equal(int64(1), *timelineBody.Rows[0].AnchorMessageID) + + files := postExploreJSON(t, srv, test.filesPath, `{ + "predicate":{"query":"alpha","search_mode":"semantic","candidate_snapshot_id":"`+snapshot+`"}, + "sort":{"field":"occurred_at","direction":"desc"} + }`) + requirements.Equal(http.StatusOK, files.Code, files.Body.String()) + var filesBody FileSearchHTTPResponse + requirements.NoError(json.Unmarshal(files.Body.Bytes(), &filesBody)) + requirements.Len(filesBody.Files, 1) + assertions.Equal(int64(11), filesBody.Files[0].ID) + assertions.Equal(snapshot, filesBody.CandidateSnapshotID) + requirements.NotNil(filesBody.SearchProvenance.VectorGeneration) + assertions.Equal(int64(7), *filesBody.SearchProvenance.VectorGeneration) + }) + } +} + +func TestIdentitySemanticSnapshotRejectsWrongPredicateAndExpiry(t *testing.T) { + now := time.Date(2026, 7, 19, 12, 0, 0, 0, time.UTC) + srv := newReviewSemanticServerWithHits(t, []vector.Hit{{MessageID: 1, Score: .9, Rank: 1}}) + baseStore, ok := srv.store.(*mockStore) + require.True(t, ok) + srv.store = &fileCatalogStore{mockStore: baseStore, files: map[int64]store.FileMetadata{ + 11: {ID: 11, MessageID: 1, ConversationID: 101, Filename: "older.txt"}, + }} + srv.exploreState = newExploreServerState(func() time.Time { return now }) + snapshot := mintIdentitySnapshot(t, srv, "/api/v1/people/search", `{ + "predicate":{"query":"alpha","search_mode":"semantic"},"limit":25 + }`) + + for _, test := range []struct { + name string + path string + body string + }{ + {name: "timeline wrong query", path: "/api/v1/people/1/timeline", body: `{"query":"beta","search_mode":"semantic","candidate_snapshot_id":"` + snapshot + `"}`}, + {name: "timeline wrong mode", path: "/api/v1/people/1/timeline", body: `{"query":"alpha","search_mode":"hybrid","candidate_snapshot_id":"` + snapshot + `"}`}, + {name: "files wrong query", path: "/api/v1/people/1/files/search", body: `{"predicate":{"query":"beta","search_mode":"semantic","candidate_snapshot_id":"` + snapshot + `"},"sort":{"field":"occurred_at","direction":"desc"}}`}, + {name: "files wrong mode", path: "/api/v1/people/1/files/search", body: `{"predicate":{"query":"alpha","search_mode":"hybrid","candidate_snapshot_id":"` + snapshot + `"},"sort":{"field":"occurred_at","direction":"desc"}}`}, + } { + t.Run(test.name, func(t *testing.T) { + response := postExploreJSON(t, srv, test.path, test.body) + assertExploreError(t, response, http.StatusConflict, "candidate_snapshot_expired") + }) + } + + now = now.Add(exploreCandidateSnapshotTTL + time.Second) + expired := postExploreJSON(t, srv, "/api/v1/people/1/timeline", `{ + "query":"alpha","search_mode":"semantic","candidate_snapshot_id":"`+snapshot+`" + }`) + assertExploreError(t, expired, http.StatusConflict, "candidate_snapshot_expired") + expiredFiles := postExploreJSON(t, srv, "/api/v1/people/1/files/search", `{ + "predicate":{"query":"alpha","search_mode":"semantic","candidate_snapshot_id":"`+snapshot+`"}, + "sort":{"field":"occurred_at","direction":"desc"} + }`) + assertExploreError(t, expiredFiles, http.StatusConflict, "candidate_snapshot_expired") +} + +func TestIdentityScopeRejectsConflictingPredicateAndCrossIdentityCursor(t *testing.T) { + requirements := require.New(t) + srv := newReviewSemanticServerWithHits(t, []vector.Hit{ + {MessageID: 1, Score: .9, Rank: 1}, + {MessageID: 2, Score: .8, Rank: 2}, + }) + baseStore, ok := srv.store.(*mockStore) + requirements.True(ok) + srv.store = &fileCatalogStore{mockStore: baseStore, files: map[int64]store.FileMetadata{ + 11: {ID: 11, MessageID: 1, ConversationID: 101, Filename: "older.txt"}, + 12: {ID: 12, MessageID: 2, ConversationID: 102, Filename: "newest.pdf"}, + }} + conflict := postExploreJSON(t, srv, "/api/v1/people/1/timeline", `{ + "filters":[{"dimension":"participant","values":["2"]}] + }`) + assertExploreError(t, conflict, http.StatusConflict, "identity_scope_conflict") + + snapshot := mintIdentitySnapshot(t, srv, "/api/v1/people/search", `{ + "predicate":{"query":"alpha","search_mode":"semantic"},"limit":25 + }`) + first := postExploreJSON(t, srv, "/api/v1/people/1/timeline", `{ + "query":"alpha","search_mode":"semantic","candidate_snapshot_id":"`+snapshot+`","limit":1 + }`) + requirements.Equal(http.StatusOK, first.Code, first.Body.String()) + var page ExploreHTTPResponse + requirements.NoError(json.Unmarshal(first.Body.Bytes(), &page)) + requirements.NotEmpty(page.NextCursor) + crossIdentity := postExploreJSON(t, srv, "/api/v1/people/2/timeline", `{ + "query":"alpha","search_mode":"semantic","candidate_snapshot_id":"`+snapshot+`","cursor":"`+page.NextCursor+`","limit":1 + }`) + assertExploreError(t, crossIdentity, http.StatusBadRequest, "invalid_cursor") + + firstFiles := postExploreJSON(t, srv, "/api/v1/people/1/files/search", `{ + "predicate":{"query":"alpha","search_mode":"semantic","candidate_snapshot_id":"`+snapshot+`"}, + "sort":{"field":"occurred_at","direction":"desc"},"limit":1 + }`) + requirements.Equal(http.StatusOK, firstFiles.Code, firstFiles.Body.String()) + var filePage FileSearchHTTPResponse + requirements.NoError(json.Unmarshal(firstFiles.Body.Bytes(), &filePage)) + requirements.NotEmpty(filePage.NextCursor) + crossIdentityFiles := postExploreJSON(t, srv, "/api/v1/people/2/files/search", `{ + "predicate":{"query":"alpha","search_mode":"semantic","candidate_snapshot_id":"`+snapshot+`"}, + "sort":{"field":"occurred_at","direction":"desc"},"cursor":"`+filePage.NextCursor+`","limit":1 + }`) + assertExploreError(t, crossIdentityFiles, http.StatusBadRequest, "invalid_cursor") +} + +func TestIdentityScopeNarrowsCompatibleMultiValueBaseFilter(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := newIdentityScopeSemanticServer(t) + timeline := postExploreJSON(t, srv, "/api/v1/people/1/timeline", `{ + "filters":[{"dimension":"participant","values":["1","2"]}] + }`) + requirements.Equal(http.StatusOK, timeline.Code, timeline.Body.String()) + var body ExploreHTTPResponse + requirements.NoError(json.Unmarshal(timeline.Body.Bytes(), &body)) + requirements.Len(body.Rows, 1) + requirements.NotNil(body.Rows[0].AnchorMessageID) + assertions.Equal(int64(1), *body.Rows[0].AnchorMessageID) +} + +func assertExploreError(t *testing.T, response interface { + Result() *http.Response +}, status int, code string) { + t.Helper() + result := response.Result() + defer func() { require.NoError(t, result.Body.Close()) }() + require.Equal(t, status, result.StatusCode) + var body ErrorResponse + require.NoError(t, json.NewDecoder(result.Body).Decode(&body)) + assert.Equal(t, code, body.Error) +} + +func mintIdentitySnapshot(t *testing.T, srv *Server, path, request string) string { + t.Helper() + response := postExploreJSON(t, srv, path, request) + require.Equal(t, http.StatusOK, response.Code, response.Body.String()) + var body struct { + CandidateSnapshotID string `json:"candidate_snapshot_id"` + } + require.NoError(t, json.Unmarshal(response.Body.Bytes(), &body)) + require.NotEmpty(t, body.CandidateSnapshotID) + return body.CandidateSnapshotID +} + +func newIdentityScopeSemanticServer(t *testing.T) *Server { + t.Helper() + engine := newIdentityScopeDuckDBFixture(t) + backend := &fakeVectorBackend{ + active: &vector.Generation{ID: 7, Model: "test", Dimension: 2, Fingerprint: "test:2", State: vector.GenerationActive}, + searchHits: []vector.Hit{ + {MessageID: 1, Score: .9, Rank: 1}, + {MessageID: 2, Score: .8, Rank: 2}, + }, + } + hybridEngine := hybrid.NewEngine(backend, nil, realEmbedder{dim: 2}, hybrid.Config{ExpectedFingerprint: "test:2"}) + baseStore := &mockStore{messages: []APIMessage{{ID: 1}, {ID: 2}}, total: 2, stats: &StoreStats{}} + catalog := &fileCatalogStore{mockStore: baseStore, files: map[int64]store.FileMetadata{ + 11: {ID: 11, MessageID: 1, ConversationID: 101, Filename: "alice.txt"}, + 12: {ID: 12, MessageID: 2, ConversationID: 102, Filename: "bob.pdf"}, + }} + return NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, Store: catalog, Engine: engine, + HybridEngine: hybridEngine, Backend: backend, Logger: testLogger(), + }) +} + +func newIdentityScopeDuckDBFixture(t *testing.T) *query.DuckDBEngine { + t.Helper() + analyticsDir := t.TempDir() + db, err := sql.Open("duckdb", "") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, db.Close()) }) + tables := []struct { + dir, file, columns, values string + empty bool + }{ + {dir: "messages/year=2026", file: "messages.parquet", columns: "id, source_id, source_message_id, conversation_id, subject, snippet, sent_at, size_estimate, has_attachments, attachment_count, deleted_from_source_at, sender_id, message_type, year, month", values: `(1::BIGINT, 1::BIGINT, 'm1', 101::BIGINT, 'Alice', 'alpha', TIMESTAMP '2026-07-18 10:00:00', 100::BIGINT, true, 1::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2026, 7), (2::BIGINT, 1::BIGINT, 'm2', 102::BIGINT, 'Bob', 'alpha', TIMESTAMP '2026-07-18 11:00:00', 200::BIGINT, true, 1::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2026, 7)`}, + {dir: "sources", file: "sources.parquet", columns: "id, account_email, source_type", values: `(1::BIGINT, 'archive@example.net', 'gmail')`}, + {dir: "participants", file: "participants.parquet", columns: "id, email_address, domain, display_name, phone_number", values: `(1::BIGINT, 'alice@example.com', 'example.com', 'Alice', ''), (2::BIGINT, 'bob@other.example', 'other.example', 'Bob', '')`}, + {dir: "participant_identifiers", file: "participant_identifiers.parquet", columns: "participant_id, identifier_type, identifier_value, display_value, is_primary", values: `(1::BIGINT, 'email', 'alice@example.com', 'alice@example.com', true), (2::BIGINT, 'email', 'bob@other.example', 'bob@other.example', true)`}, + {dir: "message_recipients", file: "message_recipients.parquet", columns: "message_id, participant_id, recipient_type, display_name", values: `(1::BIGINT, 1::BIGINT, 'from', 'Alice'), (2::BIGINT, 2::BIGINT, 'from', 'Bob')`}, + {dir: "labels", file: "labels.parquet", columns: "id, name", values: `(0::BIGINT, '')`, empty: true}, + {dir: "message_labels", file: "message_labels.parquet", columns: "message_id, label_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "attachments", file: "attachments.parquet", columns: "attachment_id, message_id, size, filename", values: `(11::BIGINT, 1::BIGINT, 10::BIGINT, 'alice.txt'), (12::BIGINT, 2::BIGINT, 20::BIGINT, 'bob.pdf')`}, + {dir: "conversations", file: "conversations.parquet", columns: "id, source_conversation_id, title, conversation_type", values: `(101::BIGINT, 'c1', '', 'email'), (102::BIGINT, 'c2', '', 'email')`}, + {dir: "conversation_participants", file: "conversation_participants.parquet", columns: "conversation_id, participant_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "owner_participants", file: "owner_participants.parquet", columns: "source_id, participant_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "participant_clusters", file: "participant_clusters.parquet", columns: "participant_id, canonical_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + } + for _, table := range tables { + dir := filepath.Join(analyticsDir, table.dir) + require.NoError(t, os.MkdirAll(dir, 0o755)) + where := "" + if table.empty { + where = " WHERE false" + } + path := filepath.ToSlash(filepath.Join(dir, table.file)) + _, err := db.Exec(fmt.Sprintf("COPY (SELECT * FROM (VALUES %s) AS t(%s)%s) TO '%s' (FORMAT PARQUET)", table.values, table.columns, where, path)) + require.NoError(t, err, "write %s", table.dir) + } + fingerprint, err := query.CacheDatasetFingerprint(analyticsDir) + require.NoError(t, err) + state, err := json.Marshal(query.CacheSyncState{ + LastMessageID: 2, LastSyncAt: time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC), SchemaVersion: query.CacheSchemaVersion, + PublishedAt: time.Date(2026, 7, 18, 12, 1, 0, 0, time.UTC), DatasetFingerprint: fingerprint, + }) + require.NoError(t, err) + require.NoError(t, os.WriteFile(query.CacheStatePath(analyticsDir), state, 0o600)) + engine, err := query.NewDuckDBEngine(analyticsDir, "", nil) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, engine.Close()) }) + return engine +} diff --git a/internal/api/people_test.go b/internal/api/people_test.go new file mode 100644 index 000000000..b512753a6 --- /dev/null +++ b/internal/api/people_test.go @@ -0,0 +1,361 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/query/querytest" + "go.kenn.io/msgvault/internal/testutil" +) + +type peopleAPIEngine struct { + *querytest.MockEngine + + peopleRequest query.PersonSearchRequest + peopleResult *query.PersonSearchResponse + personSummaryRequest query.ExploreRequest + personSummaryResult *query.PersonSearchResponse + domainRequest query.DomainSearchRequest + domainResult *query.DomainSearchResponse + domainSummaryRequest query.ExploreRequest + domainSummaryResult *query.DomainSearchResponse + person *query.PersonSummary + personClusterMembers []int64 + domain *query.DomainSummary + timeline query.ExploreRequest + timelineResult *query.ExploreResponse + peopleErr error +} + +func (e *peopleAPIEngine) SearchPeople(_ context.Context, request query.PersonSearchRequest) (*query.PersonSearchResponse, error) { + e.peopleRequest = request + return e.peopleResult, e.peopleErr +} + +func (e *peopleAPIEngine) GetPerson(_ context.Context, _ int64, _ query.Context, clusterMemberIDs []int64) (*query.PersonSummary, error) { + e.personClusterMembers = clusterMemberIDs + return e.person, e.peopleErr +} + +func (e *peopleAPIEngine) GetPersonSummary(_ context.Context, _ int64, request query.ExploreRequest) (*query.PersonSearchResponse, error) { + e.personSummaryRequest = request + return e.personSummaryResult, e.peopleErr +} + +func (e *peopleAPIEngine) SearchDomains(_ context.Context, request query.DomainSearchRequest) (*query.DomainSearchResponse, error) { + e.domainRequest = request + return e.domainResult, e.peopleErr +} + +func (e *peopleAPIEngine) GetDomain(_ context.Context, _ string, _ query.Context) (*query.DomainSummary, error) { + return e.domain, e.peopleErr +} + +func (e *peopleAPIEngine) GetDomainSummary(_ context.Context, _ string, request query.ExploreRequest) (*query.DomainSearchResponse, error) { + e.domainSummaryRequest = request + return e.domainSummaryResult, e.peopleErr +} + +func (e *peopleAPIEngine) Explore(_ context.Context, request query.ExploreRequest) (*query.ExploreResponse, error) { + e.timeline = request + return e.timelineResult, e.peopleErr +} + +func (e *peopleAPIEngine) ExploreCoverage(context.Context, query.ExploreCoverageRequest) (*query.ExploreCoverageResponse, error) { + return &query.ExploreCoverageResponse{}, e.peopleErr +} + +func (e *peopleAPIEngine) ExploreGroups(context.Context, query.ExploreGroupRequest) (*query.ExploreGroupResponse, error) { + return &query.ExploreGroupResponse{}, e.peopleErr +} + +func (e *peopleAPIEngine) ExploreSelectionStats(context.Context, query.ExploreSelectionRequest) (*query.ExploreSelectionStats, error) { + return &query.ExploreSelectionStats{}, e.peopleErr +} + +func (e *peopleAPIEngine) ExploreFiles(context.Context, query.ExploreFilesRequest) (*query.ExploreFilesResponse, error) { + return &query.ExploreFilesResponse{}, e.peopleErr +} + +func (e *peopleAPIEngine) ExploreMatchCounts(context.Context, query.ExploreMatchCountsRequest) (*query.ExploreMatchCountsResponse, error) { + return &query.ExploreMatchCountsResponse{}, e.peopleErr +} + +func newPeopleAPIServer(engine *peopleAPIEngine) *Server { + return newPeopleAPIServerWithStore(engine, &mockStore{}) +} + +func newPeopleAPIServerWithStore(engine *peopleAPIEngine, store MessageStore) *Server { + return NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: store, Engine: engine, Logger: testLogger(), + }) +} + +func TestPeopleSearchResolvesCanonicalFullTextCandidatesAndReturnsAuthority(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := &peopleAPIEngine{MockEngine: &querytest.MockEngine{}, peopleResult: &query.PersonSearchResponse{ + Rows: []query.PersonSummary{{ID: 12, DisplayLabel: "Shared Name"}}, TotalCount: 1, + CacheRevision: "cache-people", SearchProvenance: query.SearchProvenance{LexicalIndexRevision: "resolved"}, + }} + store := &mockStore{messages: []APIMessage{{ID: 42}}, total: 1} + request := httptest.NewRequest(http.MethodPost, "/api/v1/people/search", bytes.NewBufferString(`{ + "predicate":{"query":"needle","search_mode":"full_text"}, + "identity_query":"Shared Name","limit":25 + }`)) + request.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + newPeopleAPIServerWithStore(engine, store).Router().ServeHTTP(response, request) + + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + assertions.Equal(query.SearchFullText, engine.peopleRequest.Explore.Search.Mode) + assertions.Equal([]int64{42}, engine.peopleRequest.Explore.Search.CandidateMessageIDs) + assertions.NotEmpty(engine.peopleRequest.Explore.Search.LexicalIndexRevision) + var body PersonSearchHTTPResponse + requirements.NoError(json.NewDecoder(response.Body).Decode(&body)) + assertions.Equal(query.SearchProvenance{LexicalIndexRevision: "resolved"}, body.SearchProvenance) +} + +func TestPeopleSearchNamesUnavailableSemanticAuthorityWithoutFallback(t *testing.T) { + assertions := assert.New(t) + engine := &peopleAPIEngine{MockEngine: &querytest.MockEngine{}, peopleResult: &query.PersonSearchResponse{ + Rows: []query.PersonSummary{{ID: 11}}, TotalCount: 1, CacheRevision: "cache", + }} + request := httptest.NewRequest(http.MethodPost, "/api/v1/people/search", bytes.NewBufferString(`{ + "predicate":{"query":"needle","search_mode":"semantic"},"limit":25 + }`)) + request.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + newPeopleAPIServer(engine).Router().ServeHTTP(response, request) + + assertions.Equal(http.StatusServiceUnavailable, response.Code, response.Body.String()) + assertions.Contains(response.Body.String(), "vector_not_enabled") + assertions.Empty(engine.peopleRequest.Explore.Search.Mode, "unavailable semantic search must not call DuckDB with a broadened predicate") +} + +func TestContextualSummaryPOSTsCarryCanonicalSearchAndReturnNamed404(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + engine := &peopleAPIEngine{MockEngine: &querytest.MockEngine{}, + personSummaryResult: &query.PersonSearchResponse{Rows: []query.PersonSummary{{ID: 11, DisplayLabel: "Person", ActivityCount: 1}}, CacheRevision: "cache-person", SearchProvenance: query.SearchProvenance{LexicalIndexRevision: "person-rev"}}, + domainSummaryResult: &query.DomainSearchResponse{Rows: []query.DomainSummary{}, CacheRevision: "cache-domain"}, + } + store := &mockStore{messages: []APIMessage{{ID: 42}}, total: 1} + srv := newPeopleAPIServerWithStore(engine, store) + body := `{"query":"needle","search_mode":"full_text","filters":[{"dimension":"source","values":["7"]}]}` + + person := httptest.NewRecorder() + personRequest := httptest.NewRequest(http.MethodPost, "/api/v1/people/11/summary", bytes.NewBufferString(body)) + personRequest.Header.Set("Content-Type", "application/json") + srv.Router().ServeHTTP(person, personRequest) + requirements.Equal(http.StatusOK, person.Code, person.Body.String()) + assertions.Equal([]int64{7}, engine.personSummaryRequest.Context.SourceIDs) + assertions.Equal([]int64{42}, engine.personSummaryRequest.Search.CandidateMessageIDs) + var personBody PersonContextSummaryHTTPResponse + requirements.NoError(json.NewDecoder(person.Body).Decode(&personBody)) + assertions.Equal(int64(1), personBody.Summary.ActivityCount) + assertions.Equal(query.SearchProvenance{LexicalIndexRevision: "person-rev"}, personBody.SearchProvenance) + + domain := httptest.NewRecorder() + domainRequest := httptest.NewRequest(http.MethodPost, "/api/v1/domains/example.com/summary", bytes.NewBufferString(body)) + domainRequest.Header.Set("Content-Type", "application/json") + srv.Router().ServeHTTP(domain, domainRequest) + assertions.Equal(http.StatusNotFound, domain.Code, domain.Body.String()) + assertions.Contains(domain.Body.String(), "domain_not_found") +} + +func TestPeopleSearchForwardsCanonicalContextAndNeverAcceptsNameAsIdentity(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + when := time.Date(2026, 7, 19, 10, 0, 0, 0, time.UTC) + engine := &peopleAPIEngine{MockEngine: &querytest.MockEngine{}, peopleResult: &query.PersonSearchResponse{ + Rows: []query.PersonSummary{ + {ID: 11, DisplayLabel: "Shared Name", ActivityCount: 2, FirstAt: when, LastAt: when}, + {ID: 12, DisplayLabel: "Shared Name", ActivityCount: 1, FirstAt: when, LastAt: when}, + }, TotalCount: 2, CacheRevision: "cache-people", + }} + request := httptest.NewRequest(http.MethodPost, "/api/v1/people/search", bytes.NewBufferString(`{ + "predicate":{"filters":[{"dimension":"source","values":["7"]}]}, + "identity_query":" Shared Name ","sort":{"field":"display_label","direction":"asc"},"limit":25 + }`)) + request.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + newPeopleAPIServer(engine).Router().ServeHTTP(response, request) + + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + var result PersonSearchHTTPResponse + requirements.NoError(json.NewDecoder(response.Body).Decode(&result)) + requirements.Len(result.Rows, 2) + assertions.NotEqual(result.Rows[0].ID, result.Rows[1].ID) + assertions.Equal([]int64{7}, engine.peopleRequest.Explore.Context.SourceIDs) + assertions.Equal("Shared Name", engine.peopleRequest.Query) + assertions.Equal(query.SortSpec{Field: "display_label", Direction: "asc"}, engine.peopleRequest.Sort) +} + +func TestPersonDetailAndTimelineRequireDurablePositiveID(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + when := time.Date(2026, 7, 19, 10, 0, 0, 0, time.UTC) + engine := &peopleAPIEngine{MockEngine: &querytest.MockEngine{}, + person: &query.PersonSummary{ID: 11, DisplayLabel: "Person", FirstAt: when, LastAt: when}, + timelineResult: &query.ExploreResponse{Rows: []query.EntryRow{{Key: "source:1:message:1", OccurredAt: when}}, TotalCount: 1, CacheRevision: "cache-timeline"}, + } + srv := newPeopleAPIServerWithStore(engine, &mockStore{messages: []APIMessage{{ID: 42}}, total: 1}) + + detail := httptest.NewRecorder() + srv.Router().ServeHTTP(detail, httptest.NewRequest(http.MethodGet, "/api/v1/people/11", nil)) + requirements.Equal(http.StatusOK, detail.Code, detail.Body.String()) + + timeline := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/api/v1/people/11/timeline", bytes.NewBufferString(`{"query":"needle","search_mode":"full_text","limit":25}`)) + request.Header.Set("Content-Type", "application/json") + srv.Router().ServeHTTP(timeline, request) + requirements.Equal(http.StatusOK, timeline.Code, timeline.Body.String()) + assertions.Equal([]int64{11}, engine.timeline.Context.ParticipantIDs) + assertions.Equal([]int64{42}, engine.timeline.Search.CandidateMessageIDs) + assertions.NotEmpty(engine.timeline.Search.LexicalIndexRevision) + assertions.Equal(query.PageSpec{Limit: 25}, engine.timeline.Page) + + for _, path := range []string{"/api/v1/people/0", "/api/v1/people/name", "/api/v1/people/-4/timeline"} { + method := http.MethodGet + var body *bytes.Buffer + if path[len(path)-9:] == "/timeline" { + method, body = http.MethodPost, bytes.NewBufferString(`{}`) + } else { + body = bytes.NewBuffer(nil) + } + response := httptest.NewRecorder() + req := httptest.NewRequest(method, path, body) + req.Header.Set("Content-Type", "application/json") + srv.Router().ServeHTTP(response, req) + assertions.Equal(http.StatusBadRequest, response.Code, path) + } +} + +func TestDomainEndpointsNormalizeExactDomainAndRejectAmbiguousOrSQLLikeValues(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + when := time.Date(2026, 7, 19, 10, 0, 0, 0, time.UTC) + engine := &peopleAPIEngine{MockEngine: &querytest.MockEngine{}, + domain: &query.DomainSummary{Domain: "example.com", ActivityCount: 2, FirstAt: when, LastAt: when}, + domainResult: &query.DomainSearchResponse{Rows: []query.DomainSummary{{Domain: "example.com", FirstAt: when, LastAt: when}}, TotalCount: 1, CacheRevision: "cache-domains"}, + timelineResult: &query.ExploreResponse{Rows: []query.EntryRow{}, CacheRevision: "cache-timeline"}, + } + srv := newPeopleAPIServer(engine) + + detail := httptest.NewRecorder() + srv.Router().ServeHTTP(detail, httptest.NewRequest(http.MethodGet, "/api/v1/domains/EXAMPLE.COM", nil)) + requirements.Equal(http.StatusOK, detail.Code, detail.Body.String()) + + timeline := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPost, "/api/v1/domains/EXAMPLE.COM/timeline", bytes.NewBufferString(`{}`)) + request.Header.Set("Content-Type", "application/json") + srv.Router().ServeHTTP(timeline, request) + requirements.Equal(http.StatusOK, timeline.Code, timeline.Body.String()) + assertions.Equal([]string{"example.com"}, engine.timeline.Context.Domains) + + for _, path := range []string{"/api/v1/domains/%20", "/api/v1/domains/example.com%20OR%201=1"} { + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, httptest.NewRequest(http.MethodGet, path, nil)) + assertions.Equal(http.StatusBadRequest, response.Code, path) + } +} + +// TestGetPersonComposesClusterBlockFromStoreForLinkedParticipant covers the +// handler-level composition documented in ClusterLookupStore and +// handleGetPerson: for a linked participant, the handler must resolve +// cluster membership from a real store (not a mock), forward every member +// ID to the query layer so identifiers span the whole cluster, and attach a +// PersonCluster with the canonical ID and every store-owned edge. +func TestGetPersonComposesClusterBlockFromStoreForLinkedParticipant(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + st := testutil.NewTestStore(t) + primary, err := st.EnsureParticipant("primary@example.com", "Primary", "example.com") + requirements.NoError(err) + secondary, err := st.EnsureParticipant("secondary@example.com", "Secondary", "example.com") + requirements.NoError(err) + _, err = st.LinkParticipants(primary, secondary) + requirements.NoError(err) + + engine := &peopleAPIEngine{MockEngine: &querytest.MockEngine{}, person: &query.PersonSummary{ + ID: primary, DisplayLabel: "Primary", + Identifiers: []query.PersonIdentifier{ + {Type: "email", Value: "primary@example.com", IsPrimary: true, Provenance: "participant_identifiers", ParticipantID: primary}, + {Type: "email", Value: "secondary@example.com", IsPrimary: true, Provenance: "participant_identifiers", ParticipantID: secondary}, + }, + }} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: st, Engine: engine, Logger: testLogger(), + }) + + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/people/%d", primary), nil)) + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + assertions.ElementsMatch([]int64{primary, secondary}, engine.personClusterMembers, + "the handler must resolve cluster membership from the store and forward it to the query layer") + + var body query.PersonSummary + requirements.NoError(json.NewDecoder(response.Body).Decode(&body)) + requirements.NotNil(body.Cluster, "a linked participant's detail must carry a cluster block") + lo, hi := min(primary, secondary), max(primary, secondary) + assertions.Equal(lo, body.Cluster.CanonicalID, "canonical ID is the cluster's smallest member") + assertions.ElementsMatch([]int64{primary, secondary}, body.Cluster.MemberIDs) + assertions.Equal([]query.PersonClusterEdge{{ParticipantA: lo, ParticipantB: hi}}, body.Cluster.Edges) + requirements.Len(body.Identifiers, 2, "identifiers must span every cluster member") + byParticipant := map[int64]string{} + for _, identifier := range body.Identifiers { + byParticipant[identifier.ParticipantID] = identifier.Value + } + assertions.Equal("primary@example.com", byParticipant[primary]) + assertions.Equal("secondary@example.com", byParticipant[secondary]) +} + +// TestGetPersonOmitsClusterBlockForUnlinkedParticipant is the negative case: +// an unlinked participant's detail must not carry a Cluster block, and the +// handler must not widen the query-layer call with a member list. +func TestGetPersonOmitsClusterBlockForUnlinkedParticipant(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + st := testutil.NewTestStore(t) + solo, err := st.EnsureParticipant("solo@example.com", "Solo", "example.com") + requirements.NoError(err) + + engine := &peopleAPIEngine{MockEngine: &querytest.MockEngine{}, person: &query.PersonSummary{ID: solo, DisplayLabel: "Solo"}} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: st, Engine: engine, Logger: testLogger(), + }) + + response := httptest.NewRecorder() + srv.Router().ServeHTTP(response, httptest.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/people/%d", solo), nil)) + requirements.Equal(http.StatusOK, response.Code, response.Body.String()) + assertions.Nil(engine.personClusterMembers) + + var body query.PersonSummary + requirements.NoError(json.NewDecoder(response.Body).Decode(&body)) + assertions.Nil(body.Cluster) +} + +func TestPeopleEndpointsNameUnavailableCacheInsteadOfReturningEmpty(t *testing.T) { + assertions := assert.New(t) + engine := &peopleAPIEngine{MockEngine: &querytest.MockEngine{}, peopleErr: &query.CacheUnavailableError{Readiness: query.CacheStaleSchema}} + request := httptest.NewRequest(http.MethodPost, "/api/v1/people/search", bytes.NewBufferString(`{}`)) + request.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + newPeopleAPIServer(engine).Router().ServeHTTP(response, request) + assertions.Equal(http.StatusServiceUnavailable, response.Code) + assertions.Contains(response.Body.String(), "stale_schema") +} diff --git a/internal/api/relationship_timeline_test.go b/internal/api/relationship_timeline_test.go new file mode 100644 index 000000000..9c7341c50 --- /dev/null +++ b/internal/api/relationship_timeline_test.go @@ -0,0 +1,283 @@ +package api + +import ( + "database/sql" + "encoding/json" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + "time" + + _ "github.com/duckdb/duckdb-go/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/query/querytest" +) + +const ( + rtOwnerID = int64(1) + rtPatID = int64(2) + rtPatWorkID = int64(3) + rtBystanderID = int64(4) +) + +const relationshipTimelineMessagesCols = "id, source_id, source_message_id, conversation_id, subject, snippet, " + + "sent_at, size_estimate, has_attachments, attachment_count, deleted_from_source_at, sender_id, message_type, is_from_me, year, month" + +// writeRelationshipTimelineFixture writes (or rewrites in place) the +// Parquet fixture for the relationship timeline HTTP tests: an owner, a +// counterpart (Pat) linked with a chat-only alias (Pat Work) into one +// identity cluster, and an unlinked bystander used to simulate a later +// identity link. Three chat messages share conversation 500 — two at +// 23:00/23:30 UTC on 2026-07-13, one at 00:30 UTC the next day — so +// America/Chicago collapses them into one chat_burst alongside one email +// and one meeting (three rows total), matching the query-layer fixture. +// +// linkBystander controls whether the bystander is folded into Pat's +// cluster, simulating "rebuild the cache with a new link": it changes both +// participant_clusters and IdentityRevision, so CacheRevision changes too +// (see query.CacheSyncState.Revision), letting a test call this twice +// against the same analyticsDir to exercise cursor drift detection without +// tearing down the engine. +func writeRelationshipTimelineFixture(t *testing.T, analyticsDir string, now time.Time, linkBystander bool) { + t.Helper() + db, err := sql.Open("duckdb", "") + require.NoError(t, err) + defer func() { require.NoError(t, db.Close()) }() + + day1Late1 := time.Date(2026, 7, 13, 23, 0, 0, 0, time.UTC) + day1Late2 := time.Date(2026, 7, 13, 23, 30, 0, 0, time.UTC) + day2Early := time.Date(2026, 7, 14, 0, 30, 0, 0, time.UTC) + + type msgRow struct { + id, convID, fromID, toID int64 + isFromMe bool + messageType string + sentAt time.Time + } + rows := []msgRow{ + {1, 500, rtPatID, rtOwnerID, false, "imessage", day1Late1}, + {2, 500, rtPatID, rtOwnerID, false, "imessage", day1Late2}, + {3, 500, rtPatID, rtOwnerID, false, "imessage", day2Early}, + {4, 601, rtPatID, rtOwnerID, false, "email", time.Date(2026, 7, 12, 12, 0, 0, 0, time.UTC)}, + {5, 602, rtOwnerID, rtPatID, true, "calendar_event", time.Date(2026, 7, 11, 9, 0, 0, 0, time.UTC)}, + } + var messageRows, recipientRows []string + for _, m := range rows { + messageRows = append(messageRows, fmt.Sprintf( + "(%d::BIGINT, 1::BIGINT, 'm%d', %d::BIGINT, '', 'Preview %d', TIMESTAMP '%s', 10::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, %s, %v, %d, %d)", + m.id, m.id, m.convID, m.id, m.sentAt.Format("2006-01-02 15:04:05"), sqlQuote(m.messageType), m.isFromMe, m.sentAt.Year(), int(m.sentAt.Month()))) + recipientRows = append(recipientRows, + fmt.Sprintf("(%d::BIGINT, %d::BIGINT, 'from', '')", m.id, m.fromID), + fmt.Sprintf("(%d::BIGINT, %d::BIGINT, 'to', '')", m.id, m.toID)) + } + + clusterRows := "(2::BIGINT, 2::BIGINT), (3::BIGINT, 2::BIGINT), (4::BIGINT, 4::BIGINT)" + identityRevision := int64(1) + if linkBystander { + clusterRows = "(2::BIGINT, 2::BIGINT), (3::BIGINT, 2::BIGINT), (4::BIGINT, 2::BIGINT)" + identityRevision = 2 + } + + tables := []struct { + dir, file, columns, values string + empty bool + }{ + {dir: "messages/year=2026", file: "messages.parquet", columns: relationshipTimelineMessagesCols, values: strings.Join(messageRows, ",\n")}, + {dir: "sources", file: "sources.parquet", columns: "id, account_email, source_type", values: `(1::BIGINT, 'owner@example.com', 'gmail')`}, + { + dir: "participants", file: "participants.parquet", columns: "id, email_address, domain, display_name, phone_number", + values: `(1::BIGINT, 'owner@example.com', 'example.com', 'Owner', ''), + (2::BIGINT, 'pat@chat.example', 'chat.example', 'Pat', ''), + (3::BIGINT, 'pat@work.example', 'work.example', 'Pat Work', ''), + (4::BIGINT, 'pat@personal.example', 'personal.example', 'Pat Personal', '')`, + }, + {dir: "participant_identifiers", file: "participant_identifiers.parquet", columns: "participant_id, identifier_type, identifier_value, display_value, is_primary", values: `(0::BIGINT, '', '', '', false)`, empty: true}, + {dir: "message_recipients", file: "message_recipients.parquet", columns: "message_id, participant_id, recipient_type, display_name", values: strings.Join(recipientRows, ",\n")}, + {dir: "labels", file: "labels.parquet", columns: "id, name", values: `(0::BIGINT, '')`, empty: true}, + {dir: "message_labels", file: "message_labels.parquet", columns: "message_id, label_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "attachments", file: "attachments.parquet", columns: "attachment_id, message_id, size, filename", values: `(0::BIGINT, 0::BIGINT, 0::BIGINT, '')`, empty: true}, + { + dir: "conversations", file: "conversations.parquet", columns: "id, source_conversation_id, title, conversation_type", + values: `(500::BIGINT, 'c500', '', 'direct_chat'), + (601::BIGINT, 'c601', '', 'email'), + (602::BIGINT, 'c602', '', 'calendar')`, + }, + { + dir: "conversation_participants", file: "conversation_participants.parquet", columns: "conversation_id, participant_id", + values: `(500::BIGINT, 1::BIGINT), (500::BIGINT, 2::BIGINT)`, + }, + {dir: "owner_participants", file: "owner_participants.parquet", columns: "source_id, participant_id", values: `(1::BIGINT, 1::BIGINT)`}, + {dir: "participant_clusters", file: "participant_clusters.parquet", columns: "participant_id, canonical_id", values: clusterRows}, + } + for _, table := range tables { + dir := filepath.Join(analyticsDir, table.dir) + require.NoError(t, os.MkdirAll(dir, 0o755)) + where := "" + if table.empty { + where = " WHERE false" + } + path := filepath.ToSlash(filepath.Join(dir, table.file)) + _, err := db.Exec(fmt.Sprintf("COPY (SELECT * FROM (VALUES %s) AS t(%s)%s) TO '%s' (FORMAT PARQUET)", table.values, table.columns, where, path)) + require.NoError(t, err, "write %s", table.dir) + } + + fingerprint, err := query.CacheDatasetFingerprint(analyticsDir) + require.NoError(t, err) + state, err := json.Marshal(query.CacheSyncState{ + LastMessageID: 5, LastSyncAt: now, SchemaVersion: query.CacheSchemaVersion, + PublishedAt: now, DatasetFingerprint: fingerprint, IdentityRevision: identityRevision, + }) + require.NoError(t, err) + require.NoError(t, os.WriteFile(query.CacheStatePath(analyticsDir), state, 0o600)) +} + +// newRelationshipTimelineDuckDBFixture builds a fresh engine over a freshly +// written fixture and returns both, so a test can later call +// writeRelationshipTimelineFixture again against the same directory to +// simulate a cache rebuild without recreating the engine. +func newRelationshipTimelineDuckDBFixture(t *testing.T, now time.Time) (*query.DuckDBEngine, string) { + t.Helper() + analyticsDir := t.TempDir() + writeRelationshipTimelineFixture(t, analyticsDir, now, false) + engine, err := query.NewDuckDBEngine(analyticsDir, "", nil) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, engine.Close()) }) + return engine, analyticsDir +} + +func TestRelationshipTimelineOverHTTP(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + now := time.Date(2026, 7, 20, 0, 0, 0, 0, time.UTC) + engine, _ := newRelationshipTimelineDuckDBFixture(t, now) + srv := newTestServerWithEngine(t, engine) + + response := postExploreJSON(t, srv, fmt.Sprintf("/api/v1/relationships/%d/timeline", rtPatID), `{"timezone":"America/Chicago"}`) + require.Equal(http.StatusOK, response.Code, response.Body.String()) + var page RelationshipTimelineHTTPResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &page)) + + assert.Equal(rtPatID, page.CanonicalID) + require.Len(page.Rows, 3, "one chat burst + one email + one meeting") + assert.Equal(int64(3), page.TotalCount) + + burst := page.Rows[0] + assert.Equal("chat_burst", burst.Kind) + assert.Equal(int64(3), burst.MessageCount) + assert.Equal("email", page.Rows[1].Kind) + assert.Equal("event", page.Rows[2].Kind) +} + +func TestRelationshipTimelineResolvesAnyMemberIDOverHTTP(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + now := time.Date(2026, 7, 20, 0, 0, 0, 0, time.UTC) + engine, _ := newRelationshipTimelineDuckDBFixture(t, now) + srv := newTestServerWithEngine(t, engine) + + // rtPatWorkID (3) is a member alias, not the canonical (minimum) ID. + response := postExploreJSON(t, srv, fmt.Sprintf("/api/v1/relationships/%d/timeline", rtPatWorkID), `{"timezone":"America/Chicago"}`) + require.Equal(http.StatusOK, response.Code, response.Body.String()) + var page RelationshipTimelineHTTPResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &page)) + assert.Equal(rtPatID, page.CanonicalID, "an alias ID must resolve to the cluster's canonical ID") + assert.Equal(int64(3), page.TotalCount) +} + +func TestRelationshipTimelinePaginatesWithStableCursor(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + now := time.Date(2026, 7, 20, 0, 0, 0, 0, time.UTC) + engine, _ := newRelationshipTimelineDuckDBFixture(t, now) + srv := newTestServerWithEngine(t, engine) + + seenKeys := make(map[string]bool) + cursor := "" + for range 5 { + body := `{"timezone":"America/Chicago","limit":1}` + if cursor != "" { + body = fmt.Sprintf(`{"timezone":"America/Chicago","limit":1,"cursor":%q}`, cursor) + } + response := postExploreJSON(t, srv, fmt.Sprintf("/api/v1/relationships/%d/timeline", rtPatID), body) + require.Equal(http.StatusOK, response.Code, response.Body.String()) + var result RelationshipTimelineHTTPResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &result)) + require.Len(result.Rows, 1) + assert.Equal(int64(3), result.TotalCount) + seenKeys[result.Rows[0].Key] = true + cursor = result.NextCursor + if cursor == "" { + break + } + } + assert.Len(seenKeys, 3, "three single-row pages must cover all three distinct rows with no repeats") +} + +func TestRelationshipTimelineCursorInvalidatedOnIdentityRevisionChange(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + now := time.Date(2026, 7, 20, 0, 0, 0, 0, time.UTC) + engine, analyticsDir := newRelationshipTimelineDuckDBFixture(t, now) + srv := newTestServerWithEngine(t, engine) + + first := postExploreJSON(t, srv, fmt.Sprintf("/api/v1/relationships/%d/timeline", rtPatID), `{"timezone":"America/Chicago","limit":1}`) + require.Equal(http.StatusOK, first.Code, first.Body.String()) + var page RelationshipTimelineHTTPResponse + require.NoError(json.Unmarshal(first.Body.Bytes(), &page)) + require.NotEmpty(page.NextCursor) + + // Simulate a cache rebuild that links the bystander into Pat's cluster, + // bumping IdentityRevision (and, transitively, CacheRevision). + writeRelationshipTimelineFixture(t, analyticsDir, now, true) + + replay := postExploreJSON(t, srv, fmt.Sprintf("/api/v1/relationships/%d/timeline", rtPatID), + fmt.Sprintf(`{"timezone":"America/Chicago","limit":1,"cursor":%q}`, page.NextCursor)) + assert.Equal(http.StatusConflict, replay.Code, replay.Body.String()) + assert.Contains(replay.Body.String(), "cursor_invalidated") + assert.Contains(replay.Body.String(), "The timeline context changed; restart pagination") +} + +func TestRelationshipTimelineRejectsBadTimezone(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + now := time.Date(2026, 7, 20, 0, 0, 0, 0, time.UTC) + engine, _ := newRelationshipTimelineDuckDBFixture(t, now) + srv := newTestServerWithEngine(t, engine) + + response := postExploreJSON(t, srv, fmt.Sprintf("/api/v1/relationships/%d/timeline", rtPatID), `{"timezone":"Not/AZone"}`) + require.Equal(http.StatusBadRequest, response.Code, response.Body.String()) + assert.Contains(response.Body.String(), "invalid_explore_request") +} + +func TestRelationshipTimelineRejectsBadParticipantID(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + now := time.Date(2026, 7, 20, 0, 0, 0, 0, time.UTC) + engine, _ := newRelationshipTimelineDuckDBFixture(t, now) + srv := newTestServerWithEngine(t, engine) + + response := postExploreJSON(t, srv, "/api/v1/relationships/0/timeline", `{}`) + require.Equal(http.StatusBadRequest, response.Code, response.Body.String()) + assert.Contains(response.Body.String(), "invalid_participant_id") +} + +func TestRelationshipTimelineUnavailableUnderNonAnalyzerEngine(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + srv := newTestServerWithEngine(t, &querytest.MockEngine{}) + response := postExploreJSON(t, srv, fmt.Sprintf("/api/v1/relationships/%d/timeline", rtPatID), `{}`) + require.Equal(http.StatusServiceUnavailable, response.Code, response.Body.String()) + assert.Contains(response.Body.String(), "analytical_cache_unavailable") +} diff --git a/internal/api/relationships.go b/internal/api/relationships.go new file mode 100644 index 000000000..922b0820e --- /dev/null +++ b/internal/api/relationships.go @@ -0,0 +1,230 @@ +package api + +import ( + "fmt" + "net/http" + "slices" + "strconv" + "strings" + "time" + + "github.com/danielgtaylor/huma/v2" + "go.kenn.io/msgvault/internal/query" +) + +// RelationshipsHTTPRequest scopes and pages a relationship ranking request. +// Filters follow the same predicate shape as /explore; there is no text +// query because ranking is over reciprocity signals, not lexical/semantic +// search candidates. +type RelationshipsHTTPRequest struct { + Filters []ExploreFilter `json:"filters,omitempty"` + ShowAll bool `json:"show_all,omitempty"` + Cursor string `json:"cursor,omitempty"` + Limit int `json:"limit,omitempty" minimum:"0" maximum:"500"` +} + +// RelationshipsHTTPResponse echoes both revisions a page was computed +// against so clients can detect archive or identity drift across pages. +type RelationshipsHTTPResponse struct { + Rows []query.RelationshipRow `json:"rows"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + IdentityRevision int64 `json:"identity_revision"` + NextCursor string `json:"next_cursor,omitempty"` +} + +func (s *Server) registerRelationshipRoutes(api huma.API) { + registerExploreRoute[RelationshipsHTTPRequest, RelationshipsHTTPResponse]( + api, "listRelationships", "/relationships", "Rank counterparts by reciprocity-weighted interaction", s.handleRelationships, + ) + registerExploreRoute[RelationshipTimelineHTTPRequest, RelationshipTimelineHTTPResponse]( + api, "getRelationshipTimeline", "/relationships/{id}/timeline", + "Get one counterpart's interaction timeline, with chat grouped into local-day bursts", s.handleRelationshipTimeline, + ) +} + +func (s *Server) handleRelationships(w http.ResponseWriter, r *http.Request) { + var request RelationshipsHTTPRequest + if !decodeExploreJSON(w, r, &request) { + return + } + canonicalizeRelationshipFilters(request.Filters) + analyticalContext, err := exploreContext(request.Filters) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_filter", err.Error()) + return + } + if request.Limit == 0 { + request.Limit = exploreDefaultLimit + } + if request.Limit < 1 || request.Limit > exploreMaxLimit { + writeError(w, http.StatusBadRequest, "invalid_limit", fmt.Sprintf("limit must be between 1 and %d", exploreMaxLimit)) + return + } + canonical := request + canonical.Cursor = "" + requestHash := hashCanonicalValue(canonical, false) + offset, ok := s.parseExploreCursor(w, request.Cursor, requestHash) + if !ok { + return + } + var cursor exploreCursor + if request.Cursor != "" { + cursor, _ = s.decodeExploreCursor(request.Cursor) + } + + analyzer, ok := s.engine.(query.RelationshipAnalyzer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + result, err := analyzer.Relationships(r.Context(), query.RelationshipsRequest{ + Context: analyticalContext, ShowAll: request.ShowAll, Limit: request.Limit, Offset: offset, Now: time.Now().UTC(), + }) + if err != nil { + s.writeExploreError(w, err) + return + } + if request.Cursor != "" { + // Check identity drift before the archive-revision comparison: + // CacheSyncState.Revision() folds IdentityRevision into its hash, so + // any identity-only change (a link/unlink/merge) also changes + // CacheRevision. Checking archive revision first would make + // identity_revision_changed unreachable — every identity drift + // would surface as archive_revision_changed instead. + if cursor.IdentityRevision != result.IdentityRevision { + writeError(w, http.StatusConflict, "identity_revision_changed", "Identity clusters changed; restart pagination") + return + } + if cursor.Revision != result.CacheRevision { + writeError(w, http.StatusConflict, "archive_revision_changed", "The committed analytical cache changed; restart pagination") + return + } + } + response := RelationshipsHTTPResponse{ + Rows: result.Rows, TotalCount: result.TotalCount, + CacheRevision: result.CacheRevision, IdentityRevision: result.IdentityRevision, + } + if next := offset + len(result.Rows); next < int(result.TotalCount) { + response.NextCursor = s.encodeExploreCursor(exploreCursor{ + Offset: next, Request: requestHash, Revision: result.CacheRevision, IdentityRevision: result.IdentityRevision, + }) + } + writeJSON(w, http.StatusOK, response) +} + +// RelationshipTimelineHTTPRequest scopes and pages one counterpart's +// interaction timeline. Timezone is an IANA name used to bucket chat +// messages into local-day bursts; "" means UTC. The counterpart is fixed by +// the {id} path segment, which accepts any member of that identity's +// cluster; a "participant" filter dimension in Filters further restricts +// entries within that cluster rather than replacing the cluster scope (see +// query.RelationshipTimeline). +type RelationshipTimelineHTTPRequest struct { + Timezone string `json:"timezone,omitempty"` + Filters []ExploreFilter `json:"filters,omitempty"` + Cursor string `json:"cursor,omitempty"` + Limit int `json:"limit,omitempty" minimum:"0" maximum:"500"` +} + +// RelationshipTimelineHTTPResponse echoes the canonical cluster ID the +// {id} path segment resolved to, plus both revisions the page was computed +// against so clients can detect archive or identity drift across pages. +type RelationshipTimelineHTTPResponse struct { + CanonicalID int64 `json:"canonical_id"` + Rows []query.TimelineRow `json:"rows"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + IdentityRevision int64 `json:"identity_revision"` + NextCursor string `json:"next_cursor,omitempty"` +} + +func (s *Server) handleRelationshipTimeline(w http.ResponseWriter, r *http.Request) { + participantID, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil || participantID < 1 { + writeError(w, http.StatusBadRequest, "invalid_participant_id", "participant ID must be a positive integer") + return + } + var request RelationshipTimelineHTTPRequest + if !decodeExploreJSON(w, r, &request) { + return + } + canonicalizeRelationshipFilters(request.Filters) + analyticalContext, err := exploreContext(request.Filters) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_filter", err.Error()) + return + } + if request.Limit == 0 { + request.Limit = exploreDefaultLimit + } + if request.Limit < 1 || request.Limit > exploreMaxLimit { + writeError(w, http.StatusBadRequest, "invalid_limit", fmt.Sprintf("limit must be between 1 and %d", exploreMaxLimit)) + return + } + + analyzer, ok := s.engine.(query.RelationshipAnalyzer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + canonicalID, err := analyzer.ResolveCanonicalParticipant(r.Context(), participantID) + if err != nil { + s.writeExploreError(w, err) + return + } + + canonical := request + canonical.Cursor = "" + requestHash := hashCanonicalValue(canonical, false) + + offset := 0 + if request.Cursor != "" { + cursor, err := s.decodeExploreCursor(request.Cursor) + if err != nil || cursor.Offset < 0 || cursor.Request != requestHash || + cursor.Timezone != request.Timezone || cursor.CanonicalID != canonicalID { + writeError(w, http.StatusConflict, "cursor_invalidated", "The timeline context changed; restart pagination") + return + } + offset = cursor.Offset + } + + result, err := analyzer.RelationshipTimeline(r.Context(), query.RelationshipTimelineRequest{ + CanonicalID: canonicalID, Timezone: request.Timezone, Context: analyticalContext, + Limit: request.Limit, Offset: offset, + }) + if err != nil { + s.writeExploreError(w, err) + return + } + if request.Cursor != "" { + cursor, _ := s.decodeExploreCursor(request.Cursor) + if cursor.Revision != result.CacheRevision || cursor.IdentityRevision != result.IdentityRevision { + writeError(w, http.StatusConflict, "cursor_invalidated", "The timeline context changed; restart pagination") + return + } + } + response := RelationshipTimelineHTTPResponse{ + CanonicalID: canonicalID, Rows: result.Rows, TotalCount: result.TotalCount, + CacheRevision: result.CacheRevision, IdentityRevision: result.IdentityRevision, + } + if next := offset + len(result.Rows); next < int(result.TotalCount) { + response.NextCursor = s.encodeExploreCursor(exploreCursor{ + Offset: next, Request: requestHash, Revision: result.CacheRevision, IdentityRevision: result.IdentityRevision, + Timezone: request.Timezone, CanonicalID: canonicalID, + }) + } + writeJSON(w, http.StatusOK, response) +} + +func canonicalizeRelationshipFilters(filters []ExploreFilter) { + for i := range filters { + filters[i].Dimension = strings.ToLower(strings.TrimSpace(filters[i].Dimension)) + for j := range filters[i].Values { + filters[i].Values[j] = strings.TrimSpace(filters[i].Values[j]) + } + slices.Sort(filters[i].Values) + filters[i].Values = slices.Compact(filters[i].Values) + } + slices.SortFunc(filters, func(a, b ExploreFilter) int { return strings.Compare(a.Dimension, b.Dimension) }) +} diff --git a/internal/api/relationships_test.go b/internal/api/relationships_test.go new file mode 100644 index 000000000..0c28bea74 --- /dev/null +++ b/internal/api/relationships_test.go @@ -0,0 +1,254 @@ +package api + +import ( + "database/sql" + "encoding/json" + "fmt" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + "time" + + _ "github.com/duckdb/duckdb-go/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/query/querytest" +) + +const ( + relOwnerID = int64(1) + relAliceID = int64(2) + relAlice2ID = int64(3) + relNewsletterID = int64(4) +) + +// newRelationshipsDuckDBFixture builds a real DuckDB/Parquet engine seeded +// with: an owner (relOwnerID), a reciprocal counterpart (relAliceID) linked +// into a cluster with a chat-only alias (relAlice2ID), and an inbound-only +// newsletter sender (relNewsletterID). Mirrors the scenario used by the +// query-package fixture test, built directly as Parquet since the typed +// TestDataBuilder lives in an unexported _test.go file in another package. +func newRelationshipsDuckDBFixture(t *testing.T, now time.Time) *query.DuckDBEngine { + t.Helper() + engine, _ := newRelationshipsDuckDBFixtureWithDir(t, now) + return engine +} + +// newRelationshipsDuckDBFixtureWithDir is newRelationshipsDuckDBFixture plus +// the analytics directory, for tests that need to mutate the committed cache +// state file directly (e.g. simulating a real identity-revision bump). +func newRelationshipsDuckDBFixtureWithDir(t *testing.T, now time.Time) (*query.DuckDBEngine, string) { + t.Helper() + analyticsDir := t.TempDir() + db, err := sql.Open("duckdb", "") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, db.Close()) }) + + var messageRows, recipientRows []string + nextID := int64(1) + addMessage := func(fromID, toID int64, isFromMe bool, messageType string, sentAt time.Time) { + id := nextID + nextID++ + messageRows = append(messageRows, fmt.Sprintf( + "(%d::BIGINT, 1::BIGINT, 'm%d', %d::BIGINT, '', 'Preview %d', TIMESTAMP '%s', 10::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, %s, %v, %d, %d)", + id, id, id, id, sentAt.Format("2006-01-02 15:04:05"), sqlQuote(messageType), isFromMe, sentAt.Year(), int(sentAt.Month()))) + recipientRows = append(recipientRows, + fmt.Sprintf("(%d::BIGINT, %d::BIGINT, 'from', '')", id, fromID), + fmt.Sprintf("(%d::BIGINT, %d::BIGINT, 'to', '')", id, toID)) + } + for i := range 3 { + addMessage(relOwnerID, relAliceID, true, "email", now.AddDate(0, 0, -(3-i))) + } + addMessage(relOwnerID, relAliceID, false, "calendar_event", now.AddDate(0, 0, -2)) + addMessage(relAlice2ID, relOwnerID, false, "imessage", now.AddDate(0, 0, -1)) + for i := range 50 { + addMessage(relNewsletterID, relOwnerID, false, "email", now.AddDate(0, 0, -(10+i))) + } + + tables := []struct { + dir, file, columns, values string + empty bool + }{ + { + dir: "messages/year=2026", file: "messages.parquet", + columns: "id, source_id, source_message_id, conversation_id, subject, snippet, sent_at, size_estimate, has_attachments, attachment_count, deleted_from_source_at, sender_id, message_type, is_from_me, year, month", + values: strings.Join(messageRows, ",\n"), + }, + {dir: "sources", file: "sources.parquet", columns: "id, account_email, source_type", values: `(1::BIGINT, 'owner@example.com', 'gmail')`}, + { + dir: "participants", file: "participants.parquet", columns: "id, email_address, domain, display_name, phone_number", + values: `(1::BIGINT, 'owner@example.com', 'example.com', 'Owner', ''), + (2::BIGINT, 'alice@example.com', 'example.com', 'Alice', ''), + (3::BIGINT, 'alice@chat.example', 'chat.example', 'Alice Chat', ''), + (4::BIGINT, 'newsletter@example.com', 'example.com', 'Newsletter', '')`, + }, + {dir: "participant_identifiers", file: "participant_identifiers.parquet", columns: "participant_id, identifier_type, identifier_value, display_value, is_primary", values: `(0::BIGINT, '', '', '', false)`, empty: true}, + {dir: "message_recipients", file: "message_recipients.parquet", columns: "message_id, participant_id, recipient_type, display_name", values: strings.Join(recipientRows, ",\n")}, + {dir: "labels", file: "labels.parquet", columns: "id, name", values: `(0::BIGINT, '')`, empty: true}, + {dir: "message_labels", file: "message_labels.parquet", columns: "message_id, label_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "attachments", file: "attachments.parquet", columns: "attachment_id, message_id, size, filename", values: `(0::BIGINT, 0::BIGINT, 0::BIGINT, '')`, empty: true}, + {dir: "conversations", file: "conversations.parquet", columns: "id, source_conversation_id, title, conversation_type", values: `(0::BIGINT, '', '', 'email')`, empty: true}, + {dir: "conversation_participants", file: "conversation_participants.parquet", columns: "conversation_id, participant_id", values: `(0::BIGINT, 0::BIGINT)`, empty: true}, + {dir: "owner_participants", file: "owner_participants.parquet", columns: "source_id, participant_id", values: `(1::BIGINT, 1::BIGINT)`}, + { + dir: "participant_clusters", file: "participant_clusters.parquet", columns: "participant_id, canonical_id", + values: `(2::BIGINT, 2::BIGINT), (3::BIGINT, 2::BIGINT)`, + }, + } + for _, table := range tables { + dir := filepath.Join(analyticsDir, table.dir) + require.NoError(t, os.MkdirAll(dir, 0o755)) + where := "" + if table.empty { + where = " WHERE false" + } + path := filepath.ToSlash(filepath.Join(dir, table.file)) + _, err := db.Exec(fmt.Sprintf("COPY (SELECT * FROM (VALUES %s) AS t(%s)%s) TO '%s' (FORMAT PARQUET)", table.values, table.columns, where, path)) + require.NoError(t, err, "write %s", table.dir) + } + + fingerprint, err := query.CacheDatasetFingerprint(analyticsDir) + require.NoError(t, err) + state, err := json.Marshal(query.CacheSyncState{ + LastMessageID: nextID - 1, LastSyncAt: now, SchemaVersion: query.CacheSchemaVersion, + PublishedAt: now, DatasetFingerprint: fingerprint, + }) + require.NoError(t, err) + require.NoError(t, os.WriteFile(query.CacheStatePath(analyticsDir), state, 0o600)) + + engine, err := query.NewDuckDBEngine(analyticsDir, "", nil) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, engine.Close()) }) + return engine, analyticsDir +} + +func sqlQuote(s string) string { + return "'" + strings.ReplaceAll(s, "'", "''") + "'" +} + +func TestRelationshipsRanksAndGatesOverHTTP(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + srv := newTestServerWithEngine(t, newRelationshipsDuckDBFixture(t, now)) + + response := postExploreJSON(t, srv, "/api/v1/relationships", `{}`) + require.Equal(http.StatusOK, response.Code, response.Body.String()) + var page RelationshipsHTTPResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &page)) + + require.Len(page.Rows, 1, "the newsletter must be gated out by default") + assert.Equal(relAliceID, page.Rows[0].CanonicalID) + assert.Equal([]int64{relAliceID, relAlice2ID}, page.Rows[0].MemberIDs) + assert.Equal(int64(3), page.Rows[0].Signals.SentCount) + assert.Equal(int64(1), page.Rows[0].Signals.MeetingCount) + assert.NotEmpty(page.CacheRevision) + + showAll := postExploreJSON(t, srv, "/api/v1/relationships", `{"show_all":true}`) + require.Equal(http.StatusOK, showAll.Code, showAll.Body.String()) + var allPage RelationshipsHTTPResponse + require.NoError(json.Unmarshal(showAll.Body.Bytes(), &allPage)) + assert.Len(allPage.Rows, 2, "show_all must include the gated newsletter") + ids := []int64{allPage.Rows[0].CanonicalID, allPage.Rows[1].CanonicalID} + assert.ElementsMatch([]int64{relAliceID, relNewsletterID}, ids) +} + +func TestRelationshipsCursorConflictsOnRevisionDrift(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + srv := newTestServerWithEngine(t, newRelationshipsDuckDBFixture(t, now)) + + first := postExploreJSON(t, srv, "/api/v1/relationships", `{"show_all":true,"limit":1}`) + require.Equal(http.StatusOK, first.Code, first.Body.String()) + var page RelationshipsHTTPResponse + require.NoError(json.Unmarshal(first.Body.Bytes(), &page)) + require.NotEmpty(page.NextCursor, "the first page must offer a cursor into the second row") + + // Legitimate cursor still works for pagination. + second := postExploreJSON(t, srv, "/api/v1/relationships", `{"show_all":true,"limit":1,"cursor":"`+page.NextCursor+`"}`) + assert.Equal(http.StatusOK, second.Code, second.Body.String()) + + // Doctor a decoded copy of the cursor with a stale revision, re-signed by + // the real server key, and confirm it is rejected as drifted rather than + // silently accepted. + decoded, err := srv.decodeExploreCursor(page.NextCursor) + require.NoError(err) + decoded.Revision = "cache-doctored" + doctored := srv.encodeExploreCursor(decoded) + conflict := postExploreJSON(t, srv, "/api/v1/relationships", `{"show_all":true,"limit":1,"cursor":"`+doctored+`"}`) + assert.Equal(http.StatusConflict, conflict.Code, conflict.Body.String()) + assert.Contains(conflict.Body.String(), "archive_revision_changed") + + decoded.Revision = page.CacheRevision + decoded.IdentityRevision = page.IdentityRevision + 1 + doctoredIdentity := srv.encodeExploreCursor(decoded) + identityConflict := postExploreJSON(t, srv, "/api/v1/relationships", `{"show_all":true,"limit":1,"cursor":"`+doctoredIdentity+`"}`) + assert.Equal(http.StatusConflict, identityConflict.Code, identityConflict.Body.String()) + assert.Contains(identityConflict.Body.String(), "identity_revision_changed") +} + +// TestRelationshipsCursorReportsIdentityDriftDistinctlyFromArchiveDrift +// covers the 409 code precedence: CacheSyncState.Revision() folds +// IdentityRevision into its hash, so a real identity-only refresh (a +// link/unlink/merge, never touching PublishedAt or the message shards) +// changes CacheRevision too, alongside IdentityRevision. The two prior +// doctored-cursor cases above only vary Revision or only vary +// IdentityRevision in isolation, so neither exercises this: they never +// prove which code wins when both actually drift together, as they do on a +// real refresh. Simulating that refresh by bumping IdentityRevision alone in +// the committed cache state must still report identity_revision_changed, +// not archive_revision_changed. +func TestRelationshipsCursorReportsIdentityDriftDistinctlyFromArchiveDrift(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + engine, analyticsDir := newRelationshipsDuckDBFixtureWithDir(t, now) + srv := newTestServerWithEngine(t, engine) + + first := postExploreJSON(t, srv, "/api/v1/relationships", `{"show_all":true,"limit":1}`) + require.Equal(http.StatusOK, first.Code, first.Body.String()) + var page RelationshipsHTTPResponse + require.NoError(json.Unmarshal(first.Body.Bytes(), &page)) + require.NotEmpty(page.NextCursor, "the first page must offer a cursor into the second row") + + state, err := query.ReadCacheSyncState(analyticsDir) + require.NoError(err, "ReadCacheSyncState") + state.IdentityRevision++ + data, err := json.Marshal(state) + require.NoError(err) + require.NoError(os.WriteFile(query.CacheStatePath(analyticsDir), data, 0o600)) + + resp := postExploreJSON(t, srv, "/api/v1/relationships", `{"show_all":true,"limit":1,"cursor":"`+page.NextCursor+`"}`) + assert.Equal(http.StatusConflict, resp.Code, resp.Body.String()) + assert.Contains(resp.Body.String(), "identity_revision_changed") + assert.NotContains(resp.Body.String(), "archive_revision_changed") +} + +func TestRelationshipsRejectsOutOfRangeLimit(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + srv := newTestServerWithEngine(t, newRelationshipsDuckDBFixture(t, now)) + + response := postExploreJSON(t, srv, "/api/v1/relationships", fmt.Sprintf(`{"limit":%d}`, exploreMaxLimit+1)) + require.Equal(http.StatusBadRequest, response.Code, response.Body.String()) + assert.Contains(response.Body.String(), "invalid_limit") +} + +func TestRelationshipsUnavailableUnderNonAnalyzerEngine(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + srv := newTestServerWithEngine(t, &querytest.MockEngine{}) + response := postExploreJSON(t, srv, "/api/v1/relationships", `{}`) + require.Equal(http.StatusServiceUnavailable, response.Code, response.Body.String()) + assert.Contains(response.Body.String(), "analytical_cache_unavailable") +} diff --git a/internal/api/routes.go b/internal/api/routes.go index b8ec2eb39..40277b9ff 100644 --- a/internal/api/routes.go +++ b/internal/api/routes.go @@ -84,6 +84,10 @@ func errorCodeForStatus(status int) string { return "unsupported_media_type" case http.StatusUnprocessableEntity: return "validation_failed" + case http.StatusPreconditionFailed: + return "precondition_failed" + case http.StatusPreconditionRequired: + return "precondition_required" case http.StatusTooManyRequests: return "rate_limited" case http.StatusServiceUnavailable: @@ -142,7 +146,7 @@ func withAPIKeySecurity(op huma.Operation) huma.Operation { func (s *Server) humaAuthMiddleware(ctx huma.Context, next func(huma.Context)) { req, _ := humago.Unwrap(ctx) - if s.apiRequestAuthorized(req) { + if s.requestAuthentication(req).Mode != AuthModeRequired { next(ctx) return } @@ -161,6 +165,7 @@ func writeHumaError(ctx huma.Context, status int, code string, message string) { } func (s *Server) registerHumaRoutes(api huma.API, apiV1 huma.API) { + s.registerSessionRoutes(api) registerRawHumaJSONRoute[HealthResponse](api, huma.Operation{ OperationID: "health", Method: http.MethodGet, @@ -197,6 +202,16 @@ func (s *Server) registerHumaRoutes(api huma.API, apiV1 huma.API) { }, s.handleDaemonShutdown) registerAPIV1RawHumaJSONRoute[StatsResponse](apiV1, "getStats", http.MethodGet, "/stats", "Get archive statistics", s.handleStats) + s.registerSettingsRoutes(apiV1) + s.registerSavedViewRoutes(apiV1) + s.registerExploreRoutes(apiV1) + s.registerFilesRoutes(apiV1) + s.registerPeopleRoutes(apiV1) + s.registerRelationshipRoutes(apiV1) + s.registerIdentityLinkRoutes(apiV1) + s.registerTaskIntegrationRoutes(apiV1) + s.registerTaskLinkRoutes(apiV1) + s.registerSearchCoverageRoute(apiV1) registerAPIV1RawHumaJSONRoute[cliInitDBResponse](apiV1, "initCLIArchive", http.MethodPost, "/cli/init-db", "Initialize the archive for CLI use", s.handleCLIInitDB) registerAPIV1RawHumaJSONRoute[cliStatsResponse](apiV1, "getCLIStats", http.MethodGet, "/cli/stats", "Get CLI-compatible archive statistics", s.handleCLIStats) registerAPIV1RawHumaJSONRoute[cliSearchResponse](apiV1, "searchCLI", http.MethodGet, "/cli/search", "Search messages for CLI output", s.handleCLISearch) @@ -251,7 +266,22 @@ func (s *Server) registerHumaRoutes(api huma.API, apiV1 huma.API) { registerAPIV1RawHumaJSONRoute[MessageListResponse](apiV1, "listMessages", http.MethodGet, "/messages", "List messages", s.handleListMessages) registerAPIV1RawHumaJSONRoute[MessageDetail](apiV1, "getMessage", http.MethodGet, "/messages/{id}", "Get one message", s.handleGetMessage) + registerAPIV1RawHumaJSONRoute[ConversationResponse](apiV1, "getConversation", http.MethodGet, "/conversations/{id}", "Get a bounded containing conversation", s.handleGetConversation) registerAPIV1RawHumaJSONRoute[AttachmentInfo](apiV1, "getAttachment", http.MethodGet, "/attachments/{id}", "Get attachment metadata", s.handleGetAttachment) + registerAPIV1RawHumaBinaryRoute( + apiV1, + "getFileContent", + http.MethodGet, + "/files/{id}/content", + "Download content for one authoritative file row", + "*/*", + s.handleGetFileContent, + http.StatusBadRequest, + http.StatusUnauthorized, + http.StatusNotFound, + http.StatusInternalServerError, + http.StatusServiceUnavailable, + ) registerAPIV1RawHumaBinaryRoute( apiV1, "getAttachmentContent", @@ -317,6 +347,9 @@ func (s *Server) registerHumaRoutes(api huma.API, apiV1 huma.API) { registerAPIV1RawHumaJSONRoute[ListDeletionsResponse]( apiV1, "listDeletions", http.MethodGet, "/deletions", "List staged deletion manifests", s.handleListDeletions) + registerAPIV1RawHumaJSONRoute[DeletionManifestDetail]( + apiV1, "getDeletion", http.MethodGet, "/deletions/{id}", + "Inspect a staged deletion manifest", s.handleGetDeletion) registerAPIV1RawHumaJSONRoute[CancelDeletionResponse]( apiV1, "cancelDeletion", http.MethodDelete, "/deletions/{id}", "Cancel a staged deletion manifest", s.handleCancelDeletion) @@ -466,13 +499,47 @@ func rawRouteParameters(operationID string) []*huma.Param { return paginationParams("page", "page_size") case "getMessage": return []*huma.Param{pathIntegerParam("Message ID")} + case "listMessageTasks", "createOrLinkMessageTask": + params := []*huma.Param{pathIntegerParam("Archived email message ID")} + if operationID == "createOrLinkMessageTask" { + params = append(params, param("X-Request-Id", "header", "string", "Browser-generated retry-stable request ID", true)) + } + return params + case "searchIntegrationTasks": + return []*huma.Param{queryStringParam("q", "Task title search within the configured project", true)} + case "unlinkMessageTask": + return []*huma.Param{ + pathIntegerParam("Archived email message ID"), + pathStringParam("task_id", "External task ID"), + } + case "getConversation": + return []*huma.Param{ + pathIntegerParam("Conversation ID"), + queryRequiredIntegerParam("anchor", "Selected message ID anchoring the chronological window"), + queryIntegerParam("before", "Messages before the anchor (default 25, max 50)"), + queryIntegerParam("after", "Messages after the anchor (default 25, max 50)"), + queryStringParam("start", + "Lower UTC bound, inclusive (RFC3339). Restricts the window, before/after "+ + "counts, and has_before/has_after to messages in [start, end)", false), + queryStringParam("end", + "Upper UTC bound, exclusive (RFC3339). Restricts the window, before/after "+ + "counts, and has_before/has_after to messages in [start, end)", false), + } case "listDeletions": return []*huma.Param{queryStringParam("status", "Filter manifests by status (pending, in_progress, completed, failed, cancelled)", false)} - case "cancelDeletion": + case "getDeletion", "cancelDeletion": return []*huma.Param{pathStringParam("id", "Deletion manifest ID")} case "getAttachment": return []*huma.Param{pathIntegerParam("Attachment ID")} + case "getFile", "getFileContent": + return []*huma.Param{pathIntegerParam("File attachment ID")} + case "getPerson", "getPersonTimeline", "getPersonContextSummary", "searchPersonFiles": + return []*huma.Param{pathIntegerParam("Durable participant ID")} + case "getRelationshipTimeline": + return []*huma.Param{pathIntegerParam("Any member participant ID of the counterpart's identity cluster")} + case "getDomain", "getDomainTimeline", "getDomainContextSummary", "searchDomainFiles": + return []*huma.Param{pathStringParam("domain", "Exact normalized domain fact")} case "getAttachmentContent": return []*huma.Param{pathStringParam("hash", "Attachment SHA-256 content hash")} case "getMessageInlinePart": diff --git a/internal/api/saved_views.go b/internal/api/saved_views.go new file mode 100644 index 000000000..dd9485e1b --- /dev/null +++ b/internal/api/saved_views.go @@ -0,0 +1,418 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" + + "github.com/danielgtaylor/huma/v2" + "go.kenn.io/msgvault/internal/jsonexact" + "go.kenn.io/msgvault/internal/store" +) + +const savedViewsPath = "/api/v1/saved-views" + +type SavedViewStore interface { + CreateSavedView(ctx context.Context, input store.SavedViewInput) (*store.SavedView, error) + ListSavedViews(ctx context.Context) ([]store.SavedView, error) + GetSavedView(ctx context.Context, id int64) (*store.SavedView, error) + UpdateSavedView(ctx context.Context, id, expectedRevision int64, input store.SavedViewInput) (*store.SavedView, error) + DeleteSavedView(ctx context.Context, id, expectedRevision int64) error +} + +type SavedView struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description *string `json:"description,omitempty"` + CanonicalState store.SavedViewStateEnvelope `json:"canonical_state"` + SchemaVersion int `json:"schema_version"` + Revision int64 `json:"revision"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type SavedViewsResponse struct { + SavedViews []SavedView `json:"saved_views"` +} + +type CreateSavedViewRequest struct { + Name string `json:"name"` + Description *string `json:"description,omitempty"` + CanonicalState store.SavedViewStateEnvelope `json:"canonical_state"` + SchemaVersion int `json:"schema_version"` +} + +type PatchSavedViewRequest struct { + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + CanonicalState *store.SavedViewStateEnvelope `json:"canonical_state,omitempty"` + SchemaVersion *int `json:"schema_version,omitempty"` +} + +func (s *Server) registerSavedViewRoutes(api huma.API) { + list := rawAPIV1Operation("listSavedViews", http.MethodGet, "/saved-views", "List shared analytical Saved Views") + list.Responses = jsonResponsesFor[SavedViewsResponse](api) + registerRawHumaRoute(api, list, s.handleListSavedViews) + + create := rawAPIV1Operation("createSavedView", http.MethodPost, "/saved-views", "Create a shared analytical Saved View") + create.RequestBody = jsonRequestBodyFor[CreateSavedViewRequest](api) + create.Responses = jsonResponsesFor[SavedView](api, http.StatusCreated) + addSavedViewETagHeader(create.Responses[httpStatusKey(http.StatusCreated)]) + addErrorResponses(api, create.Responses, http.StatusBadRequest, http.StatusConflict, http.StatusServiceUnavailable) + registerRawHumaRoute(api, create, s.handleCreateSavedView) + + get := rawAPIV1Operation("getSavedView", http.MethodGet, "/saved-views/{id}", "Get a shared analytical Saved View") + addSavedViewIDParameter(&get) + get.Responses = jsonResponsesFor[SavedView](api) + addSavedViewETagHeader(get.Responses[httpStatusKey(http.StatusOK)]) + addErrorResponses(api, get.Responses, http.StatusNotFound, http.StatusServiceUnavailable) + registerRawHumaRoute(api, get, s.handleGetSavedView) + + patch := rawAPIV1Operation("patchSavedView", http.MethodPatch, "/saved-views/{id}", "Update a shared analytical Saved View") + addSavedViewIDParameter(&patch) + addSavedViewIfMatchParameter(&patch) + patch.RequestBody = jsonRequestBodyFor[PatchSavedViewRequest](api) + patch.Responses = jsonResponsesFor[SavedView](api) + addSavedViewETagHeader(patch.Responses[httpStatusKey(http.StatusOK)]) + addErrorResponses(api, patch.Responses, http.StatusBadRequest, http.StatusConflict, http.StatusNotFound, + http.StatusPreconditionRequired, http.StatusServiceUnavailable) + registerRawHumaRoute(api, patch, s.handlePatchSavedView) + + remove := rawAPIV1Operation("deleteSavedView", http.MethodDelete, "/saved-views/{id}", "Delete a shared analytical Saved View") + addSavedViewIDParameter(&remove) + addSavedViewIfMatchParameter(&remove) + remove.Responses = rawHumaResponses(http.StatusNoContent) + remove.Responses["default"] = errorResponseFor(api) + addErrorResponses(api, remove.Responses, http.StatusBadRequest, http.StatusUnauthorized, + http.StatusConflict, http.StatusNotFound, http.StatusPreconditionRequired, + http.StatusInternalServerError, http.StatusServiceUnavailable) + registerRawHumaRoute(api, remove, s.handleDeleteSavedView) +} + +func addSavedViewIDParameter(operation *huma.Operation) { + operation.Parameters = append(operation.Parameters, &huma.Param{ + Name: "id", In: "path", Required: true, Description: "Saved View ID", + Schema: &huma.Schema{Type: huma.TypeInteger, Format: "int64"}, + }) +} + +func addSavedViewIfMatchParameter(operation *huma.Operation) { + operation.Parameters = append(operation.Parameters, &huma.Param{ + Name: "If-Match", In: "header", Required: true, + Description: "Strong ETag returned by the latest Saved View read", + Schema: &huma.Schema{Type: huma.TypeString}, + }) +} + +func addSavedViewETagHeader(response *huma.Response) { + response.Headers = map[string]*huma.Param{ + "ETag": { + Description: "Strong Saved View revision tag for optimistic concurrency", + Schema: &huma.Schema{Type: huma.TypeString}, + }, + } +} + +func addErrorResponses(api huma.API, responses map[string]*huma.Response, statuses ...int) { + for _, status := range statuses { + responses[httpStatusKey(status)] = errorResponseFor(api) + } +} + +func (s *Server) handleListSavedViews(w http.ResponseWriter, r *http.Request) { + if !s.requireSavedViewStore(w) { + return + } + views, err := s.savedViewStore.ListSavedViews(r.Context()) + if err != nil { + s.writeSavedViewError(w, err) + return + } + response := SavedViewsResponse{SavedViews: make([]SavedView, 0, len(views))} + for i := range views { + view, err := savedViewResponse(&views[i]) + if err != nil { + writeError(w, http.StatusInternalServerError, "saved_view_read_failed", err.Error()) + return + } + response.SavedViews = append(response.SavedViews, view) + } + w.Header().Set("Cache-Control", "no-store") + writeJSON(w, http.StatusOK, response) +} + +func (s *Server) handleCreateSavedView(w http.ResponseWriter, r *http.Request) { + if !s.requireSavedViewStore(w) { + return + } + var request CreateSavedViewRequest + fields, ok := decodeSavedViewRequest(w, r, &request) + if !ok { + return + } + canonicalState, present := fields["canonical_state"] + if !present || isJSONNull(canonicalState) { + writeError(w, http.StatusBadRequest, "bad_request", "canonical_state is required and must not be null") + return + } + input := savedViewInput(request.Name, request.Description, canonicalState, request.SchemaVersion) + created, err := s.savedViewStore.CreateSavedView(r.Context(), input) + if err != nil { + s.writeSavedViewError(w, err) + return + } + response, err := savedViewResponse(created) + if err != nil { + writeError(w, http.StatusInternalServerError, "saved_view_read_failed", err.Error()) + return + } + w.Header().Set("ETag", savedViewETag(*created)) + w.Header().Set("Location", savedViewsPath+"/"+strconv.FormatInt(created.ID, 10)) + w.Header().Set("Cache-Control", "no-store") + writeJSON(w, http.StatusCreated, response) +} + +func (s *Server) handleGetSavedView(w http.ResponseWriter, r *http.Request) { + if !s.requireSavedViewStore(w) { + return + } + id, ok := savedViewID(w, r) + if !ok { + return + } + view, err := s.savedViewStore.GetSavedView(r.Context(), id) + if err != nil { + s.writeSavedViewError(w, err) + return + } + response, err := savedViewResponse(view) + if err != nil { + writeError(w, http.StatusInternalServerError, "saved_view_read_failed", err.Error()) + return + } + w.Header().Set("ETag", savedViewETag(*view)) + w.Header().Set("Cache-Control", "no-store") + writeJSON(w, http.StatusOK, response) +} + +func (s *Server) handlePatchSavedView(w http.ResponseWriter, r *http.Request) { + if !s.requireSavedViewStore(w) { + return + } + id, ok := savedViewID(w, r) + if !ok { + return + } + revision, ok := savedViewIfMatch(w, r, id) + if !ok { + return + } + var request PatchSavedViewRequest + fields, ok := decodeSavedViewRequest(w, r, &request) + if !ok { + return + } + if canonicalState, present := fields["canonical_state"]; present && isJSONNull(canonicalState) { + writeError(w, http.StatusBadRequest, "bad_request", "canonical_state must not be null") + return + } + if request.Name == nil && request.Description == nil && request.CanonicalState == nil && request.SchemaVersion == nil { + writeError(w, http.StatusBadRequest, "bad_request", "At least one Saved View field is required") + return + } + current, err := s.savedViewStore.GetSavedView(r.Context(), id) + if err != nil { + s.writeSavedViewError(w, err) + return + } + if current.Revision != revision { + s.writeSavedViewError(w, store.ErrSavedViewRevisionConflict) + return + } + input := store.SavedViewInput{ + Name: current.Name, Description: current.Description, + CanonicalState: current.CanonicalState, SchemaVersion: current.SchemaVersion, + } + if request.Name != nil { + input.Name = *request.Name + } + if request.Description != nil { + input.Description = normalizedDescription(request.Description) + } + if request.CanonicalState != nil { + input.CanonicalState = append(json.RawMessage(nil), fields["canonical_state"]...) + } + if request.SchemaVersion != nil { + input.SchemaVersion = *request.SchemaVersion + } + updated, err := s.savedViewStore.UpdateSavedView(r.Context(), id, revision, input) + if err != nil { + s.writeSavedViewError(w, err) + return + } + response, err := savedViewResponse(updated) + if err != nil { + writeError(w, http.StatusInternalServerError, "saved_view_read_failed", err.Error()) + return + } + w.Header().Set("ETag", savedViewETag(*updated)) + w.Header().Set("Cache-Control", "no-store") + writeJSON(w, http.StatusOK, response) +} + +func (s *Server) handleDeleteSavedView(w http.ResponseWriter, r *http.Request) { + if !s.requireSavedViewStore(w) { + return + } + id, ok := savedViewID(w, r) + if !ok { + return + } + revision, ok := savedViewIfMatch(w, r, id) + if !ok { + return + } + if err := s.savedViewStore.DeleteSavedView(r.Context(), id, revision); err != nil { + s.writeSavedViewError(w, err) + return + } + w.Header().Set("Cache-Control", "no-store") + w.WriteHeader(http.StatusNoContent) +} + +func (s *Server) requireSavedViewStore(w http.ResponseWriter) bool { + if s.savedViewStore != nil { + return true + } + writeError(w, http.StatusServiceUnavailable, "saved_views_unavailable", "Saved Views are unavailable") + return false +} + +func decodeSavedViewRequest( + w http.ResponseWriter, r *http.Request, target any, +) (map[string]json.RawMessage, bool) { + body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20)) + if err != nil { + writeError(w, http.StatusBadRequest, "bad_request", "Invalid Saved View request") + return nil, false + } + if err := jsonexact.Validate(body, target); err != nil { + writeError(w, http.StatusBadRequest, "bad_request", "Invalid Saved View request") + return nil, false + } + decoder := json.NewDecoder(bytes.NewReader(body)) + decoder.DisallowUnknownFields() + decoder.UseNumber() + if err := decoder.Decode(target); err != nil { + writeError(w, http.StatusBadRequest, "bad_request", "Invalid Saved View request") + return nil, false + } + if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + writeError(w, http.StatusBadRequest, "bad_request", "Invalid Saved View request") + return nil, false + } + var fields map[string]json.RawMessage + if err := json.Unmarshal(body, &fields); err != nil || fields == nil { + writeError(w, http.StatusBadRequest, "bad_request", "Invalid Saved View request") + return nil, false + } + return fields, true +} + +func isJSONNull(value json.RawMessage) bool { + return bytes.Equal(bytes.TrimSpace(value), []byte("null")) +} + +func savedViewInput( + name string, description *string, state json.RawMessage, schemaVersion int, +) store.SavedViewInput { + return store.SavedViewInput{ + Name: name, Description: normalizedDescription(description), + CanonicalState: append(json.RawMessage(nil), state...), SchemaVersion: schemaVersion, + } +} + +func normalizedDescription(description *string) *string { + if description == nil || strings.TrimSpace(*description) == "" { + return nil + } + value := *description + return &value +} + +func savedViewResponse(view *store.SavedView) (SavedView, error) { + var state store.SavedViewStateEnvelope + decoder := json.NewDecoder(strings.NewReader(string(view.CanonicalState))) + decoder.UseNumber() + if err := decoder.Decode(&state); err != nil { + return SavedView{}, fmt.Errorf("decode Saved View %d canonical state: %w", view.ID, err) + } + return SavedView{ + ID: view.ID, Name: view.Name, Description: view.Description, + CanonicalState: state, SchemaVersion: view.SchemaVersion, Revision: view.Revision, + CreatedAt: view.CreatedAt, UpdatedAt: view.UpdatedAt, + }, nil +} + +func savedViewID(w http.ResponseWriter, r *http.Request) (int64, bool) { + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil || id <= 0 { + writeError(w, http.StatusBadRequest, "invalid_saved_view_id", "Saved View ID must be a positive integer") + return 0, false + } + return id, true +} + +func savedViewETag(view store.SavedView) string { + return fmt.Sprintf(`"saved-view-%d-r%d"`, view.ID, view.Revision) +} + +func savedViewIfMatch(w http.ResponseWriter, r *http.Request, id int64) (int64, bool) { + values := r.Header.Values("If-Match") + if len(values) == 0 || (len(values) == 1 && strings.TrimSpace(values[0]) == "") { + writeError(w, http.StatusPreconditionRequired, "if_match_required", "If-Match is required") + return 0, false + } + if len(values) != 1 { + writeError(w, http.StatusBadRequest, "invalid_if_match", "If-Match must contain exactly one revision tag") + return 0, false + } + prefix := fmt.Sprintf(`"saved-view-%d-r`, id) + value := strings.TrimSpace(values[0]) + if !strings.HasPrefix(value, prefix) || !strings.HasSuffix(value, `"`) { + writeError(w, http.StatusBadRequest, "invalid_if_match", "If-Match is not a Saved View revision tag") + return 0, false + } + revision, err := strconv.ParseInt(strings.TrimSuffix(strings.TrimPrefix(value, prefix), `"`), 10, 64) + if err != nil || revision <= 0 { + writeError(w, http.StatusBadRequest, "invalid_if_match", "If-Match is not a Saved View revision tag") + return 0, false + } + return revision, true +} + +func (s *Server) writeSavedViewError(w http.ResponseWriter, err error) { + if s.writeIfContextError(w, err) { + return + } + switch { + case errors.Is(err, store.ErrSavedViewNotFound): + writeError(w, http.StatusNotFound, "saved_view_not_found", "Saved View not found") + case errors.Is(err, store.ErrSavedViewNameConflict): + writeError(w, http.StatusConflict, "saved_view_name_conflict", "A Saved View with that name already exists") + case errors.Is(err, store.ErrSavedViewRevisionConflict): + writeError(w, http.StatusConflict, "saved_view_revision_conflict", "Saved View changed; reload and retry") + case errors.Is(err, store.ErrSavedViewInvalidState), + errors.Is(err, store.ErrSavedViewUnsupportedSchemaVersion): + writeError(w, http.StatusBadRequest, "invalid_saved_view", err.Error()) + default: + writeError(w, http.StatusInternalServerError, "saved_view_failed", "Saved View operation failed") + } +} diff --git a/internal/api/saved_views_test.go b/internal/api/saved_views_test.go new file mode 100644 index 000000000..390e5bce6 --- /dev/null +++ b/internal/api/saved_views_test.go @@ -0,0 +1,305 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/store" + "go.kenn.io/msgvault/internal/testutil" +) + +func TestSavedViewsAuthenticatedCRUDAndSharedSessionVisibility(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := newSavedViewTestServer(t) + first := loginSavedViewSession(t, srv) + second := loginSavedViewSession(t, srv) + + unauthorized := performSavedViewRequest(t, srv, http.MethodGet, savedViewsPath, nil, nil) + assertions.Equal(http.StatusUnauthorized, unauthorized.Code, unauthorized.Body.String()) + + create := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(`{ + "name":"Invoices", + "description":"Quarterly review", + "canonical_state":{"query":"invoice","search_mode":"full_text","filters":[{"field":"source_id","operator":"in","values":["1"]}],"presentation":"table"}, + "schema_version":1 + }`), first.mutationHeaders()) + requirements.Equal(http.StatusCreated, create.Code, create.Body.String()) + created := decodeSavedView(t, create) + assertions.NotZero(created.ID) + assertions.Equal(int64(1), created.Revision) + assertions.Equal(savedViewETag(created), create.Header().Get("ETag")) + assertions.Equal(savedViewsPath+"/"+strconv.FormatInt(created.ID, 10), create.Header().Get("Location")) + + list := performSavedViewRequest(t, srv, http.MethodGet, savedViewsPath, nil, second.headers()) + requirements.Equal(http.StatusOK, list.Code, list.Body.String()) + var listed SavedViewsResponse + requirements.NoError(json.Unmarshal(list.Body.Bytes(), &listed)) + requirements.Len(listed.SavedViews, 1) + assertions.Equal(created.ID, listed.SavedViews[0].ID, + "a view created by one authenticated browser session is visible to another") + + itemPath := savedViewsPath + "/" + strconv.FormatInt(created.ID, 10) + get := performSavedViewRequest(t, srv, http.MethodGet, itemPath, nil, second.headers()) + requirements.Equal(http.StatusOK, get.Code, get.Body.String()) + assertions.Equal(savedViewETag(created), get.Header().Get("ETag")) + + patchHeaders := second.mutationHeaders() + patchHeaders.Set("If-Match", get.Header().Get("ETag")) + patch := performSavedViewRequest(t, srv, http.MethodPatch, itemPath, + []byte(`{"name":"Invoices 2026"}`), patchHeaders) + requirements.Equal(http.StatusOK, patch.Code, patch.Body.String()) + updated := decodeSavedView(t, patch) + assertions.Equal(int64(2), updated.Revision) + assertions.Equal("Invoices 2026", updated.Name) + assertions.Equal(savedViewETag(updated), patch.Header().Get("ETag")) + + staleHeaders := first.mutationHeaders() + staleHeaders.Set("If-Match", savedViewETag(created)) + stale := performSavedViewRequest(t, srv, http.MethodPatch, itemPath, + []byte(`{"name":"Stale edit"}`), staleHeaders) + assertions.Equal(http.StatusConflict, stale.Code, stale.Body.String()) + + duplicate := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(`{"name":"Invoices 2026","canonical_state":{},"schema_version":1}`), first.mutationHeaders()) + assertions.Equal(http.StatusConflict, duplicate.Code, duplicate.Body.String()) + + missing := performSavedViewRequest(t, srv, http.MethodGet, savedViewsPath+"/999999", nil, second.headers()) + assertions.Equal(http.StatusNotFound, missing.Code, missing.Body.String()) + + deleteHeaders := first.mutationHeaders() + deleteHeaders.Set("If-Match", savedViewETag(updated)) + deleted := performSavedViewRequest(t, srv, http.MethodDelete, itemPath, nil, deleteHeaders) + assertions.Equal(http.StatusNoContent, deleted.Code, deleted.Body.String()) + missing = performSavedViewRequest(t, srv, http.MethodGet, itemPath, nil, second.headers()) + assertions.Equal(http.StatusNotFound, missing.Code, missing.Body.String()) +} + +func TestSavedViewsAPIRejectsInvalidStateAndRevisionHeaders(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := newSavedViewTestServer(t) + session := loginSavedViewSession(t, srv) + + invalid := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(`{"name":"Invalid","canonical_state":{"selection":[1]},"schema_version":1}`), + session.mutationHeaders()) + assertions.Equal(http.StatusBadRequest, invalid.Code, invalid.Body.String()) + + nestedInvalid := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(`{"name":"Nested invalid","canonical_state":{"filters":[{"field":"sender","operator":"eq","values":["alice@example.com"],"results":[{"id":1}]}]},"schema_version":1}`), + session.mutationHeaders()) + assertions.Equal(http.StatusBadRequest, nestedInvalid.Code, nestedInvalid.Body.String()) + + wrongNestedType := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(`{"name":"Wrong nested type","canonical_state":{"sort":[{"field":"sent_at","direction":["desc"]}]},"schema_version":1}`), + session.mutationHeaders()) + assertions.Equal(http.StatusBadRequest, wrongNestedType.Code, wrongNestedType.Body.String()) + + for name, body := range map[string]string{ + "omitted": `{"name":"Missing state","schema_version":1}`, + "null": `{"name":"Null state","canonical_state":null,"schema_version":1}`, + } { + t.Run("create canonical state "+name, func(t *testing.T) { + response := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(body), session.mutationHeaders()) + assert.Equal(t, http.StatusBadRequest, response.Code, response.Body.String()) + }) + } + + createdResponse := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(`{"name":"Current","canonical_state":{},"schema_version":1}`), session.mutationHeaders()) + requirements.Equal(http.StatusCreated, createdResponse.Code, createdResponse.Body.String()) + created := decodeSavedView(t, createdResponse) + path := savedViewsPath + "/" + strconv.FormatInt(created.ID, 10) + + missingMatch := performSavedViewRequest(t, srv, http.MethodPatch, path, + []byte(`{"name":"Changed"}`), session.mutationHeaders()) + assertions.Equal(http.StatusPreconditionRequired, missingMatch.Code, missingMatch.Body.String()) + + malformedMatchHeaders := session.mutationHeaders() + malformedMatchHeaders.Set("If-Match", `"not-a-saved-view-revision"`) + malformedMatch := performSavedViewRequest(t, srv, http.MethodDelete, path, nil, malformedMatchHeaders) + assertions.Equal(http.StatusBadRequest, malformedMatch.Code, malformedMatch.Body.String()) + + multipleMatchHeaders := session.mutationHeaders() + multipleMatchHeaders.Add("If-Match", savedViewETag(created)) + multipleMatchHeaders.Add("If-Match", savedViewETag(created)) + multipleMatch := performSavedViewRequest(t, srv, http.MethodDelete, path, nil, multipleMatchHeaders) + assertions.Equal(http.StatusBadRequest, multipleMatch.Code, multipleMatch.Body.String()) + + patchOmissionHeaders := session.mutationHeaders() + patchOmissionHeaders.Set("If-Match", savedViewETag(created)) + patchOmission := performSavedViewRequest(t, srv, http.MethodPatch, path, + []byte(`{"name":"Canonical state preserved"}`), patchOmissionHeaders) + requirements.Equal(http.StatusOK, patchOmission.Code, patchOmission.Body.String()) + preserved := decodeSavedView(t, patchOmission) + assertions.JSONEq(`{}`, string(preserved.CanonicalState)) + + patchNullHeaders := session.mutationHeaders() + patchNullHeaders.Set("If-Match", savedViewETag(preserved)) + patchNull := performSavedViewRequest(t, srv, http.MethodPatch, path, + []byte(`{"name":"Must not apply","canonical_state":null}`), patchNullHeaders) + assertions.Equal(http.StatusBadRequest, patchNull.Code, patchNull.Body.String()) + unchanged := performSavedViewRequest(t, srv, http.MethodGet, path, nil, session.headers()) + requirements.Equal(http.StatusOK, unchanged.Code, unchanged.Body.String()) + assertions.Equal("Canonical state preserved", decodeSavedView(t, unchanged).Name) +} + +func TestSavedViewsAPIValidatesServerGroupingCatalog(t *testing.T) { + srv := newSavedViewTestServer(t) + session := loginSavedViewSession(t, srv) + + for _, dimension := range exploreGroupDimensions { + t.Run("accept "+string(dimension), func(t *testing.T) { + body := fmt.Sprintf( + `{"name":"Group %s","canonical_state":{"grouping":[%q]},"schema_version":1}`, + dimension, dimension, + ) + response := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(body), session.mutationHeaders()) + require.Equal(t, http.StatusCreated, response.Code, response.Body.String()) + }) + } + + invalid := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(`{"name":"Unsupported","canonical_state":{"grouping":["sender"]},"schema_version":1}`), + session.mutationHeaders()) + assert.Equal(t, http.StatusBadRequest, invalid.Code, invalid.Body.String()) + + createdResponse := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(`{"name":"Before invalid patch","canonical_state":{"grouping":["source"]},"schema_version":1}`), + session.mutationHeaders()) + require.Equal(t, http.StatusCreated, createdResponse.Code, createdResponse.Body.String()) + created := decodeSavedView(t, createdResponse) + headers := session.mutationHeaders() + headers.Set("If-Match", savedViewETag(created)) + patched := performSavedViewRequest(t, srv, http.MethodPatch, + savedViewsPath+"/"+strconv.FormatInt(created.ID, 10), + []byte(`{"canonical_state":{"grouping":["arbitrary"]}}`), headers) + assert.Equal(t, http.StatusBadRequest, patched.Code, patched.Body.String()) +} + +func TestSavedViewsAPIRejectsMixedCaseJSONAliases(t *testing.T) { + createBodies := map[string]string{ + "request name": `{"Name":"Alias","canonical_state":{},"schema_version":1}`, + "request canonical state": `{"name":"Alias","Canonical_State":{},"schema_version":1}`, + "null request state": `{"name":"Alias","CANONICAL_STATE":null,"schema_version":1}`, + "envelope filters": `{"name":"Alias","canonical_state":{"Filters":null},"schema_version":1}`, + "nested filter field": `{"name":"Alias","canonical_state":{"filters":[{"Field":"sender","operator":"eq","values":["alice@example.com"]}]},"schema_version":1}`, + "nested sort direction": `{"name":"Alias","canonical_state":{"sort":[{"field":"sent_at","Direction":"desc"}]},"schema_version":1}`, + } + for name, body := range createBodies { + t.Run("create "+name, func(t *testing.T) { + srv := newSavedViewTestServer(t) + session := loginSavedViewSession(t, srv) + response := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(body), session.mutationHeaders()) + assert.Equal(t, http.StatusBadRequest, response.Code, response.Body.String()) + }) + } + + patchBodies := map[string]string{ + "request name": `{"Name":"Must not apply"}`, + "null request state": `{"name":"Must not apply","Canonical_State":null}`, + "envelope filters": `{"canonical_state":{"Filters":null}}`, + "nested filter operator": `{"canonical_state":{"filters":[{"field":"sender","Operator":"eq","values":["alice@example.com"]}]}}`, + "nested sort direction": `{"canonical_state":{"sort":[{"field":"sent_at","Direction":"desc"}]}}`, + } + for name, body := range patchBodies { + t.Run("patch "+name, func(t *testing.T) { + requirements := require.New(t) + srv := newSavedViewTestServer(t) + session := loginSavedViewSession(t, srv) + createdResponse := performSavedViewRequest(t, srv, http.MethodPost, savedViewsPath, + []byte(`{"name":"Original","canonical_state":{},"schema_version":1}`), session.mutationHeaders()) + requirements.Equal(http.StatusCreated, createdResponse.Code, createdResponse.Body.String()) + created := decodeSavedView(t, createdResponse) + path := savedViewsPath + "/" + strconv.FormatInt(created.ID, 10) + headers := session.mutationHeaders() + headers.Set("If-Match", savedViewETag(created)) + + response := performSavedViewRequest(t, srv, http.MethodPatch, path, []byte(body), headers) + assert.Equal(t, http.StatusBadRequest, response.Code, response.Body.String()) + gotResponse := performSavedViewRequest(t, srv, http.MethodGet, path, nil, session.headers()) + requirements.Equal(http.StatusOK, gotResponse.Code, gotResponse.Body.String()) + got := decodeSavedView(t, gotResponse) + assert.Equal(t, "Original", got.Name) + assert.Equal(t, int64(1), got.Revision) + }) + } +} + +type savedViewSession struct { + cookie *http.Cookie + csrf string +} + +func (s savedViewSession) headers() http.Header { + return http.Header{"Cookie": []string{s.cookie.String()}} +} + +func (s savedViewSession) mutationHeaders() http.Header { + headers := s.headers() + headers.Set("Origin", "http://example.com") + headers.Set(csrfHeaderName, s.csrf) + return headers +} + +func newSavedViewTestServer(t *testing.T) *Server { + t.Helper() + st := testutil.NewSQLiteTestStore(t) + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIKey: testSessionAPIKey}}, + Store: st, + SavedViewStore: st, + Logger: testLogger(), + }) + t.Cleanup(func() { require.NoError(t, srv.Shutdown(context.Background())) }) + return srv +} + +func loginSavedViewSession(t *testing.T, srv *Server) savedViewSession { + t.Helper() + login := performSavedViewRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), nil) + require.Equal(t, http.StatusOK, login.Code, login.Body.String()) + status := decodeSessionStatus(t, login) + return savedViewSession{cookie: requireSessionCookie(t, login), csrf: status.CSRFToken} +} + +func performSavedViewRequest( + t *testing.T, srv *Server, method, path string, body []byte, headers http.Header, +) *httptest.ResponseRecorder { + t.Helper() + req := httptest.NewRequest(method, "http://example.com"+path, bytes.NewReader(body)) + req.RemoteAddr = "192.0.2.10:4242" + if len(body) > 0 { + req.Header.Set("Content-Type", "application/json") + } + for name, values := range headers { + for _, value := range values { + req.Header.Add(name, value) + } + } + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + return resp +} + +func decodeSavedView(t *testing.T, response *httptest.ResponseRecorder) store.SavedView { + t.Helper() + var view store.SavedView + require.NoError(t, json.Unmarshal(response.Body.Bytes(), &view)) + return view +} diff --git a/internal/api/search_coverage.go b/internal/api/search_coverage.go new file mode 100644 index 000000000..1522f407d --- /dev/null +++ b/internal/api/search_coverage.go @@ -0,0 +1,306 @@ +package api + +import ( + "context" + "errors" + "fmt" + "math" + "net/http" + "slices" + "strings" + + "github.com/danielgtaylor/huma/v2" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/vector" +) + +type SearchCoverageStatus string + +const ( + SearchCoverageDisabled SearchCoverageStatus = "disabled" + SearchCoverageInitializing SearchCoverageStatus = "initializing" + SearchCoverageStale SearchCoverageStatus = "stale" + SearchCoverageIncomplete SearchCoverageStatus = "incomplete" + SearchCoverageUnavailable SearchCoverageStatus = "unavailable" + SearchCoverageReady SearchCoverageStatus = "ready" +) + +type SearchCoverageAction string + +const ( + SearchCoverageActionRetry SearchCoverageAction = "retry" + SearchCoverageActionBuildIndex SearchCoverageAction = "build_index" +) + +type SearchCoverageRequest struct { + Filters []ExploreFilter `json:"filters,omitempty"` +} + +type SearchCoverageResponse struct { + EligibleCount int64 `json:"eligible_count"` + EmbeddedCount int64 `json:"embedded_count"` + Percentage float64 `json:"percentage"` + VectorGeneration *int64 `json:"vector_generation,omitempty"` + VectorFingerprint string `json:"vector_fingerprint,omitempty"` + CacheRevision string `json:"cache_revision"` + Status SearchCoverageStatus `json:"status" enum:"disabled,initializing,stale,incomplete,unavailable,ready"` + Detail string `json:"detail,omitempty"` + Actions []SearchCoverageAction `json:"actions"` +} + +func (s *Server) registerSearchCoverageRoute(api huma.API) { + op := rawAPIV1Operation("getSearchCoverage", http.MethodPost, "/search/coverage", "Get semantic index coverage for an analytical context") + op.Tags = []string{"Exploration"} + op.RequestBody = jsonRequestBodyFor[SearchCoverageRequest](api) + op.Responses = jsonResponsesFor[SearchCoverageResponse](api) + addErrorResponses(api, op.Responses, http.StatusBadRequest, http.StatusServiceUnavailable) + registerRawHumaRoute(api, op, s.handleSearchCoverage) +} + +func (s *Server) handleSearchCoverage(w http.ResponseWriter, r *http.Request) { + var request SearchCoverageRequest + if !decodeExploreJSON(w, r, &request) { + return + } + canonical := ExploreHTTPRequest{Filters: request.Filters} + canonicalizeExploreRequest(&canonical) + ctx, err := exploreContext(canonical.Filters) + if err != nil { + writeError(w, http.StatusBadRequest, "invalid_filter", err.Error()) + return + } + _, _, cfg := s.vectorComponents() + ctx = semanticCoverageContext(ctx, cfg.Embed.Scope.BuildScope()) + explorer, ok := s.engine.(query.Explorer) + if !ok { + writeExploreUnavailable(w, query.CacheAbsent) + return + } + s.refreshVectorStatusIfStale(r.Context()) + status, detail := s.VectorStatus() + response := SearchCoverageResponse{ + Actions: make([]SearchCoverageAction, 0), + } + backendStatus := coverageStatusForVectorStatus(status) + _, backend, _ := s.vectorComponents() + var generation *vector.Generation + fullBuildAvailable := false + resolvedStatus := backendStatus + if backend != nil && backendStatus != SearchCoverageDisabled && backendStatus != SearchCoverageUnavailable { + backendStatus, detail, generation, fullBuildAvailable = resolveSearchCoverageGeneration( + r.Context(), backend, cfg, backendStatus, detail, + ) + resolvedStatus = backendStatus + } + counter, canCount := backend.(vector.FilteredCoverageBackend) + if generation != nil && !canCount { + backendStatus = SearchCoverageUnavailable + detail = "The vector backend cannot report filtered coverage" + generation = nil + fullBuildAvailable = false + } + if generation != nil { + generationID := int64(generation.ID) + response.VectorGeneration = &generationID + response.VectorFingerprint = generation.Fingerprint + } + + var afterMessageID int64 + for { + population, err := explorer.ExploreCoverage(r.Context(), query.ExploreCoverageRequest{ + Context: ctx, AfterMessageID: afterMessageID, Limit: vector.FilteredCoverageBatchSize, + }) + if err != nil { + s.writeExploreError(w, err) + return + } + if response.CacheRevision == "" { + response.CacheRevision = population.CacheRevision + } else if response.CacheRevision != population.CacheRevision { + writeError(w, http.StatusServiceUnavailable, "cache_changed", "Analytical cache changed while coverage was computed; retry") + return + } + if err := validateCoveragePage(afterMessageID, population.MessageIDs, population.NextAfterMessageID); err != nil { + writeError(w, http.StatusInternalServerError, "coverage_page_invalid", err.Error()) + return + } + response.EligibleCount += int64(len(population.MessageIDs)) + if generation != nil { + count, err := counter.EmbeddedMessageCountForIDs(r.Context(), generation.ID, population.MessageIDs) + if err != nil { + response.EmbeddedCount = 0 + backendStatus = SearchCoverageUnavailable + detail = "Filtered semantic coverage could not be read" + generation = nil + fullBuildAvailable = false + } else { + response.EmbeddedCount += count + } + } + if population.NextAfterMessageID == nil { + break + } + afterMessageID = *population.NextAfterMessageID + } + if generation != nil { + currentStatus, _, current, _ := resolveSearchCoverageGeneration( + r.Context(), backend, cfg, resolvedStatus, detail, + ) + if !sameCoverageGeneration(resolvedStatus, *generation, currentStatus, current) { + writeError(w, http.StatusServiceUnavailable, "vector_generation_changed", + "Vector generation changed while coverage was computed; retry") + return + } + } + + response.Percentage = coveragePercentage(response.EmbeddedCount, response.EligibleCount) + response.Status, response.Detail = backendStatus, detail + if generation != nil && backendStatus != SearchCoverageStale && response.EmbeddedCount < response.EligibleCount { + response.Status = SearchCoverageIncomplete + } else if generation != nil && backendStatus != SearchCoverageStale { + response.Status = SearchCoverageReady + } + response.Actions = s.searchCoverageActions(response.Status, fullBuildAvailable) + writeJSON(w, http.StatusOK, response) +} + +func resolveSearchCoverageGeneration( + ctx context.Context, + backend vector.Backend, + cfg vector.Config, + cachedStatus SearchCoverageStatus, + detail string, +) (SearchCoverageStatus, string, *vector.Generation, bool) { + if backend == nil { + return SearchCoverageUnavailable, "The vector backend is unavailable", nil, false + } + expectedFingerprint := "" + if cfg.Enabled { + expectedFingerprint = cfg.GenerationFingerprint() + } + generation, err := vector.ResolveActiveForFingerprint(ctx, backend, expectedFingerprint) + if err == nil { + if cachedStatus == SearchCoverageStale { + return SearchCoverageStale, detail, &generation, true + } + return SearchCoverageReady, "", &generation, false + } + switch { + case errors.Is(err, vector.ErrIndexStale): + active, activeErr := backend.ActiveGeneration(ctx) + if activeErr != nil { + return SearchCoverageUnavailable, "The active vector generation could not be read", nil, false + } + building, buildingErr := backend.BuildingGeneration(ctx) + fullBuildAvailable := buildingErr == nil && (building == nil || + expectedFingerprint == "" || building.Fingerprint == expectedFingerprint) + return SearchCoverageStale, err.Error(), &active, fullBuildAvailable + case errors.Is(err, vector.ErrIndexBuilding): + building, buildingErr := backend.BuildingGeneration(ctx) + if buildingErr != nil || building == nil { + return SearchCoverageUnavailable, "The building vector generation could not be read", nil, false + } + if expectedFingerprint != "" && building.Fingerprint != expectedFingerprint { + return SearchCoverageUnavailable, "A vector build is running for a different embedding configuration", nil, false + } + return SearchCoverageInitializing, "The first semantic index build is in progress", nil, false + case errors.Is(err, vector.ErrNotEnabled): + return SearchCoverageUnavailable, "No vector generation has been initialized", nil, true + default: + if detail == "" { + detail = "The vector generation state could not be read" + } + return SearchCoverageUnavailable, detail, nil, false + } +} + +func sameCoverageGeneration( + wantStatus SearchCoverageStatus, + want vector.Generation, + gotStatus SearchCoverageStatus, + got *vector.Generation, +) bool { + return got != nil && gotStatus == wantStatus && got.ID == want.ID && + got.State == want.State && got.Fingerprint == want.Fingerprint && + got.MessageCount == want.MessageCount +} + +func validateCoveragePage(afterMessageID int64, ids []int64, next *int64) error { + previous := afterMessageID + for _, id := range ids { + if id <= previous { + return fmt.Errorf("coverage page is not strictly ordered after message %d", previous) + } + previous = id + } + if next != nil && (len(ids) == 0 || *next != previous) { + return errors.New("coverage page cursor does not match its final message") + } + return nil +} + +func semanticCoverageContext(ctx query.Context, scope vector.BuildScope) query.Context { + // Vector generations cover only active archive messages. + if ctx.Deletion == query.DeletionDeleted { + ctx.MessageTypes = []string{"\x00no-semantic-eligible-message-type"} + ctx.Deletion = query.DeletionActive + return ctx + } + ctx.Deletion = query.DeletionActive + if scope.IsEmpty() { + return ctx + } + if len(ctx.MessageTypes) == 0 { + ctx.MessageTypes = slices.Clone(scope.MessageTypes) + return ctx + } + eligible := make([]string, 0, len(ctx.MessageTypes)) + for _, messageType := range ctx.MessageTypes { + if scope.ContainsMessageType(strings.ToLower(messageType)) { + eligible = append(eligible, messageType) + } + } + if len(eligible) == 0 { + eligible = []string{"\x00no-semantic-eligible-message-type"} + } + ctx.MessageTypes = eligible + return ctx +} + +func coveragePercentage(embedded, eligible int64) float64 { + if eligible == 0 { + return 100 + } + return math.Round((float64(embedded)/float64(eligible))*1000) / 10 +} + +func coverageStatusForVectorStatus(status VectorStatus) SearchCoverageStatus { + switch status { + case VectorStatusDisabled: + return SearchCoverageDisabled + case VectorStatusInitializing: + return SearchCoverageInitializing + case VectorStatusStale: + return SearchCoverageStale + case VectorStatusReady: + return SearchCoverageReady + default: + return SearchCoverageUnavailable + } +} + +func (s *Server) searchCoverageActions( + status SearchCoverageStatus, + fullBuildAvailable bool, +) []SearchCoverageAction { + actions := make([]SearchCoverageAction, 0, 2) + if status == SearchCoverageUnavailable || (status == SearchCoverageStale && !fullBuildAvailable) { + actions = append(actions, SearchCoverageActionRetry) + } + if fullBuildAvailable { + if _, ok := s.store.(CLIRunner); ok && s.cfg != nil && s.cfg.Vector.Enabled { + actions = append(actions, SearchCoverageActionBuildIndex) + } + } + return actions +} diff --git a/internal/api/search_coverage_sqlite_test.go b/internal/api/search_coverage_sqlite_test.go new file mode 100644 index 000000000..943988747 --- /dev/null +++ b/internal/api/search_coverage_sqlite_test.go @@ -0,0 +1,130 @@ +//go:build fts5 && sqlite_vec + +package api + +import ( + "context" + "database/sql" + "encoding/json" + "net/http" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/store" + "go.kenn.io/msgvault/internal/testutil" + "go.kenn.io/msgvault/internal/vector" + "go.kenn.io/msgvault/internal/vector/sqlitevec" +) + +func TestSearchCoverageRealGenerationStateMatrix(t *testing.T) { + vectorCfg := vector.Config{ + Enabled: true, + Embeddings: vector.EmbeddingsConfig{ + Model: "test-model", Dimension: 2, MaxInputChars: 1000, + }, + } + tests := []struct { + name string + generation string + vectorStatus VectorStatus + wantStatus SearchCoverageStatus + wantEmbedded int64 + wantActions []SearchCoverageAction + }{ + { + name: "no generation", wantStatus: SearchCoverageUnavailable, + wantActions: []SearchCoverageAction{SearchCoverageActionRetry, SearchCoverageActionBuildIndex}, + }, + { + name: "matching first build", generation: "building-matching", + vectorStatus: VectorStatusInitializing, + wantStatus: SearchCoverageInitializing, + }, + { + name: "different first build", generation: "building-different", + vectorStatus: VectorStatusInitializing, + wantStatus: SearchCoverageUnavailable, + wantActions: []SearchCoverageAction{SearchCoverageActionRetry}, + }, + { + name: "matching active", generation: "active-matching", + wantStatus: SearchCoverageReady, wantEmbedded: 2, + }, + { + name: "stale active", generation: "active-different", + wantStatus: SearchCoverageStale, wantEmbedded: 2, + wantActions: []SearchCoverageAction{SearchCoverageActionBuildIndex}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + vectorStatus := tt.vectorStatus + if vectorStatus == "" { + vectorStatus = VectorStatusReady + } + backend := newRealCoverageBackend(t, vectorCfg, tt.generation) + cfg := &config.Config{Server: config.ServerConfig{APIPort: 8080}, Vector: vectorCfg} + srv := NewServerWithOptions(ServerOptions{ + Config: cfg, Store: &mockStore{stats: &StoreStats{}}, Engine: newExploreDuckDBFixture(t), + Backend: backend, VectorCfg: vectorCfg, VectorStatus: vectorStatus, Logger: testLogger(), + }) + + response := postExploreJSON(t, srv, "/api/v1/search/coverage", `{ + "filters":[{"dimension":"source","values":["1"]}] + }`) + require.Equal(t, http.StatusOK, response.Code, response.Body.String()) + var body SearchCoverageResponse + require.NoError(t, json.Unmarshal(response.Body.Bytes(), &body)) + assert.Equal(t, tt.wantStatus, body.Status) + assert.Equal(t, int64(2), body.EligibleCount) + assert.Equal(t, tt.wantEmbedded, body.EmbeddedCount) + assert.ElementsMatch(t, tt.wantActions, body.Actions) + }) + } +} + +func newRealCoverageBackend(t *testing.T, cfg vector.Config, generationState string) *sqlitevec.Backend { + t.Helper() + ctx := context.Background() + st := testutil.NewSQLiteTestStore(t) + source, err := st.GetOrCreateSource("gmail", "coverage@example.com") + require.NoError(t, err) + conversationID, err := st.EnsureConversationWithType(source.ID, "coverage", "email_thread", "Coverage") + require.NoError(t, err) + for _, sourceMessageID := range []string{"one", "two"} { + _, err := st.UpsertMessage(&store.Message{ + SourceID: source.ID, SourceMessageID: sourceMessageID, + ConversationID: conversationID, MessageType: "email", + Subject: sql.NullString{String: sourceMessageID, Valid: true}, + }) + require.NoError(t, err) + } + backend, err := sqlitevec.Open(ctx, sqlitevec.Options{ + Path: filepath.Join(t.TempDir(), "vectors.db"), Dimension: 2, MainDB: st.DB(), + }) + require.NoError(t, err) + t.Cleanup(func() { _ = backend.Close() }) + if generationState == "" { + return backend + } + fingerprint := cfg.GenerationFingerprint() + if generationState == "building-different" || generationState == "active-different" { + fingerprint = "different" + } + generationID, err := backend.CreateGeneration(ctx, "test-model", 2, fingerprint) + require.NoError(t, err) + if generationState == "building-matching" || generationState == "building-different" { + return backend + } + require.NoError(t, backend.Upsert(ctx, generationID, []vector.Chunk{ + {MessageID: 1, Vector: []float32{1, 0}}, + {MessageID: 2, Vector: []float32{0, 1}}, + })) + require.NoError(t, st.SetEmbedGen(ctx, []int64{1, 2}, int64(generationID))) + require.NoError(t, backend.ActivateGeneration(ctx, generationID, true)) + return backend +} diff --git a/internal/api/search_coverage_test.go b/internal/api/search_coverage_test.go new file mode 100644 index 000000000..c9aae9317 --- /dev/null +++ b/internal/api/search_coverage_test.go @@ -0,0 +1,515 @@ +package api + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/vector" +) + +type filteredCoverageBackend struct { + vector.Backend + + active vector.Generation + activeErr error + building *vector.Generation + buildingErr error + embeddedIDs map[int64]struct{} + countAll bool + countErr error + countCalls [][]int64 + mu sync.Mutex +} + +func (b *filteredCoverageBackend) BuildingGeneration(context.Context) (*vector.Generation, error) { + return b.building, b.buildingErr +} + +func (b *filteredCoverageBackend) ActiveGeneration(context.Context) (vector.Generation, error) { + if b.activeErr != nil { + return vector.Generation{}, b.activeErr + } + return b.active, nil +} + +func (b *filteredCoverageBackend) EmbeddedMessageCountForIDs( + _ context.Context, + _ vector.GenerationID, + ids []int64, +) (int64, error) { + b.mu.Lock() + b.countCalls = append(b.countCalls, append([]int64(nil), ids...)) + b.mu.Unlock() + if b.countErr != nil { + return 0, b.countErr + } + if b.countAll { + return int64(len(ids)), nil + } + var count int64 + for _, id := range ids { + if _, ok := b.embeddedIDs[id]; ok { + count++ + } + } + return count, nil +} + +type coveragePagingEngine struct { + query.Engine + query.Explorer + + coverage func(context.Context, query.ExploreCoverageRequest) (*query.ExploreCoverageResponse, error) +} + +func (e *coveragePagingEngine) ExploreCoverage( + ctx context.Context, + request query.ExploreCoverageRequest, +) (*query.ExploreCoverageResponse, error) { + return e.coverage(ctx, request) +} + +func TestSearchCoverageReportsEveryNamedStateInFilteredContext(t *testing.T) { + generation := vector.Generation{ + ID: 7, Model: "test", Dimension: 2, Fingerprint: "test:2", State: vector.GenerationActive, + } + tests := []struct { + name string + status VectorStatus + backend *filteredCoverageBackend + stale bool + wantStatus SearchCoverageStatus + wantEmbedded int64 + wantPercentage float64 + wantGeneration *int64 + wantActions []SearchCoverageAction + }{ + {name: "disabled", status: VectorStatusDisabled, wantStatus: SearchCoverageDisabled}, + {name: "initializing", status: VectorStatusInitializing, wantStatus: SearchCoverageInitializing}, + { + name: "stale generation", status: VectorStatusReady, stale: true, + backend: &filteredCoverageBackend{active: generation, embeddedIDs: map[int64]struct{}{1: {}, 2: {}}}, + wantStatus: SearchCoverageStale, wantEmbedded: 2, wantPercentage: 100, wantGeneration: new(int64(7)), + }, + { + name: "incomplete", status: VectorStatusReady, + backend: &filteredCoverageBackend{active: generation, embeddedIDs: map[int64]struct{}{2: {}}}, + wantStatus: SearchCoverageIncomplete, wantEmbedded: 1, wantPercentage: 50, wantGeneration: new(int64(7)), + }, + { + name: "unavailable backend", status: VectorStatusReady, + backend: &filteredCoverageBackend{activeErr: errors.New("vector database is busy")}, + wantStatus: SearchCoverageUnavailable, wantActions: []SearchCoverageAction{SearchCoverageActionRetry}, + }, + { + name: "ready", status: VectorStatusReady, + backend: &filteredCoverageBackend{active: generation, embeddedIDs: map[int64]struct{}{1: {}, 2: {}, 3: {}}}, + wantStatus: SearchCoverageReady, wantEmbedded: 2, wantPercentage: 100, wantGeneration: new(int64(7)), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + var backend vector.Backend + if tt.backend != nil { + backend = tt.backend + } + engine := newExploreDuckDBFixture(t) + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: &mockStore{stats: &StoreStats{}}, Engine: engine, Backend: backend, + VectorStatus: tt.status, Logger: testLogger(), + }) + if tt.stale { + srv.SetVectorStale("configured generation changed") + } + + response := postExploreJSON(t, srv, "/api/v1/search/coverage", `{ + "filters":[{"dimension":"source","values":["1"]}] + }`) + + require.Equal(http.StatusOK, response.Code, response.Body.String()) + var body SearchCoverageResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assert.Equal(tt.wantStatus, body.Status) + assert.Equal(int64(2), body.EligibleCount, "source filter must define coverage denominator") + assert.Equal(tt.wantEmbedded, body.EmbeddedCount) + assert.InDelta(tt.wantPercentage, body.Percentage, 0.001) + assert.Equal(tt.wantGeneration, body.VectorGeneration) + assert.NotEmpty(body.CacheRevision) + assert.ElementsMatch(tt.wantActions, body.Actions) + }) + } +} + +func TestSameCoverageGenerationRejectsInPlaceTopUp(t *testing.T) { + want := vector.Generation{ + ID: 7, State: vector.GenerationActive, Fingerprint: "test:2", MessageCount: 10, + } + got := want + got.MessageCount++ + + assert.False(t, sameCoverageGeneration(SearchCoverageReady, want, SearchCoverageReady, &got)) +} + +func TestSearchCoverageIntersectsCanonicalContextWithEmbeddingScope(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + engine := newExploreDuckDBFixture(t) + generation := vector.Generation{ID: 9, Fingerprint: "test:2", State: vector.GenerationActive} + backend := &filteredCoverageBackend{active: generation, embeddedIDs: map[int64]struct{}{1: {}, 2: {}, 3: {}}} + cfg := &config.Config{Server: config.ServerConfig{APIPort: 8080}} + cfg.Vector.Embed.Scope.MessageTypes = []string{"email"} + srv := NewServerWithOptions(ServerOptions{ + Config: cfg, Store: &mockStore{stats: &StoreStats{}}, Engine: engine, Backend: backend, + VectorCfg: cfg.Vector, Logger: testLogger(), + }) + + response := postExploreJSON(t, srv, "/api/v1/search/coverage", `{ + "filters":[ + {"dimension":"source","values":["2"]}, + {"dimension":"deletion","values":["active"]} + ] + }`) + + require.Equal(http.StatusOK, response.Code, response.Body.String()) + var body SearchCoverageResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assert.Equal(int64(1), body.EligibleCount) + assert.Equal(int64(1), body.EmbeddedCount) + assert.Equal(SearchCoverageReady, body.Status) +} + +func TestSearchCoverageOpenAPIContract(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + doc := OpenAPIDocument() + op := doc.Paths["/api/v1/search/coverage"].Post + require.NotNil(op) + assert.Equal("getSearchCoverage", op.OperationID) + for _, status := range []string{"200", "400", "503"} { + assert.Contains(op.Responses, status) + } + schema := doc.Components.Schemas.Map()["SearchCoverageResponse"] + require.NotNil(schema) + assert.ElementsMatch( + []any{"disabled", "initializing", "stale", "incomplete", "unavailable", "ready"}, + schema.Properties["status"].Enum, + ) + assert.False(schema.Properties["actions"].Nullable) + assert.ElementsMatch([]any{"retry", "build_index"}, schema.Properties["actions"].Items.Enum) +} + +func TestSearchCoverageAccumulatesBoundedExactPages(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + const total = 700 + pageCalls := 0 + engine := &coveragePagingEngine{coverage: func( + _ context.Context, + request query.ExploreCoverageRequest, + ) (*query.ExploreCoverageResponse, error) { + pageCalls++ + assert.Equal(vector.FilteredCoverageBatchSize, request.Limit) + start := request.AfterMessageID + 1 + if start > total { + return &query.ExploreCoverageResponse{CacheRevision: "cache:test"}, nil + } + end := min(start+int64(request.Limit)-1, total) + ids := make([]int64, 0, end-start+1) + for id := start; id <= end; id++ { + ids = append(ids, id) + } + var next *int64 + if end < total { + next = new(end) + } + return &query.ExploreCoverageResponse{ + MessageIDs: ids, NextAfterMessageID: next, CacheRevision: "cache:test", + }, nil + }} + backend := &filteredCoverageBackend{ + active: vector.Generation{ID: 7, Fingerprint: "", State: vector.GenerationActive}, + countAll: true, + } + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: &mockStore{stats: &StoreStats{}}, Engine: engine, Backend: backend, + VectorStatus: VectorStatusReady, Logger: testLogger(), + }) + + response := postExploreJSON(t, srv, "/api/v1/search/coverage", `{}`) + require.Equal(http.StatusOK, response.Code, response.Body.String()) + var body SearchCoverageResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assert.Equal(int64(total), body.EligibleCount) + assert.Equal(int64(total), body.EmbeddedCount) + assert.Equal(SearchCoverageReady, body.Status) + assert.Equal(3, pageCalls) + require.Len(backend.countCalls, 3) + for _, ids := range backend.countCalls { + assert.LessOrEqual(len(ids), vector.FilteredCoverageBatchSize) + } +} + +func TestSearchCoverageRejectsGenerationActivationDuringPagination(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + backend := &filteredCoverageBackend{ + active: vector.Generation{ID: 7, Fingerprint: "model:2", State: vector.GenerationActive}, + countAll: true, + } + pageCalls := 0 + engine := &coveragePagingEngine{coverage: func( + _ context.Context, + request query.ExploreCoverageRequest, + ) (*query.ExploreCoverageResponse, error) { + pageCalls++ + assert.Equal(vector.FilteredCoverageBatchSize, request.Limit) + if pageCalls == 1 { + ids := make([]int64, vector.FilteredCoverageBatchSize) + for i := range ids { + ids[i] = int64(i + 1) + } + return &query.ExploreCoverageResponse{ + MessageIDs: ids, NextAfterMessageID: new(int64(vector.FilteredCoverageBatchSize)), + CacheRevision: "cache:test", + }, nil + } + backend.active = vector.Generation{ID: 8, Fingerprint: "model:2", State: vector.GenerationActive} + return &query.ExploreCoverageResponse{ + MessageIDs: []int64{int64(vector.FilteredCoverageBatchSize + 1)}, CacheRevision: "cache:test", + }, nil + }} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: &mockStore{stats: &StoreStats{}}, Engine: engine, Backend: backend, + VectorStatus: VectorStatusReady, Logger: testLogger(), + }) + + response := postExploreJSON(t, srv, "/api/v1/search/coverage", `{}`) + + assert.Equal(http.StatusServiceUnavailable, response.Code, response.Body.String()) + var body ErrorResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assert.Equal("vector_generation_changed", body.Error) + assert.Equal(2, pageCalls) + require.Len(backend.countCalls, 2) + assert.Len(backend.countCalls[0], vector.FilteredCoverageBatchSize) +} + +func TestSearchCoverageRejectsCacheRevisionDriftDuringPagination(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + pageCalls := 0 + engine := &coveragePagingEngine{coverage: func( + _ context.Context, + _ query.ExploreCoverageRequest, + ) (*query.ExploreCoverageResponse, error) { + pageCalls++ + if pageCalls == 1 { + return &query.ExploreCoverageResponse{ + MessageIDs: []int64{1}, NextAfterMessageID: new(int64(1)), CacheRevision: "cache:one", + }, nil + } + return &query.ExploreCoverageResponse{MessageIDs: []int64{2}, CacheRevision: "cache:two"}, nil + }} + backend := &filteredCoverageBackend{ + active: vector.Generation{ID: 7, State: vector.GenerationActive}, countAll: true, + } + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: &mockStore{stats: &StoreStats{}}, Engine: engine, Backend: backend, + VectorStatus: VectorStatusReady, Logger: testLogger(), + }) + + response := postExploreJSON(t, srv, "/api/v1/search/coverage", `{}`) + + assert.Equal(http.StatusServiceUnavailable, response.Code, response.Body.String()) + var body ErrorResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assert.Equal("cache_changed", body.Error) + assert.Equal(2, pageCalls) + assert.Len(backend.countCalls, 1) +} + +func TestSearchCoverageStopsOnPagedErrorAndCancellation(t *testing.T) { + tests := []struct { + name string + err error + }{ + {name: "query error", err: errors.New("coverage page failed")}, + {name: "canceled", err: context.Canceled}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + calls := 0 + engine := &coveragePagingEngine{coverage: func( + _ context.Context, + _ query.ExploreCoverageRequest, + ) (*query.ExploreCoverageResponse, error) { + calls++ + if calls == 2 { + return nil, tt.err + } + return &query.ExploreCoverageResponse{ + MessageIDs: []int64{1}, NextAfterMessageID: new(int64(1)), CacheRevision: "cache:test", + }, nil + }} + backend := &filteredCoverageBackend{ + active: vector.Generation{ID: 7, State: vector.GenerationActive}, countAll: true, + } + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: &mockStore{stats: &StoreStats{}}, Engine: engine, Backend: backend, + VectorStatus: VectorStatusReady, Logger: testLogger(), + }) + + response := postExploreJSON(t, srv, "/api/v1/search/coverage", `{}`) + assert.NotEqual(t, http.StatusOK, response.Code, response.Body.String()) + assert.Equal(t, 2, calls) + assert.Len(t, backend.countCalls, 1) + }) + } +} + +func TestSearchCoverageRetryDoesNotRequireCLIRunner(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + engine := newExploreDuckDBFixture(t) + readOnlyStore := struct{ MessageStore }{MessageStore: &mockStore{stats: &StoreStats{}}} + backend := &filteredCoverageBackend{activeErr: errors.New("vector database is busy")} + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Store: readOnlyStore, Engine: engine, Backend: backend, + VectorStatus: VectorStatusReady, Logger: testLogger(), + }) + + response := postExploreJSON(t, srv, "/api/v1/search/coverage", `{}`) + require.Equal(http.StatusOK, response.Code, response.Body.String()) + var body SearchCoverageResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assert.Equal(SearchCoverageUnavailable, body.Status) + assert.Equal([]SearchCoverageAction{SearchCoverageActionRetry}, body.Actions) +} + +func TestSearchCoverageResolvesGenerationStateAndRefreshesStale(t *testing.T) { + vectorCfg := vector.Config{ + Enabled: true, + Embeddings: vector.EmbeddingsConfig{ + Model: "test-model", Dimension: 2, MaxInputChars: 1000, + }, + } + fingerprint := vectorCfg.GenerationFingerprint() + matching := vector.Generation{ID: 7, Fingerprint: fingerprint, State: vector.GenerationActive} + tests := []struct { + name string + status VectorStatus + backend *filteredCoverageBackend + wantStatus SearchCoverageStatus + wantAction []SearchCoverageAction + }{ + { + name: "matching first build", status: VectorStatusReady, + backend: &filteredCoverageBackend{ + activeErr: vector.ErrNoActiveGeneration, + building: &vector.Generation{ID: 8, Fingerprint: fingerprint, State: vector.GenerationBuilding}, + }, + wantStatus: SearchCoverageInitializing, + }, + { + name: "no generation", status: VectorStatusReady, + backend: &filteredCoverageBackend{ + activeErr: vector.ErrNoActiveGeneration, + }, + wantStatus: SearchCoverageUnavailable, + wantAction: []SearchCoverageAction{SearchCoverageActionRetry, SearchCoverageActionBuildIndex}, + }, + { + name: "different first build", status: VectorStatusReady, + backend: &filteredCoverageBackend{ + activeErr: vector.ErrNoActiveGeneration, + building: &vector.Generation{ID: 8, Fingerprint: "different", State: vector.GenerationBuilding}, + }, + wantStatus: SearchCoverageUnavailable, + wantAction: []SearchCoverageAction{SearchCoverageActionRetry}, + }, + { + name: "latched stale refreshed", status: VectorStatusStale, + backend: &filteredCoverageBackend{ + active: matching, embeddedIDs: map[int64]struct{}{1: {}, 2: {}, 3: {}}, + }, + wantStatus: SearchCoverageReady, + }, + { + name: "stale active without building generation", status: VectorStatusReady, + backend: &filteredCoverageBackend{ + active: vector.Generation{ID: 9, Fingerprint: "different", State: vector.GenerationActive}, + embeddedIDs: map[int64]struct{}{1: {}, 2: {}, 3: {}}, + }, + wantStatus: SearchCoverageStale, + wantAction: []SearchCoverageAction{SearchCoverageActionBuildIndex}, + }, + { + name: "stale active with matching building generation", status: VectorStatusReady, + backend: &filteredCoverageBackend{ + active: vector.Generation{ID: 9, Fingerprint: "different", State: vector.GenerationActive}, + building: &vector.Generation{ID: 10, Fingerprint: fingerprint, State: vector.GenerationBuilding}, + embeddedIDs: map[int64]struct{}{1: {}, 2: {}, 3: {}}, + }, + wantStatus: SearchCoverageStale, + wantAction: []SearchCoverageAction{SearchCoverageActionBuildIndex}, + }, + { + name: "stale active with mismatched building generation", status: VectorStatusReady, + backend: &filteredCoverageBackend{ + active: vector.Generation{ID: 9, Fingerprint: "different", State: vector.GenerationActive}, + building: &vector.Generation{ID: 10, Fingerprint: "other", State: vector.GenerationBuilding}, + embeddedIDs: map[int64]struct{}{1: {}, 2: {}, 3: {}}, + }, + wantStatus: SearchCoverageStale, + wantAction: []SearchCoverageAction{SearchCoverageActionRetry}, + }, + { + name: "stale active with building lookup failure", status: VectorStatusReady, + backend: &filteredCoverageBackend{ + active: vector.Generation{ID: 9, Fingerprint: "different", State: vector.GenerationActive}, + buildingErr: errors.New("building generation unavailable"), + embeddedIDs: map[int64]struct{}{1: {}, 2: {}, 3: {}}, + }, + wantStatus: SearchCoverageStale, + wantAction: []SearchCoverageAction{SearchCoverageActionRetry}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + cfg := &config.Config{Server: config.ServerConfig{APIPort: 8080}, Vector: vectorCfg} + srv := NewServerWithOptions(ServerOptions{ + Config: cfg, Store: &mockStore{stats: &StoreStats{}}, Engine: newExploreDuckDBFixture(t), + Backend: tt.backend, VectorCfg: vectorCfg, VectorStatus: tt.status, Logger: testLogger(), + }) + if tt.status == VectorStatusStale { + srv.SetVectorStale("old status") + } + + response := postExploreJSON(t, srv, "/api/v1/search/coverage", `{}`) + require.Equal(http.StatusOK, response.Code, response.Body.String()) + var body SearchCoverageResponse + require.NoError(json.Unmarshal(response.Body.Bytes(), &body)) + assert.Equal(tt.wantStatus, body.Status) + assert.ElementsMatch(tt.wantAction, body.Actions) + }) + } +} diff --git a/internal/api/server.go b/internal/api/server.go index 79d3405a2..52d9b93e2 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -11,6 +11,7 @@ import ( "net" "net/http" "net/http/pprof" + "net/netip" "strconv" "sync" "sync/atomic" @@ -21,8 +22,11 @@ import ( "go.kenn.io/msgvault/internal/scheduler" "go.kenn.io/msgvault/internal/search" "go.kenn.io/msgvault/internal/store" + "go.kenn.io/msgvault/internal/taskclient" + "go.kenn.io/msgvault/internal/tasklinks" "go.kenn.io/msgvault/internal/vector" "go.kenn.io/msgvault/internal/vector/hybrid" + webapp "go.kenn.io/msgvault/internal/web" ) // MessageStore defines the store operations the API needs. @@ -35,6 +39,16 @@ type MessageStore interface { SearchMessagesQuery(q *search.Query, offset, limit int) ([]APIMessage, int64, error) } +type TaskLinkOperations interface { + Create(ctx context.Context, key string, create taskclient.TaskCreate, identity tasklinks.MessageIdentity, addedAt time.Time) (taskclient.Task, error) + Link(ctx context.Context, taskID string, identity tasklinks.MessageIdentity, addedAt time.Time) (taskclient.Task, error) + Unlink(ctx context.Context, taskID string, identity tasklinks.MessageIdentity) (taskclient.Task, error) + Lookup(ctx context.Context, identity tasklinks.MessageIdentity) tasklinks.LookupResult + Search(ctx context.Context, query string) ([]tasklinks.TaskSummary, error) +} + +type TaskIdentityResolver func(context.Context, *APIMessage) (tasklinks.MessageIdentity, error) + // ctxMessageSearcher is an optional extension of MessageStore for stores that // accept a context on the search path. handleSearch prefers it so an // abandoned or timed-out request cancels the underlying query instead of @@ -128,6 +142,7 @@ type AttachmentBlobStore interface { type Server struct { cfg *config.Config store MessageStore + savedViewStore SavedViewStore engine query.Engine // Query engine for aggregates and TUI support sqlQueryRunner SQLQueryRunner shutdownToken string @@ -172,6 +187,12 @@ type Server struct { // index clear; the worker snapshots this generation before probing and // memoizes a "complete" observation only on an even, unchanged value. ftsRebuildGen atomic.Uint64 + // settingsPendingRestart remains set after the first successful browser + // config edit for the lifetime of this daemon process. + settingsPendingRestart atomic.Bool + // settingsConfigEditor is the persisted config transaction boundary. Tests + // replace it to deterministically exercise post-publication error handling. + settingsConfigEditor func(string, string, []config.Edit) (config.ConfigFile, error) // activity reports request-scoped work that health should surface even // though it runs outside (or with more detail than) the operation gate, // e.g. the first-search FTS completeness probe and backfill progress. @@ -196,7 +217,19 @@ type Server struct { // blobStore serves attachment bytes for handleCLIAttachment. Nil in tests // and embedded callers that construct a Server without options, which // fall back to the legacy loose-file open. - blobStore AttachmentBlobStore + blobStore AttachmentBlobStore + spaHandler http.Handler + sessions *sessionStore + // trustedProxies contains only explicitly configured direct proxy peers. + // Forwarded scheme/host data is ignored for every other RemoteAddr. + trustedProxies []netip.Prefix + exploreState *exploreServerState + exploreCursorKey [32]byte + // taskIntegrationProbe performs server-side discovery and capability + // validation. It is never exposed to the browser with its credentials. + taskIntegrationProbe TaskIntegrationProbe + taskLinkOperations TaskLinkOperations + taskIdentityResolver TaskIdentityResolver } type SQLQueryRunner func(ctx context.Context, sql string) (*query.QueryResult, error) @@ -224,8 +257,12 @@ const ( // ServerOptions configures the API server. type ServerOptions struct { - Config *config.Config - Store MessageStore + Config *config.Config + Store MessageStore + // SavedViewStore owns durable analytical view definitions. It is separate + // from the minimal MessageStore so API consumers do not need to implement + // unrelated persistence methods. + SavedViewStore SavedViewStore Engine query.Engine // Optional: query engine for aggregates and TUI support SQLQueryRunner SQLQueryRunner ShutdownToken string @@ -259,6 +296,15 @@ type ServerOptions struct { // whether aggregate views run on the cache or live SQL. Empty omits the // field. AnalyticsMode string + // SPAHandler overrides the embedded browser application handler. Nil uses + // internal/web.Handler and is the production default. Tests may inject a + // handler built over an in-memory filesystem. + SPAHandler http.Handler + // TaskIntegrationProbe overrides provider-neutral task discovery for tests. + // Nil uses taskclient.Evaluate. + TaskIntegrationProbe TaskIntegrationProbe + TaskLinkOperations TaskLinkOperations + TaskIdentityResolver TaskIdentityResolver } // NewServer creates a new API server. @@ -277,27 +323,47 @@ func NewServerWithOptions(opts ServerOptions) *Server { if timeout <= 0 { timeout = 60 * time.Second } + taskProbe := opts.TaskIntegrationProbe + if taskProbe == nil { + taskProbe = taskclient.Evaluate + } s := &Server{ - cfg: opts.Config, - store: opts.Store, - engine: opts.Engine, - sqlQueryRunner: opts.SQLQueryRunner, - shutdownToken: opts.ShutdownToken, - shutdownFunc: opts.ShutdownFunc, - hybridEngine: opts.HybridEngine, - vectorCfg: opts.VectorCfg, - backend: opts.Backend, - scheduler: opts.Scheduler, - logger: opts.Logger, - requestTimeout: timeout, - queryTimeout: QueryEndpointTimeout, - inProgressThreshold: inProgressLogThreshold, - inProgressInterval: inProgressLogInterval, - daemonVersion: opts.DaemonVersion, - analyticsMode: opts.AnalyticsMode, - idleTracker: opts.IdleTracker, - operationGate: opts.OperationGate, - blobStore: opts.BlobStore, + cfg: opts.Config, + store: opts.Store, + savedViewStore: opts.SavedViewStore, + engine: opts.Engine, + sqlQueryRunner: opts.SQLQueryRunner, + shutdownToken: opts.ShutdownToken, + shutdownFunc: opts.ShutdownFunc, + hybridEngine: opts.HybridEngine, + vectorCfg: opts.VectorCfg, + backend: opts.Backend, + scheduler: opts.Scheduler, + logger: opts.Logger, + requestTimeout: timeout, + queryTimeout: QueryEndpointTimeout, + inProgressThreshold: inProgressLogThreshold, + inProgressInterval: inProgressLogInterval, + daemonVersion: opts.DaemonVersion, + analyticsMode: opts.AnalyticsMode, + idleTracker: opts.IdleTracker, + operationGate: opts.OperationGate, + blobStore: opts.BlobStore, + spaHandler: opts.SPAHandler, + sessions: newSessionStore(defaultSessionTTL), + exploreState: newExploreServerState(time.Now), + exploreCursorKey: newExploreCursorKey(), + trustedProxies: trustedProxyPrefixes(opts.Config.Server.TrustedProxies), + settingsConfigEditor: config.EditConfigFile, + taskIntegrationProbe: taskProbe, + taskLinkOperations: opts.TaskLinkOperations, + taskIdentityResolver: opts.TaskIdentityResolver, + } + if s.taskIdentityResolver == nil { + s.taskIdentityResolver = s.resolveTaskMessageIdentity + } + if s.taskLinkOperations == nil { + s.taskLinkOperations = newTaskLinkBackend(opts.Config) } s.vectorStatus = opts.VectorStatus if s.vectorStatus == "" { @@ -319,11 +385,14 @@ func (s *Server) setupRouter() http.Handler { s.registerHumaRoutes(api, apiV1) s.registerPprofHandlers(mux) - // Catch-all so unknown paths and trailing-slash misses return the JSON - // ErrorResponse envelope the contract declares, instead of Go's default - // text/plain "404 page not found". More specific patterns (API routes, - // /debug/pprof/, /openapi.*, /docs) still take precedence over "/". - mux.HandleFunc("/", s.handleNotFound) + // Registered API, debug, OpenAPI, and docs routes retain priority over the + // root handler. The root serves exact embedded assets and safe navigation; + // everything else delegates to the JSON ErrorResponse fallback. + spaHandler := s.spaHandler + if spaHandler == nil { + spaHandler = webapp.Handler(http.HandlerFunc(s.handleNotFound)) + } + mux.Handle("/", spaHandler) // CORS middleware (config-driven; disabled when no origins configured) corsConfig := CORSConfig{ @@ -340,11 +409,14 @@ func (s *Server) setupRouter() http.Handler { // Rate limiting (10 req/sec with burst of 20) s.rateLimiter = NewRateLimiter(10, 20) - // The operation gate sits inside rate limiting and checks API auth - // itself, so unauthenticated or rate-limited requests are rejected - // before they can register as gate waiters or observe operation state. + // Request security classification and CSRF checks sit inside rate limiting + // but outside the operation gate, so rejected browser mutations never wait + // on or observe archive work. The gate still checks API auth itself so + // unauthenticated requests do not register as waiters. var h http.Handler = mux h = operationGateMiddleware(s.operationGate, s.apiRequestAuthorized)(h) + h = s.csrfMiddleware(h) + h = s.requestSecurityMiddleware(h) h = RateLimitMiddleware(s.rateLimiter, s.loopbackRateLimitExempt)(h) h = CORSMiddleware(corsConfig)(h) h = s.timeoutMiddleware(h) @@ -354,6 +426,8 @@ func (s *Server) setupRouter() http.Handler { h = s.recoverMiddleware(h) h = s.loggerMiddleware(h) h = requestIDMiddleware(h) + h = sessionCacheControlMiddleware(h) + h = settingsNoStoreMiddleware(h) return h } @@ -437,6 +511,9 @@ func (s *Server) Shutdown(ctx context.Context) error { if s.rateLimiter != nil { s.rateLimiter.Close() } + if s.sessions != nil { + s.sessions.Close() + } if s.server == nil { return nil } @@ -674,24 +751,10 @@ func (w *trackingResponseWriter) WroteHeader() bool { // returns true in exactly those two cases, so it is reused here to avoid the // auth logic drifting. func (s *Server) loopbackRateLimitExempt(r *http.Request) bool { - return isLoopbackRequest(r) && s.apiRequestAuthorized(r) -} - -func (s *Server) apiRequestAuthorized(r *http.Request) bool { - // Skip auth if no API key configured. - if s.cfg.Server.APIKey == "" { - return true - } - - authHeader := r.Header.Get("Authorization") - if authHeader == "" { - authHeader = r.Header.Get("X-Api-Key") - } - if len(authHeader) > 7 && authHeader[:7] == "Bearer " { - authHeader = authHeader[7:] + if r.Method == http.MethodPost && r.URL.Path == sessionLoginPath { + return false } - - return subtle.ConstantTimeCompare([]byte(authHeader), []byte(s.cfg.Server.APIKey)) == 1 + return isLoopbackRequest(r) && s.apiRequestAuthorized(r) } func (s *Server) logUnauthorizedAPIRequest(r *http.Request) { diff --git a/internal/api/server_test.go b/internal/api/server_test.go index d112fb0bc..d77e864db 100644 --- a/internal/api/server_test.go +++ b/internal/api/server_test.go @@ -250,13 +250,16 @@ type mockStore struct { // Call counts so tests can assert that bulk hydration paths use // GetMessagesSummariesByIDs (one round-trip) instead of looping // GetMessage (per-hit N+1). - getMessageCalls atomic.Int32 - getSummariesByIDsCalls atomic.Int32 - getSummariesByIDsLastIDs []int64 - searchMessagesCalls atomic.Int32 - searchMessagesQueryCalls atomic.Int32 - searchMessagesQueryLast *search.Query - needsFTSBackfillCalls atomic.Int32 + getMessageCalls atomic.Int32 + getSummariesByIDsCalls atomic.Int32 + getSummariesByIDsLastIDs []int64 + searchMessagesCalls atomic.Int32 + searchMessagesQueryCalls atomic.Int32 + searchMessagesQueryLast *search.Query + searchMessagesQueryFunc func(*search.Query, int, int) ([]APIMessage, int64, error) + searchMessagesQueryLimits []int + searchMessagesQueryTransferred int + needsFTSBackfillCalls atomic.Int32 sourcesByLookup map[string][]*store.Source sourcesByLookupErr error @@ -339,6 +342,7 @@ func (m *mockStore) SearchMessagesQueryContext(ctx context.Context, q *search.Qu func (m *mockStore) SearchMessagesQuery(q *search.Query, offset, limit int) ([]APIMessage, int64, error) { m.searchMessagesQueryCalls.Add(1) + m.searchMessagesQueryLimits = append(m.searchMessagesQueryLimits, limit) if q != nil { cp := *q cp.AccountIDs = append([]int64(nil), q.AccountIDs...) @@ -347,6 +351,12 @@ func (m *mockStore) SearchMessagesQuery(q *search.Query, offset, limit int) ([]A } else { m.searchMessagesQueryLast = nil } + if m.searchMessagesQueryFunc != nil { + messages, total, err := m.searchMessagesQueryFunc(q, offset, limit) + m.searchMessagesQueryTransferred += len(messages) + return messages, total, err + } + m.searchMessagesQueryTransferred += len(m.messages) return m.messages, m.total, nil } diff --git a/internal/api/session.go b/internal/api/session.go new file mode 100644 index 000000000..829bba64c --- /dev/null +++ b/internal/api/session.go @@ -0,0 +1,119 @@ +package api + +import ( + "crypto/rand" + "crypto/sha256" + "crypto/subtle" + "encoding/base64" + "fmt" + "io" + "sync" + "time" +) + +const ( + defaultSessionTTL = 24 * time.Hour + randomTokenBytes = 32 + // Keep login latency independent of the total number of stored sessions. + // New logins opportunistically reclaim expired entries in bounded batches. + sessionPurgeScanLimit = 16 +) + +type browserSession struct { + CSRFToken string + ExpiresAt time.Time +} + +type sessionStore struct { + mu sync.Mutex + sessions map[string]browserSession + ttl time.Duration + now func() time.Time + random io.Reader +} + +func newSessionStore(ttl time.Duration) *sessionStore { + return &sessionStore{ + sessions: make(map[string]browserSession), + ttl: ttl, + now: time.Now, + random: rand.Reader, + } +} + +func (s *sessionStore) create() (string, browserSession, error) { + id, err := s.randomToken() + if err != nil { + return "", browserSession{}, fmt.Errorf("generate session ID: %w", err) + } + csrfToken, err := s.randomToken() + if err != nil { + return "", browserSession{}, fmt.Errorf("generate CSRF token: %w", err) + } + now := s.now() + session := browserSession{ + CSRFToken: csrfToken, + ExpiresAt: now.Add(s.ttl), + } + s.mu.Lock() + s.purgeExpiredLocked(now) + s.sessions[id] = session + s.mu.Unlock() + return id, session, nil +} + +func (s *sessionStore) purgeExpiredLocked(now time.Time) { + inspected := 0 + for id, session := range s.sessions { + if inspected == sessionPurgeScanLimit { + return + } + inspected++ + if !now.Before(session.ExpiresAt) { + delete(s.sessions, id) + } + } +} + +func (s *sessionStore) lookup(id string) (browserSession, bool) { + if id == "" { + return browserSession{}, false + } + s.mu.Lock() + defer s.mu.Unlock() + session, ok := s.sessions[id] + if !ok { + return browserSession{}, false + } + if !s.now().Before(session.ExpiresAt) { + delete(s.sessions, id) + return browserSession{}, false + } + return session, true +} + +func (s *sessionStore) delete(id string) { + s.mu.Lock() + delete(s.sessions, id) + s.mu.Unlock() +} + +func (s *sessionStore) Close() { + s.mu.Lock() + clear(s.sessions) + s.mu.Unlock() +} + +func (s *sessionStore) randomToken() (string, error) { + raw := make([]byte, randomTokenBytes) + if _, err := io.ReadFull(s.random, raw); err != nil { + return "", err + } + return base64.RawURLEncoding.EncodeToString(raw), nil +} + +func constantTimeAPIKeyEqual(got, want string) bool { + gotHash := sha256.Sum256([]byte(got)) + wantHash := sha256.Sum256([]byte(want)) + return subtle.ConstantTimeCompare(gotHash[:], wantHash[:]) == 1 +} diff --git a/internal/api/session_routes.go b/internal/api/session_routes.go new file mode 100644 index 000000000..4204c2cc2 --- /dev/null +++ b/internal/api/session_routes.go @@ -0,0 +1,168 @@ +package api + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/danielgtaylor/huma/v2" +) + +const ( + sessionPath = "/api/session" + sessionLoginPath = "/api/session/login" + sessionCookieName = "msgvault_session" +) + +// AuthMode describes why the current request may access protected API routes. +type AuthMode string + +const ( + AuthModeLoopback AuthMode = "loopback" + AuthModeAPIKey AuthMode = "api_key" + AuthModeSession AuthMode = "session" + AuthModeRequired AuthMode = "required" +) + +// SessionLoginRequest exchanges the active daemon API key for an in-memory +// browser session. +type SessionLoginRequest struct { + APIKey string `json:"api_key"` +} + +// SessionStatus reports the request's effective authentication mode. The CSRF +// token is returned only for a valid browser session so mutation middleware +// can enforce session-bound requests without exposing it to other auth modes. +type SessionStatus struct { + AuthMode AuthMode `json:"auth_mode" enum:"loopback,api_key,session,required"` + CSRFToken string `json:"csrf_token,omitempty"` + HTTPS bool `json:"https"` + PlainHTTPWarning bool `json:"plain_http_warning"` +} + +func (s *Server) registerSessionRoutes(api huma.API) { + login := huma.Operation{ + OperationID: "loginSession", + Method: http.MethodPost, + Path: sessionLoginPath, + Tags: []string{"Session"}, + Summary: "Create an in-memory browser session", + Errors: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusTooManyRequests, http.StatusInternalServerError}, + RequestBody: jsonRequestBodyFor[SessionLoginRequest](api), + Responses: jsonResponsesFor[SessionStatus](api), + } + registerRawHumaRoute(api, login, s.handleSessionLogin) + + bootstrap := huma.Operation{ + OperationID: "getSession", + Method: http.MethodGet, + Path: sessionPath, + Tags: []string{"Session"}, + Summary: "Get browser authentication status", + Responses: jsonResponsesFor[SessionStatus](api), + } + registerRawHumaRoute(api, bootstrap, s.handleSessionBootstrap) + + logout := huma.Operation{ + OperationID: "logoutSession", + Method: http.MethodDelete, + Path: sessionPath, + Tags: []string{"Session"}, + Summary: "Delete the current browser session", + Errors: []int{http.StatusTooManyRequests}, + Responses: map[string]*huma.Response{ + httpStatusKey(http.StatusNoContent): {Description: http.StatusText(http.StatusNoContent)}, + httpStatusKey(http.StatusTooManyRequests): errorResponseFor(api), + "default": errorResponseFor(api), + }, + } + registerRawHumaRoute(api, logout, s.handleSessionLogout) +} + +func sessionCacheControlMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == sessionPath || r.URL.Path == sessionLoginPath { + w.Header().Set("Cache-Control", "no-store") + } + next.ServeHTTP(w, r) + }) +} + +func (s *Server) handleSessionLogin(w http.ResponseWriter, r *http.Request) { + var input SessionLoginRequest + decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&input); err != nil { + writeError(w, http.StatusBadRequest, "bad_request", "Invalid session login request") + return + } + if s.cfg.Server.APIKey == "" || !constantTimeAPIKeyEqual(input.APIKey, s.cfg.Server.APIKey) { + writeError(w, http.StatusUnauthorized, "unauthorized", "Invalid API key") + return + } + + id, session, err := s.sessions.create() + if err != nil { + s.logger.Error("create browser session", "error", err) + writeError(w, http.StatusInternalServerError, "internal_error", "Could not create browser session") + return + } + https := requestUsesHTTPS(r) + // Secure follows the verified connection scheme; plain HTTP support is an + // explicit deployment mode surfaced by PlainHTTPWarning. + //nolint:gosec // Host-only, HttpOnly, and Strict are fixed below; Secure is connection-dependent. + http.SetCookie(w, &http.Cookie{ + Name: sessionCookieName, + Value: id, + Path: "/", + Expires: session.ExpiresAt, + MaxAge: max(1, int(s.sessions.ttl/time.Second)), + HttpOnly: true, + Secure: https, + SameSite: http.SameSiteStrictMode, + }) + writeJSON(w, http.StatusOK, sessionStatus(AuthModeSession, session.CSRFToken, https)) +} + +func (s *Server) handleSessionBootstrap(w http.ResponseWriter, r *http.Request) { + auth := s.requestAuthentication(r) + csrfToken := "" + if auth.Mode == AuthModeSession { + csrfToken = auth.Session.CSRFToken + } + writeJSON(w, http.StatusOK, sessionStatus(auth.Mode, csrfToken, requestUsesHTTPS(r))) +} + +func (s *Server) handleSessionLogout(w http.ResponseWriter, r *http.Request) { + if cookie, err := r.Cookie(sessionCookieName); err == nil { + s.sessions.delete(cookie.Value) + } + //nolint:gosec // Expiration mirrors the connection-dependent flags of the session cookie being cleared. + http.SetCookie(w, &http.Cookie{ + Name: sessionCookieName, + Value: "", + Path: "/", + Expires: time.Unix(1, 0), + MaxAge: -1, + HttpOnly: true, + Secure: requestUsesHTTPS(r), + SameSite: http.SameSiteStrictMode, + }) + w.WriteHeader(http.StatusNoContent) +} + +func sessionStatus(mode AuthMode, csrfToken string, https bool) SessionStatus { + return SessionStatus{ + AuthMode: mode, + CSRFToken: csrfToken, + HTTPS: https, + PlainHTTPWarning: !https, + } +} + +func requestUsesHTTPS(r *http.Request) bool { + if security, ok := securityFromRequest(r); ok { + return security.scheme == "https" + } + return r.TLS != nil +} diff --git a/internal/api/session_test.go b/internal/api/session_test.go new file mode 100644 index 000000000..a42cc6272 --- /dev/null +++ b/internal/api/session_test.go @@ -0,0 +1,458 @@ +package api + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/base64" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" +) + +const testSessionAPIKey = "test-session-api-key" + +func TestSessionStoreCreatesOpaqueExpiringSessions(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + now := time.Date(2026, time.July, 18, 12, 0, 0, 0, time.UTC) + store := newSessionStore(time.Hour) + store.now = func() time.Time { return now } + + id, session, err := store.create() + requirements.NoError(err) + requirements.NotEmpty(id) + assertions.NotContains(id, testSessionAPIKey) + rawID, err := base64.RawURLEncoding.DecodeString(id) + requirements.NoError(err) + assertions.Len(rawID, 32, "session IDs use 256 bits of randomness") + assertions.NotEmpty(session.CSRFToken) + assertions.Equal(now.Add(time.Hour), session.ExpiresAt) + + got, ok := store.lookup(id) + requirements.True(ok) + assertions.Equal(session, got) + + now = now.Add(time.Hour) + _, ok = store.lookup(id) + assertions.False(ok, "a session expires at its deadline") +} + +func TestSessionStoreDeleteAndCloseClearSessions(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + store := newSessionStore(time.Hour) + firstID, _, err := store.create() + requirements.NoError(err) + secondID, _, err := store.create() + requirements.NoError(err) + + store.delete(firstID) + _, ok := store.lookup(firstID) + assertions.False(ok) + _, ok = store.lookup(secondID) + requirements.True(ok) + + store.Close() + _, ok = store.lookup(secondID) + assertions.False(ok) +} + +func TestSessionStoreCreatePurgesOnlyBoundedExpiredEntries(t *testing.T) { + const expectedPurgeScanLimit = 16 + requirements := require.New(t) + now := time.Date(2026, time.July, 18, 12, 0, 0, 0, time.UTC) + store := newSessionStore(time.Minute) + store.now = func() time.Time { return now } + + initialSessions := expectedPurgeScanLimit + 5 + for range initialSessions { + _, _, err := store.create() + requirements.NoError(err) + } + requirements.Len(store.sessions, initialSessions) + + now = now.Add(time.Minute) + _, _, err := store.create() + requirements.NoError(err) + assert.Len(t, store.sessions, initialSessions+1-expectedPurgeScanLimit, + "one create purges at most the bounded scan quota") +} + +func TestConstantTimeAPIKeyEqual(t *testing.T) { + assert.True(t, constantTimeAPIKeyEqual(testSessionAPIKey, testSessionAPIKey)) + assert.False(t, constantTimeAPIKeyEqual("wrong-session-api-key", testSessionAPIKey)) + assert.False(t, constantTimeAPIKeyEqual("short", testSessionAPIKey)) +} + +func TestSessionOpenAPIRoutesDoNotReplaceAPIKeySecurity(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + doc := OpenAPIDocument() + requirements.NotNil(doc.Paths[sessionPath]) + assertions.Empty(doc.Paths[sessionPath].Get.Security) + assertions.Empty(doc.Paths[sessionPath].Delete.Security) + for _, responseKey := range []string{"429", "default"} { + logoutError := doc.Paths[sessionPath].Delete.Responses[responseKey] + requirements.NotNil(logoutError, "logout response %s", responseKey) + logoutJSONError := logoutError.Content["application/json"] + requirements.NotNil(logoutJSONError, "logout response %s JSON content", responseKey) + requirements.NotNil(logoutJSONError.Schema, "logout response %s schema", responseKey) + assertions.Equal("#/components/schemas/ErrorResponse", logoutJSONError.Schema.Ref, + "logout response %s schema", responseKey) + } + requirements.NotNil(doc.Paths[sessionLoginPath]) + assertions.Empty(doc.Paths[sessionLoginPath].Post.Security) + + health := doc.Paths["/api/v1/health"].Get + requirements.Len(health.Security, 1) + assertions.Contains(health.Security[0], apiKeySecurityScheme) + assertions.Len(doc.Components.SecuritySchemes, 1, "cookie sessions stay additive to the documented API-key scheme") +} + +func TestLoginSuccessCreatesOpaqueHostOnlyCookie(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := newSessionTestServer(t, testSessionAPIKey) + + resp := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), nil, false) + requirements.Equal(http.StatusOK, resp.Code, resp.Body.String()) + status := decodeSessionStatus(t, resp) + assertions.Equal(AuthModeSession, status.AuthMode) + assertions.NotEmpty(status.CSRFToken) + assertions.False(status.HTTPS) + assertions.True(status.PlainHTTPWarning) + assertions.Equal("no-store", resp.Header().Get("Cache-Control")) + + cookie := requireSessionCookie(t, resp) + assertions.NotContains(cookie.Value, testSessionAPIKey) + rawID, err := base64.RawURLEncoding.DecodeString(cookie.Value) + requirements.NoError(err) + assertions.Len(rawID, 32) + assertions.Empty(cookie.Domain, "the session cookie must remain host-only") + assertions.Equal("/", cookie.Path) + assertions.True(cookie.HttpOnly) + assertions.Equal(http.SameSiteStrictMode, cookie.SameSite) + assertions.False(cookie.Secure) +} + +func TestLoginFailureDoesNotCreateCookie(t *testing.T) { + srv := newSessionTestServer(t, testSessionAPIKey) + + resp := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"wrong-key"}`), nil, false) + assert.Equal(t, http.StatusUnauthorized, resp.Code) + assert.Empty(t, resp.Result().Cookies()) + assert.Equal(t, "no-store", resp.Header().Get("Cache-Control")) +} + +func TestLoginOverDirectTLSUsesSecureCookie(t *testing.T) { + assertions := assert.New(t) + srv := newSessionTestServer(t, testSessionAPIKey) + + resp := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), nil, true) + require.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) + status := decodeSessionStatus(t, resp) + assertions.True(status.HTTPS) + assertions.False(status.PlainHTTPWarning) + assertions.True(requireSessionCookie(t, resp).Secure) +} + +func TestSessionBootstrapModes(t *testing.T) { + t.Run("loopback", func(t *testing.T) { + assertions := assert.New(t) + srv := newSessionTestServer(t, "") + resp := performSessionRequest(t, srv, http.MethodGet, sessionPath, nil, nil, false) + require.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) + status := decodeSessionStatus(t, resp) + assertions.Equal(AuthModeLoopback, status.AuthMode) + assertions.Empty(status.CSRFToken) + assertions.Equal("no-store", resp.Header().Get("Cache-Control")) + }) + + t.Run("trusted API key", func(t *testing.T) { + srv := newSessionTestServer(t, testSessionAPIKey) + headers := http.Header{"X-Api-Key": []string{testSessionAPIKey}} + resp := performSessionRequest(t, srv, http.MethodGet, sessionPath, nil, headers, false) + require.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) + status := decodeSessionStatus(t, resp) + assert.Equal(t, AuthModeAPIKey, status.AuthMode) + assert.Empty(t, status.CSRFToken) + }) + + t.Run("browser session", func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := newSessionTestServer(t, testSessionAPIKey) + login := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), nil, false) + requirements.Equal(http.StatusOK, login.Code, login.Body.String()) + loginStatus := decodeSessionStatus(t, login) + cookie := requireSessionCookie(t, login) + + headers := http.Header{"Cookie": []string{cookie.String()}} + resp := performSessionRequest(t, srv, http.MethodGet, sessionPath, nil, headers, false) + requirements.Equal(http.StatusOK, resp.Code, resp.Body.String()) + status := decodeSessionStatus(t, resp) + assertions.Equal(AuthModeSession, status.AuthMode) + assertions.Equal(loginStatus.CSRFToken, status.CSRFToken) + + health := performSessionRequest(t, srv, http.MethodGet, "/api/v1/health", nil, headers, false) + assertions.Equal(http.StatusOK, health.Code, health.Body.String()) + }) + + t.Run("API key wins over browser session", func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := newSessionTestServer(t, testSessionAPIKey) + login := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), nil, false) + requirements.Equal(http.StatusOK, login.Code, login.Body.String()) + cookie := requireSessionCookie(t, login) + + headers := http.Header{ + "Cookie": []string{cookie.String()}, + "X-Api-Key": []string{testSessionAPIKey}, + } + resp := performSessionRequest(t, srv, http.MethodGet, sessionPath, nil, headers, false) + requirements.Equal(http.StatusOK, resp.Code, resp.Body.String()) + status := decodeSessionStatus(t, resp) + assertions.Equal(AuthModeAPIKey, status.AuthMode) + assertions.Empty(status.CSRFToken) + }) + + t.Run("unauthenticated remote", func(t *testing.T) { + srv := newSessionTestServer(t, testSessionAPIKey) + resp := performSessionRequest(t, srv, http.MethodGet, sessionPath, nil, nil, false) + require.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) + status := decodeSessionStatus(t, resp) + assert.Equal(t, AuthModeRequired, status.AuthMode) + assert.Empty(t, status.CSRFToken) + }) +} + +func TestLoginIgnoresUntrustedForwardedHTTPS(t *testing.T) { + assertions := assert.New(t) + srv := newSessionTestServer(t, testSessionAPIKey) + headers := http.Header{"X-Forwarded-Proto": []string{"https"}} + + resp := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), headers, false) + require.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) + status := decodeSessionStatus(t, resp) + assertions.False(status.HTTPS) + assertions.True(status.PlainHTTPWarning) + assertions.False(requireSessionCookie(t, resp).Secure) +} + +func TestSessionExpiryRevokesCookieAuthentication(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := newSessionTestServer(t, testSessionAPIKey) + now := time.Date(2026, time.July, 18, 12, 0, 0, 0, time.UTC) + srv.sessions.now = func() time.Time { return now } + srv.sessions.ttl = time.Minute + + login := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), nil, false) + requirements.Equal(http.StatusOK, login.Code, login.Body.String()) + cookie := requireSessionCookie(t, login) + + now = now.Add(time.Minute) + headers := http.Header{"Cookie": []string{cookie.String()}} + resp := performSessionRequest(t, srv, http.MethodGet, sessionPath, nil, headers, false) + requirements.Equal(http.StatusOK, resp.Code, resp.Body.String()) + assertions.Equal(AuthModeRequired, decodeSessionStatus(t, resp).AuthMode) + + health := performSessionRequest(t, srv, http.MethodGet, "/api/v1/health", nil, headers, false) + assertions.Equal(http.StatusUnauthorized, health.Code) +} + +func TestSessionLogoutInvalidatesCookie(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + srv := newSessionTestServer(t, testSessionAPIKey) + login := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), nil, false) + requirements.Equal(http.StatusOK, login.Code, login.Body.String()) + loginStatus := decodeSessionStatus(t, login) + cookie := requireSessionCookie(t, login) + + headers := http.Header{ + "Cookie": []string{cookie.String()}, + "Origin": []string{"http://example.com"}, + csrfHeaderName: []string{loginStatus.CSRFToken}, + } + logout := performSessionRequest(t, srv, http.MethodDelete, sessionPath, nil, headers, false) + assertions.Equal(http.StatusNoContent, logout.Code, logout.Body.String()) + assertions.Equal("no-store", logout.Header().Get("Cache-Control")) + cleared := requireSessionCookie(t, logout) + assertions.Empty(cleared.Value) + assertions.Negative(cleared.MaxAge) + + bootstrap := performSessionRequest(t, srv, http.MethodGet, sessionPath, nil, headers, false) + requirements.Equal(http.StatusOK, bootstrap.Code, bootstrap.Body.String()) + assertions.Equal(AuthModeRequired, decodeSessionStatus(t, bootstrap).AuthMode) +} + +func TestSessionMutationsBypassHeldArchiveOperationGate(t *testing.T) { + oldLimit := operationGateWaitLimit + operationGateWaitLimit = 20 * time.Millisecond + t.Cleanup(func() { operationGateWaitLimit = oldLimit }) + + gate := NewSerialOperationGate() + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIKey: testSessionAPIKey}}, + Logger: testLogger(), + OperationGate: gate, + }) + t.Cleanup(func() { + require.NoError(t, srv.Shutdown(context.Background())) + }) + + login := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), nil, false) + require.Equal(t, http.StatusOK, login.Code, login.Body.String()) + loginStatus := decodeSessionStatus(t, login) + cookie := requireSessionCookie(t, login) + + releaseGate, ok := gate.BeginLabeledWorkContext(context.Background(), "archive sync") + require.True(t, ok) + defer releaseGate() + + t.Run("logout with ambient session", func(t *testing.T) { + headers := http.Header{ + "Cookie": []string{cookie.String()}, + "Origin": []string{"http://example.com"}, + csrfHeaderName: []string{loginStatus.CSRFToken}, + } + resp := performSessionRequest(t, srv, http.MethodDelete, sessionPath, nil, headers, false) + assert.Equal(t, http.StatusNoContent, resp.Code, resp.Body.String()) + }) + + t.Run("login with ambient API key", func(t *testing.T) { + headers := http.Header{"X-Api-Key": []string{testSessionAPIKey}} + resp := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), headers, false) + assert.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) + }) +} + +func TestSessionRestartInvalidatesCookie(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + first := newSessionTestServer(t, testSessionAPIKey) + login := performSessionRequest(t, first, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"`+testSessionAPIKey+`"}`), nil, false) + requirements.Equal(http.StatusOK, login.Code, login.Body.String()) + cookie := requireSessionCookie(t, login) + + second := newSessionTestServer(t, testSessionAPIKey) + headers := http.Header{"Cookie": []string{cookie.String()}} + bootstrap := performSessionRequest(t, second, http.MethodGet, sessionPath, nil, headers, false) + requirements.Equal(http.StatusOK, bootstrap.Code, bootstrap.Body.String()) + assertions.Equal(AuthModeRequired, decodeSessionStatus(t, bootstrap).AuthMode) +} + +func TestLoginAlwaysRateLimitedOnLoopback(t *testing.T) { + tests := []struct { + name string + body []byte + headers http.Header + firstStatus int + }{ + { + name: "wrong key", + body: []byte(`{"api_key":"wrong-key"}`), + firstStatus: http.StatusUnauthorized, + }, + { + name: "ambient API key authentication", + body: []byte(`{"api_key":"` + testSessionAPIKey + `"}`), + headers: http.Header{"X-Api-Key": []string{testSessionAPIKey}}, + firstStatus: http.StatusOK, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + srv := newSessionTestServer(t, testSessionAPIKey) + // Remove token replenishment so the assertion is deterministic even on a + // slow or heavily instrumented test runner. + srv.rateLimiter.rate = 0 + srv.rateLimiter.burst = 1 + + first := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + tt.body, tt.headers, false) + assert.Equal(t, tt.firstStatus, first.Code, first.Body.String()) + + second := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + tt.body, tt.headers, false) + assert.Equal(t, http.StatusTooManyRequests, second.Code, second.Body.String()) + assert.Equal(t, "no-store", second.Header().Get("Cache-Control")) + }) + } +} + +func newSessionTestServer(t *testing.T, apiKey string) *Server { + t.Helper() + srv := NewServer(&config.Config{Server: config.ServerConfig{APIKey: apiKey}}, nil, nil, testLogger()) + t.Cleanup(func() { + require.NoError(t, srv.Shutdown(context.Background())) + }) + return srv +} + +func performSessionRequest( + t *testing.T, + srv *Server, + method string, + path string, + body []byte, + headers http.Header, + tlsConnection bool, +) *httptest.ResponseRecorder { + t.Helper() + req := httptest.NewRequest(method, path, bytes.NewReader(body)) + req.RemoteAddr = "127.0.0.1:4242" + if len(body) > 0 { + req.Header.Set("Content-Type", "application/json") + } + for name, values := range headers { + for _, value := range values { + req.Header.Add(name, value) + } + } + if tlsConnection { + req.TLS = &tls.ConnectionState{} + } + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + return resp +} + +func decodeSessionStatus(t *testing.T, resp *httptest.ResponseRecorder) SessionStatus { + t.Helper() + var status SessionStatus + require.NoError(t, json.NewDecoder(resp.Body).Decode(&status)) + return status +} + +func requireSessionCookie(t *testing.T, resp *httptest.ResponseRecorder) *http.Cookie { + t.Helper() + for _, cookie := range resp.Result().Cookies() { + if cookie.Name == sessionCookieName { + return cookie + } + } + require.FailNow(t, "session cookie not found") + return nil +} diff --git a/internal/api/settings.go b/internal/api/settings.go new file mode 100644 index 000000000..1ecd2367a --- /dev/null +++ b/internal/api/settings.go @@ -0,0 +1,448 @@ +package api + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "math" + "net/http" + "strings" + + "github.com/danielgtaylor/huma/v2" + "go.kenn.io/msgvault/internal/config" +) + +const settingsPath = "/api/v1/settings" + +// SecretSettingState is the only representation of a secret returned to a +// browser. The configured value never crosses the API boundary. +type SecretSettingState struct { + Configured bool `json:"configured"` +} + +// SettingValue is an explicit JSON union. Exactly one member is populated, +// keeping generated Go and TypeScript clients typed without exposing an +// unstructured config map. +type SettingValue struct { + String *string `json:"string,omitempty"` + Integer *int `json:"integer,omitempty"` + Number *float64 `json:"number,omitempty"` + Boolean *bool `json:"boolean,omitempty"` + Strings *[]string `json:"strings,omitempty"` +} + +// Setting describes one browser-managed allowlisted config value. +type Setting struct { + Key string `json:"key"` + Group string `json:"group"` + Kind string `json:"kind"` + Value *SettingValue `json:"value,omitempty"` + Secret *SecretSettingState `json:"secret,omitempty"` + Options []string `json:"options,omitempty"` + RestartRequired bool `json:"restart_required"` + Testable bool `json:"testable,omitempty"` +} + +type SettingsResponse struct { + Settings []Setting `json:"settings"` + PendingRestart bool `json:"pending_restart"` +} + +type SecretSettingUpdate struct { + Action string `json:"action" enum:"set,clear"` + Value string `json:"value,omitempty"` +} + +type SettingUpdate struct { + Key string `json:"key"` + Value *SettingValue `json:"value,omitempty"` + Secret *SecretSettingUpdate `json:"secret,omitempty"` +} + +type SettingsPatchRequest struct { + Updates []SettingUpdate `json:"updates" minItems:"1" nullable:"false"` + ConfirmAPIKeyRestart bool `json:"confirm_api_key_restart,omitempty"` +} + +type settingDefinition struct { + key string + group string + kind string + options []string + testable bool + secret func(*config.Config) bool + read func(*config.Config) any +} + +var settingsCatalog = []settingDefinition{ + stringSetting("web.default_search_mode", "browser", []string{exploreSearchModeFullText, exploreSearchModeSemantic, exploreSearchModeHybrid}, func(c *config.Config) string { return c.Web.DefaultSearchMode }), + stringSetting("web.theme", "browser", []string{"system", "light", "dark"}, func(c *config.Config) string { return c.Web.Theme }), + stringSetting("web.density", "browser", []string{"compact", "comfortable"}, func(c *config.Config) string { return c.Web.Density }), + stringSetting("server.bind_addr", "server", nil, func(c *config.Config) string { return c.Server.BindAddr }), + intSetting("server.api_port", "server", func(c *config.Config) int { return c.Server.APIPort }), + secretSetting("server.api_key", "server", func(c *config.Config) bool { return c.Server.APIKey != "" }), + boolSetting("server.allow_insecure", "server", func(c *config.Config) bool { return c.Server.AllowInsecure }), + stringArraySetting("server.trusted_proxies", "server", func(c *config.Config) []string { return c.Server.TrustedProxies }), + stringSetting("analytics.engine", "archive", []string{"auto", "sql", "duckdb"}, func(c *config.Config) string { return c.Analytics.Engine }), + boolSetting("analytics.auto_build_cache", "archive", func(c *config.Config) bool { return c.Analytics.AutoBuildCache }), + boolSetting("vector.enabled", "search", func(c *config.Config) bool { return c.Vector.Enabled }), + stringSetting("vector.backend", "search", []string{"sqlite-vec", "pgvector"}, func(c *config.Config) string { return c.Vector.Backend }), + stringSetting("vector.db_path", "search", nil, func(c *config.Config) string { return c.Vector.DBPath }), + boolSetting("vector.skip_extension_create", "search", func(c *config.Config) bool { return c.Vector.SkipExtensionCreate }), + testableStringSetting("vector.embeddings.endpoint", "search", func(c *config.Config) string { return c.Vector.Embeddings.Endpoint }), + stringSetting("vector.embeddings.api_key_env", "search", nil, func(c *config.Config) string { return c.Vector.Embeddings.APIKeyEnv }), + stringSetting("vector.embeddings.model", "search", nil, func(c *config.Config) string { return c.Vector.Embeddings.Model }), + intSetting("vector.embeddings.dimension", "search", func(c *config.Config) int { return c.Vector.Embeddings.Dimension }), + intSetting("vector.embeddings.batch_size", "search", func(c *config.Config) int { return c.Vector.Embeddings.BatchSize }), + intSetting("vector.embeddings.max_retries", "search", func(c *config.Config) int { return c.Vector.Embeddings.MaxRetries }), + intSetting("vector.embeddings.max_input_chars", "search", func(c *config.Config) int { return c.Vector.Embeddings.MaxInputChars }), + intSetting("vector.embeddings.eta_window", "search", func(c *config.Config) int { return c.Vector.Embeddings.ETAWindow }), + stringSetting("vector.embed.schedule.cron", "search", nil, func(c *config.Config) string { return c.Vector.Embed.Schedule.Cron }), + boolSetting("vector.embed.schedule.run_after_sync", "search", func(c *config.Config) bool { return c.Vector.Embed.Schedule.RunAfterSync }), + stringArraySetting("vector.embed.scope.message_types", "search", func(c *config.Config) []string { return c.Vector.Embed.Scope.MessageTypes }), + intSetting("vector.search.rrf_k", "search", func(c *config.Config) int { return c.Vector.Search.RRFK }), + intSetting("vector.search.k_per_signal", "search", func(c *config.Config) int { return c.Vector.Search.KPerSignal }), + numberSetting("vector.search.subject_boost", "search", func(c *config.Config) float64 { return c.Vector.Search.SubjectBoost }), + boolSetting("beeper.enabled", "sources", func(c *config.Config) bool { return c.Beeper.Enabled }), + stringSetting("beeper.schedule", "sources", nil, func(c *config.Config) string { return c.Beeper.Schedule }), + boolSetting("integrations.tasks.enabled", "integrations", func(c *config.Config) bool { return c.Integrations.Tasks.Enabled }), + testableStringSetting("integrations.tasks.endpoint", "integrations", func(c *config.Config) string { return c.Integrations.Tasks.Endpoint }), + secretSetting("integrations.tasks.api_key", "integrations", func(c *config.Config) bool { return c.Integrations.Tasks.APIKey != "" }), + stringSetting("integrations.tasks.default_project", "integrations", nil, func(c *config.Config) string { return c.Integrations.Tasks.DefaultProject }), +} + +func (s *Server) registerSettingsRoutes(api huma.API) { + get := rawAPIV1Operation("getSettings", http.MethodGet, "/settings", "Get browser-managed settings") + get.Responses = jsonResponsesFor[SettingsResponse](api) + addSettingsETagHeader(get.Responses[httpStatusKey(http.StatusOK)]) + registerRawHumaRoute(api, get, s.handleGetSettings) + + patch := rawAPIV1Operation("patchSettings", http.MethodPatch, "/settings", "Update browser-managed settings") + patch.Parameters = append(patch.Parameters, &huma.Param{ + Name: "If-Match", + In: "header", + Description: "Strong ETag returned by the latest settings read", + Required: true, + Schema: &huma.Schema{Type: huma.TypeString}, + }) + patch.RequestBody = jsonRequestBodyFor[SettingsPatchRequest](api) + patch.Responses = jsonResponsesFor[SettingsResponse](api) + for _, status := range []int{ + http.StatusBadRequest, + http.StatusConflict, + http.StatusPreconditionFailed, + http.StatusPreconditionRequired, + http.StatusUnprocessableEntity, + } { + patch.Responses[httpStatusKey(status)] = errorResponseFor(api) + } + addSettingsETagHeader(patch.Responses[httpStatusKey(http.StatusOK)]) + registerRawHumaRoute(api, patch, s.handlePatchSettings) +} + +func addSettingsETagHeader(response *huma.Response) { + response.Headers = map[string]*huma.Param{ + "ETag": { + Description: "Strong content hash for optimistic concurrency", + Schema: &huma.Schema{Type: huma.TypeString}, + }, + } +} + +func stringSetting(key, group string, options []string, read func(*config.Config) string) settingDefinition { + return settingDefinition{key: key, group: group, kind: "string", options: options, read: func(c *config.Config) any { return read(c) }} +} + +func testableStringSetting(key, group string, read func(*config.Config) string) settingDefinition { + definition := stringSetting(key, group, nil, read) + definition.testable = true + return definition +} + +func intSetting(key, group string, read func(*config.Config) int) settingDefinition { + return settingDefinition{key: key, group: group, kind: "integer", read: func(c *config.Config) any { return read(c) }} +} + +func numberSetting(key, group string, read func(*config.Config) float64) settingDefinition { + return settingDefinition{key: key, group: group, kind: "number", read: func(c *config.Config) any { return read(c) }} +} + +func boolSetting(key, group string, read func(*config.Config) bool) settingDefinition { + return settingDefinition{key: key, group: group, kind: "boolean", read: func(c *config.Config) any { return read(c) }} +} + +func stringArraySetting(key, group string, read func(*config.Config) []string) settingDefinition { + return settingDefinition{key: key, group: group, kind: "string_array", read: func(c *config.Config) any { return read(c) }} +} + +func secretSetting(key, group string, configured func(*config.Config) bool) settingDefinition { + return settingDefinition{key: key, group: group, kind: "secret", secret: configured} +} + +func settingsDefinitionByKey() map[string]settingDefinition { + result := make(map[string]settingDefinition, len(settingsCatalog)) + for _, definition := range settingsCatalog { + result[definition.key] = definition + } + return result +} + +func (s *Server) handleGetSettings(w http.ResponseWriter, _ *http.Request) { + snapshot, cfg, err := s.readPersistedSettings() + if err != nil { + writeError(w, http.StatusInternalServerError, "settings_read_failed", err.Error()) + return + } + w.Header().Set("ETag", snapshot.ETag) + w.Header().Set("Cache-Control", "no-store") + writeJSON(w, http.StatusOK, buildSettingsResponse(cfg, s.settingsPendingRestart.Load())) +} + +func (s *Server) handlePatchSettings(w http.ResponseWriter, r *http.Request) { + ifMatches := r.Header.Values("If-Match") + if len(ifMatches) != 1 || strings.TrimSpace(ifMatches[0]) == "" { + writeError(w, http.StatusPreconditionRequired, "if_match_required", "If-Match is required") + return + } + var request SettingsPatchRequest + decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&request); err != nil { + writeError(w, http.StatusBadRequest, "bad_request", "Invalid settings request") + return + } + if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + writeError(w, http.StatusBadRequest, "bad_request", "Invalid settings request") + return + } + if len(request.Updates) == 0 { + writeError(w, http.StatusBadRequest, "bad_request", "At least one settings update is required") + return + } + edits, changesAPIKey, err := settingsEdits(request.Updates) + if err != nil { + writeError(w, http.StatusBadRequest, "bad_request", err.Error()) + return + } + if changesAPIKey && !request.ConfirmAPIKeyRestart { + writeError(w, http.StatusBadRequest, "api_key_restart_confirmation_required", + "Changing the API key requires confirmation because it takes effect after restart") + return + } + + editor := s.settingsConfigEditor + if editor == nil { + editor = config.EditConfigFile + } + snapshot, err := editor(s.cfg.ConfigFilePath(), ifMatches[0], edits) + if err != nil { + if errors.Is(err, config.ErrConfigChanged) { + s.settingsPendingRestart.Store(true) + } + switch { + case errors.Is(err, config.ErrConfigChanged): + writeError(w, http.StatusInternalServerError, "settings_write_failed", + "Settings changed, but the write did not complete cleanly; restart is required") + case errors.Is(err, config.ErrConfigConflict): + writeError(w, http.StatusPreconditionFailed, "settings_conflict", "The config file changed; reload settings and retry") + case errors.Is(err, config.ErrAmbiguousConfigTarget), errors.Is(err, config.ErrUnsafeConfigTarget): + writeError(w, http.StatusConflict, "settings_edit_rejected", err.Error()) + case errors.Is(err, config.ErrInvalidConfigCandidate): + writeError(w, http.StatusUnprocessableEntity, "validation_failed", err.Error()) + default: + writeError(w, http.StatusInternalServerError, "settings_write_failed", "Could not write settings") + } + return + } + // A nil editor error means the candidate is already the committed config. + // Record that fact before decoding the response snapshot so a subsequent + // load failure cannot make the daemon report a false non-pending state. + s.settingsPendingRestart.Store(true) + loaded, err := config.LoadConfigFile(snapshot, "") + if err != nil { + writeError(w, http.StatusInternalServerError, "settings_read_failed", err.Error()) + return + } + w.Header().Set("ETag", snapshot.ETag) + w.Header().Set("Cache-Control", "no-store") + writeJSON(w, http.StatusOK, buildSettingsResponse(loaded, true)) +} + +func (s *Server) readPersistedSettings() (config.ConfigFile, *config.Config, error) { + snapshot, err := config.ReadConfigFile(s.cfg.ConfigFilePath()) + if err != nil { + return config.ConfigFile{}, nil, err + } + if !snapshot.Exists { + return snapshot, config.NewDefaultConfig(), nil + } + loaded, err := config.LoadConfigFile(snapshot, "") + if err != nil { + return config.ConfigFile{}, nil, err + } + return snapshot, loaded, nil +} + +func settingsNoStoreMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == settingsPath { + w.Header().Set("Cache-Control", "no-store") + } + next.ServeHTTP(w, r) + }) +} + +func buildSettingsResponse(cfg *config.Config, pendingRestart bool) SettingsResponse { + settings := make([]Setting, 0, len(settingsCatalog)) + for _, definition := range settingsCatalog { + setting := Setting{ + Key: definition.key, + Group: definition.group, + Kind: definition.kind, + Options: definition.options, + RestartRequired: true, + Testable: definition.testable, + } + if definition.secret != nil { + setting.Secret = &SecretSettingState{Configured: definition.secret(cfg)} + } else { + setting.Value = settingValue(definition.kind, definition.read(cfg)) + } + settings = append(settings, setting) + } + return SettingsResponse{Settings: settings, PendingRestart: pendingRestart} +} + +func settingsEdits(updates []SettingUpdate) ([]config.Edit, bool, error) { + definitions := settingsDefinitionByKey() + seen := make(map[string]struct{}, len(updates)) + edits := make([]config.Edit, 0, len(updates)) + changesAPIKey := false + for _, update := range updates { + definition, ok := definitions[update.Key] + if !ok { + return nil, false, fmt.Errorf("setting %q is not browser-managed", update.Key) + } + if _, duplicate := seen[update.Key]; duplicate { + return nil, false, fmt.Errorf("setting %q is updated more than once", update.Key) + } + seen[update.Key] = struct{}{} + var value any + if definition.secret != nil { + if update.Secret == nil || update.Value != nil { + return nil, false, fmt.Errorf("setting %q must use a secret action", update.Key) + } + switch update.Secret.Action { + case "set": + if update.Secret.Value == "" { + return nil, false, fmt.Errorf("setting %q cannot be set to an empty secret", update.Key) + } + value = update.Secret.Value + case "clear": + if update.Secret.Value != "" { + return nil, false, fmt.Errorf("setting %q clear action cannot include a value", update.Key) + } + value = "" + default: + return nil, false, fmt.Errorf("setting %q has an invalid secret action", update.Key) + } + } else { + if update.Secret != nil || update.Value == nil { + return nil, false, fmt.Errorf("setting %q requires a value", update.Key) + } + converted, err := convertSettingValue(definition.kind, update.Value) + if err != nil { + return nil, false, fmt.Errorf("setting %q: %w", update.Key, err) + } + value = converted + } + if update.Key == "server.api_key" { + changesAPIKey = true + } + edits = append(edits, config.Edit{Key: update.Key, Value: value}) + } + return edits, changesAPIKey, nil +} + +func settingValue(kind string, value any) *SettingValue { + result := &SettingValue{} + switch kind { + case "string": + if typed, ok := value.(string); ok { + result.String = &typed + } + case "boolean": + if typed, ok := value.(bool); ok { + result.Boolean = &typed + } + case "integer": + if typed, ok := value.(int); ok { + result.Integer = &typed + } + case "number": + if typed, ok := value.(float64); ok { + result.Number = &typed + } + case "string_array": + if typed, ok := value.([]string); ok { + stringsValue := append([]string{}, typed...) + result.Strings = &stringsValue + } + } + return result +} + +func convertSettingValue(kind string, value *SettingValue) (any, error) { + if value == nil { + return nil, errors.New("value is required") + } + populated := 0 + if value.String != nil { + populated++ + } + if value.Integer != nil { + populated++ + } + if value.Number != nil { + populated++ + } + if value.Boolean != nil { + populated++ + } + if value.Strings != nil { + populated++ + } + if populated != 1 { + return nil, errors.New("value must contain exactly one typed member") + } + switch kind { + case "string": + if value.String == nil { + return nil, errors.New("value must be a string") + } + return *value.String, nil + case "boolean": + if value.Boolean == nil { + return nil, errors.New("value must be a boolean") + } + return *value.Boolean, nil + case "integer": + if value.Integer == nil { + return nil, errors.New("value must be an integer") + } + return *value.Integer, nil + case "number": + if value.Number == nil || math.IsInf(*value.Number, 0) || math.IsNaN(*value.Number) { + return nil, errors.New("value must be a finite number") + } + return *value.Number, nil + case "string_array": + if value.Strings == nil { + return nil, errors.New("value must be an array of strings") + } + return *value.Strings, nil + default: + return nil, errors.New("unsupported setting type") + } +} diff --git a/internal/api/settings_test.go b/internal/api/settings_test.go new file mode 100644 index 000000000..ddf027d9e --- /dev/null +++ b/internal/api/settings_test.go @@ -0,0 +1,372 @@ +package api + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "log/slog" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" +) + +func TestGetSettingsUsesAllowlistETagAndSecretStates(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + srv, _ := newSettingsTestServer(t, "# keep\n[web]\ntheme = \"dark\"\n"+ + "[server]\napi_key = \"test-api-key\"\n"+ + "[integrations.tasks]\napi_key = \"task-secret\"\n"+ + "[unsupported]\nprivate_value = \"must-not-leak\"\n") + resp := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "test-api-key") + require.Equal(http.StatusOK, resp.Code, resp.Body.String()) + assert.NotEmpty(resp.Header().Get("ETag")) + assert.Equal("no-store", resp.Header().Get("Cache-Control")) + + var body SettingsResponse + require.NoError(json.Unmarshal(resp.Body.Bytes(), &body)) + byKey := settingsByKey(body.Settings) + require.NotNil(byKey["web.theme"].Value) + require.NotNil(byKey["web.theme"].Value.String) + assert.Equal("dark", *byKey["web.theme"].Value.String) + assert.Equal(&SecretSettingState{Configured: true}, byKey["server.api_key"].Secret) + assert.Nil(byKey["server.api_key"].Value) + assert.Equal(&SecretSettingState{Configured: true}, byKey["integrations.tasks.api_key"].Secret) + require.NotNil(byKey["server.trusted_proxies"].Value) + assert.NotNil(byKey["server.trusted_proxies"].Value.Strings) + assert.NotContains(byKey, "unsupported.private_value") + for _, setting := range body.Settings { + assert.True(setting.RestartRequired, setting.Key) + } + assert.NotContains(resp.Body.String(), "test-api-key") + assert.NotContains(resp.Body.String(), "task-secret") + assert.NotContains(resp.Body.String(), "must-not-leak") +} + +func TestPatchSettingsRequiresMatchingETag(t *testing.T) { + assert := assert.New(t) + srv, path := newSettingsTestServer(t, "[web]\ntheme = \"system\"\n") + + missing := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"updates":[{"key":"web.theme","value":{"string":"dark"}}]}`), "", "") + assert.Equal(http.StatusPreconditionRequired, missing.Code, missing.Body.String()) + + mismatch := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"updates":[{"key":"web.theme","value":{"string":"dark"}}]}`), "\"sha256-stale\"", "") + assert.Equal(http.StatusPreconditionFailed, mismatch.Code, mismatch.Body.String()) + got, err := os.ReadFile(path) + require.NoError(t, err) + assert.Equal("[web]\ntheme = \"system\"\n", string(got)) +} + +func TestPatchSettingsPreservesFileAndReturnsNewETag(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + srv, path := newSettingsTestServer(t, "# operator comment\n[unknown]\nkeep = true\n\n"+ + "[web]\ntheme = \"system\" # display\n") + require.NoError(os.Chmod(path, 0o640)) + get := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "") + etag := get.Header().Get("ETag") + + patch := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"updates":[{"key":"web.theme","value":{"string":"dark"}}]}`), etag, "") + require.Equal(http.StatusOK, patch.Code, patch.Body.String()) + assert.NotEqual(etag, patch.Header().Get("ETag")) + got, err := os.ReadFile(path) + require.NoError(err) + assert.Equal("# operator comment\n[unknown]\nkeep = true\n\n[web]\ntheme = \"dark\" # display\n", string(got)) + info, err := os.Stat(path) + require.NoError(err) + assert.Equal(os.FileMode(0o640), info.Mode().Perm()) +} + +func TestPatchSettingsValidatesWholeCandidateAndRejectsUnknownKeys(t *testing.T) { + tests := []struct { + name string + body string + status int + }{ + { + name: "invalid catalog value", + body: `{"updates":[{"key":"analytics.engine","value":{"string":"invalid"}}]}`, + status: http.StatusUnprocessableEntity, + }, + { + name: "unsupported key", + body: `{"updates":[{"key":"unsupported.private_value","value":{"string":"changed"}}]}`, + status: http.StatusBadRequest, + }, + { + name: "secret sent as ordinary value", + body: `{"updates":[{"key":"server.api_key","value":{"string":"leak"}}]}`, + status: http.StatusBadRequest, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + before := "[analytics]\nengine = \"auto\"\n[unsupported]\nprivate_value = \"keep\"\n" + srv, path := newSettingsTestServer(t, before) + get := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "") + resp := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, []byte(tt.body), + get.Header().Get("ETag"), "") + assert.Equal(t, tt.status, resp.Code, resp.Body.String()) + got, err := os.ReadFile(path) + require.NoError(t, err) + assert.Equal(t, before, string(got)) + }) + } +} + +func TestPatchSettingsProtectsAPIKeyRestartSequencing(t *testing.T) { + t.Run("confirmation required", func(t *testing.T) { + srv, _ := newSettingsTestServer(t, "[server]\napi_key = \"old-key\"\n") + get := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "old-key") + resp := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"updates":[{"key":"server.api_key","secret":{"action":"set","value":"new-key"}}]}`), + get.Header().Get("ETag"), "old-key") + assert.Equal(t, http.StatusBadRequest, resp.Code, resp.Body.String()) + }) + + t.Run("full candidate prevents remote self lockout", func(t *testing.T) { + srv, _ := newSettingsTestServer(t, "[server]\nbind_addr = \"0.0.0.0\"\napi_key = \"old-key\"\n") + get := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "old-key") + resp := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"confirm_api_key_restart":true,"updates":[{"key":"server.api_key","secret":{"action":"clear"}}]}`), + get.Header().Get("ETag"), "old-key") + assert.Equal(t, http.StatusUnprocessableEntity, resp.Code, resp.Body.String()) + }) + + t.Run("new key remains pending until restart", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + srv, path := newSettingsTestServer(t, "[server]\napi_key = \"old-key\"\n") + get := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "old-key") + resp := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"confirm_api_key_restart":true,"updates":[{"key":"server.api_key","secret":{"action":"set","value":"new-key"}}]}`), + get.Header().Get("ETag"), "old-key") + require.Equal(http.StatusOK, resp.Code, resp.Body.String()) + assert.Equal("old-key", srv.cfg.Server.APIKey) + got, err := os.ReadFile(path) + require.NoError(err) + assert.Contains(string(got), "api_key = \"new-key\"") + assert.NotContains(resp.Body.String(), "new-key") + + stillActive := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "old-key") + assert.Equal(http.StatusOK, stillActive.Code, stillActive.Body.String()) + var persisted SettingsResponse + require.NoError(json.Unmarshal(stillActive.Body.Bytes(), &persisted)) + assert.True(persisted.PendingRestart) + + restartedConfig, err := config.Load(path, "") + require.NoError(err) + restarted := NewServer(restartedConfig, nil, nil, slog.New(slog.DiscardHandler)) + oldSession := performSessionRequest(t, restarted, http.MethodGet, sessionPath, nil, + http.Header{"Cookie": []string{requireSessionCookie(t, performSessionRequest( + t, srv, http.MethodPost, sessionLoginPath, []byte(`{"api_key":"old-key"}`), nil, false, + )).String()}}, false) + require.Equal(http.StatusOK, oldSession.Code, oldSession.Body.String()) + assert.Equal(AuthModeRequired, decodeSessionStatus(t, oldSession).AuthMode) + assert.Empty(oldSession.Header().Values("Set-Cookie"), "bootstrap must not reissue authority for the stale cookie") + + oldKey := performSettingsRequest(t, restarted, http.MethodGet, settingsPath, nil, "", "old-key") + assert.Equal(http.StatusUnauthorized, oldKey.Code, oldKey.Body.String()) + newKey := performSettingsRequest(t, restarted, http.MethodGet, settingsPath, nil, "", "new-key") + assert.Equal(http.StatusOK, newKey.Code, newKey.Body.String()) + }) +} + +func TestSettingsErrorsAreNotCached(t *testing.T) { + srv, _ := newSettingsTestServer(t, "[web]\ntheme = \"system\"\n") + resp := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"updates":[{"key":"web.theme","value":{"string":"dark"}}]}`), "", "") + + assert.Equal(t, http.StatusPreconditionRequired, resp.Code, resp.Body.String()) + assert.Equal(t, "no-store", resp.Header().Get("Cache-Control")) +} + +func TestSettingsMiddlewareErrorsAreNotCached(t *testing.T) { + srv, _ := newSettingsTestServer(t, "[server]\napi_key = \"test-api-key\"\n") + login := performSessionRequest(t, srv, http.MethodPost, sessionLoginPath, + []byte(`{"api_key":"test-api-key"}`), nil, false) + require.Equal(t, http.StatusOK, login.Code, login.Body.String()) + cookie := requireSessionCookie(t, login) + resp := performSessionRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"updates":[{"key":"web.theme","value":{"string":"dark"}}]}`), + http.Header{"Cookie": []string{cookie.String()}}, false) + + assert.Equal(t, http.StatusForbidden, resp.Code, resp.Body.String()) + assert.Equal(t, "no-store", resp.Header().Get("Cache-Control")) +} + +func TestPatchSettingsRejectsTrailingJSON(t *testing.T) { + srv, _ := newSettingsTestServer(t, "[web]\ntheme = \"system\"\n") + get := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "") + resp := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"updates":[{"key":"web.theme","value":{"string":"dark"}}]} {}`), + get.Header().Get("ETag"), "") + + assert.Equal(t, http.StatusBadRequest, resp.Code, resp.Body.String()) +} + +func TestPatchSettingsClassifiesFilesystemFailureAsServerError(t *testing.T) { + srv, path := newSettingsTestServer(t, "[web]\ntheme = \"system\"\n") + get := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "") + dir := filepath.Dir(path) + require.NoError(t, os.Chmod(dir, 0o500)) + t.Cleanup(func() { require.NoError(t, os.Chmod(dir, 0o700)) }) + + resp := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"updates":[{"key":"web.theme","value":{"string":"dark"}}]}`), + get.Header().Get("ETag"), "") + + assert.Equal(t, http.StatusInternalServerError, resp.Code, resp.Body.String()) + assert.Equal(t, "no-store", resp.Header().Get("Cache-Control")) +} + +func TestPatchSettingsMarksRestartPendingWhenPublishedWriteReturnsError(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + srv, path := newSettingsTestServer(t, "[web]\ntheme = \"system\"\n") + get := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "") + srv.settingsConfigEditor = func(configPath, ifMatch string, edits []config.Edit) (config.ConfigFile, error) { + require.Equal(path, configPath) + require.Equal(get.Header().Get("ETag"), ifMatch) + require.Len(edits, 1) + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"dark\"\n"), 0o600)) + return config.ConfigFile{}, fmt.Errorf("%w: cleanup failed", config.ErrConfigChanged) + } + + patch := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"updates":[{"key":"web.theme","value":{"string":"dark"}}]}`), + get.Header().Get("ETag"), "") + assert.Equal(http.StatusInternalServerError, patch.Code, patch.Body.String()) + + after := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "") + require.Equal(http.StatusOK, after.Code, after.Body.String()) + var persisted SettingsResponse + require.NoError(json.Unmarshal(after.Body.Bytes(), &persisted)) + assert.True(persisted.PendingRestart) +} + +func TestPatchSettingsMarksRestartPendingBeforeLoadingCommittedSnapshot(t *testing.T) { + assert := assert.New(t) + srv, _ := newSettingsTestServer(t, "[web]\ntheme = \"system\"\n") + get := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "") + srv.settingsConfigEditor = func(string, string, []config.Edit) (config.ConfigFile, error) { + return config.ConfigFile{ + LogicalPath: "config.toml", + Path: "config.toml", + Content: []byte("invalid = ["), + ETag: `"sha256-committed"`, + Exists: true, + }, nil + } + + patch := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"updates":[{"key":"web.theme","value":{"string":"dark"}}]}`), + get.Header().Get("ETag"), "") + assert.Equal(http.StatusInternalServerError, patch.Code, patch.Body.String()) + assert.True(srv.settingsPendingRestart.Load()) +} + +func TestPatchSettingsPrefersChangedOutcomeOverConflictClassification(t *testing.T) { + srv, _ := newSettingsTestServer(t, "[web]\ntheme = \"system\"\n") + get := performSettingsRequest(t, srv, http.MethodGet, settingsPath, nil, "", "") + srv.settingsConfigEditor = func(string, string, []config.Edit) (config.ConfigFile, error) { + return config.ConfigFile{}, errors.Join(config.ErrConfigChanged, config.ErrConfigConflict) + } + + patch := performSettingsRequest(t, srv, http.MethodPatch, settingsPath, + []byte(`{"updates":[{"key":"web.theme","value":{"string":"dark"}}]}`), + get.Header().Get("ETag"), "") + assert.Equal(t, http.StatusInternalServerError, patch.Code, patch.Body.String()) +} + +func TestSettingsOpenAPIContract(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + doc := OpenAPIDocument() + require.NotNil(doc.Paths[settingsPath]) + get := doc.Paths[settingsPath].Get + patch := doc.Paths[settingsPath].Patch + require.NotNil(get) + require.NotNil(patch) + assert.Contains(get.Responses["200"].Headers, "ETag") + require.Len(patch.Parameters, 1) + assert.Equal("If-Match", patch.Parameters[0].Name) + assert.Equal("header", patch.Parameters[0].In) + assert.True(patch.Parameters[0].Required) + for _, status := range []string{"400", "409", "412", "422", "428"} { + assert.Contains(patch.Responses, status) + } + assert.Equal("1.24.0", doc.Info.Version) + + settingValue := doc.Components.Schemas.Map()["SettingValue"] + require.NotNil(settingValue) + assert.Len(settingValue.OneOf, 5) + assert.Empty(settingValue.Properties) + for _, arm := range settingValue.OneOf { + assert.Len(arm.Required, 1) + assert.Equal([]string{arm.Required[0]}, arm.Required) + assert.Equal(false, arm.AdditionalProperties) + } + setting := doc.Components.Schemas.Map()["Setting"] + require.NotNil(setting) + assert.ElementsMatch([]any{"browser", "server", "archive", "search", "sources", "integrations"}, setting.Properties["group"].Enum) + assert.ElementsMatch([]any{"string", "integer", "number", "boolean", "string_array", "secret"}, setting.Properties["kind"].Enum) + patchRequest := doc.Components.Schemas.Map()["SettingsPatchRequest"] + require.NotNil(patchRequest) + assert.False(patchRequest.Properties["updates"].Nullable) +} + +func newSettingsTestServer(t *testing.T, content string) (*Server, string) { + t.Helper() + dir := t.TempDir() + path := filepath.Join(dir, "config.toml") + require.NoError(t, os.WriteFile(path, []byte(content), 0o600)) + cfg, err := config.Load(path, "") + require.NoError(t, err) + logger := slog.New(slog.DiscardHandler) + return NewServer(cfg, nil, nil, logger), path +} + +//nolint:unparam // Keep the actual route visible at each HTTP test call site. +func performSettingsRequest( + t *testing.T, + srv *Server, + method string, + path string, + body []byte, + ifMatch string, + apiKey string, +) *httptest.ResponseRecorder { + t.Helper() + req := httptest.NewRequest(method, path, bytes.NewReader(body)) + req.RemoteAddr = "127.0.0.1:12345" + if len(body) > 0 { + req.Header.Set("Content-Type", "application/json") + } + if ifMatch != "" { + req.Header.Set("If-Match", ifMatch) + } + if apiKey != "" { + req.Header.Set("X-Api-Key", apiKey) + } + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + return resp +} + +func settingsByKey(settings []Setting) map[string]Setting { + result := make(map[string]Setting, len(settings)) + for _, setting := range settings { + result[setting.Key] = setting + } + return result +} diff --git a/internal/api/spa_test.go b/internal/api/spa_test.go new file mode 100644 index 000000000..083497d6c --- /dev/null +++ b/internal/api/spa_test.go @@ -0,0 +1,49 @@ +package api + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + webapp "go.kenn.io/msgvault/internal/web" +) + +func TestSPARoutePreservesRegisteredAPIPriorityAndServesNavigation(t *testing.T) { + assert := assert.New(t) + shell := []byte("<!doctype html><title>test shell") + spa := webapp.NewHandler(fstest.MapFS{ + "index.html": &fstest.MapFile{Data: shell}, + }, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + writeError(w, http.StatusNotFound, "not_found", "No route matches "+r.Method+" "+r.URL.Path) + })) + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{Server: config.ServerConfig{APIPort: 8080}}, + Logger: testLogger(), + SPAHandler: spa, + }) + + health := httptest.NewRecorder() + srv.Router().ServeHTTP(health, httptest.NewRequest(http.MethodGet, "/health", nil)) + assert.Equal(http.StatusOK, health.Code) + assert.Contains(health.Header().Get("Content-Type"), "application/json") + assert.NotContains(health.Body.String(), "test shell") + + navigation := httptest.NewRecorder() + srv.Router().ServeHTTP(navigation, httptest.NewRequest(http.MethodGet, "/not/a/real/route", nil)) + assert.Equal(http.StatusOK, navigation.Code) + assert.Equal("text/html; charset=utf-8", navigation.Header().Get("Content-Type")) + assert.Equal(string(shell), navigation.Body.String()) + + unknownAPI := httptest.NewRecorder() + srv.Router().ServeHTTP(unknownAPI, httptest.NewRequest(http.MethodGet, "/api/v1/not-a-real-route", nil)) + assert.Equal(http.StatusNotFound, unknownAPI.Code) + assert.Contains(unknownAPI.Header().Get("Content-Type"), "application/json") + var response ErrorResponse + require.NoError(t, json.NewDecoder(unknownAPI.Body).Decode(&response)) + assert.Equal("not_found", response.Error) +} diff --git a/internal/api/task_integration.go b/internal/api/task_integration.go new file mode 100644 index 000000000..6ffd08fef --- /dev/null +++ b/internal/api/task_integration.go @@ -0,0 +1,61 @@ +package api + +import ( + "context" + "net/http" + + "github.com/danielgtaylor/huma/v2" + "go.kenn.io/msgvault/internal/taskclient" +) + +const ( + taskIntegrationStatusPath = "/api/v1/integrations/tasks/status" + taskIntegrationTestPath = "/api/v1/integrations/tasks/test" +) + +// TaskIntegrationProbe is the server-side capability boundary. Tests replace +// it with a synthetic probe; production uses taskclient.Evaluate. +type TaskIntegrationProbe func(context.Context, taskclient.IntegrationConfig) taskclient.Status + +type TaskIntegrationStatusResponse struct { + State taskclient.State `json:"state" enum:"disabled,not_found,authentication_required,unreachable,incompatible,wrong_project,ready" doc:"Current task integration state"` + Project string `json:"project,omitempty" doc:"Configured project when available"` + Message string `json:"message" doc:"Credential-free status explanation"` + SecurityNote string `json:"security_note,omitempty" doc:"Platform-specific local socket security limitation"` +} + +func (s *Server) registerTaskIntegrationRoutes(api huma.API) { + registerAPIV1RawHumaJSONRoute[TaskIntegrationStatusResponse]( + api, + "getTaskIntegrationStatus", + http.MethodGet, + "/integrations/tasks/status", + "Get task integration capability status", + s.handleTaskIntegrationStatus, + ) + registerAPIV1RawHumaJSONRoute[TaskIntegrationStatusResponse]( + api, + "testTaskIntegration", + http.MethodPost, + "/integrations/tasks/test", + "Test task integration discovery, authentication, capabilities, and project", + s.handleTaskIntegrationStatus, + ) +} + +func (s *Server) handleTaskIntegrationStatus(w http.ResponseWriter, r *http.Request) { + cfg := s.cfg.Integrations.Tasks + status := s.taskIntegrationProbe(r.Context(), taskclient.IntegrationConfig{ + Enabled: cfg.Enabled, + Endpoint: cfg.Endpoint, + APIKey: cfg.APIKey, + DefaultProject: cfg.DefaultProject, + }) + w.Header().Set("Cache-Control", "no-store") + writeJSON(w, http.StatusOK, TaskIntegrationStatusResponse{ + State: status.State, + Project: status.Project, + Message: status.Message, + SecurityNote: status.SecurityNote, + }) +} diff --git a/internal/api/task_integration_test.go b/internal/api/task_integration_test.go new file mode 100644 index 000000000..f6d560115 --- /dev/null +++ b/internal/api/task_integration_test.go @@ -0,0 +1,101 @@ +package api + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/taskclient" +) + +func TestTaskIntegrationStatusAndTestStayServerSide(t *testing.T) { + var calls int + var received taskclient.IntegrationConfig + probe := TaskIntegrationProbe(func(_ context.Context, cfg taskclient.IntegrationConfig) taskclient.Status { + calls++ + received = cfg + return taskclient.Status{ + State: taskclient.StateReady, + Project: "test-project", + Message: "Task integration is ready.", + } + }) + cfg := &config.Config{ + Server: config.ServerConfig{APIKey: "msgvault-test-api-key"}, + Integrations: config.IntegrationsConfig{Tasks: config.TaskIntegrationConfig{ + Enabled: true, + Endpoint: "https://tasks.example.com", + APIKey: "upstream-secret-test-key", + DefaultProject: "test-project", + }}, + } + srv := NewServerWithOptions(ServerOptions{ + Config: cfg, + Logger: testLogger(), + TaskIntegrationProbe: probe, + }) + + for _, test := range []struct { + name string + method string + path string + }{ + {name: "status", method: http.MethodGet, path: taskIntegrationStatusPath}, + {name: "test", method: http.MethodPost, path: taskIntegrationTestPath}, + } { + t.Run(test.name, func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + req := httptest.NewRequest(test.method, test.path, nil) + req.Header.Set("X-Api-Key", "msgvault-test-api-key") + resp := httptest.NewRecorder() + + srv.Router().ServeHTTP(resp, req) + + requirements.Equal(http.StatusOK, resp.Code, resp.Body.String()) + assertions.Equal("no-store", resp.Header().Get("Cache-Control")) + var body TaskIntegrationStatusResponse + requirements.NoError(json.Unmarshal(resp.Body.Bytes(), &body)) + assertions.Equal(taskclient.StateReady, body.State) + assertions.Equal("test-project", body.Project) + assertions.NotContains(resp.Body.String(), "upstream-secret-test-key") + assertions.NotContains(resp.Body.String(), "tasks.example.com") + }) + } + + assert.Equal(t, 2, calls) + assert.Equal(t, "upstream-secret-test-key", received.APIKey) + assert.Equal(t, "https://tasks.example.com", received.Endpoint) +} + +func TestTaskIntegrationStatusMapsRuntimeConfigWithoutAmbientDiscovery(t *testing.T) { + var received taskclient.IntegrationConfig + srv := NewServerWithOptions(ServerOptions{ + Config: &config.Config{ + Server: config.ServerConfig{}, + Integrations: config.IntegrationsConfig{Tasks: config.TaskIntegrationConfig{ + Enabled: false, + DefaultProject: "test-project", + }}, + }, + Logger: testLogger(), + TaskIntegrationProbe: func(_ context.Context, cfg taskclient.IntegrationConfig) taskclient.Status { + received = cfg + return taskclient.Status{State: taskclient.StateDisabled, Message: "Task integration is disabled."} + }, + }) + req := httptest.NewRequest(http.MethodGet, taskIntegrationStatusPath, nil) + req.RemoteAddr = "127.0.0.1:32145" + resp := httptest.NewRecorder() + + srv.Router().ServeHTTP(resp, req) + + require.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) + assert.False(t, received.Enabled) + assert.Empty(t, received.DescriptorPath) +} diff --git a/internal/api/task_links.go b/internal/api/task_links.go new file mode 100644 index 000000000..afe96dd98 --- /dev/null +++ b/internal/api/task_links.go @@ -0,0 +1,290 @@ +package api + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" + + "github.com/danielgtaylor/huma/v2" + "go.kenn.io/msgvault/internal/store" + "go.kenn.io/msgvault/internal/taskclient" + "go.kenn.io/msgvault/internal/tasklinks" +) + +const ( + taskLinkEmailMessageType = "email" + MaxTaskLinkRequestBytes = 64 << 10 + maxTaskSearchQueryLength = 256 +) + +type TaskLinkMutationRequest struct { + TaskID string `json:"task_id,omitempty" doc:"Existing task to link; omit to create a task"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Priority string `json:"priority,omitempty"` + Labels []string `json:"labels,omitempty"` + AddedAt string `json:"added_at" doc:"Browser-generated retry-stable RFC3339 timestamp for the outbound mail link"` +} + +type TaskLinkTask struct { + ID string `json:"id"` + Project string `json:"project"` + Title string `json:"title"` + Revision string `json:"revision,omitempty"` +} + +type TaskLinkMutationResponse struct { + Task TaskLinkTask `json:"task"` +} + +type TaskLinkOutboundMetadata struct { + ArchiveUID string `json:"archive_uid"` + MessageID int64 `json:"message_id"` + ConversationID int64 `json:"conversation_id"` + Subject string `json:"subject"` + From string `json:"from"` + SentAt string `json:"sent_at"` + SourceType string `json:"source_type"` + SourceIdentifier string `json:"source_identifier"` + SourceMessageID string `json:"source_message_id"` +} + +type TaskLinkLookupResponse struct { + tasklinks.IndexStatus + + Tasks []tasklinks.TaskSummary `json:"tasks"` + OutboundMetadata TaskLinkOutboundMetadata `json:"outbound_metadata"` +} + +type TaskSearchResponse struct { + Tasks []tasklinks.TaskSummary `json:"tasks"` +} + +func (s *Server) registerTaskLinkRoutes(api huma.API) { + registerAPIV1RawHumaJSONRouteWithRequest[TaskLinkMutationRequest, TaskLinkMutationResponse](api, + "createOrLinkMessageTask", http.MethodPost, "/messages/{id}/tasks", "Create or link a task for an archived email", s.handleCreateOrLinkMessageTask, http.StatusCreated) + registerAPIV1RawHumaJSONRoute[TaskLinkLookupResponse](api, + "listMessageTasks", http.MethodGet, "/messages/{id}/tasks", "List tasks linked to an archived email", s.handleListMessageTasks) + registerAPIV1RawHumaJSONRoute[TaskLinkMutationResponse](api, + "unlinkMessageTask", http.MethodDelete, "/messages/{id}/tasks/{task_id}", "Unlink a task from an archived email", s.handleUnlinkMessageTask) + registerAPIV1RawHumaJSONRoute[TaskSearchResponse](api, + "searchIntegrationTasks", http.MethodGet, "/integrations/tasks/search", "Search tasks in the configured project", s.handleSearchIntegrationTasks) +} + +func (s *Server) taskMessage(r *http.Request) (tasklinks.MessageIdentity, *apiHTTPError) { + id, err := strconv.ParseInt(r.PathValue("id"), 10, 64) + if err != nil || id < 1 { + return tasklinks.MessageIdentity{}, newAPIHTTPError(http.StatusBadRequest, "invalid_message_id", "Message ID must be a positive integer") + } + message, err := s.getMessage(r.Context(), id) + if err != nil { + return tasklinks.MessageIdentity{}, newAPIHTTPError(http.StatusNotFound, "not_found", "Message not found") + } + if message.MessageType != taskLinkEmailMessageType { + return tasklinks.MessageIdentity{}, newAPIHTTPError(http.StatusUnprocessableEntity, "email_required", "Task links are available only for concrete email rows") + } + identity, err := s.taskIdentityResolver(r.Context(), message) + if err != nil { + return tasklinks.MessageIdentity{}, newAPIHTTPError(http.StatusServiceUnavailable, "identity_unavailable", "Archive identity is unavailable") + } + return identity, nil +} + +func (s *Server) handleCreateOrLinkMessageTask(w http.ResponseWriter, r *http.Request) { + browserRequestID := strings.TrimSpace(r.Header.Get("X-Request-Id")) + if browserRequestID == "" { + writeError(w, http.StatusBadRequest, "request_id_required", "X-Request-Id is required") + return + } + identity, httpErr := s.taskMessage(r) + if httpErr != nil { + writeAPIHTTPError(w, httpErr) + return + } + if s.taskLinkOperations == nil { + writeError(w, http.StatusServiceUnavailable, "task_integration_unavailable", "Task integration is unavailable") + return + } + var request TaskLinkMutationRequest + r.Body = http.MaxBytesReader(w, r.Body, MaxTaskLinkRequestBytes) + decoder := json.NewDecoder(r.Body) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&request); err != nil { + var maxBytesErr *http.MaxBytesError + if errors.As(err, &maxBytesErr) { + writeError(w, http.StatusRequestEntityTooLarge, "request_too_large", "Task request is too large") + return + } + writeError(w, http.StatusBadRequest, "invalid_request", "Invalid task request") + return + } + if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + writeError(w, http.StatusBadRequest, "invalid_request", "Task request must contain exactly one JSON value") + return + } + addedAt, err := time.Parse(time.RFC3339, request.AddedAt) + if err != nil { + writeError(w, http.StatusBadRequest, "added_at_required", "added_at must be an RFC3339 timestamp") + return + } + var task taskclient.Task + if request.TaskID != "" { + task, err = s.taskLinkOperations.Link(r.Context(), request.TaskID, identity, addedAt) + } else { + if strings.TrimSpace(request.Title) == "" { + writeError(w, http.StatusBadRequest, "title_required", "Task title is required") + return + } + key := taskIdempotencyKey(browserRequestID, identity) + task, err = s.taskLinkOperations.Create(r.Context(), key, taskclient.TaskCreate{Title: request.Title, Notes: request.Description, Priority: request.Priority, Labels: request.Labels}, identity, addedAt) + } + if err != nil { + writeTaskLinkError(w, err) + return + } + writeJSON(w, http.StatusCreated, TaskLinkMutationResponse{Task: publicTask(task)}) +} + +func (s *Server) handleListMessageTasks(w http.ResponseWriter, r *http.Request) { + identity, httpErr := s.taskMessage(r) + if httpErr != nil { + writeAPIHTTPError(w, httpErr) + return + } + if s.taskLinkOperations == nil { + writeJSON(w, http.StatusOK, taskLinkLookupResponse(identity, tasklinks.LookupResult{IndexStatus: tasklinks.IndexStatus{State: tasklinks.StateUnavailable, Complete: false, Reason: tasklinks.ReasonUnavailable}, Tasks: []tasklinks.TaskSummary{}})) + return + } + writeJSON(w, http.StatusOK, taskLinkLookupResponse(identity, s.taskLinkOperations.Lookup(r.Context(), identity))) +} + +func taskLinkLookupResponse(identity tasklinks.MessageIdentity, result tasklinks.LookupResult) TaskLinkLookupResponse { + metadata := tasklinks.NewMailLink(identity, time.Time{}) + return TaskLinkLookupResponse{IndexStatus: result.IndexStatus, Tasks: result.Tasks, OutboundMetadata: TaskLinkOutboundMetadata{ + ArchiveUID: metadata.ArchiveUID, MessageID: metadata.MessageID, ConversationID: metadata.ConversationID, + Subject: metadata.Subject, From: metadata.From, SentAt: metadata.SentAt, + SourceType: metadata.SourceType, SourceIdentifier: metadata.SourceIdentifier, SourceMessageID: metadata.SourceMessageID, + }} +} + +func (s *Server) handleSearchIntegrationTasks(w http.ResponseWriter, r *http.Request) { + if s.taskLinkOperations == nil { + writeError(w, http.StatusServiceUnavailable, "task_integration_unavailable", "Task integration is unavailable") + return + } + query := strings.TrimSpace(r.URL.Query().Get("q")) + if query == "" || len(query) > maxTaskSearchQueryLength { + writeError(w, http.StatusBadRequest, "invalid_query", "Task search query is required and must be at most 256 bytes") + return + } + tasks, err := s.taskLinkOperations.Search(r.Context(), query) + if err != nil { + writeTaskLinkError(w, err) + return + } + if tasks == nil { + tasks = []tasklinks.TaskSummary{} + } + writeJSON(w, http.StatusOK, TaskSearchResponse{Tasks: tasks}) +} + +func (s *Server) handleUnlinkMessageTask(w http.ResponseWriter, r *http.Request) { + identity, httpErr := s.taskMessage(r) + if httpErr != nil { + writeAPIHTTPError(w, httpErr) + return + } + if s.taskLinkOperations == nil { + writeError(w, http.StatusServiceUnavailable, "task_integration_unavailable", "Task integration is unavailable") + return + } + taskID := r.PathValue("task_id") + if taskID == "" { + writeError(w, http.StatusBadRequest, "task_id_required", "Task ID is required") + return + } + task, err := s.taskLinkOperations.Unlink(r.Context(), taskID, identity) + if err != nil { + writeTaskLinkError(w, err) + return + } + writeJSON(w, http.StatusOK, TaskLinkMutationResponse{Task: publicTask(task)}) +} + +func taskIdempotencyKey(requestID string, identity tasklinks.MessageIdentity) string { + material := fmt.Sprintf("%s\x00%s\x00%s\x00%s\x00%s\x00%d", requestID, identity.ArchiveUID, identity.SourceType, identity.SourceIdentifier, identity.SourceMessageID, identity.MessageID) + digest := sha256.Sum256([]byte(material)) + return "msgvault-" + hex.EncodeToString(digest[:]) +} + +func publicTask(task taskclient.Task) TaskLinkTask { + return TaskLinkTask{ID: task.ID, Project: task.Project, Title: task.Title, Revision: task.Revision} +} + +func writeTaskLinkError(w http.ResponseWriter, err error) { + switch { + case errors.Is(err, taskclient.ErrConflict): + writeError(w, http.StatusConflict, "revision_conflict", "Task changed again; retry the operation") + case errors.Is(err, taskclient.ErrAuthenticationRequired): + writeError(w, http.StatusUnauthorized, "authentication_required", "Task service authentication is required") + case errors.Is(err, taskclient.ErrIncompatible): + writeError(w, http.StatusServiceUnavailable, "task_integration_incompatible", "Task service is incompatible") + case errors.Is(err, taskclient.ErrRequestRejected): + writeError(w, http.StatusUnprocessableEntity, "task_request_rejected", "The task service rejected this request; correct the request and retry") + case errors.Is(err, tasklinks.ErrUnsafeMailLinks): + writeError(w, http.StatusConflict, "unsafe_task_metadata", "Existing task metadata cannot be updated safely") + default: + writeError(w, http.StatusServiceUnavailable, "task_integration_unavailable", "Task service is unavailable") + } +} + +type archiveUIDReader interface { + ArchiveUID() (string, error) + ArchiveRevision() (string, error) +} + +func (s *Server) resolveTaskMessageIdentity(_ context.Context, message *APIMessage) (tasklinks.MessageIdentity, error) { + archive, ok := s.store.(archiveUIDReader) + if !ok { + return tasklinks.MessageIdentity{}, errors.New("archive identity unavailable") + } + uid, err := archive.ArchiveUID() + if err != nil { + return tasklinks.MessageIdentity{}, err + } + revision, err := archive.ArchiveRevision() + if err != nil { + return tasklinks.MessageIdentity{}, err + } + sources, ok := s.store.(SourceStatusStore) + if !ok { + return tasklinks.MessageIdentity{}, errors.New("source identity unavailable") + } + all, err := sources.ListSources("") + if err != nil { + return tasklinks.MessageIdentity{}, err + } + var sourceType, identifier string + for _, source := range all { + if source.ID == message.SourceID { + sourceType, identifier = source.SourceType, source.Identifier + break + } + } + if sourceType == "" { + return tasklinks.MessageIdentity{}, fmt.Errorf("source %d not found", message.SourceID) + } + return tasklinks.MessageIdentity{ArchiveUID: uid, ArchiveRevision: revision, MessageID: message.ID, ConversationID: message.ConversationID, + Subject: message.Subject, From: message.From, SentAt: message.SentAt, SourceType: sourceType, + SourceIdentifier: identifier, SourceMessageID: message.SourceMessageID}, nil +} + +var _ archiveUIDReader = (*store.Store)(nil) diff --git a/internal/api/task_links_backend.go b/internal/api/task_links_backend.go new file mode 100644 index 000000000..b5f9d0232 --- /dev/null +++ b/internal/api/task_links_backend.go @@ -0,0 +1,168 @@ +package api + +import ( + "context" + "errors" + "path/filepath" + "strings" + "sync" + "time" + + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/taskclient" + "go.kenn.io/msgvault/internal/tasklinks" +) + +const taskReverseIndexLimit = tasklinks.HardMaxTasks + +type taskLinkBackend struct { + config config.TaskIntegrationConfig + index *tasklinks.Index + lastIndexStatus tasklinks.IndexStatus + lastIndexErr error + mu sync.Mutex +} + +func newTaskLinkBackend(cfg *config.Config) *taskLinkBackend { + backend := &taskLinkBackend{ + config: cfg.Integrations.Tasks, + index: tasklinks.NewIndex(filepath.Join(cfg.Data.DataDir, "cache", "task-links.json"), nil), + } + if err := backend.index.Load(); err != nil { + backend.lastIndexStatus = indexFailureStatus(tasklinks.IndexStatus{}, err) + backend.lastIndexErr = err + } + return backend +} + +func (b *taskLinkBackend) integrationConfig() taskclient.IntegrationConfig { + return taskclient.IntegrationConfig{Enabled: b.config.Enabled, Endpoint: b.config.Endpoint, + APIKey: b.config.APIKey, DefaultProject: b.config.DefaultProject} +} + +func (b *taskLinkBackend) connect(ctx context.Context) (*taskclient.Client, taskclient.Project, error) { + return taskclient.Connect(ctx, b.integrationConfig()) +} + +func (b *taskLinkBackend) projectPath() string { return strings.TrimSpace(b.config.DefaultProject) } + +func (b *taskLinkBackend) cacheIdentity(identity tasklinks.MessageIdentity) tasklinks.CacheIdentity { + return tasklinks.CacheIdentity{Project: b.projectPath(), ArchiveUID: identity.ArchiveUID, ArchiveRevision: identity.ArchiveRevision} +} + +func (b *taskLinkBackend) Create(ctx context.Context, key string, create taskclient.TaskCreate, identity tasklinks.MessageIdentity, addedAt time.Time) (taskclient.Task, error) { + client, project, err := b.connect(ctx) + if err != nil { + return taskclient.Task{}, err + } + task, err := (tasklinks.Service{Client: client, Project: b.projectPath(), Now: func() time.Time { return addedAt }}).Create(ctx, key, create, identity) + if err == nil { + b.refresh(ctx, client, project, identity) + } + return task, err +} + +func (b *taskLinkBackend) Link(ctx context.Context, taskID string, identity tasklinks.MessageIdentity, addedAt time.Time) (taskclient.Task, error) { + client, project, err := b.connect(ctx) + if err != nil { + return taskclient.Task{}, err + } + task, err := (tasklinks.Service{Client: client, Project: b.projectPath(), Now: func() time.Time { return addedAt }}).Link(ctx, taskID, identity) + if err == nil { + b.refresh(ctx, client, project, identity) + } + return task, err +} + +func (b *taskLinkBackend) Unlink(ctx context.Context, taskID string, identity tasklinks.MessageIdentity) (taskclient.Task, error) { + client, project, err := b.connect(ctx) + if err != nil { + return taskclient.Task{}, err + } + task, err := (tasklinks.Service{Client: client, Project: b.projectPath()}).Unlink(ctx, taskID, identity) + if err == nil { + b.refresh(ctx, client, project, identity) + } + return task, err +} + +func (b *taskLinkBackend) Search(ctx context.Context, query string) ([]tasklinks.TaskSummary, error) { + client, _, err := b.connect(ctx) + if err != nil { + return nil, err + } + listed, err := client.SearchTasks(ctx, b.projectPath(), query, 25) + if err != nil { + return nil, err + } + result := make([]tasklinks.TaskSummary, 0, len(listed.Tasks)) + for _, task := range listed.Tasks { + result = append(result, tasklinks.TaskSummary{ID: task.ID, Title: task.Title, Revision: task.Revision}) + } + return result, nil +} + +func (b *taskLinkBackend) refresh(ctx context.Context, client *taskclient.Client, project taskclient.Project, identity tasklinks.MessageIdentity) { + b.mu.Lock() + defer b.mu.Unlock() + b.lastIndexStatus, b.lastIndexErr = b.index.Rebuild(ctx, client, b.cacheIdentity(identity), project.Revision, taskReverseIndexLimit) +} + +func (b *taskLinkBackend) Lookup(ctx context.Context, identity tasklinks.MessageIdentity) tasklinks.LookupResult { + expected := b.cacheIdentity(identity) + client, project, err := b.connect(ctx) + if err != nil { + b.mu.Lock() + defer b.mu.Unlock() + return b.degradedLookup(expected, identity, err) + } + b.mu.Lock() + defer b.mu.Unlock() + status, rebuildErr := b.index.Rebuild(ctx, client, expected, project.Revision, taskReverseIndexLimit) + b.lastIndexStatus, b.lastIndexErr = status, rebuildErr + result := b.index.Lookup(expected, identity, true) + if rebuildErr != nil { + result.IndexStatus = indexFailureStatus(status, rebuildErr) + } + return result +} + +func (b *taskLinkBackend) degradedLookup(expected tasklinks.CacheIdentity, identity tasklinks.MessageIdentity, integrationErr error) tasklinks.LookupResult { + authenticated := !errors.Is(integrationErr, taskclient.ErrAuthenticationRequired) + result := b.index.Lookup(expected, identity, authenticated) + result.Complete = false + if b.lastIndexErr == nil { + switch { + case !authenticated: + result.State, result.Reason = tasklinks.StateAuthenticationRequired, "authentication_required" + case !b.config.Enabled: + result.State, result.Reason = tasklinks.StateDisabled, "disabled" + case errors.Is(integrationErr, taskclient.ErrWrongProject): + result.State, result.Reason = tasklinks.StateWrongProject, "wrong_project" + case errors.Is(integrationErr, taskclient.ErrNotFound): + result.State, result.Reason = tasklinks.StateNotFound, "not_found" + case errors.Is(integrationErr, taskclient.ErrIncompatible), errors.Is(integrationErr, taskclient.ErrInsecureDescriptor), errors.Is(integrationErr, taskclient.ErrInsecureEndpoint), errors.Is(integrationErr, taskclient.ErrInvalidResponse), errors.Is(integrationErr, taskclient.ErrResponseTooLarge), errors.Is(integrationErr, taskclient.ErrRedirect): + result.State, result.Reason = tasklinks.StateIncompatible, "incompatible" + default: + result.State, result.Reason = tasklinks.StateUnavailable, tasklinks.ReasonUnavailable + } + } else { + result.IndexStatus = indexFailureStatus(b.lastIndexStatus, b.lastIndexErr) + } + if !authenticated { + result.Tasks = []tasklinks.TaskSummary{} + } + return result +} + +func indexFailureStatus(status tasklinks.IndexStatus, err error) tasklinks.IndexStatus { + status.Complete = false + if errors.Is(err, tasklinks.ErrDiskCacheSecurityUnsupported) { + status.State = tasklinks.StateUnavailable + status.Reason = tasklinks.ReasonCachePersistenceUnsupported + } else if status.Reason == "" { + status.State = tasklinks.StateStale + status.Reason = tasklinks.ReasonPersistenceFailure + } + return status +} diff --git a/internal/api/task_links_backend_test.go b/internal/api/task_links_backend_test.go new file mode 100644 index 000000000..ef7fc360a --- /dev/null +++ b/internal/api/task_links_backend_test.go @@ -0,0 +1,232 @@ +package api + +import ( + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/taskclient" + "go.kenn.io/msgvault/internal/tasklinks" +) + +type backendListClient struct{ tasks []taskclient.Task } + +func (c backendListClient) ListTasks(context.Context, string, int, string) (taskclient.TaskList, error) { + return taskclient.TaskList{Tasks: c.tasks}, nil +} + +func readyTaskBackendServer(t *testing.T, tasks []taskclient.Task) *httptest.Server { + t.Helper() + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/api/v1/capabilities": + assert.NoError(t, json.NewEncoder(w).Encode(taskclient.Capabilities{ + ProtocolVersion: taskclient.ProtocolVersion, RevisionReads: true, ConditionalMutation: true, + ConflictResponses: true, IdempotentCreation: true, ProjectOperations: true, MetadataOperations: true, + })) + case "/api/v1/projects/project": + assert.NoError(t, json.NewEncoder(w).Encode(taskclient.Project{ID: "project-id", Name: "project", Revision: "project-r1"})) + case "/api/v1/projects/project/tasks": + assert.NoError(t, json.NewEncoder(w).Encode(taskclient.TaskList{Tasks: tasks})) + default: + http.NotFound(w, r) + } + })) +} + +func TestTaskLinkBackendSurfacesCachePersistenceFailuresWithoutIdentityRelabeling(t *testing.T) { + identity := tasklinks.MessageIdentity{ArchiveUID: "archive-a", ArchiveRevision: "1", MessageID: 42, + SourceType: "gmail", SourceIdentifier: "archive@example.com", SourceMessageID: "source-42"} + linkedTask := func(id, title string) taskclient.Task { + return taskclient.Task{ID: id, Project: "project", Title: title, Revision: "r1", + Metadata: tasklinks.MetadataWithLink(nil, tasklinks.NewMailLink(identity, time.Now()))} + } + readyServer := readyTaskBackendServer(t, []taskclient.Task{linkedTask("fresh", "Fresh current-session title")}) + t.Cleanup(readyServer.Close) + + t.Run("fresh unsupported persistence keeps scanned current-session data", func(t *testing.T) { + assertions := assert.New(t) + index := tasklinks.NewIndexWithOptions(filepath.Join(t.TempDir(), "cache", "tasks.json"), tasklinks.IndexOptions{ + Now: time.Now, PersistencePolicy: func() error { return tasklinks.ErrDiskCacheSecurityUnsupported }, + }) + backend := &taskLinkBackend{config: config.TaskIntegrationConfig{Enabled: true, Endpoint: readyServer.URL, APIKey: "test-key", DefaultProject: "project"}, index: index} + + result := backend.Lookup(context.Background(), identity) + + assertions.Equal(tasklinks.StateReady, result.State) + assertions.Equal(tasklinks.ReasonCachePersistenceUnsupported, result.Reason) + assertions.True(result.Complete) + assertions.Len(result.Tasks, 1) + assertions.Equal("Fresh current-session title", result.Tasks[0].Title) + }) + + t.Run("generic fresh save failure is not relabeled as wrong project", func(t *testing.T) { + assertions := assert.New(t) + index := tasklinks.NewIndexWithOptions(filepath.Join(t.TempDir(), "cache", "tasks.json"), tasklinks.IndexOptions{ + Now: time.Now, PersistencePolicy: func() error { return errors.New("synthetic disk failure") }, + }) + backend := &taskLinkBackend{config: config.TaskIntegrationConfig{Enabled: true, Endpoint: readyServer.URL, APIKey: "test-key", DefaultProject: "project"}, index: index} + + result := backend.Lookup(context.Background(), identity) + + assertions.Equal(tasklinks.StateStale, result.State) + assertions.Equal(tasklinks.ReasonPersistenceFailure, result.Reason) + assertions.False(result.Complete) + assertions.Empty(result.Tasks) + }) + + t.Run("refresh adopts safe data but authentication still redacts it", func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + persistenceSupported := true + index := tasklinks.NewIndexWithOptions(filepath.Join(t.TempDir(), "cache", "tasks.json"), tasklinks.IndexOptions{ + Now: time.Now, + PersistencePolicy: func() error { + if persistenceSupported { + return nil + } + return tasklinks.ErrDiskCacheSecurityUnsupported + }, + }) + cacheIdentity := tasklinks.CacheIdentity{Project: "project", ArchiveUID: "archive-a", ArchiveRevision: "1"} + _, err := index.Rebuild(context.Background(), backendListClient{tasks: []taskclient.Task{linkedTask("old", "Last-good title")}}, cacheIdentity, "project-r0", 100) + requirements.NoError(err) + persistenceSupported = false + backend := &taskLinkBackend{config: config.TaskIntegrationConfig{Enabled: true, Endpoint: readyServer.URL, APIKey: "test-key", DefaultProject: "project"}, index: index} + + result := backend.Lookup(context.Background(), identity) + + assertions.Equal(tasklinks.StateReady, result.State) + assertions.Equal(tasklinks.ReasonCachePersistenceUnsupported, result.Reason) + assertions.True(result.Complete) + requirements.Len(result.Tasks, 1) + assertions.Equal("Fresh current-session title", result.Tasks[0].Title) + + wrongProjectServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if r.URL.Path == "/api/v1/capabilities" { + assert.NoError(t, json.NewEncoder(w).Encode(taskclient.Capabilities{ + ProtocolVersion: taskclient.ProtocolVersion, RevisionReads: true, ConditionalMutation: true, + ConflictResponses: true, IdempotentCreation: true, ProjectOperations: true, MetadataOperations: true, + })) + return + } + assert.NoError(t, json.NewEncoder(w).Encode(taskclient.Project{ID: "other-id", Name: "other", Revision: "p1"})) + })) + t.Cleanup(wrongProjectServer.Close) + incompatibleServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + assert.NoError(t, json.NewEncoder(w).Encode(taskclient.Capabilities{ProtocolVersion: taskclient.ProtocolVersion})) + })) + t.Cleanup(incompatibleServer.Close) + authServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusUnauthorized) })) + t.Cleanup(authServer.Close) + unavailableServer := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) + unavailableEndpoint := unavailableServer.URL + unavailableServer.Close() + + for _, test := range []struct { + name string + endpoint string + state tasklinks.IndexState + reason string + redacted bool + }{ + {name: "wrong project", endpoint: wrongProjectServer.URL, state: tasklinks.StateWrongProject, reason: "wrong_project"}, + {name: "incompatible", endpoint: incompatibleServer.URL, state: tasklinks.StateIncompatible, reason: "incompatible"}, + {name: "authentication required", endpoint: authServer.URL, state: tasklinks.StateAuthenticationRequired, reason: "authentication_required", redacted: true}, + {name: "unavailable", endpoint: unavailableEndpoint, state: tasklinks.StateUnavailable, reason: tasklinks.ReasonUnavailable}, + } { + t.Run(test.name, func(t *testing.T) { + assertions := assert.New(t) + backend.config.Endpoint = test.endpoint + + degraded := backend.Lookup(context.Background(), identity) + + assertions.Equal(test.state, degraded.State) + assertions.Equal(test.reason, degraded.Reason) + assertions.False(degraded.Complete) + if test.redacted { + assertions.Empty(degraded.Tasks) + } else { + assertions.Len(degraded.Tasks, 1) + assertions.Equal("Fresh current-session title", degraded.Tasks[0].Title) + } + }) + } + }) +} + +func TestTaskLinkBackendDegradedStatesUseOnlySafeCache(t *testing.T) { + identity := tasklinks.MessageIdentity{ArchiveUID: "archive-a", ArchiveRevision: "1", MessageID: 42, + SourceType: "gmail", SourceIdentifier: "archive@example.com", SourceMessageID: "source-42"} + cacheIdentity := tasklinks.CacheIdentity{Project: "project", ArchiveUID: "archive-a", ArchiveRevision: "1"} + newBackend := func(t *testing.T, cfg config.TaskIntegrationConfig) *taskLinkBackend { + t.Helper() + index := tasklinks.NewIndex(filepath.Join(t.TempDir(), "cache", "tasks.json"), time.Now) + linked := taskclient.Task{ID: "task-1", Project: "project", Title: "Cached title", Revision: "r1", + Metadata: tasklinks.MetadataWithLink(nil, tasklinks.NewMailLink(identity, time.Now()))} + _, err := index.Rebuild(context.Background(), backendListClient{tasks: []taskclient.Task{linked}}, cacheIdentity, "remote-1", 100) + require.NoError(t, err) + return &taskLinkBackend{config: cfg, index: index} + } + + t.Run("disabled retains identity-safe titles", func(t *testing.T) { + backend := newBackend(t, config.TaskIntegrationConfig{Enabled: false, DefaultProject: "project"}) + result := backend.Lookup(context.Background(), identity) + assert.Equal(t, tasklinks.StateDisabled, result.State) + require.Len(t, result.Tasks, 1) + assert.Equal(t, "Cached title", result.Tasks[0].Title) + }) + + t.Run("authentication redacts titles", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + })) + t.Cleanup(server.Close) + backend := newBackend(t, config.TaskIntegrationConfig{Enabled: true, Endpoint: server.URL, APIKey: "bad-key", DefaultProject: "project"}) + result := backend.Lookup(context.Background(), identity) + assert.Equal(t, tasklinks.StateAuthenticationRequired, result.State) + assert.Empty(t, result.Tasks) + }) + + t.Run("incompatible retains identity-safe titles", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + assert.NoError(t, json.NewEncoder(w).Encode(taskclient.Capabilities{ProtocolVersion: taskclient.ProtocolVersion})) + })) + t.Cleanup(server.Close) + backend := newBackend(t, config.TaskIntegrationConfig{Enabled: true, Endpoint: server.URL, APIKey: "test-key", DefaultProject: "project"}) + result := backend.Lookup(context.Background(), identity) + assert.Equal(t, tasklinks.StateIncompatible, result.State) + require.Len(t, result.Tasks, 1) + }) + + t.Run("wrong project retains cache only for configured identity", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/api/v1/capabilities": + assert.NoError(t, json.NewEncoder(w).Encode(taskclient.Capabilities{ProtocolVersion: taskclient.ProtocolVersion, + RevisionReads: true, ConditionalMutation: true, ConflictResponses: true, IdempotentCreation: true, + ProjectOperations: true, MetadataOperations: true})) + default: + assert.NoError(t, json.NewEncoder(w).Encode(taskclient.Project{ID: "other-id", Name: "other", Revision: "p1"})) + } + })) + t.Cleanup(server.Close) + backend := newBackend(t, config.TaskIntegrationConfig{Enabled: true, Endpoint: server.URL, APIKey: "test-key", DefaultProject: "project"}) + result := backend.Lookup(context.Background(), identity) + assert.Equal(t, tasklinks.StateWrongProject, result.State) + require.Len(t, result.Tasks, 1) + }) +} diff --git a/internal/api/task_links_test.go b/internal/api/task_links_test.go new file mode 100644 index 000000000..2429cfa92 --- /dev/null +++ b/internal/api/task_links_test.go @@ -0,0 +1,271 @@ +package api + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/config" + "go.kenn.io/msgvault/internal/taskclient" + "go.kenn.io/msgvault/internal/tasklinks" +) + +type taskLinkMessageStore struct { + *mockStore + + message *APIMessage +} + +func (s *taskLinkMessageStore) GetMessage(id int64) (*APIMessage, error) { + if s.message == nil || s.message.ID != id { + return nil, errors.New("not found") + } + return s.message, nil +} + +func (s *taskLinkMessageStore) GetMessageContext(_ context.Context, id int64) (*APIMessage, error) { + return s.GetMessage(id) +} + +type fakeTaskLinkOperations struct { + createKeys []string + creates []taskclient.TaskCreate + identities []tasklinks.MessageIdentity + lookup tasklinks.LookupResult + search []tasklinks.TaskSummary + err error +} + +func (f *fakeTaskLinkOperations) Create(_ context.Context, key string, create taskclient.TaskCreate, identity tasklinks.MessageIdentity, addedAt time.Time) (taskclient.Task, error) { + f.createKeys = append(f.createKeys, key) + f.identities = append(f.identities, identity) + create.Metadata = tasklinks.MetadataWithLink(create.Metadata, tasklinks.NewMailLink(identity, addedAt)) + f.creates = append(f.creates, create) + return taskclient.Task{ID: "task-1", Project: "project", Title: create.Title, Revision: "r1"}, f.err +} +func (f *fakeTaskLinkOperations) Link(_ context.Context, _ string, identity tasklinks.MessageIdentity, _ time.Time) (taskclient.Task, error) { + f.identities = append(f.identities, identity) + return taskclient.Task{ID: "task-1", Project: "project", Title: "Linked", Revision: "r1"}, f.err +} +func (f *fakeTaskLinkOperations) Unlink(_ context.Context, _ string, identity tasklinks.MessageIdentity) (taskclient.Task, error) { + f.identities = append(f.identities, identity) + return taskclient.Task{ID: "task-1", Project: "project", Title: "Linked", Revision: "r2"}, f.err +} +func (f *fakeTaskLinkOperations) Lookup(context.Context, tasklinks.MessageIdentity) tasklinks.LookupResult { + return f.lookup +} +func (f *fakeTaskLinkOperations) Search(context.Context, string) ([]tasklinks.TaskSummary, error) { + return f.search, f.err +} + +func emailMessage(messageType string) *APIMessage { + return &APIMessage{ID: 42, SourceID: 3, SourceMessageID: "source-42", ConversationID: 7, + Subject: "Synthetic subject", From: "sender@example.com", SentAt: time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC), MessageType: messageType} +} + +func taskLinkServer(t *testing.T, messageType string, operations TaskLinkOperations) *Server { + t.Helper() + return NewServerWithOptions(ServerOptions{Config: &config.Config{Server: config.ServerConfig{APIKey: "test-key"}}, + Store: &taskLinkMessageStore{mockStore: &mockStore{}, message: emailMessage(messageType)}, Logger: testLogger(), TaskLinkOperations: operations, + TaskIdentityResolver: func(_ context.Context, message *APIMessage) (tasklinks.MessageIdentity, error) { + return tasklinks.MessageIdentity{ArchiveUID: "archive-a", ArchiveRevision: "1", MessageID: message.ID, ConversationID: message.ConversationID, + Subject: message.Subject, From: message.From, SentAt: message.SentAt, SourceType: "gmail", + SourceIdentifier: "archive@example.com", SourceMessageID: message.SourceMessageID}, nil + }, + }) +} + +func TestCreateTaskForEmailDerivesStableServerIdempotency(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + ops := &fakeTaskLinkOperations{} + srv := taskLinkServer(t, "email", ops) + body := []byte(`{"title":"Follow up","description":"Synthetic notes","priority":"high","labels":["mail","follow-up"],"added_at":"2026-07-19T01:02:03Z"}`) + for range 2 { + req := httptest.NewRequest(http.MethodPost, "/api/v1/messages/42/tasks", bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Api-Key", "test-key") + req.Header.Set("X-Request-Id", "browser-request-1") + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + require.Equal(http.StatusCreated, resp.Code, resp.Body.String()) + assert.NotContains(resp.Body.String(), "archive-a") + } + require.Len(ops.createKeys, 2) + assert.Equal(ops.createKeys[0], ops.createKeys[1]) + assert.NotEqual("browser-request-1", ops.createKeys[0]) + require.Len(ops.creates, 2) + assert.Equal(ops.creates[0], ops.creates[1], "a browser retry must send the identical external create payload") + links := tasklinks.MailLinks(ops.creates[0].Metadata) + require.Len(links, 1) + assert.Equal("2026-07-19T01:02:03Z", links[0].AddedAt) + assert.Equal("high", ops.creates[0].Priority) + assert.Equal([]string{"mail", "follow-up"}, ops.creates[0].Labels) +} + +func TestCreateTaskRequiresBrowserRequestID(t *testing.T) { + srv := taskLinkServer(t, "email", &fakeTaskLinkOperations{}) + req := httptest.NewRequest(http.MethodPost, "/api/v1/messages/42/tasks", bytes.NewReader([]byte(`{"title":"Follow up","added_at":"2026-07-19T01:02:03Z"}`))) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Api-Key", "test-key") + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + assert.Equal(t, http.StatusBadRequest, resp.Code, resp.Body.String()) +} + +func TestCreateTaskOpenAPIRequiresBrowserRequestID(t *testing.T) { + doc := OpenAPIDocument() + operation := doc.Paths["/api/v1/messages/{id}/tasks"].Post + require.NotNil(t, operation) + for _, parameter := range operation.Parameters { + if parameter.Name == "X-Request-Id" { + assert.True(t, parameter.Required) + return + } + } + require.Fail(t, "X-Request-Id parameter missing from task create contract") +} + +func TestCreateTaskRejectsOversizedAndTrailingJSONBodies(t *testing.T) { + srv := taskLinkServer(t, "email", &fakeTaskLinkOperations{}) + for _, tc := range []struct { + name string + body []byte + want int + }{ + {name: "oversized", body: append([]byte(`{"title":"`), append(bytes.Repeat([]byte("x"), MaxTaskLinkRequestBytes), []byte(`","added_at":"2026-07-19T01:02:03Z"}`)...)...), want: http.StatusRequestEntityTooLarge}, + {name: "trailing object", body: []byte(`{"title":"Follow up","added_at":"2026-07-19T01:02:03Z"} {}`), want: http.StatusBadRequest}, + } { + t.Run(tc.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/api/v1/messages/42/tasks", bytes.NewReader(tc.body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Api-Key", "test-key") + req.Header.Set("X-Request-Id", "browser-request-1") + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + assert.Equal(t, tc.want, resp.Code, resp.Body.String()) + }) + } +} + +func TestTaskLinkAPIsRejectNonEmailConcreteRows(t *testing.T) { + srv := taskLinkServer(t, "imessage", &fakeTaskLinkOperations{}) + for _, tc := range []struct{ method, path string }{ + {http.MethodGet, "/api/v1/messages/42/tasks"}, + {http.MethodPost, "/api/v1/messages/42/tasks"}, + {http.MethodDelete, "/api/v1/messages/42/tasks/task-1"}, + } { + req := httptest.NewRequest(tc.method, tc.path, bytes.NewReader([]byte(`{"task_id":"task-1","title":"Synthetic"}`))) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Api-Key", "test-key") + req.Header.Set("X-Request-Id", "browser-request-1") + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + assert.Equal(t, http.StatusUnprocessableEntity, resp.Code, resp.Body.String()) + } +} + +func TestTaskLookupAlwaysReturnsIndexAuthorityFields(t *testing.T) { + assert := assert.New(t) + now := time.Date(2026, 7, 19, 1, 0, 0, 0, time.UTC) + ops := &fakeTaskLinkOperations{lookup: tasklinks.LookupResult{IndexStatus: tasklinks.IndexStatus{ + State: tasklinks.StatePartial, Complete: false, LastScan: now, RemoteRevision: "remote-1", Reason: tasklinks.ReasonSafetyLimit}, + Tasks: []tasklinks.TaskSummary{{ID: "task-1", Title: "Linked task"}}}} + srv := taskLinkServer(t, "email", ops) + req := httptest.NewRequest(http.MethodGet, "/api/v1/messages/42/tasks", nil) + req.Header.Set("X-Api-Key", "test-key") + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + require.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) + var got TaskLinkLookupResponse + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &got)) + assert.False(got.Complete) + assert.Equal(tasklinks.StatePartial, got.State) + assert.Equal("remote-1", got.RemoteRevision) + assert.Equal(tasklinks.ReasonSafetyLimit, got.Reason) + assert.Equal(now, got.LastScan) + require.Len(t, got.Tasks, 1) + assert.Equal("archive-a", got.OutboundMetadata.ArchiveUID) + assert.Equal(int64(42), got.OutboundMetadata.MessageID) + assert.Equal(int64(7), got.OutboundMetadata.ConversationID) + assert.Equal("Synthetic subject", got.OutboundMetadata.Subject) + assert.Equal("sender@example.com", got.OutboundMetadata.From) + assert.Equal("gmail", got.OutboundMetadata.SourceType) + assert.Equal("archive@example.com", got.OutboundMetadata.SourceIdentifier) + assert.Equal("source-42", got.OutboundMetadata.SourceMessageID) +} + +func TestTaskLookupDisclosesExactSanitizedOutboundMetadata(t *testing.T) { + assert := assert.New(t) + identity := tasklinks.MessageIdentity{ + ArchiveUID: " archive\x00-" + strings.Repeat("a", tasklinks.MaxSnapshotFieldBytes), + MessageID: 42, ConversationID: 7, + Subject: " subject\x00-" + strings.Repeat("s", tasklinks.MaxSnapshotFieldBytes), + From: " sender\x00-" + strings.Repeat("f", tasklinks.MaxSnapshotFieldBytes), + SentAt: time.Date(2026, 7, 19, 1, 2, 3, 0, time.FixedZone("offset", -5*60*60)), + SourceType: " gmail\x00-" + strings.Repeat("t", tasklinks.MaxSnapshotFieldBytes), + SourceIdentifier: " archive@example.com\x00-" + strings.Repeat("i", tasklinks.MaxSnapshotFieldBytes), + SourceMessageID: " source-42\x00-" + strings.Repeat("m", tasklinks.MaxSnapshotFieldBytes), + } + want := tasklinks.NewMailLink(identity, time.Time{}) + + got := taskLinkLookupResponse(identity, tasklinks.LookupResult{}).OutboundMetadata + + assert.Equal(want.ArchiveUID, got.ArchiveUID) + assert.Equal(want.MessageID, got.MessageID) + assert.Equal(want.ConversationID, got.ConversationID) + assert.Equal(want.Subject, got.Subject) + assert.Equal(want.From, got.From) + assert.Equal(want.SentAt, got.SentAt) + assert.Equal(want.SourceType, got.SourceType) + assert.Equal(want.SourceIdentifier, got.SourceIdentifier) + assert.Equal(want.SourceMessageID, got.SourceMessageID) + assert.NotContains(got.Subject, "\x00") + assert.LessOrEqual(len(got.Subject), tasklinks.MaxSnapshotFieldBytes) +} + +func TestTaskSearchReturnsConfiguredProjectResults(t *testing.T) { + ops := &fakeTaskLinkOperations{search: []tasklinks.TaskSummary{{ID: "task-1", Title: "Synthetic result", Revision: "r1"}}} + srv := taskLinkServer(t, "email", ops) + req := httptest.NewRequest(http.MethodGet, "/api/v1/integrations/tasks/search?q=Synthetic", nil) + req.Header.Set("X-Api-Key", "test-key") + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + require.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) + var got TaskSearchResponse + require.NoError(t, json.Unmarshal(resp.Body.Bytes(), &got)) + assert.Equal(t, ops.search, got.Tasks) +} + +func TestTaskLinkSecondConflictIsSurfaced(t *testing.T) { + ops := &fakeTaskLinkOperations{err: taskclient.ErrConflict} + srv := taskLinkServer(t, "email", ops) + req := httptest.NewRequest(http.MethodPost, "/api/v1/messages/42/tasks", bytes.NewReader([]byte(`{"task_id":"task-1","added_at":"2026-07-19T01:02:03Z"}`))) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Api-Key", "test-key") + req.Header.Set("X-Request-Id", "browser-request-1") + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + assert.Equal(t, http.StatusConflict, resp.Code, resp.Body.String()) +} + +func TestTaskLinkRequestRejectedIsSurfacedAsClientError(t *testing.T) { + ops := &fakeTaskLinkOperations{err: taskclient.ErrRequestRejected} + srv := taskLinkServer(t, "email", ops) + req := httptest.NewRequest(http.MethodPost, "/api/v1/messages/42/tasks", bytes.NewReader([]byte(`{"task_id":"task-1","added_at":"2026-07-19T01:02:03Z"}`))) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Api-Key", "test-key") + req.Header.Set("X-Request-Id", "browser-request-1") + resp := httptest.NewRecorder() + srv.Router().ServeHTTP(resp, req) + require.Equal(t, http.StatusUnprocessableEntity, resp.Code, resp.Body.String()) + assert.Contains(t, resp.Body.String(), "task_request_rejected") +} diff --git a/internal/api/validation_test.go b/internal/api/validation_test.go index 533f72645..2f699f246 100644 --- a/internal/api/validation_test.go +++ b/internal/api/validation_test.go @@ -130,12 +130,11 @@ func TestOutOfRangePaginationClamps(t *testing.T) { } } -func TestUnknownPathReturnsJSON404(t *testing.T) { +func TestUnknownAPIPathReturnsJSON404(t *testing.T) { targets := []string{ "/api/v1/nonexistent", "/api/v1/messages/", "/api/v1/stats/", - "/totally/unknown", } for _, target := range targets { t.Run(target, func(t *testing.T) { diff --git a/internal/backupapp/app_test.go b/internal/backupapp/app_test.go index 59ca0bf80..8de28695a 100644 --- a/internal/backupapp/app_test.go +++ b/internal/backupapp/app_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "encoding/json" + "os" "path/filepath" "testing" @@ -12,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "go.kenn.io/kit/backup" "go.kenn.io/msgvault/internal/backupapp" + "go.kenn.io/msgvault/internal/store" ) // seedDB creates the minimal msgvault-shaped schema (same shape as the @@ -121,3 +123,55 @@ func TestAppConstants(t *testing.T) { []string{"vectors.db", "analytics/", "logs/", "imports/", "tmp/", "locks"}, app.ExcludedPaths()) } + +func TestBackupRestorePreservesSavedViews(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + ctx := context.Background() + dataDir := t.TempDir() + dbPath := filepath.Join(dataDir, "msgvault.db") + attachmentsDir := filepath.Join(dataDir, "attachments") + requirements.NoError(os.MkdirAll(attachmentsDir, 0o700)) + + st, err := store.OpenForTest(dbPath) + requirements.NoError(err) + requirements.NoError(st.InitSchema()) + archiveUID, err := st.ArchiveUID() + requirements.NoError(err) + description := "Shared analytical definition" + created, err := st.CreateSavedView(ctx, store.SavedViewInput{ + Name: "Invoices", + Description: &description, + CanonicalState: json.RawMessage(`{"query":"invoice","search_mode":"full_text"}`), + SchemaVersion: store.CurrentSavedViewSchemaVersion, + }) + requirements.NoError(err) + t.Cleanup(func() { _ = st.Close() }) + + repository, err := backup.Init(filepath.Join(t.TempDir(), "repository")) + requirements.NoError(err) + app := backupapp.New("test") + _, err = backup.Create(ctx, repository, app, backup.CreateOptions{ + DBPath: dbPath, ContentDir: attachmentsDir, DataDir: dataDir, + }) + requirements.NoError(err) + + restoreResult, err := backup.Restore(ctx, repository, app, backup.RestoreOptions{ + TargetDir: filepath.Join(t.TempDir(), "restored"), + }) + requirements.NoError(err) + restored, err := store.OpenForTest(restoreResult.DBPath) + requirements.NoError(err) + t.Cleanup(func() { _ = restored.Close() }) + restoredArchiveUID, err := restored.ArchiveUID() + requirements.NoError(err) + assertions.Equal(archiveUID, restoredArchiveUID, "backup and restore must preserve archive identity") + + got, err := restored.GetSavedView(ctx, created.ID) + requirements.NoError(err) + assertions.Equal(created.Name, got.Name) + assertions.Equal(created.Description, got.Description) + assertions.JSONEq(string(created.CanonicalState), string(got.CanonicalState)) + assertions.Equal(created.SchemaVersion, got.SchemaVersion) + assertions.Equal(created.Revision, got.Revision) +} diff --git a/internal/cacheops/identity_refresh.go b/internal/cacheops/identity_refresh.go new file mode 100644 index 000000000..2073cfdf4 --- /dev/null +++ b/internal/cacheops/identity_refresh.go @@ -0,0 +1,369 @@ +package cacheops + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "time" + + _ "github.com/duckdb/duckdb-go/v2" // DuckDB driver (database/sql) + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/store" +) + +const ( + datasetOwnerParticipants = "owner_participants" + datasetParticipantClusters = "participant_clusters" +) + +// identityDatasets lists the Parquet dataset directories RefreshIdentityDatasets +// re-exports. Both are always fully replaced, matching how the full cache +// builder treats them (see replacesCacheDataset in cmd/msgvault/cmd). +var identityDatasets = []string{datasetOwnerParticipants, datasetParticipantClusters} + +// ErrNoCommittedCache is returned by RefreshIdentityDatasets when +// analyticsDir has no committed cache publication (no _last_sync.json) to +// refresh. Callers should treat this like any other cache-unavailable +// condition (the API layer maps it to the standard response). +var ErrNoCommittedCache = errors.New("cacheops: no committed analytics cache to refresh") + +// RefreshIdentityDatasets re-exports owner_participants and +// participant_clusters from st into the committed cache at analyticsDir, +// leaving every message-derived dataset (including is_from_me, baked into +// the message shards at export time) untouched, and stamps the new +// identity revision into _last_sync.json. It returns the stamped revision. +// It deliberately does NOT advance AccountIdentityRevision — see +// publishIdentityDatasets. +// +// Both datasets are derived entirely from st's own queries (never from a +// SQLite file path), so the same code path runs for SQLite- and +// PostgreSQL-backed archives alike: the owner_participants query below has +// no dialect-specific SQL, so no ATTACH or per-backend branching is needed, +// and it never crashes a PostgreSQL archive. +// +// Concurrency: callers must hold the analytics cache build lock +// (query.CacheBuildLockPath) exclusively for the duration of this call — +// the same lock a full cache build holds. This function does not acquire it +// itself: a caller that already holds it (the build-cache flow wires this +// in for identity-drift-only staleness) would self-deadlock on a second +// exclusive acquisition of the same underlying flock file. With that lock +// held, concurrent Parquet readers (which take it shared) safely wait out +// the rename below, exactly as they wait out a full build. +func RefreshIdentityDatasets(ctx context.Context, st *store.Store, analyticsDir string) (int64, error) { + state, err := query.ReadCacheSyncState(analyticsDir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return 0, fmt.Errorf("%w: %s", ErrNoCommittedCache, analyticsDir) + } + return 0, fmt.Errorf("read cache sync state before identity refresh: %w", err) + } + + revision, err := st.IdentityRevision() + if err != nil { + return 0, fmt.Errorf("read identity revision: %w", err) + } + + // No-op short-circuit: every link/unlink API call re-runs this refresh + // even when the mutation changed nothing (e.g. re-linking two already- + // linked participants), and re-publishing unconditionally would rewrite + // the Parquet datasets and advance PublishedAt/DatasetFingerprint for + // no reason — needlessly 409-ing every active pagination cursor even + // though the committed data is unchanged. Short-circuit only when the + // committed revision already matches the current one AND both dataset + // directories are actually on disk: a prior refresh that failed before + // reaching publishIdentityDatasets leaves the committed revision + // lagging behind the current one, so this condition is false and the + // retry below still does the real work, satisfying the spec's + // requirement that retry-after-stale re-attempts the refresh. + if state.IdentityRevision == revision && identityDatasetsExist(analyticsDir) { + return revision, nil + } + + owners, err := ownerParticipantRows(ctx, st) + if err != nil { + return 0, fmt.Errorf("read owner participants: %w", err) + } + clusters, err := st.ParticipantClusters() + if err != nil { + return 0, fmt.Errorf("read participant clusters: %w", err) + } + + staging, err := newIdentityStaging(analyticsDir) + if err != nil { + return 0, err + } + defer func() { _ = staging.cleanup() }() + + db, err := sql.Open("duckdb", "") + if err != nil { + return 0, fmt.Errorf("open duckdb for identity refresh: %w", err) + } + defer func() { _ = db.Close() }() + + if err := exportOwnerParticipants(ctx, db, owners, staging.datasetDir(datasetOwnerParticipants)); err != nil { + return 0, err + } + if err := exportParticipantClusters(ctx, db, clusters, staging.datasetDir(datasetParticipantClusters)); err != nil { + return 0, err + } + + if err := publishIdentityDatasets(staging, analyticsDir, state, revision); err != nil { + return 0, err + } + return revision, nil +} + +// identityDatasetsExist reports whether every identity dataset directory +// under analyticsDir exists and has at least one file staged into it. +// exportOwnerParticipants/exportParticipantClusters always write a Parquet +// file, even for zero rows (see their comments), so an empty or missing +// directory means the dataset was never actually published — the no-op +// short-circuit in RefreshIdentityDatasets must not trust a matching +// revision alone in that case. +func identityDatasetsExist(analyticsDir string) bool { + for _, dataset := range identityDatasets { + entries, err := os.ReadDir(filepath.Join(analyticsDir, dataset)) + if err != nil || len(entries) == 0 { + return false + } + } + return true +} + +// ownerParticipantRow is one (source, participant) ownership edge: the +// source's confirmed account identity resolved to this participant. +type ownerParticipantRow struct { + sourceID int64 + participantID int64 +} + +// ownerParticipantsSQL matches every participant row that a confirmed +// account_identities address resolves to for its source, case-insensitively +// against either the durable participant email or an explicit +// participant_identifiers email, or verbatim against any non-email +// participant_identifiers row (phone, chat handle, etc. — identifiers are +// stored verbatim; only email comparisons fold case). Deliberately +// dialect-neutral (no bind placeholders, no dialect-specific functions) so +// it runs unchanged on SQLite and PostgreSQL. Kept byte-equivalent (apart +// from the ATTACH schema prefix) with the matching query in +// cmd/msgvault/cmd/build_cache.go. +const ownerParticipantsSQL = ` + SELECT DISTINCT ai.source_id, p.id AS participant_id + FROM account_identities ai + JOIN participants p + ON p.email_address IS NOT NULL AND lower(p.email_address) = lower(ai.address) + UNION + SELECT DISTINCT ai.source_id, pi.participant_id + FROM account_identities ai + JOIN participant_identifiers pi + ON (pi.identifier_type = 'email' AND lower(pi.identifier_value) = lower(ai.address)) + OR (pi.identifier_type != 'email' AND pi.identifier_value = ai.address) +` + +// ownerParticipantRows reads the current owner_participants edges through +// st's own connection. +func ownerParticipantRows(ctx context.Context, st *store.Store) ([]ownerParticipantRow, error) { + rows, err := st.DB().QueryContext(ctx, ownerParticipantsSQL) + if err != nil { + return nil, fmt.Errorf("query owner participants: %w", err) + } + defer func() { _ = rows.Close() }() + + var out []ownerParticipantRow + for rows.Next() { + var r ownerParticipantRow + if err := rows.Scan(&r.sourceID, &r.participantID); err != nil { + return nil, fmt.Errorf("scan owner participant row: %w", err) + } + out = append(out, r) + } + return out, rows.Err() +} + +// exportOwnerParticipants writes rows to dir/owner_participants.parquet via +// a DuckDB temp table, mirroring the participant_clusters export pattern: +// Go-computed data has no SQL source to COPY from directly, so it is +// staged through a temp table first. Always writes a file, even for zero +// rows, so the dataset directory required by query.RequiredParquetDirs +// always exists. +func exportOwnerParticipants(ctx context.Context, db *sql.DB, rows []ownerParticipantRow, dir string) error { + if _, err := db.ExecContext(ctx, + `CREATE TEMP TABLE tmp_owner_participants (source_id BIGINT, participant_id BIGINT)`, + ); err != nil { + return fmt.Errorf("create owner participants temp table: %w", err) + } + if len(rows) > 0 { + values := make([]string, 0, len(rows)) + for _, r := range rows { + values = append(values, fmt.Sprintf("(%d, %d)", r.sourceID, r.participantID)) + } + insertSQL := "INSERT INTO tmp_owner_participants (source_id, participant_id) VALUES " + + strings.Join(values, ", ") + if _, err := db.ExecContext(ctx, insertSQL); err != nil { + return fmt.Errorf("populate owner participants temp table: %w", err) + } + } + escapedDir := strings.ReplaceAll(dir, "'", "''") + if _, err := db.ExecContext(ctx, fmt.Sprintf(` + COPY ( + SELECT source_id, participant_id FROM tmp_owner_participants + ) TO '%s/owner_participants.parquet' ( + FORMAT PARQUET, + COMPRESSION 'zstd' + ) + `, escapedDir)); err != nil { + return fmt.Errorf("export owner participants: %w", err) + } + if _, err := db.ExecContext(ctx, `DROP TABLE tmp_owner_participants`); err != nil { + return fmt.Errorf("drop owner participants temp table: %w", err) + } + return nil +} + +// exportParticipantClusters writes clusters (participant_id -> canonical +// cluster ID) to dir/participant_clusters.parquet via a DuckDB temp table, +// the same approach cmd/msgvault/cmd/build_cache.go uses for its full-build +// export: ParticipantClusters does graph traversal in Go, so it cannot be +// expressed as a COPY query directly. +func exportParticipantClusters(ctx context.Context, db *sql.DB, clusters map[int64]int64, dir string) error { + if _, err := db.ExecContext(ctx, + `CREATE TEMP TABLE tmp_participant_clusters (participant_id BIGINT, canonical_id BIGINT)`, + ); err != nil { + return fmt.Errorf("create participant clusters temp table: %w", err) + } + if len(clusters) > 0 { + values := make([]string, 0, len(clusters)) + for participantID, canonicalID := range clusters { + values = append(values, fmt.Sprintf("(%d, %d)", participantID, canonicalID)) + } + insertSQL := "INSERT INTO tmp_participant_clusters (participant_id, canonical_id) VALUES " + + strings.Join(values, ", ") + if _, err := db.ExecContext(ctx, insertSQL); err != nil { + return fmt.Errorf("populate participant clusters temp table: %w", err) + } + } + escapedDir := strings.ReplaceAll(dir, "'", "''") + if _, err := db.ExecContext(ctx, fmt.Sprintf(` + COPY ( + SELECT participant_id, canonical_id FROM tmp_participant_clusters + ) TO '%s/participant_clusters.parquet' ( + FORMAT PARQUET, + COMPRESSION 'zstd' + ) + `, escapedDir)); err != nil { + return fmt.Errorf("export participant clusters: %w", err) + } + if _, err := db.ExecContext(ctx, `DROP TABLE tmp_participant_clusters`); err != nil { + return fmt.Errorf("drop participant clusters temp table: %w", err) + } + return nil +} + +// identityStaging is a same-parent staging directory for the two identity +// datasets, mirroring cmd/msgvault/cmd/cache_publication.go's cacheStaging +// convention (a dot-prefixed sibling of analyticsDir) without depending on +// that unexported type: cacheops cannot import cmd, since cmd imports +// cacheops. +type identityStaging struct { + root string +} + +func newIdentityStaging(analyticsDir string) (*identityStaging, error) { + parent := filepath.Dir(filepath.Clean(analyticsDir)) + if err := os.MkdirAll(parent, 0o755); err != nil { + return nil, fmt.Errorf("create analytics cache parent: %w", err) + } + prefix := "." + filepath.Base(filepath.Clean(analyticsDir)) + ".identity-" + root, err := os.MkdirTemp(parent, prefix) + if err != nil { + return nil, fmt.Errorf("create identity refresh staging directory: %w", err) + } + for _, dataset := range identityDatasets { + if err := os.MkdirAll(filepath.Join(root, dataset), 0o755); err != nil { + _ = os.RemoveAll(root) + return nil, fmt.Errorf("create staged %s directory: %w", dataset, err) + } + } + return &identityStaging{root: root}, nil +} + +func (s *identityStaging) datasetDir(dataset string) string { + return filepath.Join(s.root, dataset) +} + +func (s *identityStaging) cleanup() error { + if s == nil || s.root == "" { + return nil + } + return os.RemoveAll(s.root) +} + +// publishIdentityDatasets replaces the live owner_participants and +// participant_clusters directories with their staged replacements and +// commits the updated identity revision, mirroring cmd's publishCache: +// invalidate the commit marker first, then rename, then re-fingerprint and +// write the new marker. state is the CacheSyncState already read by +// RefreshIdentityDatasets; only IdentityRevision, PublishedAt, and +// DatasetFingerprint change. +// +// state.AccountIdentityRevision is deliberately left untouched: this +// refresh only re-exports owner_participants/participant_clusters, never +// the message shards that bake in is_from_me, so it must not advance the +// marker that records those shards' freshness. Advancing it here would +// make a stale is_from_me look fresh and permanently suppress the full +// rebuild that HasAccountIdentityDrift is meant to force. +func publishIdentityDatasets(staging *identityStaging, analyticsDir string, state query.CacheSyncState, revision int64) error { + statePath := query.CacheStatePath(analyticsDir) + if err := invalidateIdentityStateFile(statePath); err != nil { + return err + } + + for _, dataset := range identityDatasets { + dest := filepath.Join(analyticsDir, dataset) + if err := os.RemoveAll(dest); err != nil { + return fmt.Errorf("remove live %s dataset: %w", dataset, err) + } + if err := os.Rename(staging.datasetDir(dataset), dest); err != nil { + return fmt.Errorf("publish %s dataset: %w", dataset, err) + } + } + + fingerprint, err := query.CacheDatasetFingerprint(analyticsDir) + if err != nil { + return fmt.Errorf("fingerprint published analytics cache: %w", err) + } + state.IdentityRevision = revision + state.PublishedAt = time.Now().UTC() + state.DatasetFingerprint = fingerprint + data, err := json.Marshal(state) + if err != nil { + return fmt.Errorf("encode cache sync state: %w", err) + } + if err := os.WriteFile(statePath, data, 0o600); err != nil { + return fmt.Errorf("save cache sync state: %w", err) + } + return nil +} + +// invalidateIdentityStateFile makes _last_sync.json unusable while the +// dataset directories below it are mid-rename, mirroring cmd's +// invalidateSyncStateFile: a reader racing the rename must see a +// missing/unparseable commit marker rather than one naming a dataset that +// no longer matches the files on disk. Removal first; if that fails (e.g. +// no directory write permission), overwrite with content that fails to +// parse. Only when neither works does it return an error. +func invalidateIdentityStateFile(statePath string) error { + removeErr := os.Remove(statePath) + if removeErr == nil || os.IsNotExist(removeErr) { + return nil + } + if writeErr := os.WriteFile(statePath, []byte("invalidated"), 0o600); writeErr != nil { + return fmt.Errorf("invalidate cache sync state: remove: %w; overwrite: %w", removeErr, writeErr) + } + return nil +} diff --git a/internal/cacheops/identity_refresh_test.go b/internal/cacheops/identity_refresh_test.go new file mode 100644 index 000000000..e7957cbe1 --- /dev/null +++ b/internal/cacheops/identity_refresh_test.go @@ -0,0 +1,253 @@ +package cacheops + +import ( + "context" + "database/sql" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/query" + "go.kenn.io/msgvault/internal/testutil/storetest" +) + +// TestRefreshIdentityDatasetsUpdatesClustersAndRevision pins the core +// contract: after LinkParticipants, a refresh writes the new cluster +// mapping and identity revision into the committed cache without touching +// the message datasets. +func TestRefreshIdentityDatasetsUpdatesClustersAndRevision(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + f := storetest.New(t) + st := f.Store + a := f.EnsureParticipant("alice@example.com", "Alice", "example.com") + b := f.EnsureParticipant("alice@personal.example", "Alice P", "personal.example") + + dir := writeCacheStatsFixture(t) + messagesFile := filepath.Join(dir, tableMessages, "year=2024", "data.parquet") + before, err := os.Stat(messagesFile) + require.NoError(err, "stat messages fixture before refresh") + + wantRevision, err := st.LinkParticipants(a, b) + require.NoError(err, "LinkParticipants") + + gotRevision, err := RefreshIdentityDatasets(context.Background(), st, dir) + require.NoError(err, "RefreshIdentityDatasets") + assert.Equal(wantRevision, gotRevision, "returned revision") + + storeRevision, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision") + assert.Equal(storeRevision, gotRevision, "returned revision matches store") + + state, err := query.ReadCacheSyncState(dir) + require.NoError(err, "ReadCacheSyncState") + assert.Equal(storeRevision, state.IdentityRevision, "_last_sync.json identity_revision") + assert.False(state.PublishedAt.IsZero(), "PublishedAt should be stamped") + + after, err := os.Stat(messagesFile) + require.NoError(err, "stat messages fixture after refresh") + assert.Equal(before.ModTime(), after.ModTime(), "message dataset mtime must be untouched") + assert.Equal(before.Size(), after.Size(), "message dataset size must be untouched") + + clusters := readInt64PairsParquet(t, dir, datasetParticipantClusters, "participant_id", "canonical_id") + want := map[int64]int64{a: min(a, b), b: min(a, b)} + assert.Equal(want, clusters, "participant_clusters parquet") +} + +// TestRefreshIdentityDatasetsPreservesAccountIdentityRevision covers Finding +// 1 of the relationships-backend review: RefreshIdentityDatasets only +// re-exports owner_participants/participant_clusters, never the message +// shards that bake in is_from_me, so it must leave the stamped +// AccountIdentityRevision untouched even while it advances IdentityRevision. +// A caller that advanced it here would make a stale is_from_me look fresh +// and permanently suppress the full rebuild account-identity drift needs. +func TestRefreshIdentityDatasetsPreservesAccountIdentityRevision(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + f := storetest.New(t) + st := f.Store + a := f.EnsureParticipant("alice@example.com", "Alice", "example.com") + b := f.EnsureParticipant("alice@personal.example", "Alice P", "personal.example") + + dir := writeCacheStatsFixture(t) + // Simulate a prior full build that stamped a nonzero account-identity + // revision (from an AddAccountIdentity/RemoveAccountIdentity call folded + // into that build). + priorState, err := query.ReadCacheSyncState(dir) + require.NoError(err, "ReadCacheSyncState before mutating") + priorState.AccountIdentityRevision = 7 + writeCacheStatsState(t, dir, priorState) + + wantRevision, err := st.LinkParticipants(a, b) + require.NoError(err, "LinkParticipants") + + _, err = RefreshIdentityDatasets(context.Background(), st, dir) + require.NoError(err, "RefreshIdentityDatasets") + + state, err := query.ReadCacheSyncState(dir) + require.NoError(err, "ReadCacheSyncState after refresh") + assert.Equal(wantRevision, state.IdentityRevision, "IdentityRevision must advance") + assert.Equal(int64(7), state.AccountIdentityRevision, + "AccountIdentityRevision must be preserved untouched by the identity-only refresh") +} + +// TestRefreshIdentityDatasetsExportsOwnerParticipants verifies the +// owner_participants dataset reflects account_identities matched against +// participants, independent of the participant_clusters mapping. +func TestRefreshIdentityDatasetsExportsOwnerParticipants(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + f := storetest.New(t) + st := f.Store + owner := f.EnsureParticipant("me@example.com", "Me", "example.com") + require.NoError(st.AddAccountIdentity(f.Source.ID, "me@example.com", "manual"), "AddAccountIdentity") + + dir := writeCacheStatsFixture(t) + + wantRevision, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision") + + revision, err := RefreshIdentityDatasets(context.Background(), st, dir) + require.NoError(err, "RefreshIdentityDatasets") + assert.Equal(wantRevision, revision, "returned revision matches store") + + owners := readInt64PairsParquet(t, dir, datasetOwnerParticipants, "source_id", "participant_id") + assert.Equal(map[int64]int64{f.Source.ID: owner}, owners, "owner_participants parquet") +} + +// TestRefreshIdentityDatasetsOwnerParticipantsMatchesNonEmailIdentifiersVerbatim +// pins ownerParticipantsSQL's non-email branch against the same contract +// cmd/msgvault/cmd/build_cache.go enforces for the full-build derivation +// (TestBuildCache_OwnerParticipantsMatchesNonEmailIdentifiersVerbatim): a +// confirmed non-email identifier (phone, chat handle) matches +// participant_identifiers verbatim, not case-insensitively. The two queries +// are documented as byte-equivalent aside from the ATTACH prefix; this test +// guards against the lightweight refresh path silently diverging from the +// full-build derivation it must match. +func TestRefreshIdentityDatasetsOwnerParticipantsMatchesNonEmailIdentifiersVerbatim(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + f := storetest.New(t) + st := f.Store + require.NoError(st.AddAccountIdentity(f.Source.ID, "+15550100001", "manual"), "confirm phone identity") + require.NoError(st.AddAccountIdentity(f.Source.ID, "@user:matrix.org", "manual"), "confirm chat-handle identity") + + phoneOwnerID, err := st.EnsureParticipantByPhone("+15550100001", "Phone Owner", "phone") + require.NoError(err) + caseVariantID, err := st.EnsureParticipantByIdentifier("matrix", "@User:matrix.org", "Case Variant") + require.NoError(err) + + dir := writeCacheStatsFixture(t) + + _, err = RefreshIdentityDatasets(context.Background(), st, dir) + require.NoError(err, "RefreshIdentityDatasets") + + db, err := sql.Open("duckdb", "") + require.NoError(err) + defer func() { _ = db.Close() }() + pattern := strings.ReplaceAll(filepath.Join(dir, datasetOwnerParticipants, "*.parquet"), "'", "''") + + var phoneRows int + require.NoError(db.QueryRow( + "SELECT COUNT(*) FROM read_parquet('"+pattern+"') WHERE participant_id = ?", phoneOwnerID, + ).Scan(&phoneRows)) + assert.Equal(1, phoneRows, "phone-identified owner must match verbatim") + + var caseVariantRows int + require.NoError(db.QueryRow( + "SELECT COUNT(*) FROM read_parquet('"+pattern+"') WHERE participant_id = ?", caseVariantID, + ).Scan(&caseVariantRows)) + assert.Equal(0, caseVariantRows, "a case-differing non-email identifier must not match verbatim") +} + +// TestRefreshIdentityDatasetsSecondCallIsNoOpWhenRevisionUnchanged covers the +// no-op short-circuit: the identity link/unlink API always re-runs this +// refresh, even when the call changed nothing (a no-op link, or a retry +// after another writer already refreshed). Republishing unconditionally +// would rewrite the datasets and advance PublishedAt/the fingerprint for no +// reason, needlessly 409-ing active pagination cursors. A second call with +// no identity mutation in between must return the same revision and leave +// the committed datasets and PublishedAt untouched. +func TestRefreshIdentityDatasetsSecondCallIsNoOpWhenRevisionUnchanged(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + f := storetest.New(t) + st := f.Store + a := f.EnsureParticipant("alice@example.com", "Alice", "example.com") + b := f.EnsureParticipant("alice@personal.example", "Alice P", "personal.example") + + dir := writeCacheStatsFixture(t) + + wantRevision, err := st.LinkParticipants(a, b) + require.NoError(err, "LinkParticipants") + + firstRevision, err := RefreshIdentityDatasets(context.Background(), st, dir) + require.NoError(err, "first RefreshIdentityDatasets") + assert.Equal(wantRevision, firstRevision) + + stateAfterFirst, err := query.ReadCacheSyncState(dir) + require.NoError(err, "ReadCacheSyncState after first refresh") + + clustersPath := filepath.Join(dir, datasetParticipantClusters, "participant_clusters.parquet") + before, err := os.Stat(clustersPath) + require.NoError(err, "stat clusters dataset after first refresh") + + secondRevision, err := RefreshIdentityDatasets(context.Background(), st, dir) + require.NoError(err, "second RefreshIdentityDatasets") + assert.Equal(wantRevision, secondRevision, "second call returns the same, unchanged revision") + + stateAfterSecond, err := query.ReadCacheSyncState(dir) + require.NoError(err, "ReadCacheSyncState after second refresh") + assert.Equal(stateAfterFirst.PublishedAt, stateAfterSecond.PublishedAt, + "a no-op second refresh must not advance PublishedAt") + assert.Equal(stateAfterFirst.DatasetFingerprint, stateAfterSecond.DatasetFingerprint, + "a no-op second refresh must not change the dataset fingerprint") + + after, err := os.Stat(clustersPath) + require.NoError(err, "stat clusters dataset after second refresh") + assert.Equal(before.ModTime(), after.ModTime(), "a no-op second refresh must not rewrite the clusters dataset") +} + +// TestRefreshIdentityDatasetsNoCommittedCache verifies the typed error path: +// refreshing against an analytics directory with no prior publication must +// fail clearly instead of fabricating a cache. +func TestRefreshIdentityDatasetsNoCommittedCache(t *testing.T) { + f := storetest.New(t) + dir := t.TempDir() + + _, err := RefreshIdentityDatasets(context.Background(), f.Store, dir) + require.Error(t, err) + assert.ErrorIs(t, err, ErrNoCommittedCache) +} + +// readInt64PairsParquet reads a two-BIGINT-column dataset (any *.parquet +// file(s) under dir/dataset) into a map, for asserting against small +// identity dataset fixtures. +func readInt64PairsParquet(t *testing.T, dir, dataset, keyCol, valueCol string) map[int64]int64 { + t.Helper() + db, err := sql.Open("duckdb", "") + require.NoError(t, err) + defer func() { _ = db.Close() }() + + pattern := strings.ReplaceAll(filepath.Join(dir, dataset, "*.parquet"), "'", "''") + rows, err := db.Query("SELECT " + keyCol + ", " + valueCol + " FROM read_parquet('" + pattern + "')") + require.NoError(t, err) + defer func() { _ = rows.Close() }() + + out := map[int64]int64{} + for rows.Next() { + var k, v int64 + require.NoError(t, rows.Scan(&k, &v)) + out[k] = v + } + require.NoError(t, rows.Err()) + return out +} diff --git a/internal/cacheops/stats.go b/internal/cacheops/stats.go index 5eefedcb2..cdefe4b29 100644 --- a/internal/cacheops/stats.go +++ b/internal/cacheops/stats.go @@ -18,6 +18,8 @@ const ( StatusNoCacheFiles = "no_cache_files" StatusNoCacheData = "no_cache_data" StatusInterrupted = "interrupted" + StatusStaleSchema = "stale_schema" + StatusDrifted = "drifted" tableAttachments = "attachments" tableMessages = "messages" @@ -57,6 +59,10 @@ func CollectStats(ctx context.Context, analyticsDir string) (*CacheStats, error) return &CacheStats{Status: StatusNoCacheFiles}, nil case query.CacheInterrupted: return &CacheStats{Status: StatusInterrupted}, nil + case query.CacheStaleSchema: + return &CacheStats{Status: StatusStaleSchema}, nil + case query.CacheDrifted: + return &CacheStats{Status: StatusDrifted}, nil case query.CacheReady: // Continue while holding the same shared lock through all Parquet reads. default: diff --git a/internal/cacheops/stats_test.go b/internal/cacheops/stats_test.go index fb92d5ca6..0dbc93991 100644 --- a/internal/cacheops/stats_test.go +++ b/internal/cacheops/stats_test.go @@ -89,6 +89,27 @@ func TestCollectStatsClassifiesCacheReadiness(t *testing.T) { require.NoError(err) assert.Equal(t, StatusInterrupted, stats.Status) }) + + t.Run("stale schema", func(t *testing.T) { + dir := writeCacheStatsFixture(t) + state, err := query.ReadCacheSyncState(dir) + require.NoError(t, err) + state.SchemaVersion-- + writeCacheStatsState(t, dir, state) + + stats, err := CollectStats(context.Background(), dir) + require.NoError(t, err) + assert.Equal(t, StatusStaleSchema, stats.Status) + }) + + t.Run("drifted", func(t *testing.T) { + dir := writeCacheStatsFixture(t) + require.NoError(t, os.WriteFile(filepath.Join(dir, "sources", "drift.parquet"), []byte("drift"), 0o600)) + + stats, err := CollectStats(context.Background(), dir) + require.NoError(t, err) + assert.Equal(t, StatusDrifted, stats.Status) + }) } func writeCacheStatsFixture(t *testing.T) string { @@ -99,14 +120,18 @@ func writeCacheStatsFixture(t *testing.T) string { defer func() { _ = db.Close() }() datasets := map[string]string{ - "messages": `SELECT 1::BIGINT AS id, 2::BIGINT AS source_id, 100::BIGINT AS size_estimate`, - "message_recipients": `SELECT 1::BIGINT AS message_id, 3::BIGINT AS participant_id, 'from'::VARCHAR AS recipient_type`, - "participants": `SELECT 3::BIGINT AS id, 'sender@example.com'::VARCHAR AS email_address, 'example.com'::VARCHAR AS domain`, - "attachments": `SELECT 1::BIGINT AS message_id, 25::BIGINT AS size`, - "sources": `SELECT 2::BIGINT AS id`, - "labels": `SELECT 4::BIGINT AS id`, - "message_labels": `SELECT 1::BIGINT AS message_id, 4::BIGINT AS label_id`, - "conversations": `SELECT 5::BIGINT AS id`, + "messages": `SELECT 1::BIGINT AS id, 2::BIGINT AS source_id, 100::BIGINT AS size_estimate`, + "message_recipients": `SELECT 1::BIGINT AS message_id, 3::BIGINT AS participant_id, 'from'::VARCHAR AS recipient_type`, + "participants": `SELECT 3::BIGINT AS id, 'sender@example.com'::VARCHAR AS email_address, 'example.com'::VARCHAR AS domain`, + "participant_identifiers": `SELECT 3::BIGINT AS participant_id, 'email'::VARCHAR AS identifier_type, 'sender@example.com'::VARCHAR AS identifier_value`, + "attachments": `SELECT 1::BIGINT AS message_id, 25::BIGINT AS size`, + "sources": `SELECT 2::BIGINT AS id`, + "labels": `SELECT 4::BIGINT AS id`, + "message_labels": `SELECT 1::BIGINT AS message_id, 4::BIGINT AS label_id`, + "conversations": `SELECT 5::BIGINT AS id`, + "conversation_participants": `SELECT 5::BIGINT AS conversation_id, 3::BIGINT AS participant_id`, + "owner_participants": `SELECT 2::BIGINT AS source_id, 3::BIGINT AS participant_id`, + "participant_clusters": `SELECT 3::BIGINT AS participant_id, 3::BIGINT AS canonical_id`, } for dataset, selectSQL := range datasets { datasetDir := filepath.Join(dir, dataset) @@ -119,11 +144,21 @@ func writeCacheStatsFixture(t *testing.T) string { require.NoError(t, err, "write %s fixture", dataset) } - stateData, err := json.Marshal(query.CacheSyncState{ - LastMessageID: 7, - LastSyncAt: time.Date(2026, time.July, 15, 12, 0, 0, 0, time.UTC), + fingerprint, err := query.CacheDatasetFingerprint(dir) + require.NoError(t, err) + writeCacheStatsState(t, dir, query.CacheSyncState{ + LastMessageID: 7, + LastSyncAt: time.Date(2026, time.July, 15, 12, 0, 0, 0, time.UTC), + SchemaVersion: query.CacheSchemaVersion, + PublishedAt: time.Date(2026, time.July, 15, 12, 1, 0, 0, time.UTC), + DatasetFingerprint: fingerprint, }) + return dir +} + +func writeCacheStatsState(t *testing.T, dir string, state query.CacheSyncState) { + t.Helper() + stateData, err := json.Marshal(state) require.NoError(t, err) require.NoError(t, os.WriteFile(query.CacheStatePath(dir), stateData, 0o600)) - return dir } diff --git a/internal/codegenfix/cmd/main.go b/internal/codegenfix/cmd/main.go new file mode 100644 index 000000000..a8ada666c --- /dev/null +++ b/internal/codegenfix/cmd/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "os" + + "go.kenn.io/msgvault/internal/codegenfix" +) + +func main() { + if len(os.Args) != 2 { + _, _ = fmt.Fprintln(os.Stderr, "usage: codegenfix ") + os.Exit(2) + } + path := os.Args[1] + // #nosec G703 -- this local build tool intentionally rewrites its caller-selected generated file. + source, err := os.ReadFile(path) + if err == nil { + source, err = codegenfix.RewriteGeneratedValidators(source) + } + if err == nil { + // #nosec G703 -- path is the same explicit generated-file argument read above. + err = os.WriteFile(path, source, 0o600) + } + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/internal/codegenfix/fix.go b/internal/codegenfix/fix.go new file mode 100644 index 000000000..6defb2ce9 --- /dev/null +++ b/internal/codegenfix/fix.go @@ -0,0 +1,61 @@ +package codegenfix + +import ( + "bytes" + "fmt" +) + +var requiredPointerValidators = [][2]string{ + {"FileMetadataResponse", "Filename"}, + {"FileMetadataResponse", "MimeType"}, + {"FileSearchRow", "Filename"}, + {"FileSearchRow", "MimeType"}, +} + +// RewriteGeneratedValidators preserves required-but-empty string fields as +// pointers while making their generated presence validation unconditional. +func RewriteGeneratedValidators(source []byte) ([]byte, error) { + result := append([]byte(nil), source...) + for _, target := range [][2]string{{"ExploreGroupsHTTPRequest", "e"}, {"FileGroupsHTTPRequest", "f"}} { + typeName, receiver := target[0], target[1] + marker := []byte("func (" + receiver + " " + typeName + ") Validate() error {\n\tvar errors runtime.ValidationErrors\n") + validation := []byte("\tif err := typesValidator.Var(" + receiver + ".Grouping, \"required,min=1,max=1\"); err != nil {\n\t\terrors = errors.Append(\"Grouping\", err)\n\t}\n") + start := bytes.Index(result, marker) + if start < 0 { + return nil, fmt.Errorf("generated %s validator shape changed", typeName) + } + endOffset := bytes.Index(result[start:], []byte("\n}\n")) + if endOffset < 0 { + return nil, fmt.Errorf("generated %s validator shape changed", typeName) + } + if validator := result[start : start+endOffset]; !bytes.Contains(validator, validation) { + insertAt := start + len(marker) + result = append(append(append([]byte(nil), result[:insertAt]...), validation...), result[insertAt:]...) + } + } + for _, target := range requiredPointerValidators { + typeName, field := target[0], target[1] + startMarker := []byte("func (f " + typeName + ") Validate() error {") + start := bytes.Index(result, startMarker) + if start < 0 { + return nil, fmt.Errorf("generated %s.%s validator shape changed", typeName, field) + } + endOffset := bytes.Index(result[start:], []byte("\n}\n")) + if endOffset < 0 { + return nil, fmt.Errorf("generated %s.%s validator shape changed", typeName, field) + } + end := start + endOffset + validator := result[start:end] + guarded := []byte("\tif f." + field + " != nil {\n\t\tif err := typesValidator.Var(f." + field + ", \"required\"); err != nil {\n\t\t\terrors = errors.Append(\"" + field + "\", err)\n\t\t}\n\t}") + required := []byte("\tif err := typesValidator.Var(f." + field + ", \"required\"); err != nil {\n\t\terrors = errors.Append(\"" + field + "\", err)\n\t}") + switch { + case bytes.Contains(validator, guarded): + rewritten := bytes.Replace(validator, guarded, required, 1) + result = append(append(append([]byte(nil), result[:start]...), rewritten...), result[end:]...) + case bytes.Contains(validator, required): + default: + return nil, fmt.Errorf("generated %s.%s validator shape changed", typeName, field) + } + } + return result, nil +} diff --git a/internal/codegenfix/fix_test.go b/internal/codegenfix/fix_test.go new file mode 100644 index 000000000..26ee70611 --- /dev/null +++ b/internal/codegenfix/fix_test.go @@ -0,0 +1,51 @@ +package codegenfix + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRewriteGeneratedValidatorsMakesRequiredPointersUnconditional(t *testing.T) { + assertions := assert.New(t) + got, err := RewriteGeneratedValidators([]byte(generatedValidatorFixture())) + require.NoError(t, err) + assertions.NotContains(string(got), "if f.Filename != nil") + assertions.NotContains(string(got), "if f.MimeType != nil") + assertions.Contains(string(got), `typesValidator.Var(e.Grouping, "required,min=1,max=1")`) + assertions.Contains(string(got), `typesValidator.Var(f.Grouping, "required,min=1,max=1")`) +} + +func TestRewriteGeneratedValidatorsRejectsMissingGroupingValidator(t *testing.T) { + _, err := RewriteGeneratedValidators([]byte("package generated\n")) + + require.ErrorContains(t, err, "ExploreGroupsHTTPRequest validator shape changed") +} + +func generatedValidatorFixture() string { + return `func (e ExploreGroupsHTTPRequest) Validate() error { + var errors runtime.ValidationErrors +} +func (f FileGroupsHTTPRequest) Validate() error { + var errors runtime.ValidationErrors +} +` + pointerValidatorFixture("FileMetadataResponse") + pointerValidatorFixture("FileSearchRow") +} + +func pointerValidatorFixture(typeName string) string { + return `func (f ` + typeName + `) Validate() error { + var errors runtime.ValidationErrors + if f.Filename != nil { + if err := typesValidator.Var(f.Filename, "required"); err != nil { + errors = errors.Append("Filename", err) + } + } + if f.MimeType != nil { + if err := typesValidator.Var(f.MimeType, "required"); err != nil { + errors = errors.Append("MimeType", err) + } + } +} +` +} diff --git a/internal/config/config.go b/internal/config/config.go index 9e9dd3103..58f360a9c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -43,6 +43,102 @@ type AnalyticsConfig struct { AutoBuildCache bool `toml:"auto_build_cache"` // Build stale/missing Parquet cache before using DuckDB } +const ( + WebSearchModeFullText = "full_text" + WebSearchModeSemantic = "semantic" + WebSearchModeHybrid = "hybrid" + WebThemeSystem = "system" + WebThemeLight = "light" + WebThemeDark = "dark" + WebDensityCompact = "compact" + WebDensityComfortable = "comfortable" + windowsOS = "windows" +) + +// WebConfig stores browser-owned defaults that do not affect the CLI or TUI. +type WebConfig struct { + DefaultSearchMode string `toml:"default_search_mode"` + Theme string `toml:"theme"` + Density string `toml:"density"` +} + +func (w *WebConfig) ApplyDefaults() { + w.DefaultSearchMode = strings.ToLower(strings.TrimSpace(w.DefaultSearchMode)) + if w.DefaultSearchMode == "" { + w.DefaultSearchMode = WebSearchModeFullText + } + w.Theme = strings.ToLower(strings.TrimSpace(w.Theme)) + if w.Theme == "" { + w.Theme = WebThemeSystem + } + w.Density = strings.ToLower(strings.TrimSpace(w.Density)) + if w.Density == "" { + w.Density = WebDensityCompact + } +} + +func (w *WebConfig) Validate() error { + if !slices.Contains([]string{WebSearchModeFullText, WebSearchModeSemantic, WebSearchModeHybrid}, w.DefaultSearchMode) { + return fmt.Errorf("invalid [web] default_search_mode %q", w.DefaultSearchMode) + } + if !slices.Contains([]string{WebThemeSystem, WebThemeLight, WebThemeDark}, w.Theme) { + return fmt.Errorf("invalid [web] theme %q", w.Theme) + } + if !slices.Contains([]string{WebDensityCompact, WebDensityComfortable}, w.Density) { + return fmt.Errorf("invalid [web] density %q", w.Density) + } + return nil +} + +// IntegrationsConfig groups optional, server-side integrations. +type IntegrationsConfig struct { + Tasks TaskIntegrationConfig `toml:"tasks"` +} + +// TaskIntegrationConfig configures a provider-neutral compatible task daemon. +type TaskIntegrationConfig struct { + Enabled bool `toml:"enabled"` + Endpoint string `toml:"endpoint"` + APIKey string `toml:"api_key"` + DefaultProject string `toml:"default_project"` +} + +func (t *TaskIntegrationConfig) ApplyDefaults() { + if strings.TrimSpace(t.DefaultProject) == "" { + t.DefaultProject = "msgvault" + } +} + +func (t *TaskIntegrationConfig) Validate() error { + endpoint := strings.TrimSpace(t.Endpoint) + if endpoint == "" { + return nil + } + u, err := url.Parse(endpoint) + if err != nil || u.Scheme == "" { + return fmt.Errorf("invalid [integrations.tasks] endpoint %q", t.Endpoint) + } + switch strings.ToLower(u.Scheme) { + case "https": + if u.Host == "" { + return fmt.Errorf("invalid [integrations.tasks] endpoint %q", t.Endpoint) + } + case "http": + host := u.Hostname() + ip := net.ParseIP(host) + if host != "localhost" && (ip == nil || !ip.IsLoopback()) { + return fmt.Errorf("invalid [integrations.tasks] endpoint %q: remote plaintext HTTP is not allowed", t.Endpoint) + } + case "unix": + if u.Path == "" { + return fmt.Errorf("invalid [integrations.tasks] endpoint %q", t.Endpoint) + } + default: + return fmt.Errorf("invalid [integrations.tasks] endpoint %q", t.Endpoint) + } + return nil +} + func (a *AnalyticsConfig) ApplyDefaults() { a.Engine = strings.ToLower(strings.TrimSpace(a.Engine)) if a.Engine == "" { @@ -72,6 +168,7 @@ type ServerConfig struct { CORSOrigins []string `toml:"cors_origins"` // Allowed CORS origins (empty = disabled) CORSCredentials bool `toml:"cors_credentials"` // Allow credentials in CORS CORSMaxAge int `toml:"cors_max_age"` // Preflight cache duration in seconds + TrustedProxies []string `toml:"trusted_proxies"` // Reverse proxy IP/CIDR allowlist for forwarded scheme and host DaemonIdleTimeout time.Duration `toml:"daemon_idle_timeout"` // Background daemon idle timeout (0 disables) DaemonAutoRestart string `toml:"daemon_auto_restart"` // never, newer, or always } @@ -86,7 +183,6 @@ func (s *ServerConfig) ApplyDefaults() { func (s *ServerConfig) Validate() error { switch s.DaemonAutoRestart { case DaemonAutoRestartNewer, DaemonAutoRestartNever, DaemonAutoRestartAlways: - return nil default: return fmt.Errorf("invalid [server] daemon_auto_restart %q (want %q, %q, or %q)", s.DaemonAutoRestart, @@ -94,6 +190,18 @@ func (s *ServerConfig) Validate() error { DaemonAutoRestartNever, DaemonAutoRestartAlways) } + for _, entry := range s.TrustedProxies { + if entry == "" { + return errors.New("invalid [server] trusted_proxies entry: value must be an IP address or CIDR") + } + if ip := net.ParseIP(entry); ip != nil { + continue + } + if _, _, err := net.ParseCIDR(entry); err != nil { + return fmt.Errorf("invalid [server] trusted_proxies entry %q: must be an IP address or CIDR", entry) + } + } + return nil } // IsLoopback returns true if the bind address is a loopback address. @@ -211,25 +319,27 @@ func (b *BackupConfig) Validate() error { } type Config struct { - Data DataConfig `toml:"data"` - Log LogConfig `toml:"log"` - OAuth OAuthConfig `toml:"oauth"` - Microsoft MicrosoftConfig `toml:"microsoft"` - Sync SyncConfig `toml:"sync"` - Chat ChatConfig `toml:"chat"` - Server ServerConfig `toml:"server"` - Analytics AnalyticsConfig `toml:"analytics"` - Remote RemoteConfig `toml:"remote"` - Vector vector.Config `toml:"vector"` - Identity IdentityConfig `toml:"identity"` - Accounts []AccountSchedule `toml:"accounts"` - SynctechSMS SynctechSMSConfig `toml:"synctech_sms"` - GCal []GCalSource `toml:"gcal"` - Beeper BeeperConfig `toml:"beeper"` - Granola []GranolaSource `toml:"granola"` - Circleback []CirclebackSource `toml:"circleback"` - Backup BackupConfig `toml:"backup"` - Discord DiscordConfig `toml:"discord"` + Data DataConfig `toml:"data"` + Log LogConfig `toml:"log"` + OAuth OAuthConfig `toml:"oauth"` + Microsoft MicrosoftConfig `toml:"microsoft"` + Sync SyncConfig `toml:"sync"` + Chat ChatConfig `toml:"chat"` + Server ServerConfig `toml:"server"` + Analytics AnalyticsConfig `toml:"analytics"` + Web WebConfig `toml:"web"` + Integrations IntegrationsConfig `toml:"integrations"` + Remote RemoteConfig `toml:"remote"` + Vector vector.Config `toml:"vector"` + Identity IdentityConfig `toml:"identity"` + Accounts []AccountSchedule `toml:"accounts"` + SynctechSMS SynctechSMSConfig `toml:"synctech_sms"` + GCal []GCalSource `toml:"gcal"` + Beeper BeeperConfig `toml:"beeper"` + Granola []GranolaSource `toml:"granola"` + Circleback []CirclebackSource `toml:"circleback"` + Backup BackupConfig `toml:"backup"` + Discord DiscordConfig `toml:"discord"` // Computed paths (not from config file) HomeDir string `toml:"-"` @@ -397,6 +507,14 @@ func NewDefaultConfig() *Config { Engine: AnalyticsEngineAuto, AutoBuildCache: true, }, + Web: WebConfig{ + DefaultSearchMode: WebSearchModeFullText, + Theme: WebThemeSystem, + Density: WebDensityCompact, + }, + Integrations: IntegrationsConfig{ + Tasks: TaskIntegrationConfig{DefaultProject: "msgvault"}, + }, Accounts: []AccountSchedule{}, SynctechSMS: SynctechSMSConfig{Sources: []SynctechSMSSource{}}, GCal: []GCalSource{}, @@ -404,6 +522,8 @@ func NewDefaultConfig() *Config { cfg.Vector.ApplyDefaults() cfg.Server.ApplyDefaults() cfg.Discord.ApplyDefaults() + cfg.Web.ApplyDefaults() + cfg.Integrations.Tasks.ApplyDefaults() return cfg } @@ -441,19 +561,46 @@ func Load(path, homeDir string) (*Config, error) { // Default config file is optional return cfg, nil } + content, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read config: %w", err) + } + return decodeConfig(cfg, path, explicit, homeDir != "", content) +} +// LoadConfigFile decodes the exact bytes captured by ReadConfigFile. Relative +// paths resolve against the operator-specified logical path, matching daemon +// startup even when that path is a symlink to a target in another directory. +func LoadConfigFile(snapshot ConfigFile, homeDir string) (*Config, error) { + if !snapshot.Exists { + return NewDefaultConfig(), nil + } + cfg := NewDefaultConfig() + if homeDir != "" { + homeDir = expandPath(homeDir) + cfg.HomeDir = homeDir + cfg.Data.DataDir = homeDir + } + decodePath := snapshot.LogicalPath + if decodePath == "" { + decodePath = snapshot.Path + } + return decodeConfig(cfg, decodePath, true, homeDir != "", snapshot.Content) +} + +func decodeConfig(cfg *Config, path string, explicit, homeOverride bool, content []byte) (*Config, error) { cfg.configPath = path // When --config points to a custom location without --home, // derive HomeDir and default DataDir from the config file's parent // directory so that tokens, database, attachments, etc. live alongside // the config. - if explicit && homeDir == "" { + if explicit && !homeOverride { cfg.HomeDir = filepath.Dir(path) cfg.Data.DataDir = cfg.HomeDir } - if _, err := toml.DecodeFile(path, cfg); err != nil { + if _, err := toml.Decode(string(content), cfg); err != nil { if strings.Contains(err.Error(), "invalid escape") || strings.Contains(err.Error(), "hexadecimal digits after") { return nil, fmt.Errorf("decode config: %w -- hint: Windows paths in TOML must use "+ @@ -505,6 +652,14 @@ func Load(path, homeDir string) (*Config, error) { if err := cfg.Analytics.Validate(); err != nil { return nil, err } + cfg.Web.ApplyDefaults() + if err := cfg.Web.Validate(); err != nil { + return nil, err + } + cfg.Integrations.Tasks.ApplyDefaults() + if err := cfg.Integrations.Tasks.Validate(); err != nil { + return nil, err + } if err := cfg.Backup.Validate(); err != nil { return nil, err } @@ -631,42 +786,45 @@ func (c *Config) ConfigFilePath() string { // Uses temp file + rename to prevent partial writes on crash. // Enforces 0600 permissions regardless of existing file mode. func (c *Config) Save() error { - path := c.ConfigFilePath() - - // Resolve symlinks so atomic rename replaces the target, not - // the symlink itself. EvalSymlinks fails on dangling symlinks - // (target doesn't exist yet), so fall back to Readlink. - if resolved, err := filepath.EvalSymlinks(path); err == nil { - path = resolved - } else if target, lErr := os.Readlink(path); lErr == nil { - if !filepath.IsAbs(target) { - target = filepath.Join(filepath.Dir(path), target) - } - path = target - } + return c.saveWithHooks(configSaveHooks{}) +} +func (c *Config) saveWithHooks(hooks configSaveHooks) error { // Ensure home directory exists if err := c.EnsureHomeDir(); err != nil { return fmt.Errorf("create config directory: %w", err) } + if err := ensureConfigParentDirectories(c.ConfigFilePath()); err != nil { + return err + } + path, pathRelease, err := prepareConfigSavePath(c.ConfigFilePath()) + if err != nil { + return fmt.Errorf("validate config path: %w", err) + } + if pathRelease != nil { + defer func() { _ = pathRelease() }() + } dir := filepath.Dir(path) - tmp, err := os.CreateTemp(dir, ".config-*.toml.tmp") + created, err := createConfigCandidate(dir) if err != nil { return fmt.Errorf("create temp config file: %w", err) } - tmpPath := tmp.Name() + tmp, tmpPath := created.file, created.path // Clean up temp file on any failure path success := false defer func() { if !success { _ = tmp.Close() - _ = os.Remove(tmpPath) + _ = created.cleanup() + } + if created.release != nil { + _ = created.release() } }() - if err := tmp.Chmod(0600); err != nil { + if err := secureConfigCandidate(tmp, tmpPath, 0o600); err != nil { return fmt.Errorf("set config file permissions: %w", err) } @@ -682,8 +840,14 @@ func (c *Config) Save() error { return fmt.Errorf("close config file: %w", err) } - if err := os.Rename(tmpPath, path); err != nil { - return fmt.Errorf("rename config file: %w", err) + published, err := publishSavedConfig(tmpPath, path, created.retained, hooks) + if published { + // Publication transfers ownership of the candidate to the live config + // immediately. A later recovery-file error must not run candidate cleanup. + success = true + } + if err != nil { + return fmt.Errorf("publish config file: %w", err) } success = true @@ -1071,7 +1235,7 @@ func expandPath(path string) string { // Strip surrounding quotes left by Windows CMD (e.g. --home 'C:\Users\foo'). // Only on Windows — Unix shells strip quotes before the process sees them, // and literal quote characters in Unix paths are valid (if unusual). - if runtime.GOOS == "windows" && len(path) >= 2 && + if runtime.GOOS == windowsOS && len(path) >= 2 && ((path[0] == '\'' && path[len(path)-1] == '\'') || (path[0] == '"' && path[len(path)-1] == '"')) { path = path[1 : len(path)-1] diff --git a/internal/config/config_save_path_other.go b/internal/config/config_save_path_other.go new file mode 100644 index 000000000..6c859eb1e --- /dev/null +++ b/internal/config/config_save_path_other.go @@ -0,0 +1,21 @@ +//go:build !windows + +package config + +import ( + "os" + "path/filepath" +) + +func prepareConfigSavePath(path string) (string, func() error, error) { + if resolved, err := filepath.EvalSymlinks(path); err == nil { + return resolved, nil, nil + } + if target, err := os.Readlink(path); err == nil { + if !filepath.IsAbs(target) { + target = filepath.Join(filepath.Dir(path), target) + } + return target, nil, nil + } + return path, nil, nil +} diff --git a/internal/config/config_save_path_windows.go b/internal/config/config_save_path_windows.go new file mode 100644 index 000000000..b232d272d --- /dev/null +++ b/internal/config/config_save_path_windows.go @@ -0,0 +1,30 @@ +//go:build windows + +package config + +import ( + "errors" + "os" + "path/filepath" +) + +func prepareConfigSavePath(path string) (string, func() error, error) { + absolute, err := filepath.Abs(path) + if err != nil { + return "", nil, err + } + authority, err := pinWindowsConfigParent(absolute) + if err != nil { + return "", nil, err + } + info, statErr := os.Lstat(absolute) + if statErr == nil && info.Mode()&os.ModeSymlink != 0 { + _ = authority.Release() + return "", nil, errors.Join(ErrUnsafeConfigTarget, errors.New("Windows config path is a reparse point")) + } + if statErr != nil && !errors.Is(statErr, os.ErrNotExist) { + _ = authority.Release() + return "", nil, statErr + } + return filepath.Clean(absolute), authority.Release, nil +} diff --git a/internal/config/config_save_unix.go b/internal/config/config_save_unix.go new file mode 100644 index 000000000..beddd2451 --- /dev/null +++ b/internal/config/config_save_unix.go @@ -0,0 +1,14 @@ +//go:build !windows + +package config + +import "os" + +type configSaveHooks struct{} + +func publishSavedConfig(candidatePath, targetPath string, _ *os.File, _ configSaveHooks) (bool, error) { + if err := os.Rename(candidatePath, targetPath); err != nil { + return false, err + } + return true, nil +} diff --git a/internal/config/config_save_windows.go b/internal/config/config_save_windows.go new file mode 100644 index 000000000..2a8dee8c1 --- /dev/null +++ b/internal/config/config_save_windows.go @@ -0,0 +1,55 @@ +//go:build windows + +package config + +import ( + "errors" + "fmt" + "os" +) + +type configSaveHooks struct { + beforeExistingRetirement func(configReplacement) error +} + +func publishSavedConfig( + candidatePath, targetPath string, + retained *os.File, + hooks configSaveHooks, +) (bool, error) { + candidate, candidateErr := readPhysicalConfigSnapshot(candidatePath) + if candidateErr != nil { + return false, candidateErr + } + target, targetErr := readConfigFileSnapshot(targetPath) + if targetErr == nil && !target.Exists { + publication, err := publishNewConfig(candidatePath, retained, target) + if publication.release != nil { + _ = publication.release() + } + return err == nil, err + } + if targetErr != nil { + return false, targetErr + } + replacement, err := beginConfigReplacement(candidatePath, targetPath, candidate, target) + if replacement.release != nil { + defer func() { _ = replacement.release() }() + } + if err != nil { + return replacement.preserveCandidate, err + } + if hooks.beforeExistingRetirement != nil { + if err := hooks.beforeExistingRetirement(replacement); err != nil { + return true, errors.Join(ErrConfigChanged, + fmt.Errorf("before displaced config retirement: %w", err)) + } + } + if replacement.cleanupDisplaced == nil { + return true, errors.Join(ErrConfigChanged, errors.New("config replacement has no retirement operation")) + } + if err := replacement.cleanupDisplaced(); err != nil { + return true, errors.Join(ErrConfigChanged, err, recoveryArtifactError(replacement)) + } + return true, nil +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go index ea14d7aa9..11be37189 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -92,6 +92,64 @@ func TestDiscordConfigTOMLRoundTrip(t *testing.T) { assert.Equal(cfg.Discord.Guilds, loaded.Discord.Guilds) } +func TestWebAndTaskIntegrationConfig(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + cfg := NewDefaultConfig() + assert.Equal("full_text", cfg.Web.DefaultSearchMode) + assert.Equal("system", cfg.Web.Theme) + assert.Equal("compact", cfg.Web.Density) + + path := filepath.Join(t.TempDir(), "config.toml") + content := "[web]\n" + + "default_search_mode = \"hybrid\"\n" + + "theme = \"dark\"\n" + + "density = \"comfortable\"\n\n" + + "[integrations.tasks]\n" + + "enabled = true\n" + + "endpoint = \"https://tasks.example.com\"\n" + + "api_key = \"test-secret\"\n" + + "default_project = \"archive\"\n" + require.NoError(os.WriteFile(path, []byte(content), 0o600)) + loaded, err := Load(path, "") + require.NoError(err) + assert.Equal("hybrid", loaded.Web.DefaultSearchMode) + assert.Equal("dark", loaded.Web.Theme) + assert.Equal("comfortable", loaded.Web.Density) + assert.True(loaded.Integrations.Tasks.Enabled) + assert.Equal("https://tasks.example.com", loaded.Integrations.Tasks.Endpoint) + assert.Equal("test-secret", loaded.Integrations.Tasks.APIKey) + assert.Equal("archive", loaded.Integrations.Tasks.DefaultProject) +} + +func TestWebConfigRejectsInvalidValues(t *testing.T) { + tests := []struct { + key string + value string + }{ + {key: "default_search_mode", value: "magic"}, + {key: "theme", value: "sepia"}, + {key: "density", value: "roomy"}, + } + for _, tt := range tests { + t.Run(tt.key, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(t, os.WriteFile(path, []byte("[web]\n"+tt.key+" = \""+tt.value+"\"\n"), 0o600)) + _, err := Load(path, "") + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid [web] "+tt.key) + }) + } +} + +func TestTaskIntegrationRejectsRemotePlaintextEndpoint(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(t, os.WriteFile(path, []byte("[integrations.tasks]\nendpoint = \"http://tasks.example.com\"\n"), 0o600)) + _, err := Load(path, "") + require.Error(t, err) + assert.Contains(t, err.Error(), "remote plaintext HTTP is not allowed") +} + func TestAccountScheduleEmpty(t *testing.T) { tmpDir := t.TempDir() t.Setenv("MSGVAULT_HOME", tmpDir) @@ -200,6 +258,49 @@ daemon_auto_restart = "sometimes" assert.Contains(t, err.Error(), "invalid [server] daemon_auto_restart") } +func TestLoadWithServerTrustedProxies(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, "config.toml") + require.NoError(t, os.WriteFile(configPath, []byte(` +[server] +trusted_proxies = ["127.0.0.1", "10.20.0.0/16", "2001:db8::1", "2001:db8:abcd::/48"] +`), 0o644)) + + cfg, err := Load(configPath, "") + require.NoError(t, err) + assert.Equal(t, []string{ + "127.0.0.1", + "10.20.0.0/16", + "2001:db8::1", + "2001:db8:abcd::/48", + }, cfg.Server.TrustedProxies) +} + +func TestLoadRejectsMalformedServerTrustedProxy(t *testing.T) { + tests := []struct { + name string + entry string + }{ + {name: "hostname", entry: "proxy.example.com"}, + {name: "invalid CIDR", entry: "10.0.0.0/99"}, + {name: "IP with port", entry: "127.0.0.1:8080"}, + {name: "empty", entry: ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := t.TempDir() + configPath := filepath.Join(tmpDir, "config.toml") + content := "[server]\ntrusted_proxies = [\"" + tt.entry + "\"]\n" + require.NoError(t, os.WriteFile(configPath, []byte(content), 0o644)) + + _, err := Load(configPath, "") + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid [server] trusted_proxies entry") + }) + } +} + func TestLoadWithAnalyticsConfig(t *testing.T) { assert := assert. New(t) @@ -941,6 +1042,18 @@ func TestSave_CreatesFileWithSecurePermissions(t *testing.T) { } } +func TestSaveCreatesMissingCustomConfigDirectories(t *testing.T) { + require := require.New(t) + root := t.TempDir() + cfg := NewDefaultConfig() + cfg.HomeDir = filepath.Join(root, "home") + cfg.configPath = filepath.Join(root, "custom", "nested", "config.toml") + + require.NoError(cfg.Save()) + _, err := os.Stat(cfg.configPath) + require.NoError(err) +} + func TestSave_TightensWeakPermissions(t *testing.T) { require := require.New(t) if runtime.GOOS == "windows" { diff --git a/internal/config/edit.go b/internal/config/edit.go new file mode 100644 index 000000000..48e53e129 --- /dev/null +++ b/internal/config/edit.go @@ -0,0 +1,1362 @@ +package config + +import ( + "bytes" + "crypto/sha256" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/BurntSushi/toml" + "github.com/robfig/cron/v3" +) + +var ( + // ErrConfigConflict indicates that the on-disk content no longer matches + // the ETag supplied by the caller. + ErrConfigConflict = errors.New("config file changed") + // ErrAmbiguousConfigTarget indicates that a targeted key occurs more than + // once and cannot be edited without guessing. + ErrAmbiguousConfigTarget = errors.New("ambiguous config edit target") + // ErrUnsafeConfigTarget indicates a dangling, non-regular, or + // ownership-unsafe symlink target. + ErrUnsafeConfigTarget = errors.New("unsafe config target") + // ErrInvalidConfigCandidate indicates that edited bytes were produced but + // the complete candidate does not satisfy the config schema or invariants. + ErrInvalidConfigCandidate = errors.New("invalid config candidate") + // ErrConfigChanged indicates that the candidate became the live config but + // a later durability or cleanup step failed and rollback was not possible. + // Callers must treat the persisted config as changed even though the write + // operation returned an error. + ErrConfigChanged = errors.New("config changed despite write error") + // ErrAtomicReplaceUnsupported indicates that this platform/filesystem cannot + // provide the conditional exchange required to protect external hand edits. + ErrAtomicReplaceUnsupported = errors.New("conditional atomic config replacement is unsupported") +) + +// Edit is one targeted TOML assignment. Key is a dotted table/key path. +type Edit struct { + Key string + Value any +} + +// ConfigFile is an immutable snapshot of one resolved config target. +type ConfigFile struct { + // LogicalPath is the operator-specified config path. It deliberately keeps + // symlinks so relative values decode exactly as they do during daemon start. + LogicalPath string + // Path is the ownership-verified physical target used for safe replacement. + Path string + Content []byte + ETag string + Mode fs.FileMode + Exists bool + identity string + // retained pins an existing file identity for the lifetime of one edit. + // Public snapshots never retain a descriptor. + retained *os.File + // parentIdentity pins the resolved containing directory for a missing file. + parentIdentity string +} + +var configEditMu sync.Mutex + +type syncDirectoryHandle interface { + Sync() error + Close() error +} + +type configFileOps struct { + initialRead func(string) (ConfigFile, error) + replace func(string, string, ConfigFile, ConfigFile) (configReplacement, error) + beforeExchange func() error + beforeExistingCleanup func(configReplacement) error + beforeMissingCleanup func(configPublication) error + openDirectory func(string) (syncDirectoryHandle, error) + publishNew func(string, *os.File, ConfigFile) (configPublication, error) + read func(string) (ConfigFile, error) +} + +type configReplacement struct { + displacedPath string + rollbackPublished func(ConfigFile) error + preserveCandidate bool + recoveryPaths []string + published ConfigFile + syncDirectory func() error + cleanupDisplaced func() error + release func() error +} + +type configCandidate struct { + file *os.File + retained *os.File + path string + cleanup func() error + release func() error +} + +type configPublication struct { + candidateRemains bool + published ConfigFile + syncDirectory func() error + rollback func(ConfigFile) error + cleanupCandidate func() error + release func() error +} + +func (replacement configReplacement) rollbackInstalledVersion() error { + if !replacement.published.Exists || replacement.rollbackPublished == nil { + return errors.Join(ErrConfigChanged, errors.New("config replacement has no verified rollback version")) + } + return replacement.rollbackPublished(replacement.published) +} + +func defaultConfigFileOps() configFileOps { + return configFileOps{ + initialRead: readConfigFileForEdit, + replace: beginConfigReplacement, + openDirectory: openConfigDirectoryForSync, + publishNew: publishNewConfig, + read: ReadConfigFile, + } +} + +// ReadConfigFile reads the config target without following unsafe symlinks. +// A missing ordinary path is represented as an empty, non-existing snapshot. +func ReadConfigFile(path string) (ConfigFile, error) { + return readConfigFile(path, false) +} + +func readConfigFileForEdit(path string) (ConfigFile, error) { + return readConfigFile(path, true) +} + +func readConfigFile(path string, retain bool) (ConfigFile, error) { + logicalPath, err := filepath.Abs(path) + if err != nil { + return ConfigFile{}, fmt.Errorf("resolve logical config path: %w", err) + } + logicalPath = filepath.Clean(logicalPath) + var snapshot ConfigFile + if retain { + snapshot, err = readConfigFileSnapshotForEdit(path) + } else { + snapshot, err = readConfigFileSnapshot(path) + } + if err != nil { + return ConfigFile{}, err + } + snapshot.LogicalPath = logicalPath + return snapshot, nil +} + +// EditConfigFile applies targeted changes after an optimistic concurrency +// check, validates the complete candidate, and atomically replaces the target. +func EditConfigFile(path, ifMatch string, edits []Edit) (ConfigFile, error) { + return editConfigFile(path, ifMatch, edits, defaultConfigFileOps()) +} + +func editConfigFile(path, ifMatch string, edits []Edit, ops configFileOps) (ConfigFile, error) { + configEditMu.Lock() + defer configEditMu.Unlock() + + readInitial := ops.initialRead + if readInitial == nil { + readInitial = ops.read + } + before, err := readInitial(path) + if err != nil && configParentIsMissing(path) { + // Windows cannot snapshot a missing final parent. Do not create it + // until the caller has proved knowledge of the empty snapshot ETag. + if ifMatch == "" || ifMatch != configETag(nil) { + return ConfigFile{}, fmt.Errorf("%w: current ETag is %s", ErrConfigConflict, configETag(nil)) + } + if len(edits) == 0 { + return ConfigFile{}, err + } + if err := ensureConfigParentDirectories(path); err != nil { + return ConfigFile{}, err + } + before, err = readInitial(path) + } + if err != nil { + return ConfigFile{}, err + } + if before.retained != nil { + defer func() { _ = before.retained.Close() }() + } + if ifMatch == "" || ifMatch != before.ETag { + return ConfigFile{}, fmt.Errorf("%w: current ETag is %s", ErrConfigConflict, before.ETag) + } + if len(edits) == 0 { + return before, nil + } + if !before.Exists { + if err := ensureConfigParentDirectories(path, before.parentIdentity); err != nil { + return ConfigFile{}, err + } + refreshed, refreshErr := readInitial(path) + if refreshErr != nil { + return ConfigFile{}, refreshErr + } + if refreshed.Exists || refreshed.ETag != before.ETag || refreshed.Path != before.Path { + if refreshed.retained != nil { + _ = refreshed.retained.Close() + } + return ConfigFile{}, fmt.Errorf("%w: config target changed while creating its directory", ErrConfigConflict) + } + before = refreshed + } + + candidate, err := applyTargetedEdits(before.Content, edits) + if err != nil { + return ConfigFile{}, err + } + dir := filepath.Dir(before.Path) + created, err := createConfigCandidate(dir) + if err != nil { + return ConfigFile{}, fmt.Errorf("create config candidate: %w", err) + } + tmp, tmpPath := created.file, created.path + keep := false + defer func() { + _ = tmp.Close() + if !keep { + _ = created.cleanup() + } + if created.release != nil { + _ = created.release() + } + }() + + mode := before.Mode.Perm() + if !before.Exists { + mode = 0o600 + } + if err := secureConfigCandidate(tmp, tmpPath, mode); err != nil { + return ConfigFile{}, fmt.Errorf("set config candidate permissions: %w", err) + } + if _, err := tmp.Write(candidate); err != nil { + return ConfigFile{}, fmt.Errorf("write config candidate: %w", err) + } + if err := tmp.Sync(); err != nil { + return ConfigFile{}, fmt.Errorf("sync config candidate: %w", err) + } + if err := tmp.Close(); err != nil { + return ConfigFile{}, fmt.Errorf("close config candidate: %w", err) + } + + loaded, err := LoadConfigFile(ConfigFile{ + LogicalPath: before.LogicalPath, + Path: tmpPath, + Content: candidate, + ETag: configETag(candidate), + Mode: mode, + Exists: true, + }, "") + if err != nil { + return ConfigFile{}, fmt.Errorf("%w: %w", ErrInvalidConfigCandidate, err) + } + if err := validateEditableCandidate(loaded); err != nil { + return ConfigFile{}, fmt.Errorf("%w: %w", ErrInvalidConfigCandidate, err) + } + + if ops.beforeExchange != nil { + if err := ops.beforeExchange(); err != nil { + return ConfigFile{}, fmt.Errorf("before config replacement: %w", err) + } + } + if before.Exists { + replacement, replaceErr := conditionalReplace(path, tmpPath, before, ops.replace, ops.read) + if replacement.release != nil { + defer func() { _ = replacement.release() }() + } + if replaceErr != nil { + if errors.Is(replaceErr, ErrConfigChanged) { + keep = replacement.preserveCandidate || replacement.displacedPath == tmpPath + } + return ConfigFile{}, replaceErr + } + if syncErr := syncReplacementDirectory(replacement, dir, ops.openDirectory); syncErr != nil { + if rollbackErr := rollbackConfigReplacement(path, before, replacement, ops.read); rollbackErr != nil { + keep = replacement.preserveCandidate || replacement.displacedPath == tmpPath + return ConfigFile{}, errors.Join( + fmt.Errorf("%w: config directory sync failed after publication", ErrConfigChanged), + syncErr, + rollbackErr, + recoveryArtifactError(replacement), + ) + } + if rollbackSyncErr := syncReplacementDirectory(replacement, dir, ops.openDirectory); rollbackSyncErr != nil { + keep = true + return ConfigFile{}, errors.Join( + fmt.Errorf("%w: rollback could not be made durable", ErrConfigChanged), + syncErr, + rollbackSyncErr, + ) + } + return ConfigFile{}, syncErr + } + if ops.beforeExistingCleanup != nil { + if err := ops.beforeExistingCleanup(replacement); err != nil { + keep = true + return ConfigFile{}, errors.Join(fmt.Errorf("%w: replacement cleanup preparation failed", ErrConfigChanged), err) + } + } + var removeErr error + if replacement.cleanupDisplaced != nil { + removeErr = replacement.cleanupDisplaced() + } else { + removeErr = errors.New("config replacement has no retirement operation") + } + if removeErr != nil { + keep = true + return ConfigFile{}, errors.Join( + fmt.Errorf("%w: replacement cleanup failed", ErrConfigChanged), + removeErr, + ) + } + // The candidate descriptor now names the live published config, while + // its former pathname has been retired as the displaced artifact. It + // must not be treated as a temporary file on later durability errors. + keep = true + if syncErr := syncReplacementDirectory(replacement, dir, ops.openDirectory); syncErr != nil { + return ConfigFile{}, errors.Join( + fmt.Errorf("%w: cleanup durability failed", ErrConfigChanged), + syncErr, + ) + } + } else { + publication, err := ops.publishNew(tmpPath, created.retained, before) + if err != nil { + if errors.Is(err, fs.ErrExist) { + return ConfigFile{}, fmt.Errorf("%w: config file was created", ErrConfigConflict) + } + return ConfigFile{}, fmt.Errorf("create config file conditionally: %w", err) + } + if publication.release != nil { + defer func() { _ = publication.release() }() + } + syncPublication := func() error { + if publication.syncDirectory != nil { + return publication.syncDirectory() + } + return syncConfigDirectory(dir, ops.openDirectory) + } + if syncErr := syncPublication(); syncErr != nil { + var rollbackErr error + if publication.rollback != nil { + rollbackErr = publication.rollback(publication.published) + } else { + rollbackErr = errors.Join(ErrConfigChanged, + errors.New("config publication has no rollback retirement operation")) + } + if rollbackErr != nil { + return ConfigFile{}, errors.Join( + fmt.Errorf("%w: config directory sync failed after creation", ErrConfigChanged), + syncErr, + fmt.Errorf("rollback created config: %w", rollbackErr), + ) + } + if rollbackSyncErr := syncPublication(); rollbackSyncErr != nil { + keep = true + return ConfigFile{}, errors.Join( + fmt.Errorf("%w: creation rollback could not be made durable", ErrConfigChanged), + syncErr, + rollbackSyncErr, + ) + } + return ConfigFile{}, syncErr + } + if !publication.candidateRemains { + keep = true + } else { + if ops.beforeMissingCleanup != nil { + if err := ops.beforeMissingCleanup(publication); err != nil { + keep = true + return ConfigFile{}, errors.Join(fmt.Errorf("%w: candidate cleanup preparation failed", ErrConfigChanged), err) + } + } + var cleanupErr error + if publication.cleanupCandidate != nil { + cleanupErr = publication.cleanupCandidate() + } else { + cleanupErr = errors.New("config publication has no candidate retirement operation") + } + if cleanupErr != nil { + keep = true + return ConfigFile{}, errors.Join( + fmt.Errorf("%w: candidate-link cleanup failed", ErrConfigChanged), + cleanupErr, + ) + } + } + if syncErr := syncPublication(); syncErr != nil { + return ConfigFile{}, errors.Join( + fmt.Errorf("%w: cleanup durability failed", ErrConfigChanged), + syncErr, + ) + } + } + keep = true + after, readErr := ops.read(path) + if readErr != nil { + return ConfigFile{}, errors.Join( + fmt.Errorf("%w: committed config could not be read", ErrConfigChanged), + readErr, + ) + } + return after, nil +} + +func configParentIsMissing(path string) bool { + absolute, err := filepath.Abs(path) + if err != nil { + return false + } + _, err = os.Stat(filepath.Dir(absolute)) + return errors.Is(err, fs.ErrNotExist) +} + +func syncReplacementDirectory(replacement configReplacement, path string, open func(string) (syncDirectoryHandle, error)) error { + if replacement.syncDirectory != nil { + return replacement.syncDirectory() + } + return syncConfigDirectory(path, open) +} + +func conditionalReplace( + requestedPath, candidatePath string, + before ConfigFile, + replace func(string, string, ConfigFile, ConfigFile) (configReplacement, error), + read func(string) (ConfigFile, error), +) (configReplacement, error) { + current, err := read(requestedPath) + if err != nil { + return configReplacement{}, err + } + if !sameConfigSnapshot(current, before) { + return configReplacement{}, fmt.Errorf("%w: config target changed before replacement", ErrConfigConflict) + } + candidateBefore, err := readPhysicalConfigSnapshot(candidatePath) + if err != nil { + return configReplacement{}, fmt.Errorf("inspect config candidate before replacement: %w", err) + } + replacement, err := replace(candidatePath, before.Path, candidateBefore, before) + if err != nil { + if errors.Is(err, ErrConfigChanged) { + return replacement, errors.Join( + fmt.Errorf("replace config file conditionally: %w", err), + recoveryArtifactError(replacement), + ) + } + if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrExist) { + return configReplacement{}, fmt.Errorf("%w: config target changed during replacement: %w", ErrConfigConflict, err) + } + return configReplacement{}, fmt.Errorf("replace config file conditionally: %w", err) + } + published, publishedErr := readPhysicalConfigSnapshot(before.Path) + if publishedErr != nil || !sameConfigVersion(published, candidateBefore) { + replacement.published = published + return replacement, errors.Join( + fmt.Errorf("%w: live config changed during replacement verification", ErrConfigChanged), + fmt.Errorf("%w: published config is not the verified candidate", ErrConfigConflict), + publishedErr, + recoveryArtifactError(replacement), + ) + } + replacement.published = published + displaced, displacedErr := readPhysicalConfigSnapshot(replacement.displacedPath) + if displacedErr != nil { + return replacement, errors.Join( + fmt.Errorf("%w: displaced config could not be verified", ErrConfigChanged), + displacedErr, + recoveryArtifactError(replacement), + ) + } + if sameConfigVersion(displaced, before) { + mapped, mapErr := read(requestedPath) + if mapErr != nil || !mapped.Exists || mapped.Path != before.Path || !sameConfigVersion(mapped, candidateBefore) { + if rollbackErr := replacement.rollbackInstalledVersion(); rollbackErr != nil { + return replacement, errors.Join( + fmt.Errorf("%w: rollback failed after config symlink race", ErrConfigChanged), + fmt.Errorf("%w: config symlink target changed during replacement", ErrConfigConflict), + fmt.Errorf("restore config after symlink race: %w", rollbackErr), + recoveryArtifactError(replacement), + ) + } + restored, restoreErr := readPhysicalConfigSnapshot(before.Path) + if restoreErr != nil || !sameConfigVersion(restored, before) { + return replacement, errors.Join( + fmt.Errorf("%w: config rollback could not be verified", ErrConfigChanged), + restoreErr, + recoveryArtifactError(replacement), + ) + } + return replacement, fmt.Errorf("%w: config symlink target changed during replacement", ErrConfigConflict) + } + return replacement, nil + } + rollbackErr := replacement.rollbackInstalledVersion() + if rollbackErr != nil { + return replacement, errors.Join( + fmt.Errorf("%w: rollback failed after replacement conflict", ErrConfigChanged), + fmt.Errorf("%w: displaced config no longer matched expected snapshot identity", ErrConfigConflict), + fmt.Errorf("restore raced config after conditional exchange: %w", rollbackErr), + recoveryArtifactError(replacement), + ) + } + restored, restoreErr := readPhysicalConfigSnapshot(before.Path) + if restoreErr != nil || !sameConfigVersion(restored, displaced) { + return replacement, errors.Join( + fmt.Errorf("%w: raced config rollback could not be verified", ErrConfigChanged), + restoreErr, + recoveryArtifactError(replacement), + ) + } + return replacement, fmt.Errorf("%w: displaced config no longer matched the original snapshot", ErrConfigConflict) +} + +func rollbackConfigReplacement(requestedPath string, before ConfigFile, replacement configReplacement, read func(string) (ConfigFile, error)) error { + current, err := readPhysicalConfigSnapshot(before.Path) + if err != nil || !sameConfigVersion(current, replacement.published) { + return errors.Join( + errors.New("refusing rollback because the published config changed"), + err, + ) + } + if err := replacement.rollbackInstalledVersion(); err != nil { + return fmt.Errorf("rollback config replacement: %w", err) + } + restored, err := read(requestedPath) + if err != nil { + return fmt.Errorf("verify rolled-back config: %w", err) + } + if !sameConfigSnapshot(restored, before) { + return errors.New("rolled-back config does not match the original snapshot") + } + return nil +} + +func sameConfigSnapshot(left, right ConfigFile) bool { + if left.Path != right.Path || left.Exists != right.Exists { + return false + } + if !left.Exists { + return left.parentIdentity != "" && left.parentIdentity == right.parentIdentity && left.ETag == right.ETag + } + return sameConfigVersion(left, right) +} + +func sameConfigVersion(left, right ConfigFile) bool { + return left.Exists && right.Exists && left.ETag == right.ETag && left.identity != "" && left.identity == right.identity +} + +func readPhysicalConfigSnapshot(path string) (ConfigFile, error) { + content, mode, identity, err := readVerifiedPhysicalConfig(path) + if err != nil { + return ConfigFile{}, err + } + return ConfigFile{Path: path, Content: content, ETag: configETag(content), Mode: mode, Exists: true, identity: identity}, nil +} + +func readVerifiedPhysicalConfig(path string) ([]byte, fs.FileMode, string, error) { + file, err := openConfigNoFollow(path) + if err != nil { + return nil, 0, "", err + } + defer func() { _ = file.Close() }() + return readVerifiedOpenedConfig(file) +} + +func readVerifiedOpenedConfig(file *os.File) ([]byte, fs.FileMode, string, error) { + info, err := file.Stat() + if err != nil { + return nil, 0, "", fmt.Errorf("stat opened config: %w", err) + } + if !info.Mode().IsRegular() { + return nil, 0, "", fmt.Errorf("%w: opened config is not regular", ErrUnsafeConfigTarget) + } + if euid, supported := effectiveUserID(); supported { + uid, verified := fileOwner(info) + if !verified || uid != euid { + return nil, 0, "", fmt.Errorf("%w: opened config is not owned by the effective user", ErrUnsafeConfigTarget) + } + } + if err := validateOpenedConfigSecurity(file); err != nil { + return nil, 0, "", fmt.Errorf("%w: opened config security is not safe: %w", ErrUnsafeConfigTarget, err) + } + identity, ok := openedFileIdentity(file, info) + if !ok { + return nil, 0, "", fmt.Errorf("%w: opened config identity is unavailable", ErrUnsafeConfigTarget) + } + content, err := io.ReadAll(file) + if err != nil { + return nil, 0, "", err + } + after, err := file.Stat() + if err != nil { + return nil, 0, "", fmt.Errorf("restat opened config: %w", err) + } + afterIdentity, ok := openedFileIdentity(file, after) + if !ok || afterIdentity != identity { + return nil, 0, "", fmt.Errorf("%w: opened config identity changed while reading", ErrUnsafeConfigTarget) + } + return content, info.Mode(), identity, nil +} + +func recoveryArtifactError(replacement configReplacement) error { + paths := append([]string(nil), replacement.recoveryPaths...) + if replacement.displacedPath != "" { + paths = append(paths, replacement.displacedPath) + } + if len(paths) == 0 { + return nil + } + return fmt.Errorf("config recovery artifact preserved at %s", strings.Join(paths, ", ")) +} + +func syncConfigDirectory(path string, open func(string) (syncDirectoryHandle, error)) error { + handle, err := open(path) + if err != nil { + return fmt.Errorf("open config directory for sync: %w", err) + } + syncErr := handle.Sync() + closeErr := handle.Close() + if syncErr != nil || closeErr != nil { + var errs []error + if syncErr != nil { + errs = append(errs, fmt.Errorf("sync config directory: %w", syncErr)) + } + if closeErr != nil { + errs = append(errs, fmt.Errorf("close config directory: %w", closeErr)) + } + return errors.Join(errs...) + } + return nil +} + +func validateEditableCandidate(cfg *Config) error { + if err := cfg.Server.ValidateSecure(); err != nil { + return err + } + if cfg.Vector.Enabled { + if err := cfg.Vector.Validate(); err != nil { + return fmt.Errorf("vector config: %w", err) + } + } + parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) + schedules := map[string]string{ + "vector.embed.schedule.cron": cfg.Vector.Embed.Schedule.Cron, + "beeper.schedule": cfg.Beeper.Schedule, + } + for index, account := range cfg.Accounts { + schedules[fmt.Sprintf("accounts[%d].schedule", index)] = account.Schedule + } + for index, source := range cfg.SynctechSMS.Sources { + schedules[fmt.Sprintf("synctech_sms.sources[%d].schedule", index)] = source.Schedule + } + for index, source := range cfg.GCal { + schedules[fmt.Sprintf("gcal[%d].schedule", index)] = source.Schedule + } + for index, source := range cfg.Granola { + schedules[fmt.Sprintf("granola[%d].schedule", index)] = source.Schedule + } + for index, source := range cfg.Circleback { + schedules[fmt.Sprintf("circleback[%d].schedule", index)] = source.Schedule + } + for key, expression := range schedules { + if expression == "" { + continue + } + if _, err := parser.Parse(expression); err != nil { + return fmt.Errorf("invalid %s %q: %w", key, expression, err) + } + } + return nil +} + +func resolveConfigTargetWithOwner(path string, owner func(fs.FileInfo) (uint64, bool)) (string, fs.FileMode, bool, error) { + absolute, err := filepath.Abs(path) + if err != nil { + return "", 0, false, fmt.Errorf("resolve config path: %w", err) + } + requestedInfo, requestedErr := os.Lstat(absolute) + requestedSymlink := requestedErr == nil && requestedInfo.Mode()&os.ModeSymlink != 0 + resolved, resolveErr := resolveOwnedSymlinks(absolute, owner) + if resolveErr != nil { + return "", 0, false, fmt.Errorf("%w: resolve config path: %w", ErrUnsafeConfigTarget, resolveErr) + } + absolute = resolved + info, err := os.Lstat(absolute) + if errors.Is(err, fs.ErrNotExist) { + if requestedSymlink { + return "", 0, false, fmt.Errorf("%w: config symlink target does not exist", ErrUnsafeConfigTarget) + } + return absolute, 0o600, false, nil + } + if err != nil { + return "", 0, false, fmt.Errorf("inspect config path: %w", err) + } + if !info.Mode().IsRegular() { + return "", 0, false, fmt.Errorf("%w: config path is not a regular file", ErrUnsafeConfigTarget) + } + if euid, supported := effectiveUserID(); supported { + uid, verified := owner(info) + if !verified || uid != euid { + return "", 0, false, fmt.Errorf("%w: config target is not owned by the effective user", ErrUnsafeConfigTarget) + } + } + return absolute, info.Mode(), true, nil +} + +// The resolved-path result is consumed on fallback platforms; native pinned +// resolver builds retain this helper for identical regression coverage. +// +//nolint:unparam +func resolveOwnedSymlinksWithReadlink( + path string, + owner func(fs.FileInfo) (uint64, bool), + readlink func(string) (string, error), +) (string, error) { + euid, supported := effectiveUserID() + current := filepath.Clean(path) + for range 255 { + volume := filepath.VolumeName(current) + root := volume + string(filepath.Separator) + relative := strings.TrimPrefix(current, root) + parts := strings.Split(relative, string(filepath.Separator)) + prefix := root + followed := false + for index, part := range parts { + if part == "" { + continue + } + candidate := filepath.Join(prefix, part) + info, err := os.Lstat(candidate) + if errors.Is(err, fs.ErrNotExist) { + return filepath.Join(append([]string{candidate}, parts[index+1:]...)...), nil + } + if err != nil { + return "", err + } + if info.Mode()&os.ModeSymlink == 0 { + prefix = candidate + continue + } + if !supported { + return "", errors.New("symlink ownership verification is unavailable") + } + uid, ok := owner(info) + if !ok || (uid != euid && uid != 0) { + return "", errors.New("symlink hop is not owned by the effective user or root") + } + beforeIdentity, beforeIdentityOK := pathEntryIdentity(candidate, info) + target, err := readlink(candidate) + if err != nil { + return "", err + } + after, err := os.Lstat(candidate) + if err != nil { + return "", errors.Join(ErrUnsafeConfigTarget, + errors.New("symlink changed while its target was inspected"), err) + } + afterIdentity, afterIdentityOK := pathEntryIdentity(candidate, after) + if after.Mode()&os.ModeSymlink == 0 || !os.SameFile(info, after) || + !beforeIdentityOK || !afterIdentityOK || beforeIdentity != afterIdentity { + return "", errors.Join(ErrUnsafeConfigTarget, + errors.New("symlink changed while its target was inspected")) + } + if !filepath.IsAbs(target) { + target = filepath.Join(filepath.Dir(candidate), target) + } + current = filepath.Join(append([]string{target}, parts[index+1:]...)...) + current = filepath.Clean(current) + followed = true + break + } + if !followed { + return current, nil + } + } + return "", errors.New("too many config symlink hops") +} + +func configETag(content []byte) string { + sum := sha256.Sum256(content) + return fmt.Sprintf("\"sha256-%x\"", sum) +} + +type tomlLine struct { + body string + eol string +} + +func applyTargetedEdits(content []byte, edits []Edit) ([]byte, error) { + lines := splitTOMLLines(string(content)) + seenEdits := make(map[string]struct{}, len(edits)) + for _, edit := range edits { + if _, duplicate := seenEdits[edit.Key]; duplicate { + return nil, fmt.Errorf("%w: duplicate requested key %q", ErrAmbiguousConfigTarget, edit.Key) + } + seenEdits[edit.Key] = struct{}{} + section, key, ok := strings.Cut(edit.Key, ".") + if !ok || section == "" || key == "" { + return nil, fmt.Errorf("invalid config edit key %q", edit.Key) + } + if lastDot := strings.LastIndex(edit.Key, "."); lastDot >= 0 { + section, key = edit.Key[:lastDot], edit.Key[lastDot+1:] + } + value, err := encodeTOMLValue(edit.Value) + if err != nil { + return nil, fmt.Errorf("encode %s: %w", edit.Key, err) + } + lines, err = editTOMLLines(lines, section, key, value) + if err != nil { + return nil, err + } + } + return joinTOMLLines(lines), nil +} + +func splitTOMLLines(content string) []tomlLine { + if content == "" { + return nil + } + var lines []tomlLine + for len(content) > 0 { + if index := strings.IndexByte(content, '\n'); index >= 0 { + body := content[:index] + eol := "\n" + if withoutCR, ok := strings.CutSuffix(body, "\r"); ok { + body = withoutCR + eol = "\r\n" + } + lines = append(lines, tomlLine{body: body, eol: eol}) + content = content[index+1:] + continue + } + lines = append(lines, tomlLine{body: content}) + break + } + return lines +} + +func joinTOMLLines(lines []tomlLine) []byte { + var out strings.Builder + for _, line := range lines { + out.WriteString(line.body) + out.WriteString(line.eol) + } + return []byte(out.String()) +} + +func editTOMLLines(lines []tomlLine, section, key, value string) ([]tomlLine, error) { + target := strings.Split(section+"."+key, ".") + currentTable := []string(nil) + type insertionTarget struct { + path []string + start, end int + } + var candidates []insertionTarget + rootEnd := len(lines) + rootDottedFamily := false + var matches []int + structural := tomlStructuralLines(lines) + for index, line := range lines { + if !structural[index] { + continue + } + if parsed, array, ok := parseTOMLTable(line.body); ok { + if rootEnd == len(lines) { + rootEnd = index + } + for candidateIndex := range candidates { + if candidates[candidateIndex].end == len(lines) { + candidates[candidateIndex].end = index + } + } + currentTable = parsed + if !array && pathPrefix(parsed, target) && len(parsed) < len(target) { + candidates = append(candidates, insertionTarget{path: parsed, start: index, end: len(lines)}) + } + continue + } + assignment, ok := assignmentKey(line.body) + if ok && currentTable == nil && len(assignment) > 1 && len(target) > 1 && + equalPath(assignment[:len(assignment)-1], target[:len(target)-1]) { + rootDottedFamily = true + } + if ok && equalPath(appendPath(currentTable, assignment), target) { + matches = append(matches, index) + } + } + if rootDottedFamily { + candidates = append(candidates, insertionTarget{start: -1, end: rootEnd}) + } + if len(matches) > 1 { + return nil, fmt.Errorf("%w: %s.%s", ErrAmbiguousConfigTarget, section, key) + } + if len(matches) == 1 { + index := matches[0] + end, suffix, multiline, err := assignmentSpan(lines, index) + if err != nil { + return nil, fmt.Errorf("edit %s.%s: %w", section, key, err) + } + replaced, err := replaceAssignmentValue(lines[index].body, value) + if err != nil { + return nil, fmt.Errorf("edit %s.%s: %w", section, key, err) + } + if multiline { + replaced += suffix + lines[index].eol = lines[end].eol + lines = append(lines[:index+1], lines[end+1:]...) + } + lines[index].body = replaced + return lines, nil + } + + eol := preferredEOL(lines) + hadFinalEOL := len(lines) == 0 || lines[len(lines)-1].eol != "" + if len(candidates) > 0 { + bestPrefix := -1 + best := -1 + ambiguous := false + for index, candidate := range candidates { + if len(candidate.path) > bestPrefix { + bestPrefix = len(candidate.path) + best = index + ambiguous = false + } else if len(candidate.path) == bestPrefix && equalPath(candidate.path, candidates[best].path) { + ambiguous = true + } + } + if ambiguous { + return nil, fmt.Errorf("%w: %s.%s", ErrAmbiguousConfigTarget, section, key) + } + candidate := candidates[best] + insertAt := candidate.end + for insertAt > candidate.start+1 && strings.TrimSpace(lines[insertAt-1].body) == "" { + insertAt-- + } + if insertAt > 0 && lines[insertAt-1].eol == "" { + lines[insertAt-1].eol = eol + } + assignment := strings.Join(target[len(candidate.path):], ".") + lineEOL := eol + if insertAt == len(lines) && !hadFinalEOL { + lineEOL = "" + } + line := tomlLine{body: assignment + " = " + value, eol: lineEOL} + lines = append(lines, tomlLine{}) + copy(lines[insertAt+1:], lines[insertAt:]) + lines[insertAt] = line + return lines, nil + } + + if len(lines) > 0 { + if lines[len(lines)-1].eol == "" { + lines[len(lines)-1].eol = eol + } + if lines[len(lines)-1].body != "" { + lines = append(lines, tomlLine{eol: eol}) + } + } + finalEOL := eol + if len(lines) > 0 && !hadFinalEOL { + finalEOL = "" + } + lines = append(lines, + tomlLine{body: "[" + section + "]", eol: eol}, + tomlLine{body: key + " = " + value, eol: finalEOL}, + ) + return lines, nil +} + +type tomlMultilineString byte + +const ( + tomlNotMultiline tomlMultilineString = iota + tomlBasicMultiline + tomlLiteralMultiline +) + +// tomlStructuralLines marks physical lines whose leading TOML syntax belongs +// to the document rather than to a multiline string value. The opening +// assignment remains structural; every continuation line is ignored until a +// valid unescaped triple-quote delimiter closes the value. +func tomlStructuralLines(lines []tomlLine) []bool { + result := make([]bool, len(lines)) + state := tomlNotMultiline + for index, line := range lines { + result[index] = state == tomlNotMultiline + state = scanTOMLMultilineState(line.body, state) + } + return result +} + +func scanTOMLMultilineState(line string, state tomlMultilineString) tomlMultilineString { + for index := 0; index < len(line); { + if state != tomlNotMultiline { + delimiter := byte('\'') + if state == tomlBasicMultiline { + delimiter = '"' + } + if line[index] != delimiter { + index++ + continue + } + run := quoteRunLength(line[index:], delimiter) + if run >= 3 && (state != tomlBasicMultiline || !tomlQuoteEscaped(line, index)) { + state = tomlNotMultiline + index += run + continue + } + index += run + continue + } + + switch line[index] { + case '#': + return state + case '"', '\'': + quote := line[index] + run := quoteRunLength(line[index:], quote) + if run >= 3 { + if quote == '"' { + state = tomlBasicMultiline + } else { + state = tomlLiteralMultiline + } + index += 3 + continue + } + index++ + for index < len(line) { + if quote == '"' && line[index] == '\\' { + index += 2 + continue + } + if line[index] == quote { + index++ + break + } + index++ + } + default: + index++ + } + } + return state +} + +func quoteRunLength(value string, quote byte) int { + count := 0 + for count < len(value) && value[count] == quote { + count++ + } + return count +} + +func tomlQuoteEscaped(line string, quoteIndex int) bool { + backslashes := 0 + for index := quoteIndex - 1; index >= 0 && line[index] == '\\'; index-- { + backslashes++ + } + return backslashes%2 == 1 +} + +func assignmentSpan(lines []tomlLine, start int) (int, string, bool, error) { + equal := indexOutsideTOMLString(lines[start].body, '=') + if equal < 0 { + return start, "", false, errors.New("assignment has no equals sign") + } + if end, suffix, multiline, ok, err := multilineStringAssignmentSpan(lines, start, equal+1); ok || err != nil { + return end, suffix, multiline, err + } + depth := 0 + opened := false + var quote byte + escaped := false + for lineIndex := start; lineIndex < len(lines); lineIndex++ { + body := lines[lineIndex].body + position := 0 + if lineIndex == start { + position = equal + 1 + } + for ; position < len(body); position++ { + char := body[position] + if escaped { + escaped = false + continue + } + if quote == '"' && char == '\\' { + escaped = true + continue + } + if quote != 0 { + if char == quote { + quote = 0 + } + continue + } + switch char { + case '"', '\'': + quote = char + case '#': + position = len(body) + case '[', '{': + opened = true + depth++ + case ']', '}': + if depth == 0 { + return start, "", false, errors.New("unbalanced TOML value") + } + depth-- + if opened && depth == 0 { + suffix := body[position+1:] + if strings.TrimSpace(suffix) != "" && !strings.HasPrefix(strings.TrimSpace(suffix), "#") { + return start, "", false, errors.New("unexpected text after multiline value") + } + return lineIndex, suffix, lineIndex > start, nil + } + } + } + if !opened { + return start, "", false, nil + } + if depth == 0 { + return lineIndex, "", lineIndex > start, nil + } + } + return start, "", false, errors.New("unterminated multiline TOML value") +} + +func multilineStringAssignmentSpan(lines []tomlLine, start, valueStart int) (int, string, bool, bool, error) { + opening := strings.TrimLeft(lines[start].body[valueStart:], " \t") + if len(opening) < 3 || (opening[:3] != `"""` && opening[:3] != `'''`) { + return start, "", false, false, nil + } + delimiter := opening[0] + openingOffset := len(lines[start].body) - len(opening) + for lineIndex := start; lineIndex < len(lines); lineIndex++ { + position := 0 + if lineIndex == start { + position = openingOffset + 3 + } + body := lines[lineIndex].body + for position < len(body) { + if body[position] != delimiter { + position++ + continue + } + run := quoteRunLength(body[position:], delimiter) + if run < 3 || (delimiter == '"' && tomlQuoteEscaped(body, position)) { + position += run + continue + } + suffix := body[position+3:] + trimmed := strings.TrimSpace(suffix) + if trimmed != "" && !strings.HasPrefix(trimmed, "#") { + return start, "", false, true, errors.New("unexpected text after multiline string value") + } + return lineIndex, suffix, lineIndex > start, true, nil + } + } + return start, "", false, true, errors.New("unterminated multiline TOML string value") +} + +func preferredEOL(lines []tomlLine) string { + for _, line := range lines { + if line.eol != "" { + return line.eol + } + } + return "\n" +} + +func parseTOMLTable(line string) ([]string, bool, bool) { + trimmed := strings.TrimSpace(line) + if !strings.HasPrefix(trimmed, "[") { + return nil, false, false + } + array := strings.HasPrefix(trimmed, "[[") + open, closeDelimiter := 1, "]" + if array { + open, closeDelimiter = 2, "]]" + } + end := strings.Index(trimmed[open:], closeDelimiter) + if end < 1 { + return nil, false, false + } + end += open + remainder := strings.TrimSpace(trimmed[end+len(closeDelimiter):]) + if remainder != "" && !strings.HasPrefix(remainder, "#") { + return nil, false, false + } + path, ok := parseTOMLKey(strings.TrimSpace(trimmed[open:end])) + return path, array, ok +} + +func assignmentKey(line string) ([]string, bool) { + equal := indexOutsideTOMLString(line, '=') + if equal < 0 { + return nil, false + } + key := strings.TrimSpace(line[:equal]) + if key == "" || strings.HasPrefix(key, "#") { + return nil, false + } + return parseTOMLKey(key) +} + +func parseTOMLKey(key string) ([]string, bool) { + var document map[string]any + if _, err := toml.Decode(key+" = true", &document); err != nil { + return nil, false + } + path := make([]string, 0, 4) + current := document + for { + if len(current) != 1 { + return nil, false + } + for segment, value := range current { + path = append(path, segment) + nested, ok := value.(map[string]any) + if !ok { + return path, true + } + current = nested + } + } +} + +func appendPath(prefix, suffix []string) []string { + result := make([]string, 0, len(prefix)+len(suffix)) + result = append(result, prefix...) + return append(result, suffix...) +} + +func pathPrefix(prefix, path []string) bool { + return len(prefix) <= len(path) && equalPath(prefix, path[:len(prefix)]) +} + +func equalPath(left, right []string) bool { + if len(left) != len(right) { + return false + } + for index := range left { + if left[index] != right[index] { + return false + } + } + return true +} + +func replaceAssignmentValue(line, value string) (string, error) { + equal := indexOutsideTOMLString(line, '=') + if equal < 0 { + return "", errors.New("assignment has no equals sign") + } + start := equal + 1 + for start < len(line) && (line[start] == ' ' || line[start] == '\t') { + start++ + } + comment := commentOutsideTOMLValue(line, start) + end := len(line) + if comment >= 0 { + end = comment + for end > start && (line[end-1] == ' ' || line[end-1] == '\t') { + end-- + } + } + return line[:start] + value + line[end:], nil +} + +func indexOutsideTOMLString(line string, target byte) int { + var quote byte + escaped := false + for index := range len(line) { + char := line[index] + if escaped { + escaped = false + continue + } + if quote == '"' && char == '\\' { + escaped = true + continue + } + if quote != 0 { + if char == quote { + quote = 0 + } + continue + } + if char == '"' || char == '\'' { + quote = char + continue + } + if char == target { + return index + } + } + return -1 +} + +func commentOutsideTOMLValue(line string, start int) int { + var quote byte + escaped := false + depth := 0 + for index := start; index < len(line); index++ { + char := line[index] + if escaped { + escaped = false + continue + } + if quote == '"' && char == '\\' { + escaped = true + continue + } + if quote != 0 { + if char == quote { + quote = 0 + } + continue + } + switch char { + case '"', '\'': + quote = char + case '[', '{': + depth++ + case ']', '}': + if depth > 0 { + depth-- + } + case '#': + if depth == 0 { + return index + } + } + } + return -1 +} + +func encodeTOMLValue(value any) (string, error) { + var encoded bytes.Buffer + if err := toml.NewEncoder(&encoded).Encode(map[string]any{"value": value}); err != nil { + return "", fmt.Errorf("encode TOML value: %w", err) + } + line := strings.TrimSuffix(encoded.String(), "\n") + const prefix = "value = " + if !strings.HasPrefix(line, prefix) || strings.ContainsRune(line, '\n') { + return "", errors.New("value is not representable as one TOML assignment") + } + return strings.TrimPrefix(line, prefix), nil +} diff --git a/internal/config/edit_authority_windows.go b/internal/config/edit_authority_windows.go new file mode 100644 index 000000000..af45c8f54 --- /dev/null +++ b/internal/config/edit_authority_windows.go @@ -0,0 +1,176 @@ +//go:build windows + +package config + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + "sync" + "unsafe" + + "golang.org/x/sys/windows" +) + +type windowsPathAuthority struct { + mu sync.Mutex + handles []windows.Handle + closed bool +} + +type windowsFileAttributeTagInfo struct { + FileAttributes uint32 + ReparseTag uint32 +} + +// pinWindowsConfigParent retains every ordinary directory component with +// share-read only. That denies later write/delete opens of the directory +// objects, preventing rename, deletion, or reparse conversion while child +// operations continue through the verified pathname. +func pinWindowsConfigParent(path string) (*windowsPathAuthority, error) { + absolute, err := filepath.Abs(path) + if err != nil { + return nil, fmt.Errorf("resolve Windows config path: %w", err) + } + parent := filepath.Dir(filepath.Clean(absolute)) + volume := filepath.VolumeName(parent) + root := volume + string(filepath.Separator) + relative := strings.TrimPrefix(parent, root) + authority := &windowsPathAuthority{} + prefix := root + for _, part := range strings.Split(relative, string(filepath.Separator)) { + if part == "" || part == "." { + continue + } + if part == ".." { + _ = authority.Release() + return nil, errors.Join(ErrUnsafeConfigTarget, errors.New("Windows config path escapes its volume root")) + } + prefix = filepath.Join(prefix, part) + handle, openErr := openWindowsAuthorityDirectory(prefix) + if openErr != nil { + _ = authority.Release() + return nil, openErr + } + authority.handles = append(authority.handles, handle) + } + return authority, nil +} + +// pinWindowsNearestExistingConfigAncestor retains the ordinary directory +// chain through the nearest existing ancestor of a missing config parent. It +// never creates the missing suffix. +func pinWindowsNearestExistingConfigAncestor(path string) (*windowsPathAuthority, string, string, error) { + absolute, err := filepath.Abs(path) + if err != nil { + return nil, "", "", fmt.Errorf("resolve Windows config path: %w", err) + } + ancestor := filepath.Dir(filepath.Clean(absolute)) + for { + info, statErr := os.Lstat(ancestor) + if statErr == nil { + if !info.IsDir() { + return nil, "", "", errors.Join(ErrUnsafeConfigTarget, + fmt.Errorf("Windows config ancestor %s is not a directory", ancestor)) + } + break + } + if !errors.Is(statErr, fs.ErrNotExist) { + return nil, "", "", statErr + } + parent := filepath.Dir(ancestor) + if parent == ancestor { + return nil, "", "", statErr + } + ancestor = parent + } + authority, err := pinWindowsConfigParent(filepath.Join(ancestor, ".config-ancestor-anchor")) + if err != nil { + return nil, "", "", err + } + opened, err := os.Open(ancestor) + if err != nil { + _ = authority.Release() + return nil, "", "", err + } + info, err := opened.Stat() + if err != nil { + _ = opened.Close() + _ = authority.Release() + return nil, "", "", err + } + identity, ok := openedFileIdentity(opened, info) + _ = opened.Close() + if !ok { + _ = authority.Release() + return nil, "", "", errors.Join(ErrUnsafeConfigTarget, + errors.New("config ancestor identity is unavailable")) + } + return authority, filepath.Clean(absolute), identity, nil +} + +func openWindowsAuthorityDirectory(path string) (windows.Handle, error) { + encoded, err := windows.UTF16PtrFromString(path) + if err != nil { + return 0, fmt.Errorf("encode Windows config directory: %w", err) + } + handle, err := windows.CreateFile( + encoded, + windows.FILE_TRAVERSE|windows.FILE_READ_ATTRIBUTES|windows.READ_CONTROL|windows.SYNCHRONIZE, + windows.FILE_SHARE_READ, + nil, + windows.OPEN_EXISTING, + windows.FILE_FLAG_OPEN_REPARSE_POINT|windows.FILE_FLAG_BACKUP_SEMANTICS, + 0, + ) + if err != nil { + return 0, fmt.Errorf("pin Windows config directory %s: %w", path, err) + } + var attributes windowsFileAttributeTagInfo + if err := windows.GetFileInformationByHandleEx( + handle, + windows.FileAttributeTagInfo, + (*byte)(unsafe.Pointer(&attributes)), + uint32(unsafe.Sizeof(attributes)), + ); err != nil { + _ = windows.CloseHandle(handle) + return 0, fmt.Errorf("inspect pinned Windows config directory %s: %w", path, err) + } + if attributes.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0 || + attributes.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 { + _ = windows.CloseHandle(handle) + return 0, errors.Join(ErrUnsafeConfigTarget, + fmt.Errorf("Windows config directory %s is not an ordinary directory", path)) + } + return handle, nil +} + +func (authority *windowsPathAuthority) Release() error { + if authority == nil { + return nil + } + authority.mu.Lock() + defer authority.mu.Unlock() + if authority.closed { + return nil + } + authority.closed = true + var errs []error + for index := len(authority.handles) - 1; index >= 0; index-- { + if err := windows.CloseHandle(authority.handles[index]); err != nil { + errs = append(errs, fmt.Errorf("release Windows config directory authority: %w", err)) + } + } + return errors.Join(errs...) +} + +func rejectWindowsReparseComponents(path string) error { + authority, err := pinWindowsConfigParent(path) + if err != nil { + return err + } + return authority.Release() +} diff --git a/internal/config/edit_candidate_other.go b/internal/config/edit_candidate_other.go new file mode 100644 index 000000000..35c32b089 --- /dev/null +++ b/internal/config/edit_candidate_other.go @@ -0,0 +1,53 @@ +//go:build !darwin && !linux && !windows + +package config + +import ( + "errors" + "fmt" + "os" +) + +func createConfigCandidate(dir string) (configCandidate, error) { + file, err := os.CreateTemp(dir, ".config-edit-*.toml.tmp") + if err != nil { + return configCandidate{}, err + } + info, err := file.Stat() + if err != nil { + _ = file.Close() + return configCandidate{}, err + } + expectedIdentity, ok := openedFileIdentity(file, info) + if !ok { + _ = file.Close() + return configCandidate{}, errors.New("config candidate identity is unavailable") + } + retained, err := os.Open(file.Name()) + if err != nil { + _ = file.Close() + return configCandidate{}, fmt.Errorf("retain config candidate: %w", err) + } + retainedInfo, err := retained.Stat() + if err != nil { + _ = retained.Close() + _ = file.Close() + return configCandidate{}, err + } + retainedIdentity, ok := openedFileIdentity(retained, retainedInfo) + if !ok || retainedIdentity != expectedIdentity { + _ = retained.Close() + _ = file.Close() + return configCandidate{}, ErrConfigConflict + } + return configCandidate{ + file: file, + retained: retained, + path: file.Name(), + cleanup: func() error { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("config retirement is unsupported on this platform; candidate preserved")) + }, + release: retained.Close, + }, nil +} diff --git a/internal/config/edit_candidate_unix.go b/internal/config/edit_candidate_unix.go new file mode 100644 index 000000000..4714b288c --- /dev/null +++ b/internal/config/edit_candidate_unix.go @@ -0,0 +1,69 @@ +//go:build darwin || linux + +package config + +import ( + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "os" + "path/filepath" + + "golang.org/x/sys/unix" +) + +func createConfigCandidate(dir string) (configCandidate, error) { + dirfd, err := unix.Open(dir, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_DIRECTORY|unix.O_NOFOLLOW, 0) + if err != nil { + return configCandidate{}, fmt.Errorf("pin candidate directory: %w", err) + } + for range 128 { + var random [16]byte + if _, err := rand.Read(random[:]); err != nil { + _ = unix.Close(dirfd) + return configCandidate{}, fmt.Errorf("generate candidate name: %w", err) + } + name := ".config-edit-" + hex.EncodeToString(random[:]) + ".toml.tmp" + fd, openErr := unix.Openat(dirfd, name, unix.O_RDWR|unix.O_CLOEXEC|unix.O_CREAT|unix.O_EXCL|unix.O_NOFOLLOW, 0o600) + if openErr == unix.EEXIST { + continue + } + if openErr != nil { + _ = unix.Close(dirfd) + return configCandidate{}, fmt.Errorf("create candidate relative to pinned directory: %w", openErr) + } + file := os.NewFile(uintptr(fd), filepath.Join(dir, name)) + info, statErr := file.Stat() + if statErr != nil { + _ = file.Close() + _ = unix.Close(dirfd) + return configCandidate{}, fmt.Errorf("identify config candidate: %w", statErr) + } + _, ok := openedFileIdentity(file, info) + if !ok { + _ = file.Close() + _ = unix.Close(dirfd) + return configCandidate{}, errors.New("config candidate identity is unavailable") + } + retainedFD, duplicateErr := unix.Dup(fd) + if duplicateErr != nil { + _ = file.Close() + _ = unix.Close(dirfd) + return configCandidate{}, fmt.Errorf("retain config candidate identity: %w", duplicateErr) + } + unix.CloseOnExec(retainedFD) + retained := os.NewFile(uintptr(retainedFD), file.Name()) + return configCandidate{ + file: file, + retained: retained, + path: file.Name(), + cleanup: func() error { + return retireConfigArtifactAt(dirfd, name, file.Name(), retained) + }, + release: func() error { return errors.Join(retained.Close(), unix.Close(dirfd)) }, + }, nil + } + _ = unix.Close(dirfd) + return configCandidate{}, errors.New("could not allocate a unique config candidate") +} diff --git a/internal/config/edit_candidate_windows.go b/internal/config/edit_candidate_windows.go new file mode 100644 index 000000000..a9a1599fd --- /dev/null +++ b/internal/config/edit_candidate_windows.go @@ -0,0 +1,112 @@ +//go:build windows + +package config + +import ( + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "os" + "path/filepath" + "unsafe" + + "golang.org/x/sys/windows" +) + +// createConfigCandidate applies the protected owner-only DACL in the +// SECURITY_ATTRIBUTES passed to CREATE_NEW. Config bytes are therefore never +// exposed through an inherited directory DACL, even briefly. +func createConfigCandidate(dir string) (configCandidate, error) { + authority, err := pinWindowsConfigParent(filepath.Join(dir, "config-candidate")) + if err != nil { + return configCandidate{}, err + } + user, err := windows.GetCurrentProcessToken().GetTokenUser() + if err != nil { + _ = authority.Release() + return configCandidate{}, fmt.Errorf("get current user SID: %w", err) + } + descriptor, err := windows.SecurityDescriptorFromString( + "O:" + user.User.Sid.String() + "D:P(A;;GA;;;" + user.User.Sid.String() + ")", + ) + if err != nil { + _ = authority.Release() + return configCandidate{}, fmt.Errorf("build config candidate security descriptor: %w", err) + } + attributes := &windows.SecurityAttributes{ + Length: uint32(unsafe.Sizeof(windows.SecurityAttributes{})), + SecurityDescriptor: descriptor, + } + for range 128 { + var random [16]byte + if _, err := rand.Read(random[:]); err != nil { + _ = authority.Release() + return configCandidate{}, err + } + path := filepath.Join(dir, ".config-edit-"+hex.EncodeToString(random[:])+".toml.tmp") + path16, err := windows.UTF16PtrFromString(path) + if err != nil { + _ = authority.Release() + return configCandidate{}, err + } + handle, err := windows.CreateFile( + path16, + windows.GENERIC_READ|windows.GENERIC_WRITE|windows.READ_CONTROL|windows.WRITE_DAC, + windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE, + attributes, + windows.CREATE_NEW, + windows.FILE_ATTRIBUTE_NORMAL, + 0, + ) + if err != nil { + if err == windows.ERROR_FILE_EXISTS || err == windows.ERROR_ALREADY_EXISTS { + continue + } + _ = authority.Release() + return configCandidate{}, err + } + file := os.NewFile(uintptr(handle), path) + info, statErr := file.Stat() + if statErr != nil { + _ = file.Close() + _ = authority.Release() + return configCandidate{}, statErr + } + _, ok := openedFileIdentity(file, info) + if !ok { + _ = file.Close() + _ = authority.Release() + return configCandidate{}, errors.New("config candidate identity is unavailable") + } + process := windows.CurrentProcess() + var retainedHandle windows.Handle + if err := windows.DuplicateHandle( + process, + handle, + process, + &retainedHandle, + windows.GENERIC_READ|windows.READ_CONTROL, + false, + 0, + ); err != nil { + _ = file.Close() + _ = authority.Release() + return configCandidate{}, fmt.Errorf("retain Windows config candidate: %w", err) + } + retained := os.NewFile(uintptr(retainedHandle), path) + return configCandidate{ + file: file, + retained: retained, + path: path, + release: func() error { + return errors.Join(retained.Close(), authority.Release()) + }, + cleanup: func() error { + return retireWindowsConfigArtifact(path, retained) + }, + }, nil + } + _ = authority.Release() + return configCandidate{}, errors.New("could not allocate a unique config candidate") +} diff --git a/internal/config/edit_cleanup_darwin.go b/internal/config/edit_cleanup_darwin.go new file mode 100644 index 000000000..68e8b9ca2 --- /dev/null +++ b/internal/config/edit_cleanup_darwin.go @@ -0,0 +1,16 @@ +//go:build darwin + +package config + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +func renameConfigNoReplaceAt(dirfd int, oldName, newName string) error { + if err := unix.RenameatxNp(dirfd, oldName, dirfd, newName, unix.RENAME_EXCL); err != nil { + return fmt.Errorf("rename config entry without replacement: %w", err) + } + return nil +} diff --git a/internal/config/edit_cleanup_linux.go b/internal/config/edit_cleanup_linux.go new file mode 100644 index 000000000..40985bfa5 --- /dev/null +++ b/internal/config/edit_cleanup_linux.go @@ -0,0 +1,16 @@ +//go:build linux + +package config + +import ( + "fmt" + + "golang.org/x/sys/unix" +) + +func renameConfigNoReplaceAt(dirfd int, oldName, newName string) error { + if err := unix.Renameat2(dirfd, oldName, dirfd, newName, unix.RENAME_NOREPLACE); err != nil { + return fmt.Errorf("rename config entry without replacement: %w", err) + } + return nil +} diff --git a/internal/config/edit_cleanup_unix.go b/internal/config/edit_cleanup_unix.go new file mode 100644 index 000000000..699380612 --- /dev/null +++ b/internal/config/edit_cleanup_unix.go @@ -0,0 +1,124 @@ +//go:build darwin || linux + +package config + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "golang.org/x/sys/unix" +) + +// retireConfigArtifactAt moves the named entry to an unpredictable recovery +// name and proves it is the retained transaction object. The recovery file and +// its contents are deliberately preserved: pathname deletion and descriptor +// truncation cannot prove that no user-created hardlink references the inode. +func retireConfigArtifactAt(dirfd int, name, displayPath string, expected *os.File) error { + return retireConfigArtifactAtWithHook(dirfd, name, displayPath, expected, nil) +} + +func retireConfigArtifactAtWithHook( + dirfd int, + name, displayPath string, + expected *os.File, + beforeFinalVerification func(string) error, +) error { + expectedInfo, err := expected.Stat() + if err != nil { + return fmt.Errorf("identify retained config retirement entry: %w", err) + } + expectedIdentity, expectedOK := openedFileIdentity(expected, expectedInfo) + if !expectedOK { + return errors.Join(ErrConfigChanged, errors.New("retained config retirement identity is unavailable")) + } + for range 128 { + quarantineName, nameErr := newConfigRetiredName() + if nameErr != nil { + return nameErr + } + if err := renameConfigNoReplaceAt(dirfd, name, quarantineName); err != nil { + if errors.Is(err, unix.ENOENT) { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("config retirement entry disappeared before quarantine")) + } + if errors.Is(err, unix.EEXIST) { + continue + } + return fmt.Errorf("quarantine config retirement entry: %w", err) + } + quarantinePath := filepath.Join(filepath.Dir(displayPath), quarantineName) + quarantined, quarantinedInfo, inspectErr := openConfigEntryAt(dirfd, quarantineName, quarantinePath) + var quarantinedIdentity string + var quarantinedOK bool + if inspectErr == nil { + quarantinedIdentity, quarantinedOK = openedFileIdentity(quarantined, quarantinedInfo) + } + if inspectErr == nil && quarantinedOK && quarantinedIdentity == expectedIdentity { + _ = quarantined.Close() + if beforeFinalVerification != nil { + if err := beforeFinalVerification(quarantinePath); err != nil { + return errors.Join(ErrConfigChanged, fmt.Errorf("before config retirement verification: %w", err)) + } + } + current, currentInfo, currentErr := openConfigEntryAt(dirfd, quarantineName, quarantinePath) + var currentIdentity string + var currentOK bool + if currentErr == nil { + currentIdentity, currentOK = openedFileIdentity(current, currentInfo) + _ = current.Close() + } + if currentErr == nil && currentOK && currentIdentity == expectedIdentity { + return nil + } + inspectErr = currentErr + } else if quarantined != nil { + _ = quarantined.Close() + } + + restoreErr := renameConfigNoReplaceAt(dirfd, quarantineName, name) + if restoreErr == nil { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("refusing to retire a substituted config entry"), inspectErr) + } + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("refusing to retire a substituted config entry"), inspectErr, + fmt.Errorf("restore substituted entry: %w", restoreErr), + fmt.Errorf("config recovery artifact preserved at %s", quarantinePath)) + } + return errors.Join(ErrConfigChanged, errors.New("could not allocate config recovery artifact")) +} + +func openConfigEntryAt(dirfd int, name, displayPath string) (*os.File, os.FileInfo, error) { + fd, err := unix.Openat(dirfd, name, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0) + if err != nil { + return nil, nil, fmt.Errorf("open config cleanup entry: %w", err) + } + file := os.NewFile(uintptr(fd), displayPath) + info, err := file.Stat() + if err != nil { + _ = file.Close() + return nil, nil, err + } + return file, info, nil +} + +func retainConfigEntryAt(dirfd int, name, displayPath, expectedIdentity string) (*os.File, error) { + fd, err := unix.Openat(dirfd, name, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0) + if err != nil { + return nil, fmt.Errorf("open retained config retirement entry: %w", err) + } + file := os.NewFile(uintptr(fd), displayPath) + info, err := file.Stat() + if err != nil { + _ = file.Close() + return nil, fmt.Errorf("identify retained config retirement entry: %w", err) + } + identity, ok := openedFileIdentity(file, info) + if !ok || identity != expectedIdentity { + _ = file.Close() + return nil, errors.Join(ErrConfigConflict, errors.New("config cleanup identity changed before retention")) + } + return file, nil +} diff --git a/internal/config/edit_exchange_darwin.go b/internal/config/edit_exchange_darwin.go new file mode 100644 index 000000000..b32475ca1 --- /dev/null +++ b/internal/config/edit_exchange_darwin.go @@ -0,0 +1,63 @@ +//go:build darwin + +package config + +import ( + "errors" + "fmt" + + "golang.org/x/sys/unix" +) + +func beginConfigReplacement(left, right string, expectedLeft, expectedRight ConfigFile) (configReplacement, error) { + if err := verifyRetainedConfigSnapshot(expectedRight); err != nil { + return configReplacement{}, err + } + dir, leftName, rightName, leftBefore, rightBefore, err := openPinnedReplacementDirectory(left, right) + if err != nil { + return configReplacement{}, err + } + if !sameConfigVersion(leftBefore, expectedLeft) || !sameConfigVersion(rightBefore, expectedRight) { + _ = unix.Close(dir) + return configReplacement{}, errors.Join(ErrConfigConflict, + errors.New("config exchange identities changed before publication")) + } + displaced, err := retainConfigEntryAt(dir, rightName, right, rightBefore.identity) + if err != nil { + _ = unix.Close(dir) + return configReplacement{}, fmt.Errorf("retain displaced config identity: %w", err) + } + if err := unix.RenameatxNp(dir, leftName, dir, rightName, unix.RENAME_SWAP); err != nil { + _ = displaced.Close() + _ = unix.Close(dir) + return configReplacement{}, fmt.Errorf("rename swap: %w", err) + } + return configReplacement{ + displacedPath: left, + rollbackPublished: func(expected ConfigFile) error { + displaced, displacedErr := readPhysicalConfigSnapshotAt(dir, leftName, left) + current, currentErr := readPhysicalConfigSnapshotAt(dir, rightName, right) + if displacedErr != nil || currentErr != nil { + return errors.Join(ErrConfigChanged, ErrConfigConflict, displacedErr, currentErr) + } + if !sameConfigVersion(displaced, rightBefore) { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("refusing rollback because the displaced config changed")) + } + if !sameConfigVersion(current, expected) { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("refusing rollback because the published config changed")) + } + if err := unix.RenameatxNp(dir, leftName, dir, rightName, unix.RENAME_SWAP); err != nil { + return fmt.Errorf("rollback rename swap: %w", err) + } + return nil + }, + published: leftBefore, + syncDirectory: func() error { return unix.Fsync(dir) }, + cleanupDisplaced: func() error { + return retireConfigArtifactAt(dir, leftName, left, displaced) + }, + release: func() error { return errors.Join(displaced.Close(), unix.Close(dir)) }, + }, nil +} diff --git a/internal/config/edit_exchange_linux.go b/internal/config/edit_exchange_linux.go new file mode 100644 index 000000000..5f9748603 --- /dev/null +++ b/internal/config/edit_exchange_linux.go @@ -0,0 +1,63 @@ +//go:build linux + +package config + +import ( + "errors" + "fmt" + + "golang.org/x/sys/unix" +) + +func beginConfigReplacement(left, right string, expectedLeft, expectedRight ConfigFile) (configReplacement, error) { + if err := verifyRetainedConfigSnapshot(expectedRight); err != nil { + return configReplacement{}, err + } + dir, leftName, rightName, leftBefore, rightBefore, err := openPinnedReplacementDirectory(left, right) + if err != nil { + return configReplacement{}, err + } + if !sameConfigVersion(leftBefore, expectedLeft) || !sameConfigVersion(rightBefore, expectedRight) { + _ = unix.Close(dir) + return configReplacement{}, errors.Join(ErrConfigConflict, + errors.New("config exchange identities changed before publication")) + } + displaced, err := retainConfigEntryAt(dir, rightName, right, rightBefore.identity) + if err != nil { + _ = unix.Close(dir) + return configReplacement{}, fmt.Errorf("retain displaced config identity: %w", err) + } + if err := unix.Renameat2(dir, leftName, dir, rightName, unix.RENAME_EXCHANGE); err != nil { + _ = displaced.Close() + _ = unix.Close(dir) + return configReplacement{}, fmt.Errorf("rename exchange: %w", err) + } + return configReplacement{ + displacedPath: left, + rollbackPublished: func(expected ConfigFile) error { + displaced, displacedErr := readPhysicalConfigSnapshotAt(dir, leftName, left) + current, currentErr := readPhysicalConfigSnapshotAt(dir, rightName, right) + if displacedErr != nil || currentErr != nil { + return errors.Join(ErrConfigChanged, ErrConfigConflict, displacedErr, currentErr) + } + if !sameConfigVersion(displaced, rightBefore) { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("refusing rollback because the displaced config changed")) + } + if !sameConfigVersion(current, expected) { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("refusing rollback because the published config changed")) + } + if err := unix.Renameat2(dir, leftName, dir, rightName, unix.RENAME_EXCHANGE); err != nil { + return fmt.Errorf("rollback rename exchange: %w", err) + } + return nil + }, + published: leftBefore, + syncDirectory: func() error { return unix.Fsync(dir) }, + cleanupDisplaced: func() error { + return retireConfigArtifactAt(dir, leftName, left, displaced) + }, + release: func() error { return errors.Join(displaced.Close(), unix.Close(dir)) }, + }, nil +} diff --git a/internal/config/edit_exchange_unix.go b/internal/config/edit_exchange_unix.go new file mode 100644 index 000000000..b790c536f --- /dev/null +++ b/internal/config/edit_exchange_unix.go @@ -0,0 +1,74 @@ +//go:build darwin || linux + +package config + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "golang.org/x/sys/unix" +) + +// openPinnedReplacementDirectory proves both names through one pinned +// directory handle. Native exchange then uses that dirfd, so replacing an +// ancestor pathname cannot redirect the transaction to another file. +func openPinnedReplacementDirectory(left, right string) (int, string, string, ConfigFile, ConfigFile, error) { + if filepath.Clean(filepath.Dir(left)) != filepath.Clean(filepath.Dir(right)) { + return -1, "", "", ConfigFile{}, ConfigFile{}, errors.New("config exchange paths are not siblings") + } + dir, err := unix.Open(filepath.Dir(right), unix.O_RDONLY|unix.O_CLOEXEC|unix.O_DIRECTORY|unix.O_NOFOLLOW, 0) + if err != nil { + return -1, "", "", ConfigFile{}, ConfigFile{}, fmt.Errorf("pin config directory: %w", err) + } + leftName, rightName := filepath.Base(left), filepath.Base(right) + leftSnapshot, leftErr := readPhysicalConfigSnapshotAt(dir, leftName, left) + rightSnapshot, rightErr := readPhysicalConfigSnapshotAt(dir, rightName, right) + if leftErr != nil || rightErr != nil { + _ = unix.Close(dir) + return -1, "", "", ConfigFile{}, ConfigFile{}, errors.Join(leftErr, rightErr) + } + // Cross-check the path snapshots. If an ancestor changed while the pinned + // directory was opened, the handle-relative and pathname identities differ. + pathLeft, pathLeftErr := readPhysicalConfigSnapshot(left) + pathRight, pathRightErr := readPhysicalConfigSnapshot(right) + if pathLeftErr != nil || pathRightErr != nil || + !sameConfigVersion(leftSnapshot, pathLeft) || !sameConfigVersion(rightSnapshot, pathRight) { + _ = unix.Close(dir) + return -1, "", "", ConfigFile{}, ConfigFile{}, errors.Join( + ErrConfigConflict, errors.New("config directory changed while being pinned"), + pathLeftErr, pathRightErr, + ) + } + return dir, leftName, rightName, leftSnapshot, rightSnapshot, nil +} + +func verifyRetainedConfigSnapshot(snapshot ConfigFile) error { + if snapshot.retained == nil { + return errors.Join(ErrConfigConflict, errors.New("existing config snapshot identity is not retained")) + } + info, err := snapshot.retained.Stat() + if err != nil { + return fmt.Errorf("identify retained existing config snapshot: %w", err) + } + identity, ok := openedFileIdentity(snapshot.retained, info) + if !ok || identity != snapshot.identity { + return errors.Join(ErrConfigConflict, errors.New("retained existing config snapshot identity changed")) + } + return nil +} + +func readPhysicalConfigSnapshotAt(dir int, name, displayPath string) (ConfigFile, error) { + fd, err := unix.Openat(dir, name, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0) + if err != nil { + return ConfigFile{}, fmt.Errorf("open pinned config entry: %w", err) + } + file := os.NewFile(uintptr(fd), displayPath) + content, mode, identity, readErr := readVerifiedOpenedConfig(file) + closeErr := file.Close() + if readErr != nil || closeErr != nil { + return ConfigFile{}, errors.Join(readErr, closeErr) + } + return ConfigFile{Path: displayPath, Content: content, ETag: configETag(content), Mode: mode, Exists: true, identity: identity}, nil +} diff --git a/internal/config/edit_exchange_unsupported.go b/internal/config/edit_exchange_unsupported.go new file mode 100644 index 000000000..a634aa9a6 --- /dev/null +++ b/internal/config/edit_exchange_unsupported.go @@ -0,0 +1,7 @@ +//go:build !darwin && !linux && !windows + +package config + +func beginConfigReplacement(_, _ string, _, _ ConfigFile) (configReplacement, error) { + return configReplacement{}, ErrAtomicReplaceUnsupported +} diff --git a/internal/config/edit_exchange_windows.go b/internal/config/edit_exchange_windows.go new file mode 100644 index 000000000..56eaf432b --- /dev/null +++ b/internal/config/edit_exchange_windows.go @@ -0,0 +1,184 @@ +//go:build windows + +package config + +import ( + "errors" + "fmt" + "io/fs" + "os" + "unsafe" + + "golang.org/x/sys/windows" +) + +var replaceFileProc = windows.NewLazySystemDLL("kernel32.dll").NewProc("ReplaceFileW") + +func moveFileWriteThrough(fromPath, toPath string) error { + from, err := windows.UTF16PtrFromString(fromPath) + if err != nil { + return err + } + to, err := windows.UTF16PtrFromString(toPath) + if err != nil { + return err + } + return windows.MoveFileEx(from, to, windows.MOVEFILE_WRITE_THROUGH) +} + +// beginConfigReplacement uses ReplaceFileW so the destination switches from +// the old config to the candidate atomically. Windows writes the displaced +// file to a separate backup path, which also gives us a native atomic rollback +// operation until the caller completes its durability checks. +func beginConfigReplacement(candidatePath, targetPath string, expectedCandidate, expectedTarget ConfigFile) (configReplacement, error) { + authority, err := pinWindowsConfigParent(targetPath) + if err != nil { + return configReplacement{}, err + } + displacedPath := candidatePath + ".displaced" + targetBefore, targetErr := readPhysicalConfigSnapshot(targetPath) + if targetErr != nil { + _ = authority.Release() + return configReplacement{}, targetErr + } + candidateBefore, candidateErr := readPhysicalConfigSnapshot(candidatePath) + if candidateErr != nil { + _ = authority.Release() + return configReplacement{}, candidateErr + } + if !sameConfigVersion(targetBefore, expectedTarget) || !sameConfigVersion(candidateBefore, expectedCandidate) { + _ = authority.Release() + return configReplacement{}, errors.Join(ErrConfigConflict, + errors.New("ReplaceFileW identities changed before publication")) + } + displaced, err := retainWindowsConfigArtifact(targetPath, targetBefore.identity) + if err != nil { + _ = authority.Release() + return configReplacement{}, err + } + replacement := configReplacement{ + displacedPath: displacedPath, + preserveCandidate: true, + recoveryPaths: []string{targetPath, candidatePath, displacedPath}, + cleanupDisplaced: func() error { + return retireWindowsConfigArtifact(displacedPath, displaced) + }, + release: func() error { + return errors.Join(displaced.Close(), authority.Release()) + }, + } + if err := replaceFile(targetPath, candidatePath, displacedPath); err != nil { + reconciled, reconciledErr := reconcileReplaceFileFailure(replacement, targetBefore, candidateBefore, err) + if reconciled.release == nil { + _ = displaced.Close() + _ = authority.Release() + } + return reconciled, reconciledErr + } + replacement.rollbackPublished = func(expected ConfigFile) error { + targetBeforeRollback, targetErr := readPhysicalConfigSnapshot(targetPath) + if targetErr != nil { + return errors.Join(ErrConfigChanged, targetErr) + } + if !sameConfigVersion(targetBeforeRollback, expected) { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("refusing ReplaceFileW rollback because the published config changed")) + } + displacedBeforeRollback, displacedErr := readPhysicalConfigSnapshot(displacedPath) + if displacedErr != nil { + return errors.Join(ErrConfigChanged, displacedErr) + } + if err := replaceFile(targetPath, displacedPath, candidatePath); err != nil { + rollbackState := configReplacement{ + displacedPath: candidatePath, + preserveCandidate: true, + recoveryPaths: []string{targetPath, displacedPath, candidatePath}, + } + _, reconciledErr := reconcileReplaceFileFailure( + rollbackState, targetBeforeRollback, displacedBeforeRollback, err, + ) + return fmt.Errorf("ReplaceFileW rollback did not complete: %w", errors.Join(ErrConfigChanged, reconciledErr)) + } + return nil + } + return replacement, nil +} + +func reconcileReplaceFileFailure( + replacement configReplacement, + targetBefore, candidateBefore ConfigFile, + callErr error, +) (configReplacement, error) { + if windowsPathsMatchOriginal(targetBefore, candidateBefore, replacement.displacedPath) { + return configReplacement{}, fmt.Errorf("ReplaceFileW left original files unchanged: %w", callErr) + } + + // ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 documents the dangerous partial + // outcome: the old target has moved to the backup name while the candidate + // retains its name. Restore the target with write-through rename, then prove + // both original identities are back where they started. + if errors.Is(callErr, windows.ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) && + pathMissing(targetBefore.Path) && pathMatchesVersion(replacement.displacedPath, targetBefore) && + pathMatchesVersion(candidateBefore.Path, candidateBefore) { + moveErr := moveFileWriteThrough(replacement.displacedPath, targetBefore.Path) + if windowsPathsMatchOriginal(targetBefore, candidateBefore, replacement.displacedPath) { + return configReplacement{}, fmt.Errorf("ReplaceFileW partial outcome was restored: %w", callErr) + } + if moveErr != nil { + callErr = errors.Join(callErr, fmt.Errorf("restore partial ReplaceFileW outcome: %w", moveErr)) + } + } + + // ERROR_UNABLE_TO_REMOVE_REPLACED and + // ERROR_UNABLE_TO_MOVE_REPLACEMENT normally leave the two named files + // unchanged. The identity check above, rather than the error code, is what + // proves that safe outcome. Every other observable state is ambiguous and + // must retain all artifacts for operator recovery. + return replacement, errors.Join( + fmt.Errorf("%w: ReplaceFileW left an unverified partial state", ErrConfigChanged), + callErr, + recoveryArtifactError(replacement), + ) +} + +func windowsPathsMatchOriginal(targetBefore, candidateBefore ConfigFile, backupPath string) bool { + return pathMatchesVersion(targetBefore.Path, targetBefore) && + pathMatchesVersion(candidateBefore.Path, candidateBefore) && pathMissing(backupPath) +} + +func pathMatchesVersion(path string, before ConfigFile) bool { + current, err := readPhysicalConfigSnapshot(path) + return err == nil && sameConfigVersion(current, before) +} + +func pathMissing(path string) bool { + _, err := os.Lstat(path) + return errors.Is(err, fs.ErrNotExist) +} + +func replaceFile(targetPath, replacementPath, backupPath string) error { + target, err := windows.UTF16PtrFromString(targetPath) + if err != nil { + return fmt.Errorf("encode replacement target: %w", err) + } + replacement, err := windows.UTF16PtrFromString(replacementPath) + if err != nil { + return fmt.Errorf("encode config candidate: %w", err) + } + backup, err := windows.UTF16PtrFromString(backupPath) + if err != nil { + return fmt.Errorf("encode displaced config path: %w", err) + } + result, _, callErr := replaceFileProc.Call( + uintptr(unsafe.Pointer(target)), + uintptr(unsafe.Pointer(replacement)), + uintptr(unsafe.Pointer(backup)), + 0, + 0, + 0, + ) + if result == 0 { + return fmt.Errorf("ReplaceFileW: %w", callErr) + } + return nil +} diff --git a/internal/config/edit_exchange_windows_test.go b/internal/config/edit_exchange_windows_test.go new file mode 100644 index 000000000..a39edbb1d --- /dev/null +++ b/internal/config/edit_exchange_windows_test.go @@ -0,0 +1,411 @@ +//go:build windows + +package config + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sys/windows" +) + +func writeSecureWindowsTestConfig(t *testing.T, path string, content []byte) { + t.Helper() + require.NoError(t, os.WriteFile(path, content, 0o600)) + file, err := os.OpenFile(path, os.O_RDWR, 0) + require.NoError(t, err) + require.NoError(t, secureConfigCandidate(file, path, 0o600)) + require.NoError(t, file.Close()) +} + +func TestWindowsConfigReplacementSupportsVerifiedRollback(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + target := filepath.Join(dir, "config.toml") + candidate := filepath.Join(dir, "candidate.toml") + writeSecureWindowsTestConfig(t, target, []byte("old")) + writeSecureWindowsTestConfig(t, candidate, []byte("new")) + + candidateBefore, err := readPhysicalConfigSnapshot(candidate) + require.NoError(err) + targetBefore, err := readPhysicalConfigSnapshot(target) + require.NoError(err) + replacement, err := beginConfigReplacement(candidate, target, candidateBefore, targetBefore) + require.NoError(err) + t.Cleanup(func() { require.NoError(replacement.release()) }) + current, err := os.ReadFile(target) + require.NoError(err) + assert.Equal([]byte("new"), current) + displaced, err := os.ReadFile(replacement.displacedPath) + require.NoError(err) + assert.Equal([]byte("old"), displaced) + + published, err := readPhysicalConfigSnapshot(target) + require.NoError(err) + replacement.published = published + require.NoError(replacement.rollbackInstalledVersion()) + restored, err := os.ReadFile(target) + require.NoError(err) + assert.Equal([]byte("old"), restored) + rolledBackCandidate, err := os.ReadFile(candidate) + require.NoError(err) + assert.Equal([]byte("new"), rolledBackCandidate) +} + +func TestWindowsRollbackPreservesLaterWriter(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "config.toml") + candidate := filepath.Join(dir, "candidate.toml") + writeSecureWindowsTestConfig(t, target, []byte("old")) + writeSecureWindowsTestConfig(t, candidate, []byte("new")) + candidateBefore, err := readPhysicalConfigSnapshot(candidate) + require.NoError(t, err) + targetBefore, err := readPhysicalConfigSnapshot(target) + require.NoError(t, err) + replacement, err := beginConfigReplacement(candidate, target, candidateBefore, targetBefore) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, replacement.release()) }) + published, err := readPhysicalConfigSnapshot(target) + require.NoError(t, err) + replacement.published = published + + require.NoError(t, os.Remove(target)) + writeSecureWindowsTestConfig(t, target, []byte("later writer")) + err = replacement.rollbackInstalledVersion() + require.ErrorIs(t, err, ErrConfigChanged) + require.ErrorIs(t, err, ErrConfigConflict) + assert.Equal(t, []byte("later writer"), mustReadFile(t, target)) + assert.FileExists(t, replacement.displacedPath) +} + +func TestWindowsRejectsReparseConfigPath(t *testing.T) { + dir := t.TempDir() + realDir := filepath.Join(dir, "real") + require.NoError(t, os.Mkdir(realDir, 0o700)) + linkDir := filepath.Join(dir, "linked") + if err := os.Symlink(realDir, linkDir); err != nil { + t.Skip("creating a Windows symlink requires optional privileges") + } + _, err := ReadConfigFile(filepath.Join(linkDir, "config.toml")) + require.ErrorIs(t, err, ErrUnsafeConfigTarget) +} + +func TestWindowsRetainedAuthorityBlocksIntermediateRenameAndAllowsReplace(t *testing.T) { + root := t.TempDir() + intermediate := filepath.Join(root, "intermediate") + parent := filepath.Join(intermediate, "config") + require.NoError(t, os.MkdirAll(parent, 0o700)) + target := filepath.Join(parent, "config.toml") + candidate := filepath.Join(parent, "candidate.toml") + writeSecureWindowsTestConfig(t, target, []byte("old")) + writeSecureWindowsTestConfig(t, candidate, []byte("new")) + targetBefore, err := readPhysicalConfigSnapshot(target) + require.NoError(t, err) + candidateBefore, err := readPhysicalConfigSnapshot(candidate) + require.NoError(t, err) + authority, err := pinWindowsConfigParent(target) + require.NoError(t, err) + encodedParent, err := windows.UTF16PtrFromString(parent) + require.NoError(t, err) + writer, err := windows.CreateFile(encodedParent, windows.GENERIC_WRITE, + windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, + nil, windows.OPEN_EXISTING, + windows.FILE_FLAG_OPEN_REPARSE_POINT|windows.FILE_FLAG_BACKUP_SEMANTICS, 0) + require.Error(t, err, "retained parent handle must deny reparse-capable write opens") + assert.Equal(t, windows.InvalidHandle, writer) + assert.ErrorIs(t, err, windows.ERROR_SHARING_VIOLATION) + + err = os.Rename(intermediate, filepath.Join(root, "redirected")) + require.Error(t, err, "retained intermediate handle must deny rename") + replacement, err := beginConfigReplacement(candidate, target, candidateBefore, targetBefore) + require.NoError(t, err, "ReplaceFileW on a child must work while parent handles are retained") + assert.Equal(t, []byte("new"), mustReadFile(t, target)) + require.NotNil(t, replacement.cleanupDisplaced) + require.NoError(t, replacement.cleanupDisplaced()) + tombstones, err := filepath.Glob(filepath.Join(parent, configRetiredPrefix+"*")) + require.NoError(t, err) + require.Len(t, tombstones, 1) + assert.Equal(t, []byte("old"), mustReadFile(t, tombstones[0])) + require.NoError(t, replacement.release()) + require.NoError(t, authority.Release()) + require.NoError(t, os.Rename(intermediate, filepath.Join(root, "redirected")), + "rename must work after every retained handle is released") +} + +func TestWindowsRetainedAuthorityAllowsMissingPublication(t *testing.T) { + parent := filepath.Join(t.TempDir(), "config") + require.NoError(t, os.Mkdir(parent, 0o700)) + target := filepath.Join(parent, "config.toml") + candidate := filepath.Join(parent, "candidate.toml") + writeSecureWindowsTestConfig(t, candidate, []byte("new")) + before, err := ReadConfigFile(target) + require.NoError(t, err) + authority, err := pinWindowsConfigParent(target) + require.NoError(t, err) + + retained, err := retainWindowsConfigArtifact(candidate, mustConfigIdentity(t, candidate)) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, retained.Close()) }) + publication, err := publishNewConfig(candidate, retained, before) + require.NoError(t, err, "MoveFileExW on a child must work while parent handles are retained") + assert.Equal(t, []byte("new"), mustReadFile(t, target)) + require.NoError(t, publication.release()) + require.NoError(t, authority.Release()) +} + +func TestWindowsAuthorityRejectsPreexistingDirectoryWriter(t *testing.T) { + parent := filepath.Join(t.TempDir(), "config") + require.NoError(t, os.Mkdir(parent, 0o700)) + encoded, err := windows.UTF16PtrFromString(parent) + require.NoError(t, err) + writer, err := windows.CreateFile(encoded, windows.GENERIC_WRITE, + windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, + nil, windows.OPEN_EXISTING, + windows.FILE_FLAG_OPEN_REPARSE_POINT|windows.FILE_FLAG_BACKUP_SEMANTICS, 0) + require.NoError(t, err) + defer windows.CloseHandle(writer) + + _, err = pinWindowsConfigParent(filepath.Join(parent, "config.toml")) + require.Error(t, err) + assert.ErrorIs(t, err, windows.ERROR_SHARING_VIOLATION) +} + +func TestWindowsEditConfigFileFullTransactions(t *testing.T) { + for _, existing := range []bool{true, false} { + name := "missing" + if existing { + name = "existing" + } + t.Run(name, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.toml") + if existing { + writeSecureWindowsTestConfig(t, path, []byte("[web]\ntheme = \"system\"\n")) + } + snapshot, err := ReadConfigFile(path) + require.NoError(t, err) + + after, err := EditConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}) + require.NoError(t, err) + assert.Equal(t, "[web]\ntheme = \"dark\"\n", string(after.Content)) + if !existing { + require.NoError(t, verifyConfigOwnerOnly(after.Path)) + } + }) + } +} + +func TestWindowsReadAndEditConfigWithMissingFinalParent(t *testing.T) { + root := t.TempDir() + parent := filepath.Join(root, "missing", "nested") + path := filepath.Join(parent, "config.toml") + + snapshot, err := ReadConfigFile(path) + require.NoError(t, err) + assert.False(t, snapshot.Exists) + assert.Empty(t, snapshot.Content) + assert.Equal(t, configETag(nil), snapshot.ETag) + assert.Equal(t, path, snapshot.Path) + assert.NoDirExists(t, parent, "reading must not create missing config directories") + + after, err := EditConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}) + require.NoError(t, err) + assert.Equal(t, "[web]\ntheme = \"dark\"\n", string(after.Content)) + assert.FileExists(t, path) + require.NoError(t, verifyConfigOwnerOnly(path)) +} + +func TestWindowsMissingParentCreationRejectsAncestorReplacement(t *testing.T) { + root := t.TempDir() + ancestor := filepath.Join(root, "authority") + require.NoError(t, os.Mkdir(ancestor, 0o700)) + path := filepath.Join(ancestor, "missing", "config.toml") + + snapshot, err := readConfigFileSnapshot(path) + require.NoError(t, err) + require.NotEmpty(t, snapshot.parentIdentity) + + displaced := filepath.Join(root, "displaced") + require.NoError(t, os.Rename(ancestor, displaced)) + require.NoError(t, os.Mkdir(ancestor, 0o700)) + + err = ensureConfigParentDirectories(path, snapshot.parentIdentity) + require.ErrorIs(t, err, ErrConfigConflict) + assert.NoDirExists(t, filepath.Dir(path)) +} + +func TestWindowsConfigSaveProducesEditableOwnerOnlyFile(t *testing.T) { + for _, existing := range []bool{false, true} { + name := "missing" + if existing { + name = "existing legacy inherited DACL" + } + t.Run(name, func(t *testing.T) { + home := t.TempDir() + path := filepath.Join(home, "config.toml") + if existing { + require.NoError(t, os.WriteFile(path, []byte("[web]\ntheme = \"light\"\n"), 0o600)) + } + cfg := NewDefaultConfig() + cfg.HomeDir = home + cfg.configPath = path + cfg.Web.Theme = "system" + require.NoError(t, cfg.Save()) + require.NoError(t, verifyConfigOwnerOnly(path)) + tombstones, globErr := filepath.Glob(filepath.Join(home, configRetiredPrefix+"*")) + require.NoError(t, globErr) + if existing { + require.Len(t, tombstones, 1) + assert.Equal(t, []byte("[web]\ntheme = \"light\"\n"), mustReadFile(t, tombstones[0])) + } else { + assert.Empty(t, tombstones) + } + + snapshot, err := ReadConfigFile(path) + require.NoError(t, err) + after, err := EditConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}) + require.NoError(t, err) + assert.Contains(t, string(after.Content), "theme = \"dark\"") + require.NoError(t, verifyConfigOwnerOnly(path)) + }) + } +} + +func TestWindowsConfigSavePreservesPublishedCandidateWhenRetirementFails(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + home := t.TempDir() + path := filepath.Join(home, "config.toml") + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"light\"\n"), 0o600)) + cfg := NewDefaultConfig() + cfg.HomeDir = home + cfg.configPath = path + cfg.Web.Theme = "system" + displacedSubstitute := []byte("displaced later writer") + candidateSubstitute := []byte("candidate later writer") + var candidatePath string + var displacedPath string + var movedDisplacedPath string + + err := cfg.saveWithHooks(configSaveHooks{ + beforeExistingRetirement: func(replacement configReplacement) error { + displacedPath = replacement.displacedPath + candidatePath = strings.TrimSuffix(displacedPath, ".displaced") + movedDisplacedPath = displacedPath + ".moved" + require.NoError(os.Rename(displacedPath, movedDisplacedPath)) + writeSecureWindowsTestConfig(t, displacedPath, displacedSubstitute) + writeSecureWindowsTestConfig(t, candidatePath, candidateSubstitute) + return nil + }, + }) + require.ErrorIs(err, ErrConfigChanged) + assert.Contains(string(mustReadFile(t, path)), "theme = \"system\"") + assert.Equal([]byte("[web]\ntheme = \"light\"\n"), mustReadFile(t, movedDisplacedPath)) + assert.Equal(displacedSubstitute, mustReadFile(t, displacedPath)) + assert.Equal(candidateSubstitute, mustReadFile(t, candidatePath)) +} + +func TestWindowsEditMigratesCurrentUserInheritedDACL(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(t, os.WriteFile(path, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + world, err := windows.StringToSid("S-1-1-0") + require.NoError(t, err) + acl, err := windows.ACLFromEntries([]windows.EXPLICIT_ACCESS{{ + AccessPermissions: windows.GENERIC_READ, + AccessMode: windows.SET_ACCESS, + Inheritance: windows.NO_INHERITANCE, + Trustee: windows.TRUSTEE{ + TrusteeForm: windows.TRUSTEE_IS_SID, + TrusteeType: windows.TRUSTEE_IS_WELL_KNOWN_GROUP, + TrusteeValue: windows.TrusteeValueFromSID(world), + }, + }}, nil) + require.NoError(t, err) + require.NoError(t, windows.SetNamedSecurityInfo(path, windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION, nil, nil, acl, nil)) + after, err := EditConfigFile(path, configETag([]byte("[web]\ntheme = \"system\"\n")), []Edit{{Key: "web.theme", Value: "dark"}}) + require.NoError(t, err) + assert.Equal(t, "[web]\ntheme = \"dark\"\n", string(after.Content)) + require.NoError(t, verifyConfigOwnerOnly(path)) + content, readErr := os.ReadFile(path) + require.NoError(t, readErr) + assert.Equal(t, "[web]\ntheme = \"dark\"\n", string(content)) +} + +func TestWindowsSnapshotIdentityDetectsRecreatedFile(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.toml") + content := []byte("[web]\ntheme = \"system\"\n") + writeSecureWindowsTestConfig(t, path, content) + first, err := ReadConfigFile(path) + require.NoError(t, err) + second, err := ReadConfigFile(path) + require.NoError(t, err) + assert.Equal(t, first.identity, second.identity) + + require.NoError(t, os.Remove(path)) + writeSecureWindowsTestConfig(t, path, content) + recreated, err := ReadConfigFile(path) + require.NoError(t, err) + assert.NotEqual(t, first.identity, recreated.identity) +} + +func TestWindowsReplaceFilePartialFailureRestoresOriginal(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "config.toml") + candidate := filepath.Join(dir, "candidate.toml") + writeSecureWindowsTestConfig(t, target, []byte("old")) + writeSecureWindowsTestConfig(t, candidate, []byte("new")) + targetBefore, err := readPhysicalConfigSnapshot(target) + require.NoError(t, err) + candidateBefore, err := readPhysicalConfigSnapshot(candidate) + require.NoError(t, err) + backup := candidate + ".displaced" + require.NoError(t, os.Rename(target, backup)) + replacement := configReplacement{ + displacedPath: backup, + preserveCandidate: true, + recoveryPaths: []string{target, candidate, backup}, + } + + _, err = reconcileReplaceFileFailure(replacement, targetBefore, candidateBefore, + windows.ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) + require.Error(t, err) + require.NotErrorIs(t, err, ErrConfigChanged) + old, readErr := os.ReadFile(target) + require.NoError(t, readErr) + assert.Equal(t, []byte("old"), old) + newCandidate, readErr := os.ReadFile(candidate) + require.NoError(t, readErr) + assert.Equal(t, []byte("new"), newCandidate) +} + +func TestWindowsReplaceFilePartialFailurePreservesRecoveryArtifacts(t *testing.T) { + dir := t.TempDir() + target := filepath.Join(dir, "config.toml") + candidate := filepath.Join(dir, "candidate.toml") + writeSecureWindowsTestConfig(t, target, []byte("old")) + writeSecureWindowsTestConfig(t, candidate, []byte("new")) + targetBefore, err := readPhysicalConfigSnapshot(target) + require.NoError(t, err) + candidateBefore, err := readPhysicalConfigSnapshot(candidate) + require.NoError(t, err) + backup := candidate + ".displaced" + require.NoError(t, os.Rename(target, backup)) + require.NoError(t, os.Mkdir(target, 0o700)) + replacement := configReplacement{ + displacedPath: backup, + preserveCandidate: true, + recoveryPaths: []string{target, candidate, backup}, + } + + replacement, err = reconcileReplaceFileFailure(replacement, targetBefore, candidateBefore, + windows.ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) + require.ErrorIs(t, err, ErrConfigChanged) + assert.True(t, replacement.preserveCandidate) + assert.FileExists(t, candidate) + assert.FileExists(t, backup) +} diff --git a/internal/config/edit_identity_darwin.go b/internal/config/edit_identity_darwin.go new file mode 100644 index 000000000..3d7f74c61 --- /dev/null +++ b/internal/config/edit_identity_darwin.go @@ -0,0 +1,28 @@ +//go:build darwin + +package config + +import ( + "fmt" + "io/fs" + "os" + "syscall" +) + +func openedUnixFileIdentity(_ *os.File, info fs.FileInfo) (string, bool) { + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return "", false + } + return fmt.Sprintf( + "darwin:%d:%d:%d:%d", + stat.Dev, + stat.Ino, + stat.Birthtimespec.Sec, + stat.Birthtimespec.Nsec, + ), true +} + +func pathEntryIdentity(_ string, info fs.FileInfo) (string, bool) { + return openedUnixFileIdentity(nil, info) +} diff --git a/internal/config/edit_identity_linux.go b/internal/config/edit_identity_linux.go new file mode 100644 index 000000000..631bb070a --- /dev/null +++ b/internal/config/edit_identity_linux.go @@ -0,0 +1,50 @@ +//go:build linux + +package config + +import ( + "fmt" + "io/fs" + "os" + + "golang.org/x/sys/unix" +) + +func openedUnixFileIdentity(file *os.File, _ fs.FileInfo) (string, bool) { + var stat unix.Statx_t + if err := unix.Statx( + int(file.Fd()), + "", + unix.AT_EMPTY_PATH|unix.AT_STATX_SYNC_AS_STAT, + unix.STATX_INO|unix.STATX_BTIME, + &stat, + ); err != nil || stat.Mask&unix.STATX_INO == 0 || stat.Mask&unix.STATX_BTIME == 0 { + return "", false + } + return formatLinuxFileIdentity(&stat), true +} + +func pathEntryIdentity(path string, _ fs.FileInfo) (string, bool) { + var stat unix.Statx_t + if err := unix.Statx( + unix.AT_FDCWD, + path, + unix.AT_SYMLINK_NOFOLLOW|unix.AT_STATX_SYNC_AS_STAT, + unix.STATX_INO|unix.STATX_BTIME, + &stat, + ); err != nil || stat.Mask&unix.STATX_INO == 0 || stat.Mask&unix.STATX_BTIME == 0 { + return "", false + } + return formatLinuxFileIdentity(&stat), true +} + +func formatLinuxFileIdentity(stat *unix.Statx_t) string { + return fmt.Sprintf( + "linux:%d:%d:%d:%d:%d", + stat.Dev_major, + stat.Dev_minor, + stat.Ino, + stat.Btime.Sec, + stat.Btime.Nsec, + ) +} diff --git a/internal/config/edit_identity_unix_other.go b/internal/config/edit_identity_unix_other.go new file mode 100644 index 000000000..1af8a789d --- /dev/null +++ b/internal/config/edit_identity_unix_other.go @@ -0,0 +1,22 @@ +//go:build aix || dragonfly || freebsd || netbsd || openbsd || solaris + +package config + +import ( + "fmt" + "io/fs" + "os" + "syscall" +) + +func openedUnixFileIdentity(_ *os.File, info fs.FileInfo) (string, bool) { + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return "", false + } + return fmt.Sprintf("unix:%d:%d", stat.Dev, stat.Ino), true +} + +func pathEntryIdentity(_ string, info fs.FileInfo) (string, bool) { + return openedUnixFileIdentity(nil, info) +} diff --git a/internal/config/edit_open_other.go b/internal/config/edit_open_other.go new file mode 100644 index 000000000..463ba708e --- /dev/null +++ b/internal/config/edit_open_other.go @@ -0,0 +1,23 @@ +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows + +package config + +import ( + "fmt" + "io/fs" + "os" +) + +func openConfigNoFollow(path string) (*os.File, error) { + return os.Open(path) +} + +func openedFileIdentity(_ *os.File, info fs.FileInfo) (string, bool) { + // These platforms cannot perform conditional replacement, but stable + // metadata still lets read-only settings snapshots function. + return fmt.Sprintf("generic:%d:%d:%d", info.Size(), info.ModTime().UnixNano(), info.Mode()), true +} + +func pathEntryIdentity(_ string, info fs.FileInfo) (string, bool) { + return openedFileIdentity(nil, info) +} diff --git a/internal/config/edit_open_unix.go b/internal/config/edit_open_unix.go new file mode 100644 index 000000000..7760a9c30 --- /dev/null +++ b/internal/config/edit_open_unix.go @@ -0,0 +1,23 @@ +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris + +package config + +import ( + "fmt" + "io/fs" + "os" + + "golang.org/x/sys/unix" +) + +func openConfigNoFollow(path string) (*os.File, error) { + fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0) + if err != nil { + return nil, fmt.Errorf("open config without following final symlink: %w", err) + } + return os.NewFile(uintptr(fd), path), nil +} + +func openedFileIdentity(file *os.File, info fs.FileInfo) (string, bool) { + return openedUnixFileIdentity(file, info) +} diff --git a/internal/config/edit_open_windows.go b/internal/config/edit_open_windows.go new file mode 100644 index 000000000..03c551f3f --- /dev/null +++ b/internal/config/edit_open_windows.go @@ -0,0 +1,58 @@ +//go:build windows + +package config + +import ( + "encoding/hex" + "fmt" + "io/fs" + "os" + "unsafe" + + "golang.org/x/sys/windows" +) + +func openConfigNoFollow(path string) (*os.File, error) { + path16, err := windows.UTF16PtrFromString(path) + if err != nil { + return nil, fmt.Errorf("encode config path: %w", err) + } + handle, err := windows.CreateFile( + path16, + windows.GENERIC_READ|windows.READ_CONTROL|windows.WRITE_DAC, + windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, + nil, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OPEN_REPARSE_POINT, + 0, + ) + if err != nil { + return nil, fmt.Errorf("open config without following final reparse point: %w", err) + } + return os.NewFile(uintptr(handle), path), nil +} + +func pathEntryIdentity(_ string, info fs.FileInfo) (string, bool) { + return fmt.Sprintf("windows-path:%d:%d:%d", info.Size(), info.ModTime().UnixNano(), info.Mode()), true +} + +func openedFileIdentity(file *os.File, _ fs.FileInfo) (string, bool) { + type fileIDInfo struct { + VolumeSerialNumber uint64 + FileID [16]byte + } + var stable fileIDInfo + if err := windows.GetFileInformationByHandleEx( + windows.Handle(file.Fd()), + windows.FileIdInfo, + (*byte)(unsafe.Pointer(&stable)), + uint32(unsafe.Sizeof(stable)), + ); err == nil { + return fmt.Sprintf("windows:%d:%s", stable.VolumeSerialNumber, hex.EncodeToString(stable.FileID[:])), true + } + var info windows.ByHandleFileInformation + if err := windows.GetFileInformationByHandle(windows.Handle(file.Fd()), &info); err != nil { + return "", false + } + return fmt.Sprintf("windows:%d:%d:%d", info.VolumeSerialNumber, info.FileIndexHigh, info.FileIndexLow), true +} diff --git a/internal/config/edit_owner_other.go b/internal/config/edit_owner_other.go new file mode 100644 index 000000000..462b7f678 --- /dev/null +++ b/internal/config/edit_owner_other.go @@ -0,0 +1,13 @@ +//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris + +package config + +import "io/fs" + +func effectiveUserID() (uint64, bool) { + return 0, false +} + +func fileOwner(fs.FileInfo) (uint64, bool) { + return 0, false +} diff --git a/internal/config/edit_owner_unix.go b/internal/config/edit_owner_unix.go new file mode 100644 index 000000000..c9b244ba4 --- /dev/null +++ b/internal/config/edit_owner_unix.go @@ -0,0 +1,25 @@ +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris + +package config + +import ( + "io/fs" + "os" + "syscall" +) + +func effectiveUserID() (uint64, bool) { + uid := os.Geteuid() + if uid < 0 { + return 0, false + } + return uint64(uid), true +} + +func fileOwner(info fs.FileInfo) (uint64, bool) { + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return 0, false + } + return uint64(stat.Uid), true +} diff --git a/internal/config/edit_parent_other.go b/internal/config/edit_parent_other.go new file mode 100644 index 000000000..3338e7b92 --- /dev/null +++ b/internal/config/edit_parent_other.go @@ -0,0 +1,24 @@ +//go:build !windows + +package config + +import ( + "fmt" + "os" + "path/filepath" +) + +// ensureConfigParentDirectories resolves and validates every existing path +// component before creating the missing directory suffix. The config file is +// still snapshotted after this step, so a concurrent file creation is observed +// by the normal ETag check. +func ensureConfigParentDirectories(path string, _ ...string) error { + resolved, err := resolveOwnedSymlinks(path, fileOwner) + if err != nil { + return fmt.Errorf("resolve config path before creating directories: %w", err) + } + if err := os.MkdirAll(filepath.Dir(resolved), 0o700); err != nil { + return fmt.Errorf("create config directory: %w", err) + } + return nil +} diff --git a/internal/config/edit_parent_windows.go b/internal/config/edit_parent_windows.go new file mode 100644 index 000000000..3d687c47e --- /dev/null +++ b/internal/config/edit_parent_windows.go @@ -0,0 +1,82 @@ +//go:build windows + +package config + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + + "go.kenn.io/msgvault/internal/fileutil" +) + +// ensureConfigParentDirectories pins the nearest existing ancestor and then +// creates, validates, and pins each missing directory one component at a time. +// A concurrently inserted reparse point is rejected before the next component +// can be created through it. +func ensureConfigParentDirectories(path string, expectedAncestorIdentity ...string) error { + absolute, err := filepath.Abs(path) + if err != nil { + return fmt.Errorf("resolve Windows config path: %w", err) + } + dir := filepath.Dir(filepath.Clean(absolute)) + ancestor := dir + var missing []string + for { + info, statErr := os.Lstat(ancestor) + if statErr == nil { + if !info.IsDir() { + return errors.Join(ErrUnsafeConfigTarget, + fmt.Errorf("Windows config parent %s is not a directory", ancestor)) + } + break + } + if !errors.Is(statErr, fs.ErrNotExist) { + return statErr + } + parent := filepath.Dir(ancestor) + if parent == ancestor { + return statErr + } + missing = append(missing, filepath.Base(ancestor)) + ancestor = parent + } + + authority, err := pinWindowsConfigParent(filepath.Join(ancestor, ".config-parent-anchor")) + if err != nil { + return err + } + defer func() { _ = authority.Release() }() + if len(expectedAncestorIdentity) > 0 && expectedAncestorIdentity[0] != "" { + opened, openErr := os.Open(ancestor) + if openErr != nil { + return openErr + } + info, statErr := opened.Stat() + if statErr != nil { + _ = opened.Close() + return statErr + } + identity, ok := openedFileIdentity(opened, info) + _ = opened.Close() + if !ok || identity != expectedAncestorIdentity[0] { + return errors.Join(ErrConfigConflict, + errors.New("config ancestor changed before creating parent directories")) + } + } + current := ancestor + for index := len(missing) - 1; index >= 0; index-- { + current = filepath.Join(current, missing[index]) + if err := fileutil.SecureMkdirAll(current, 0o700); err != nil { + return fmt.Errorf("create Windows config directory %s: %w", current, err) + } + handle, err := openWindowsAuthorityDirectory(current) + if err != nil { + return err + } + authority.handles = append(authority.handles, handle) + } + return nil +} diff --git a/internal/config/edit_publish_other.go b/internal/config/edit_publish_other.go new file mode 100644 index 000000000..7583449dc --- /dev/null +++ b/internal/config/edit_publish_other.go @@ -0,0 +1,9 @@ +//go:build !darwin && !linux && !windows + +package config + +import "os" + +func publishNewConfig(string, *os.File, ConfigFile) (configPublication, error) { + return configPublication{}, ErrAtomicReplaceUnsupported +} diff --git a/internal/config/edit_publish_unix.go b/internal/config/edit_publish_unix.go new file mode 100644 index 000000000..be395f788 --- /dev/null +++ b/internal/config/edit_publish_unix.go @@ -0,0 +1,72 @@ +//go:build darwin || linux + +package config + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "golang.org/x/sys/unix" +) + +func publishNewConfig(candidatePath string, _ *os.File, before ConfigFile) (configPublication, error) { + dirfd, err := unix.Open(filepath.Dir(before.Path), unix.O_RDONLY|unix.O_CLOEXEC|unix.O_DIRECTORY|unix.O_NOFOLLOW, 0) + if err != nil { + return configPublication{}, fmt.Errorf("pin publication directory: %w", err) + } + dir := os.NewFile(uintptr(dirfd), filepath.Dir(before.Path)) + dirInfo, err := dir.Stat() + if err != nil { + _ = dir.Close() + return configPublication{}, fmt.Errorf("identify publication directory: %w", err) + } + parentIdentity, ok := openedFileIdentity(dir, dirInfo) + if !ok || parentIdentity != before.parentIdentity { + _ = dir.Close() + return configPublication{}, errors.Join(ErrConfigConflict, errors.New("config parent changed before creation")) + } + candidateName := filepath.Base(candidatePath) + targetName := filepath.Base(before.Path) + candidate, err := readPhysicalConfigSnapshotAt(dirfd, candidateName, candidatePath) + if err != nil { + _ = dir.Close() + return configPublication{}, fmt.Errorf("publish config relative to pinned directory: %w", err) + } + retained, err := retainConfigEntryAt(dirfd, candidateName, candidatePath, candidate.identity) + if err != nil { + _ = dir.Close() + return configPublication{}, fmt.Errorf("retain published config identity: %w", err) + } + if err := renameConfigNoReplaceAt(dirfd, candidateName, targetName); err != nil { + _ = retained.Close() + _ = dir.Close() + return configPublication{}, fmt.Errorf("publish config in pinned directory: %w", err) + } + publication := configPublication{ + candidateRemains: false, + published: candidate, + syncDirectory: func() error { + if err := unix.Fsync(dirfd); err != nil { + return fmt.Errorf("sync pinned config directory: %w", err) + } + return nil + }, + release: func() error { + if err := errors.Join(retained.Close(), dir.Close()); err != nil { + return fmt.Errorf("close pinned config directory: %w", err) + } + return nil + }, + } + publication.rollback = func(expected ConfigFile) error { + current, readErr := readPhysicalConfigSnapshotAt(dirfd, targetName, before.Path) + if readErr != nil || !sameConfigVersion(current, expected) { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("refusing creation rollback because the published config changed"), readErr) + } + return retireConfigArtifactAt(dirfd, targetName, before.Path, retained) + } + return publication, nil +} diff --git a/internal/config/edit_publish_windows.go b/internal/config/edit_publish_windows.go new file mode 100644 index 000000000..cbbf59775 --- /dev/null +++ b/internal/config/edit_publish_windows.go @@ -0,0 +1,98 @@ +//go:build windows + +package config + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + + "golang.org/x/sys/windows" +) + +// publishNewConfig uses fail-if-exists MoveFileExW with WRITE_THROUGH. Unlike +// a hard-link-and-delete sequence, this is the strongest namespace durability +// primitive documented by Win32 for publishing a new file. +func publishNewConfig(candidatePath string, retained *os.File, before ConfigFile) (configPublication, error) { + targetPath := before.Path + authority, err := pinWindowsConfigParent(targetPath) + if err != nil { + return configPublication{}, err + } + parent, err := os.Open(filepath.Dir(targetPath)) + if err != nil { + _ = authority.Release() + return configPublication{}, err + } + info, statErr := parent.Stat() + if statErr != nil { + _ = parent.Close() + _ = authority.Release() + return configPublication{}, statErr + } + identity, ok := openedFileIdentity(parent, info) + if !ok || identity != before.parentIdentity { + _ = parent.Close() + _ = authority.Release() + return configPublication{}, errors.Join(ErrConfigConflict, errors.New("config parent changed before creation")) + } + candidate, candidateErr := readPhysicalConfigSnapshot(candidatePath) + if candidateErr != nil { + _ = parent.Close() + _ = authority.Release() + return configPublication{}, candidateErr + } + retainedInfo, retainErr := retained.Stat() + if retainErr != nil { + _ = parent.Close() + _ = authority.Release() + return configPublication{}, retainErr + } + retainedIdentity, retainedOK := openedFileIdentity(retained, retainedInfo) + if !retainedOK || retainedIdentity != candidate.identity { + _ = parent.Close() + _ = authority.Release() + return configPublication{}, errors.Join(ErrConfigConflict, + errors.New("published Windows candidate identity changed before move")) + } + from, err := windows.UTF16PtrFromString(candidatePath) + if err != nil { + _ = parent.Close() + _ = authority.Release() + return configPublication{}, err + } + to, err := windows.UTF16PtrFromString(targetPath) + if err != nil { + _ = parent.Close() + _ = authority.Release() + return configPublication{}, err + } + if err := windows.MoveFileEx(from, to, windows.MOVEFILE_WRITE_THROUGH); err != nil { + if errors.Is(err, windows.ERROR_ALREADY_EXISTS) || errors.Is(err, windows.ERROR_FILE_EXISTS) { + _ = parent.Close() + _ = authority.Release() + return configPublication{}, fs.ErrExist + } + _ = parent.Close() + _ = authority.Release() + return configPublication{}, fmt.Errorf("publish new config with MoveFileExW: %w", err) + } + publication := configPublication{ + candidateRemains: false, + published: candidate, + release: func() error { + return errors.Join(parent.Close(), authority.Release()) + }, + } + publication.rollback = func(expected ConfigFile) error { + current, readErr := readPhysicalConfigSnapshot(targetPath) + if readErr != nil || !sameConfigVersion(current, expected) { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("refusing creation rollback because the published config changed"), readErr) + } + return retireWindowsConfigArtifact(targetPath, retained) + } + return publication, nil +} diff --git a/internal/config/edit_resolve_darwin.go b/internal/config/edit_resolve_darwin.go new file mode 100644 index 000000000..5136b3637 --- /dev/null +++ b/internal/config/edit_resolve_darwin.go @@ -0,0 +1,63 @@ +//go:build darwin && cgo + +package config + +/* +#include +static ssize_t msgvault_freadlink(int fd, char *buf, size_t size) { + return freadlink(fd, buf, size); +} +*/ +import "C" + +import ( + "fmt" + "io/fs" + "os" + "unsafe" + + "golang.org/x/sys/unix" +) + +func openPinnedPathEntry(parent *os.File, name, displayPath string) (*os.File, fs.FileInfo, error) { + fd, err := unix.Openat(int(parent.Fd()), name, unix.O_RDONLY|unix.O_SYMLINK|unix.O_CLOEXEC, 0) + if err != nil { + return nil, nil, fmt.Errorf("open pinned path entry: %w", err) + } + file := os.NewFile(uintptr(fd), displayPath) + info, err := file.Stat() + if err != nil { + _ = file.Close() + return nil, nil, err + } + return file, info, nil +} + +func readPinnedSymlink(file *os.File) (string, error) { + buffer := make([]byte, 256) + for { + n, err := C.msgvault_freadlink(C.int(file.Fd()), (*C.char)(unsafe.Pointer(&buffer[0])), C.size_t(len(buffer))) + if n < 0 { + return "", err + } + if int(n) < len(buffer) { + return string(buffer[:n]), nil + } + buffer = make([]byte, len(buffer)*2) + } +} + +func openPinnedReadableFinal( + _ *os.File, + _ string, + pinned *os.File, + pinnedInfo fs.FileInfo, + displayPath string, +) (*os.File, fs.FileInfo, error) { + fd, err := unix.Dup(int(pinned.Fd())) + if err != nil { + return nil, nil, fmt.Errorf("duplicate readable pinned config: %w", err) + } + readable := os.NewFile(uintptr(fd), displayPath) + return readable, pinnedInfo, nil +} diff --git a/internal/config/edit_resolve_darwin_nocgo.go b/internal/config/edit_resolve_darwin_nocgo.go new file mode 100644 index 000000000..2e510b590 --- /dev/null +++ b/internal/config/edit_resolve_darwin_nocgo.go @@ -0,0 +1,48 @@ +//go:build darwin && !cgo + +package config + +import ( + "errors" + "io/fs" + "os" + + "golang.org/x/sys/unix" +) + +func openPinnedPathEntry(parent *os.File, name, displayPath string) (*os.File, fs.FileInfo, error) { + fd, err := unix.Openat(int(parent.Fd()), name, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err == unix.ELOOP { + return nil, nil, errors.Join(ErrUnsafeConfigTarget, + errors.New("symlinked config paths require a cgo-enabled Darwin build")) + } + if err != nil { + return nil, nil, err + } + file := os.NewFile(uintptr(fd), displayPath) + info, err := file.Stat() + if err != nil { + _ = file.Close() + return nil, nil, err + } + return file, info, nil +} + +func readPinnedSymlink(*os.File) (string, error) { + return "", errors.Join(ErrUnsafeConfigTarget, + errors.New("symlinked config paths require a cgo-enabled Darwin build")) +} + +func openPinnedReadableFinal( + _ *os.File, + _ string, + pinned *os.File, + pinnedInfo fs.FileInfo, + displayPath string, +) (*os.File, fs.FileInfo, error) { + fd, err := unix.Dup(int(pinned.Fd())) + if err != nil { + return nil, nil, errors.Join(ErrUnsafeConfigTarget, err) + } + return os.NewFile(uintptr(fd), displayPath), pinnedInfo, nil +} diff --git a/internal/config/edit_resolve_linux.go b/internal/config/edit_resolve_linux.go new file mode 100644 index 000000000..17db87d8d --- /dev/null +++ b/internal/config/edit_resolve_linux.go @@ -0,0 +1,67 @@ +//go:build linux + +package config + +import ( + "errors" + "fmt" + "io/fs" + "os" + + "golang.org/x/sys/unix" +) + +func openPinnedPathEntry(parent *os.File, name, displayPath string) (*os.File, fs.FileInfo, error) { + fd, err := unix.Openat(int(parent.Fd()), name, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + return nil, nil, err + } + file := os.NewFile(uintptr(fd), displayPath) + info, err := file.Stat() + if err != nil { + _ = file.Close() + return nil, nil, err + } + return file, info, nil +} + +func readPinnedSymlink(file *os.File) (string, error) { + buffer := make([]byte, 256) + for { + n, err := unix.Readlinkat(int(file.Fd()), "", buffer) + if err != nil { + return "", err + } + if n < len(buffer) { + return string(buffer[:n]), nil + } + buffer = make([]byte, len(buffer)*2) + } +} + +func openPinnedReadableFinal( + parent *os.File, + name string, + pinned *os.File, + pinnedInfo fs.FileInfo, + displayPath string, +) (*os.File, fs.FileInfo, error) { + fd, err := unix.Openat(int(parent.Fd()), name, unix.O_RDONLY|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0) + if err != nil { + return nil, nil, fmt.Errorf("open readable pinned config: %w", err) + } + readable := os.NewFile(uintptr(fd), displayPath) + info, err := readable.Stat() + if err != nil { + _ = readable.Close() + return nil, nil, fmt.Errorf("stat readable pinned config: %w", err) + } + pinnedIdentity, pinnedOK := openedFileIdentity(pinned, pinnedInfo) + readableIdentity, readableOK := openedFileIdentity(readable, info) + if !pinnedOK || !readableOK || pinnedIdentity != readableIdentity { + _ = readable.Close() + return nil, nil, errors.Join(ErrConfigConflict, + errors.New("final config entry changed before readable open")) + } + return readable, info, nil +} diff --git a/internal/config/edit_resolve_other.go b/internal/config/edit_resolve_other.go new file mode 100644 index 000000000..82fa962f7 --- /dev/null +++ b/internal/config/edit_resolve_other.go @@ -0,0 +1,37 @@ +//go:build !darwin && !linux && !windows + +package config + +import ( + "fmt" + "io/fs" + "os" +) + +func resolveOwnedSymlinks(path string, owner func(fs.FileInfo) (uint64, bool)) (string, error) { + return resolveOwnedSymlinksWithReadlink(path, owner, os.Readlink) +} + +func resolveOwnedSymlinksPinned(path string, owner func(fs.FileInfo) (uint64, bool), _ func(*os.File)) (string, error) { + return resolveOwnedSymlinks(path, owner) +} + +func readConfigFileSnapshot(path string) (ConfigFile, error) { + resolved, mode, exists, err := resolveConfigTargetWithOwner(path, fileOwner) + if err != nil { + return ConfigFile{}, err + } + var content []byte + var identity string + if exists { + content, mode, identity, err = readVerifiedPhysicalConfig(resolved) + if err != nil { + return ConfigFile{}, fmt.Errorf("read config file: %w", err) + } + } + return ConfigFile{Path: resolved, Content: content, ETag: configETag(content), Mode: mode, Exists: exists, identity: identity}, nil +} + +func readConfigFileSnapshotForEdit(path string) (ConfigFile, error) { + return readConfigFileSnapshot(path) +} diff --git a/internal/config/edit_resolve_unix.go b/internal/config/edit_resolve_unix.go new file mode 100644 index 000000000..5a82530b1 --- /dev/null +++ b/internal/config/edit_resolve_unix.go @@ -0,0 +1,213 @@ +//go:build darwin || linux + +package config + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + + "golang.org/x/sys/unix" +) + +// resolveOwnedSymlinks walks from a root directory handle and reads symlink +// contents from handles opened without following them. Intermediate pathname +// entries can therefore be renamed without redirecting this traversal. +func resolveOwnedSymlinks(path string, owner func(fs.FileInfo) (uint64, bool)) (string, error) { + return resolveOwnedSymlinksPinnedFile(path, owner, nil, nil, nil) +} + +func resolveOwnedSymlinksPinned( + path string, + owner func(fs.FileInfo) (uint64, bool), + beforeSymlinkRead func(*os.File), +) (string, error) { + return resolveOwnedSymlinksPinnedFile(path, owner, beforeSymlinkRead, nil, nil) +} + +func resolveOwnedSymlinksPinnedFile( + path string, + owner func(fs.FileInfo) (uint64, bool), + beforeSymlinkRead func(*os.File), + finalEntry func(string, *os.File, fs.FileInfo) error, + missingParent func(string, *os.File) error, +) (string, error) { + absolute, err := filepath.Abs(path) + if err != nil { + return "", err + } + euid, supported := effectiveUserID() + if !supported { + return "", errors.New("symlink ownership verification is unavailable") + } + root, err := os.Open(string(filepath.Separator)) + if err != nil { + return "", err + } + stack := []*os.File{root} + components := make([]string, 0, 16) + defer func() { + for _, handle := range stack { + _ = handle.Close() + } + }() + queue := splitAbsolutePath(absolute) + for hops := 0; len(queue) > 0; { + part := queue[0] + queue = queue[1:] + switch part { + case "", ".": + continue + case "..": + if len(stack) > 1 { + _ = stack[len(stack)-1].Close() + stack = stack[:len(stack)-1] + components = components[:len(components)-1] + } + continue + } + displayPath := filepath.Join(append([]string{string(filepath.Separator)}, append(components, part)...)...) + entry, info, openErr := openPinnedPathEntry(stack[len(stack)-1], part, displayPath) + if errors.Is(openErr, fs.ErrNotExist) { + if missingParent != nil { + if err := missingParent(displayPath, stack[len(stack)-1]); err != nil { + return "", err + } + } + return filepath.Join(append([]string{displayPath}, queue...)...), nil + } + if openErr != nil { + return "", openErr + } + if info.Mode()&os.ModeSymlink != 0 { + hops++ + if hops > 255 { + _ = entry.Close() + return "", errors.New("too many config symlink hops") + } + uid, ok := owner(info) + if !ok || (uid != euid && uid != 0) { + _ = entry.Close() + return "", errors.New("symlink hop is not owned by the effective user or root") + } + if beforeSymlinkRead != nil { + beforeSymlinkRead(entry) + } + target, readErr := readPinnedSymlink(entry) + _ = entry.Close() + if readErr != nil { + return "", fmt.Errorf("read pinned config symlink %s: %w", displayPath, readErr) + } + if filepath.IsAbs(target) { + for len(stack) > 1 { + _ = stack[len(stack)-1].Close() + stack = stack[:len(stack)-1] + } + components = components[:0] + } + queue = append(splitPathComponents(target), queue...) + if len(queue) == 0 { + return "", errors.Join(ErrUnsafeConfigTarget, errors.New("config symlink target is not a regular file")) + } + continue + } + if len(queue) == 0 { + if finalEntry != nil { + readable, readableInfo, openErr := openPinnedReadableFinal(stack[len(stack)-1], part, entry, info, displayPath) + if openErr != nil { + _ = entry.Close() + return "", openErr + } + if err := finalEntry(displayPath, readable, readableInfo); err != nil { + _ = readable.Close() + _ = entry.Close() + return "", err + } + _ = readable.Close() + } + _ = entry.Close() + return displayPath, nil + } + if !info.IsDir() { + _ = entry.Close() + return "", fmt.Errorf("config path component %s is not a directory", displayPath) + } + stack = append(stack, entry) + components = append(components, part) + } + return filepath.Join(append([]string{string(filepath.Separator)}, components...)...), nil +} + +func readConfigFileSnapshot(path string) (ConfigFile, error) { + return readConfigFileSnapshotUnix(path, false) +} + +func readConfigFileSnapshotForEdit(path string) (ConfigFile, error) { + return readConfigFileSnapshotUnix(path, true) +} + +func readConfigFileSnapshotUnix(path string, retain bool) (ConfigFile, error) { + var snapshot ConfigFile + requestedInfo, requestedErr := os.Lstat(path) + requestedSymlink := requestedErr == nil && requestedInfo.Mode()&os.ModeSymlink != 0 + resolved, err := resolveOwnedSymlinksPinnedFile(path, fileOwner, nil, + func(displayPath string, file *os.File, info fs.FileInfo) error { + if !info.Mode().IsRegular() { + return fmt.Errorf("%w: config path is not a regular file", ErrUnsafeConfigTarget) + } + content, mode, identity, readErr := readVerifiedOpenedConfig(file) + if readErr != nil { + return fmt.Errorf("read config file: %w", readErr) + } + snapshot = ConfigFile{Path: displayPath, Content: content, ETag: configETag(content), Mode: mode, Exists: true, identity: identity} + if retain { + fd, duplicateErr := unix.Dup(int(file.Fd())) + if duplicateErr != nil { + return fmt.Errorf("retain config snapshot identity: %w", duplicateErr) + } + unix.CloseOnExec(fd) + snapshot.retained = os.NewFile(uintptr(fd), displayPath) + } + return nil + }, func(displayPath string, parent *os.File) error { + info, statErr := parent.Stat() + if statErr != nil { + return statErr + } + identity, ok := openedFileIdentity(parent, info) + if !ok { + return errors.Join(ErrUnsafeConfigTarget, errors.New("config parent identity is unavailable")) + } + snapshot.Path = displayPath + snapshot.parentIdentity = identity + return nil + }) + if err != nil { + if snapshot.retained != nil { + _ = snapshot.retained.Close() + } + return ConfigFile{}, fmt.Errorf("%w: resolve config path: %w", ErrUnsafeConfigTarget, err) + } + if snapshot.Exists { + return snapshot, nil + } + if requestedSymlink { + return ConfigFile{}, errors.Join(ErrUnsafeConfigTarget, errors.New("config symlink target does not exist")) + } + snapshot.Path = resolved + snapshot.ETag = configETag(nil) + snapshot.Mode = 0o600 + return snapshot, nil +} + +func splitAbsolutePath(path string) []string { + volume := filepath.VolumeName(path) + return splitPathComponents(strings.TrimPrefix(path, volume+string(filepath.Separator))) +} + +func splitPathComponents(path string) []string { + return strings.FieldsFunc(path, func(r rune) bool { return r == '/' || r == '\\' }) +} diff --git a/internal/config/edit_resolve_windows.go b/internal/config/edit_resolve_windows.go new file mode 100644 index 000000000..a2159417e --- /dev/null +++ b/internal/config/edit_resolve_windows.go @@ -0,0 +1,76 @@ +//go:build windows + +package config + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" +) + +func resolveOwnedSymlinks(path string, _ func(fs.FileInfo) (uint64, bool)) (string, error) { + if err := rejectWindowsReparseComponents(path); err != nil { + return "", err + } + absolute, err := filepath.Abs(path) + return filepath.Clean(absolute), err +} + +func resolveOwnedSymlinksPinned(path string, owner func(fs.FileInfo) (uint64, bool), _ func(*os.File)) (string, error) { + return resolveOwnedSymlinks(path, owner) +} + +func readConfigFileSnapshot(path string) (ConfigFile, error) { + authority, err := pinWindowsConfigParent(path) + if err != nil { + if !errors.Is(err, fs.ErrNotExist) { + return ConfigFile{}, err + } + var resolved, ancestorIdentity string + authority, resolved, ancestorIdentity, err = pinWindowsNearestExistingConfigAncestor(path) + if err != nil { + return ConfigFile{}, err + } + defer func() { _ = authority.Release() }() + return ConfigFile{ + Path: resolved, ETag: configETag(nil), Mode: 0o600, + parentIdentity: ancestorIdentity, + }, nil + } + defer func() { _ = authority.Release() }() + resolved, mode, exists, err := resolveConfigTargetWithOwner(path, fileOwner) + if err != nil { + return ConfigFile{}, err + } + var content []byte + var identity string + if exists { + content, mode, identity, err = readVerifiedPhysicalConfig(resolved) + if err != nil { + return ConfigFile{}, fmt.Errorf("read config file: %w", err) + } + } else { + parent, openErr := os.Open(filepath.Dir(resolved)) + if openErr != nil { + return ConfigFile{}, openErr + } + info, statErr := parent.Stat() + if statErr != nil { + _ = parent.Close() + return ConfigFile{}, statErr + } + identity, ok := openedFileIdentity(parent, info) + _ = parent.Close() + if !ok { + return ConfigFile{}, errors.Join(ErrUnsafeConfigTarget, errors.New("config parent identity is unavailable")) + } + return ConfigFile{Path: resolved, ETag: configETag(nil), Mode: mode, parentIdentity: identity}, nil + } + return ConfigFile{Path: resolved, Content: content, ETag: configETag(content), Mode: mode, Exists: exists, identity: identity}, nil +} + +func readConfigFileSnapshotForEdit(path string) (ConfigFile, error) { + return readConfigFileSnapshot(path) +} diff --git a/internal/config/edit_retire.go b/internal/config/edit_retire.go new file mode 100644 index 000000000..3a1fdee19 --- /dev/null +++ b/internal/config/edit_retire.go @@ -0,0 +1,22 @@ +package config + +import ( + "crypto/rand" + "encoding/hex" + "fmt" +) + +// configRetiredPrefix marks deliberately retained, content-preserving recovery +// artifacts. Retirement leaves at most one artifact per transaction object +// instead of deleting a pathname or truncating an inode that may have external +// hardlinks. A future operator-facing maintenance action may manage these; +// transaction code must not mutate them. +const configRetiredPrefix = ".config-retired-" + +func newConfigRetiredName() (string, error) { + var random [16]byte + if _, err := rand.Read(random[:]); err != nil { + return "", fmt.Errorf("generate config retirement name: %w", err) + } + return configRetiredPrefix + hex.EncodeToString(random[:]) + ".tombstone", nil +} diff --git a/internal/config/edit_retire_unix_test.go b/internal/config/edit_retire_unix_test.go new file mode 100644 index 000000000..51a3b07dd --- /dev/null +++ b/internal/config/edit_retire_unix_test.go @@ -0,0 +1,86 @@ +//go:build darwin || linux + +package config + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" +) + +func TestRetireConfigArtifactLeavesContentPreservingRecoveryFile(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + path := filepath.Join(dir, "candidate.toml") + content := []byte("transaction content") + require.NoError(os.WriteFile(path, content, 0o600)) + dirfd, err := unix.Open(dir, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_DIRECTORY, 0) + require.NoError(err) + t.Cleanup(func() { require.NoError(unix.Close(dirfd)) }) + expected, err := retainConfigEntryAt(dirfd, filepath.Base(path), path, mustConfigIdentity(t, path)) + require.NoError(err) + t.Cleanup(func() { require.NoError(expected.Close()) }) + + require.NoError(retireConfigArtifactAtWithHook(dirfd, filepath.Base(path), path, expected, nil)) + assert.NoFileExists(path) + tombstones, err := filepath.Glob(filepath.Join(dir, configRetiredPrefix+"*")) + require.NoError(err) + require.Len(tombstones, 1) + assert.Equal(content, mustReadFile(t, tombstones[0])) +} + +func TestRetireConfigArtifactPreservesExpectedHardlinkAndLateSubstitute(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + path := filepath.Join(dir, "candidate.toml") + secret := []byte("transaction secret") + substitute := []byte("later writer") + require.NoError(os.WriteFile(path, secret, 0o600)) + dirfd, err := unix.Open(dir, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_DIRECTORY, 0) + require.NoError(err) + t.Cleanup(func() { require.NoError(unix.Close(dirfd)) }) + expected, err := retainConfigEntryAt(dirfd, filepath.Base(path), path, mustConfigIdentity(t, path)) + require.NoError(err) + t.Cleanup(func() { require.NoError(expected.Close()) }) + externalHardlink := filepath.Join(dir, "external-hardlink.toml") + require.NoError(os.Link(path, externalHardlink)) + retainedPath := filepath.Join(dir, "retained-expected") + + err = retireConfigArtifactAtWithHook(dirfd, filepath.Base(path), path, expected, func(path string) error { + require.NoError(os.Rename(path, retainedPath)) + return os.WriteFile(path, substitute, 0o600) + }) + require.ErrorIs(err, ErrConfigChanged) + require.ErrorIs(err, ErrConfigConflict) + assert.Equal(substitute, mustReadFile(t, path)) + assert.Equal(secret, mustReadFile(t, retainedPath)) + assert.Equal(secret, mustReadFile(t, externalHardlink)) +} + +func TestRetireConfigArtifactReportsMovedExpectedEntry(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + path := filepath.Join(dir, "candidate.toml") + content := []byte("transaction content") + require.NoError(os.WriteFile(path, content, 0o600)) + dirfd, err := unix.Open(dir, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_DIRECTORY, 0) + require.NoError(err) + t.Cleanup(func() { require.NoError(unix.Close(dirfd)) }) + expected, err := retainConfigEntryAt(dirfd, filepath.Base(path), path, mustConfigIdentity(t, path)) + require.NoError(err) + t.Cleanup(func() { require.NoError(expected.Close()) }) + moved := filepath.Join(dir, "moved-candidate.toml") + require.NoError(os.Rename(path, moved)) + + err = retireConfigArtifactAt(dirfd, filepath.Base(path), path, expected) + require.ErrorIs(err, ErrConfigChanged) + require.ErrorIs(err, ErrConfigConflict) + assert.Equal(content, mustReadFile(t, moved)) +} diff --git a/internal/config/edit_retire_windows.go b/internal/config/edit_retire_windows.go new file mode 100644 index 000000000..d57614b2c --- /dev/null +++ b/internal/config/edit_retire_windows.go @@ -0,0 +1,158 @@ +//go:build windows + +package config + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + + "golang.org/x/sys/windows" +) + +func retainWindowsConfigArtifact(path, expectedIdentity string) (*os.File, error) { + encoded, err := windows.UTF16PtrFromString(path) + if err != nil { + return nil, fmt.Errorf("encode Windows config retirement path: %w", err) + } + handle, err := windows.CreateFile( + encoded, + windows.GENERIC_READ|windows.READ_CONTROL, + windows.FILE_SHARE_READ|windows.FILE_SHARE_DELETE, + nil, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OPEN_REPARSE_POINT, + 0, + ) + if err != nil { + return nil, fmt.Errorf("retain Windows config retirement entry: %w", err) + } + file := os.NewFile(uintptr(handle), path) + info, err := file.Stat() + if err != nil { + _ = file.Close() + return nil, fmt.Errorf("identify Windows config retirement entry: %w", err) + } + identity, ok := openedFileIdentity(file, info) + if !ok || identity != expectedIdentity { + _ = file.Close() + return nil, errors.Join(ErrConfigConflict, + errors.New("Windows config retirement identity changed before retention")) + } + return file, nil +} + +func retireWindowsConfigArtifact(path string, expected *os.File) error { + return retireWindowsConfigArtifactWithHook(path, expected, nil) +} + +func retireWindowsConfigArtifactWithHook( + path string, + expected *os.File, + beforeFinalVerification func(string) error, +) error { + authority, err := pinWindowsConfigParent(path) + if err != nil { + return err + } + defer func() { _ = authority.Release() }() + expectedInfo, err := expected.Stat() + if err != nil { + return fmt.Errorf("identify retained Windows config retirement entry: %w", err) + } + expectedIdentity, ok := openedFileIdentity(expected, expectedInfo) + if !ok { + return errors.Join(ErrConfigChanged, errors.New("retained Windows config retirement identity is unavailable")) + } + for range 128 { + name, nameErr := newConfigRetiredName() + if nameErr != nil { + return nameErr + } + quarantinePath := filepath.Join(filepath.Dir(path), name) + moveErr := moveFileWriteThrough(path, quarantinePath) + if errors.Is(moveErr, windows.ERROR_FILE_NOT_FOUND) || errors.Is(moveErr, windows.ERROR_PATH_NOT_FOUND) { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("Windows config retirement entry disappeared before quarantine")) + } + if errors.Is(moveErr, windows.ERROR_ALREADY_EXISTS) || errors.Is(moveErr, windows.ERROR_FILE_EXISTS) { + continue + } + if moveErr != nil { + return fmt.Errorf("quarantine Windows config retirement entry: %w", moveErr) + } + + quarantined, inspectErr := openWindowsIdentityEntry(quarantinePath) + var quarantineIdentity string + var quarantineOK bool + if inspectErr == nil { + quarantineInfo, statErr := quarantined.Stat() + if statErr == nil { + quarantineIdentity, quarantineOK = openedFileIdentity(quarantined, quarantineInfo) + } else { + inspectErr = statErr + } + _ = quarantined.Close() + } + if inspectErr == nil && quarantineOK && quarantineIdentity == expectedIdentity { + if beforeFinalVerification != nil { + if err := beforeFinalVerification(quarantinePath); err != nil { + return errors.Join(ErrConfigChanged, + fmt.Errorf("before Windows config retirement verification: %w", err)) + } + } + current, currentErr := openWindowsIdentityEntry(quarantinePath) + var currentIdentity string + var currentOK bool + if currentErr == nil { + currentInfo, statErr := current.Stat() + if statErr == nil { + currentIdentity, currentOK = openedFileIdentity(current, currentInfo) + } else { + currentErr = statErr + } + _ = current.Close() + } + if currentErr == nil && currentOK && currentIdentity == expectedIdentity { + return nil + } + inspectErr = currentErr + } + + restoreErr := moveFileWriteThrough(quarantinePath, path) + if restoreErr == nil { + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("refusing to retire a substituted Windows config entry"), inspectErr) + } + return errors.Join(ErrConfigChanged, ErrConfigConflict, + errors.New("refusing to retire a substituted Windows config entry"), inspectErr, + fmt.Errorf("restore substituted Windows config entry: %w", restoreErr), + fmt.Errorf("config recovery artifact preserved at %s", quarantinePath)) + } + return errors.Join(ErrConfigChanged, errors.New("could not allocate Windows config recovery artifact")) +} + +func openWindowsIdentityEntry(path string) (*os.File, error) { + encoded, err := windows.UTF16PtrFromString(path) + if err != nil { + return nil, err + } + handle, err := windows.CreateFile( + encoded, + windows.GENERIC_READ, + windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, + nil, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OPEN_REPARSE_POINT, + 0, + ) + if err != nil { + if errors.Is(err, windows.ERROR_FILE_NOT_FOUND) || errors.Is(err, windows.ERROR_PATH_NOT_FOUND) { + return nil, fs.ErrNotExist + } + return nil, fmt.Errorf("open Windows config retirement entry: %w", err) + } + return os.NewFile(uintptr(handle), path), nil +} diff --git a/internal/config/edit_retire_windows_test.go b/internal/config/edit_retire_windows_test.go new file mode 100644 index 000000000..cd9681ca9 --- /dev/null +++ b/internal/config/edit_retire_windows_test.go @@ -0,0 +1,76 @@ +//go:build windows + +package config + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWindowsRetireConfigArtifactLeavesContentPreservingRecoveryFile(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + path := filepath.Join(dir, "candidate.toml") + content := []byte("transaction content") + writeSecureWindowsTestConfig(t, path, content) + expected, err := retainWindowsConfigArtifact(path, mustConfigIdentity(t, path)) + require.NoError(err) + t.Cleanup(func() { require.NoError(expected.Close()) }) + + require.NoError(retireWindowsConfigArtifactWithHook(path, expected, nil)) + assert.NoFileExists(path) + tombstones, err := filepath.Glob(filepath.Join(dir, configRetiredPrefix+"*")) + require.NoError(err) + require.Len(tombstones, 1) + assert.Equal(content, mustReadFile(t, tombstones[0])) +} + +func TestWindowsRetireConfigArtifactPreservesExpectedHardlinkAndLateSubstitute(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + path := filepath.Join(dir, "candidate.toml") + secret := []byte("transaction secret") + substitute := []byte("later writer") + writeSecureWindowsTestConfig(t, path, secret) + expected, err := retainWindowsConfigArtifact(path, mustConfigIdentity(t, path)) + require.NoError(err) + t.Cleanup(func() { require.NoError(expected.Close()) }) + externalHardlink := filepath.Join(dir, "external-hardlink.toml") + require.NoError(os.Link(path, externalHardlink)) + retainedPath := filepath.Join(dir, "retained-expected") + + err = retireWindowsConfigArtifactWithHook(path, expected, func(path string) error { + require.NoError(os.Rename(path, retainedPath)) + return os.WriteFile(path, substitute, 0o600) + }) + require.ErrorIs(err, ErrConfigChanged) + require.ErrorIs(err, ErrConfigConflict) + assert.Equal(substitute, mustReadFile(t, path)) + assert.Equal(secret, mustReadFile(t, retainedPath)) + assert.Equal(secret, mustReadFile(t, externalHardlink)) +} + +func TestWindowsRetireConfigArtifactReportsMovedExpectedEntry(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + path := filepath.Join(dir, "candidate.toml") + content := []byte("transaction content") + writeSecureWindowsTestConfig(t, path, content) + expected, err := retainWindowsConfigArtifact(path, mustConfigIdentity(t, path)) + require.NoError(err) + t.Cleanup(func() { require.NoError(expected.Close()) }) + moved := filepath.Join(dir, "moved-candidate.toml") + require.NoError(os.Rename(path, moved)) + + err = retireWindowsConfigArtifact(path, expected) + require.ErrorIs(err, ErrConfigChanged) + require.ErrorIs(err, ErrConfigConflict) + assert.Equal(content, mustReadFile(t, moved)) +} diff --git a/internal/config/edit_security_unix.go b/internal/config/edit_security_unix.go new file mode 100644 index 000000000..1c11787f3 --- /dev/null +++ b/internal/config/edit_security_unix.go @@ -0,0 +1,18 @@ +//go:build !windows + +package config + +import ( + "io/fs" + "os" +) + +func secureConfigCandidate(file *os.File, _ string, mode fs.FileMode) error { + return file.Chmod(mode) +} + +func validateOpenedConfigSecurity(*os.File) error { return nil } + +func openConfigDirectoryForSync(path string) (syncDirectoryHandle, error) { + return os.Open(path) +} diff --git a/internal/config/edit_security_windows.go b/internal/config/edit_security_windows.go new file mode 100644 index 000000000..65f22012a --- /dev/null +++ b/internal/config/edit_security_windows.go @@ -0,0 +1,186 @@ +//go:build windows + +package config + +import ( + "errors" + "fmt" + "io/fs" + "os" + "unsafe" + + "golang.org/x/sys/windows" +) + +type windowsDirectoryDurabilityBoundary struct{} + +// Windows has no supported equivalent of fsync(2) for directory handles: +// FlushFileBuffers rejects directory handles even when opened with +// FILE_FLAG_BACKUP_SEMANTICS. Candidate bytes are flushed before publication, +// ReplaceFileW (existing files) and write-through MoveFileExW (new files) are +// the Windows namespace publication boundaries. This successful no-op keeps +// the transaction supported without claiming a directory flush occurred. +func openConfigDirectoryForSync(string) (syncDirectoryHandle, error) { + return windowsDirectoryDurabilityBoundary{}, nil +} + +func (windowsDirectoryDurabilityBoundary) Sync() error { return nil } +func (windowsDirectoryDurabilityBoundary) Close() error { return nil } + +func secureConfigCandidate(file *os.File, _ string, mode fs.FileMode) error { + if err := file.Chmod(mode); err != nil { + return err + } + token := windows.GetCurrentProcessToken() + user, err := token.GetTokenUser() + if err != nil { + return fmt.Errorf("get current user SID: %w", err) + } + acl, err := ownerOnlyConfigACL(user.User.Sid) + if err != nil { + return err + } + securityInfo := windows.DACL_SECURITY_INFORMATION | windows.PROTECTED_DACL_SECURITY_INFORMATION + if err := windows.SetSecurityInfo( + windows.Handle(file.Fd()), + windows.SE_FILE_OBJECT, + windows.SECURITY_INFORMATION(securityInfo), + nil, + nil, + acl, + nil, + ); err != nil { + return fmt.Errorf("set owner-only config DACL: %w", err) + } + return verifyConfigHandleOwnerOnly(windows.Handle(file.Fd()), user.User.Sid) +} + +func ownerOnlyConfigACL(user *windows.SID) (*windows.ACL, error) { + entries := []windows.EXPLICIT_ACCESS{{ + AccessPermissions: windows.GENERIC_ALL, + AccessMode: windows.SET_ACCESS, + Inheritance: windows.NO_INHERITANCE, + Trustee: windows.TRUSTEE{ + TrusteeForm: windows.TRUSTEE_IS_SID, + TrusteeType: windows.TRUSTEE_IS_USER, + TrusteeValue: windows.TrusteeValueFromSID(user), + }, + }} + acl, err := windows.ACLFromEntries(entries, nil) + if err != nil { + return nil, fmt.Errorf("build owner-only config DACL: %w", err) + } + return acl, nil +} + +func verifyConfigOwnerOnly(path string) error { + path16, err := windows.UTF16PtrFromString(path) + if err != nil { + return err + } + handle, err := windows.CreateFile( + path16, + windows.READ_CONTROL, + windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE, + nil, + windows.OPEN_EXISTING, + windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_OPEN_REPARSE_POINT, + 0, + ) + if err != nil { + return err + } + defer windows.CloseHandle(handle) + user, err := windows.GetCurrentProcessToken().GetTokenUser() + if err != nil { + return err + } + return verifyConfigHandleOwnerOnly(handle, user.User.Sid) +} + +func validateOpenedConfigSecurity(file *os.File) error { + user, err := windows.GetCurrentProcessToken().GetTokenUser() + if err != nil { + return err + } + return ensureConfigHandleOwnerOnly(windows.Handle(file.Fd()), user.User.Sid) +} + +// ensureConfigHandleOwnerOnly migrates legacy files created with an inherited +// DACL only after proving the already-opened file is owned by the current user. +// All hardening and verification remains bound to that handle, so a pathname +// substitution cannot redirect the metadata change after ownership proof. +func ensureConfigHandleOwnerOnly(handle windows.Handle, user *windows.SID) error { + if err := verifyConfigHandleOwnerOnly(handle, user); err == nil { + return nil + } + descriptor, err := windows.GetSecurityInfo(handle, windows.SE_FILE_OBJECT, windows.OWNER_SECURITY_INFORMATION) + if err != nil { + return fmt.Errorf("read legacy config owner: %w", err) + } + owner, _, err := descriptor.Owner() + if err != nil || owner == nil || !owner.Equals(user) { + return errors.New("legacy config owner is not the current user") + } + acl, err := ownerOnlyConfigACL(user) + if err != nil { + return err + } + securityInfo := windows.DACL_SECURITY_INFORMATION | windows.PROTECTED_DACL_SECURITY_INFORMATION + if err := windows.SetSecurityInfo(handle, windows.SE_FILE_OBJECT, + windows.SECURITY_INFORMATION(securityInfo), nil, nil, acl, nil); err != nil { + return fmt.Errorf("harden legacy config DACL: %w", err) + } + return verifyConfigHandleOwnerOnly(handle, user) +} + +func verifyConfigHandleOwnerOnly(handle windows.Handle, user *windows.SID) error { + descriptor, err := windows.GetSecurityInfo(handle, windows.SE_FILE_OBJECT, + windows.OWNER_SECURITY_INFORMATION|windows.DACL_SECURITY_INFORMATION) + if err != nil { + return fmt.Errorf("read config DACL: %w", err) + } + owner, _, err := descriptor.Owner() + if err != nil || owner == nil { + return fmt.Errorf("read config owner: %w", err) + } + if !owner.Equals(user) { + return errors.New("config owner is not the current user") + } + control, _, err := descriptor.Control() + if err != nil { + return fmt.Errorf("read config DACL control: %w", err) + } + if control&windows.SE_DACL_PROTECTED == 0 { + return errors.New("config DACL permits inherited access") + } + dacl, _, err := descriptor.DACL() + if err != nil || dacl == nil { + return fmt.Errorf("read config DACL entries: %w", err) + } + type aclHeader struct { + Revision byte + Sbz1 byte + Size uint16 + AceCount uint16 + Sbz2 uint16 + } + if (*aclHeader)(unsafe.Pointer(dacl)).AceCount != 1 { + return errors.New("config DACL must contain exactly one access entry") + } + var ace *windows.ACCESS_ALLOWED_ACE + if err := windows.GetAce(dacl, 0, &ace); err != nil { + return fmt.Errorf("read config owner ACE: %w", err) + } + if ace.Header.AceType != windows.ACCESS_ALLOWED_ACE_TYPE || ace.Mask != windows.GENERIC_ALL { + return errors.New("config DACL does not grant exactly GENERIC_ALL") + } + if ace.Header.AceFlags&windows.INHERITED_ACE != 0 { + return errors.New("config DACL contains inherited access") + } + aceSID := (*windows.SID)(unsafe.Pointer(&ace.SidStart)) + if !aceSID.Equals(user) { + return errors.New("config DACL grants a principal other than the current user") + } + return nil +} diff --git a/internal/config/edit_test.go b/internal/config/edit_test.go new file mode 100644 index 000000000..b6bafb21c --- /dev/null +++ b/internal/config/edit_test.go @@ -0,0 +1,1406 @@ +package config + +import ( + "errors" + "fmt" + "io/fs" + "os" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type sequenceDirectoryOpener struct { + handles []syncDirectoryHandle + index int +} + +func (opener *sequenceDirectoryOpener) open(string) (syncDirectoryHandle, error) { + if opener.index >= len(opener.handles) { + return &failingDirectoryHandle{}, nil + } + handle := opener.handles[opener.index] + opener.index++ + return handle, nil +} + +type failingDirectoryHandle struct { + syncErr error + closeErr error +} + +func overridePinnedReplacementSync(ops *configFileOps, open func(string) (syncDirectoryHandle, error)) { + nativeReplace := ops.replace + ops.replace = func(candidatePath, targetPath string, candidateBefore, targetBefore ConfigFile) (configReplacement, error) { + replacement, err := nativeReplace(candidatePath, targetPath, candidateBefore, targetBefore) + if err == nil { + replacement.syncDirectory = func() error { + return syncConfigDirectory(filepath.Dir(targetPath), open) + } + } + return replacement, err + } +} + +func overridePinnedPublicationSync(ops *configFileOps, open func(string) (syncDirectoryHandle, error)) { + nativePublish := ops.publishNew + ops.publishNew = func(candidatePath string, retained *os.File, before ConfigFile) (configPublication, error) { + publication, err := nativePublish(candidatePath, retained, before) + if err == nil { + publication.syncDirectory = func() error { + return syncConfigDirectory(filepath.Dir(before.Path), open) + } + } + return publication, err + } +} + +func (handle *failingDirectoryHandle) Sync() error { return handle.syncErr } +func (handle *failingDirectoryHandle) Close() error { return handle.closeErr } + +func TestEditConfigPreservesUntargetedTOML(t *testing.T) { + tests := []struct { + name string + before string + edits []Edit + want string + }{ + { + name: "comments ordering quoted strings arrays and unknown sections", + before: "# operator note\n[unknown]\nanswer = 42\n\n[server]\n" + + "trusted_proxies = [\"127.0.0.1\", \"192.0.2.0/24\"] # keep placement\n" + + "bind_addr = '127.0.0.1'\napi_port = 8080 # chosen port\n", + edits: []Edit{ + {Key: "server.api_port", Value: int64(9090)}, + {Key: "server.trusted_proxies", Value: []string{"127.0.0.1", "2001:db8::1"}}, + }, + want: "# operator note\n[unknown]\nanswer = 42\n\n[server]\n" + + "trusted_proxies = [\"127.0.0.1\", \"2001:db8::1\"] # keep placement\n" + + "bind_addr = '127.0.0.1'\napi_port = 9090 # chosen port\n", + }, + { + name: "multiline array", + before: "[server]\ntrusted_proxies = [\n" + + " \"127.0.0.1\", # loopback\n \"192.0.2.0/24\",\n] # access list\nallow_insecure = false\n", + edits: []Edit{{Key: "server.trusted_proxies", Value: []string{"127.0.0.1", "2001:db8::1"}}}, + want: "[server]\ntrusted_proxies = [\"127.0.0.1\", \"2001:db8::1\"] # access list\n" + + "allow_insecure = false\n", + }, + { + name: "CRLF", + before: "# windows\r\n[web]\r\ntheme = \"system\"\r\n[unknown]\r\nkeep = true\r\n", + edits: []Edit{{Key: "web.theme", Value: "dark"}}, + want: "# windows\r\n[web]\r\ntheme = \"dark\"\r\n[unknown]\r\nkeep = true\r\n", + }, + { + name: "no final newline", + before: "[analytics]\nengine = \"auto\"", + edits: []Edit{{Key: "analytics.engine", Value: "duckdb"}}, + want: "[analytics]\nengine = \"duckdb\"", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte(tt.before), 0o640)) + before, err := ReadConfigFile(path) + require.NoError(err) + + after, err := EditConfigFile(path, before.ETag, tt.edits) + require.NoError(err) + got, err := os.ReadFile(path) + require.NoError(err) + assert.Equal(tt.want, string(got)) + assert.NotEqual(before.ETag, after.ETag) + assert.Equal([]byte(tt.want), after.Content) + }) + } +} + +func TestEditConfigCreatesMissingParentDirectories(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "nested", "settings", "config.toml") + before, err := ReadConfigFile(path) + require.NoError(err) + require.False(before.Exists) + + after, err := EditConfigFile(path, before.ETag, []Edit{{Key: "web.theme", Value: "dark"}}) + require.NoError(err) + require.True(after.Exists) + assert.Contains(t, string(after.Content), `theme = "dark"`) +} + +func TestEditConfigDoesNotCreateDirectoriesBeforeConcurrencyCheck(t *testing.T) { + require := require.New(t) + root := t.TempDir() + parent := filepath.Join(root, "nested", "settings") + path := filepath.Join(parent, "config.toml") + + _, err := EditConfigFile(path, `"sha256-not-current"`, []Edit{{Key: "web.theme", Value: "dark"}}) + require.ErrorIs(err, ErrConfigConflict) + _, statErr := os.Stat(parent) + require.ErrorIs(statErr, fs.ErrNotExist) +} + +func TestEditConfigIgnoresStructureInsideMultilineStrings(t *testing.T) { + tests := []struct { + name string + before string + want string + }{ + { + name: "basic string", + before: "description = \"\"\"\n[web]\n" + + "theme = \\\"content, not config\\\" # still content\n" + + "escaped delimiter = \\\"\"\"\n\"\"\"\n\n" + + "[web]\ntheme = \"system\"\n", + want: "description = \"\"\"\n[web]\n" + + "theme = \\\"content, not config\\\" # still content\n" + + "escaped delimiter = \\\"\"\"\n\"\"\"\n\n" + + "[web]\ntheme = \"dark\"\n", + }, + { + name: "literal string", + before: "description = '''\n[web]\n" + + "theme = 'content, not config'\n'''\n\n" + + "[web]\ntheme = \"system\"\n", + want: "description = '''\n[web]\n" + + "theme = 'content, not config'\n'''\n\n" + + "[web]\ntheme = \"dark\"\n", + }, + { + name: "delimiter in comment", + before: "# ignored delimiter: \"\"\"\n[web]\ntheme = \"system\"\n", + want: "# ignored delimiter: \"\"\"\n[web]\ntheme = \"dark\"\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte(tt.before), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + + after, err := EditConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}) + require.NoError(err) + assert.Equal(tt.want, string(after.Content)) + }) + } +} + +func TestEditConfigReplacesCompleteMultilineStringAssignment(t *testing.T) { + tests := []struct { + name string + before string + want string + }{ + { + name: "basic", + before: "[web]\n" + + "theme = \"\"\"\nold theme\nwith \\\"quotes\\\"\n\"\"\" # operator note\n" + + "density = \"compact\"\n", + want: "[web]\n" + + "theme = \"dark\" # operator note\n" + + "density = \"compact\"\n", + }, + { + name: "literal", + before: "[web]\n" + + "theme = '''\nold theme\nwith 'quotes'\n''' # operator note\n" + + "density = \"compact\"\n", + want: "[web]\n" + + "theme = \"dark\" # operator note\n" + + "density = \"compact\"\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte(tt.before), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + + after, err := EditConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}) + require.NoError(err) + assert.Equal(tt.want, string(after.Content)) + }) + } +} + +func TestEditConfigSynthesizesOnlyRequestedTables(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + before, err := ReadConfigFile(path) + require.NoError(err) + assert.False(before.Exists) + + after, err := EditConfigFile(path, before.ETag, []Edit{ + {Key: "web.theme", Value: "dark"}, + {Key: "integrations.tasks.enabled", Value: true}, + }) + require.NoError(err) + assert.True(after.Exists) + assert.Equal("[web]\ntheme = \"dark\"\n\n[integrations.tasks]\nenabled = true\n", string(after.Content)) + info, err := os.Stat(path) + require.NoError(err) + assert.Equal(os.FileMode(0o600), info.Mode().Perm()) +} + +func TestEditConfigTracksArrayTableBoundaries(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + before := "[web]\ntheme = \"system\"\n\n[[accounts]]\nemail = \"user-a@example.com\"\n" + require.NoError(os.WriteFile(path, []byte(before), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + + after, err := EditConfigFile(path, snapshot.ETag, []Edit{{Key: "web.density", Value: "compact"}}) + require.NoError(err) + assert.Equal("[web]\ntheme = \"system\"\ndensity = \"compact\"\n\n[[accounts]]\nemail = \"user-a@example.com\"\n", string(after.Content)) + loaded, err := Load(path, "") + require.NoError(err) + assert.Equal("compact", loaded.Web.Density) + assert.Equal("user-a@example.com", loaded.Accounts[0].Email) +} + +func TestEditConfigRecognizesEquivalentTOMLPaths(t *testing.T) { + tests := []struct { + name string + before string + key string + value any + want string + }{ + { + name: "quoted table and key", + before: "[\"web\"]\n\"theme\" = \"system\"\n", + key: "web.theme", + value: "dark", + want: "[\"web\"]\n\"theme\" = \"dark\"\n", + }, + { + name: "dotted quoted table", + before: "[\"integrations\".tasks]\nenabled = false\n", + key: "integrations.tasks.enabled", + value: true, + want: "[\"integrations\".tasks]\nenabled = true\n", + }, + { + name: "parent table and dotted key", + before: "[integrations]\ntasks.enabled = false\n", + key: "integrations.tasks.enabled", + value: true, + want: "[integrations]\ntasks.enabled = true\n", + }, + { + name: "parent table missing dotted key", + before: "[integrations]\nother = true\n", + key: "integrations.tasks.enabled", + value: true, + want: "[integrations]\nother = true\ntasks.enabled = true\n", + }, + { + name: "root dotted sibling missing key", + before: "integrations.tasks.endpoint = \"https://tasks.example.com\"\n[web]\ntheme = \"system\"\n", + key: "integrations.tasks.enabled", + value: true, + want: "integrations.tasks.endpoint = \"https://tasks.example.com\"\nintegrations.tasks.enabled = true\n[web]\ntheme = \"system\"\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte(tt.before), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + + after, err := EditConfigFile(path, snapshot.ETag, []Edit{{Key: tt.key, Value: tt.value}}) + require.NoError(err) + assert.Equal(t, tt.want, string(after.Content)) + }) + } +} + +func TestEditConfigRejectsSemanticDuplicateSpellings(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.toml") + before := "integrations.tasks.enabled = false\n[integrations]\ntasks.enabled = true\n" + require.NoError(t, os.WriteFile(path, []byte(before), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(t, err) + + _, err = EditConfigFile(path, snapshot.ETag, []Edit{{Key: "integrations.tasks.enabled", Value: true}}) + assert.ErrorIs(t, err, ErrAmbiguousConfigTarget) +} + +func TestEditConfigMissingKeyPreservesNoFinalNewline(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"system\""), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + + after, err := EditConfigFile(path, snapshot.ETag, []Edit{{Key: "web.density", Value: "compact"}}) + require.NoError(err) + assert.Equal(t, "[web]\ntheme = \"system\"\ndensity = \"compact\"", string(after.Content)) +} + +func TestEditConfigRejectsConcurrentAndAmbiguousWrites(t *testing.T) { + t.Run("concurrent hand edit", func(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + before, err := ReadConfigFile(path) + require.NoError(err) + require.NoError(os.WriteFile(path, []byte("# hand edit\n[web]\ntheme = \"light\"\n"), 0o600)) + + _, err = EditConfigFile(path, before.ETag, []Edit{{Key: "web.theme", Value: "dark"}}) + require.ErrorIs(err, ErrConfigConflict) + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(t, "# hand edit\n[web]\ntheme = \"light\"\n", string(got)) + }) + + t.Run("duplicate target", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + before := "[web]\ntheme = \"light\"\ntheme = \"dark\"\n" + require.NoError(os.WriteFile(path, []byte(before), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + + _, err = EditConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "system"}}) + require.ErrorIs(err, ErrAmbiguousConfigTarget) + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(before, string(got)) + }) + + t.Run("duplicate target across dotted and table forms", func(t *testing.T) { + path := filepath.Join(t.TempDir(), "config.toml") + before := "web.theme = \"light\"\n[web]\ntheme = \"dark\"\n" + require.NoError(t, os.WriteFile(path, []byte(before), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(t, err) + + _, err = EditConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "system"}}) + assert.ErrorIs(t, err, ErrAmbiguousConfigTarget) + }) +} + +func TestEditConfigDetectsRaceAtAtomicReplacement(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + beforeText := "[web]\ntheme = \"system\"\n" + handEdit := "# hand edit\n[web]\ntheme = \"light\"\n" + require.NoError(os.WriteFile(path, []byte(beforeText), 0o600)) + before, err := ReadConfigFile(path) + require.NoError(err) + ops := defaultConfigFileOps() + ops.beforeExchange = func() error { + return os.WriteFile(path, []byte(handEdit), 0o600) + } + + _, err = editConfigFile(path, before.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigConflict) + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(t, handEdit, string(got)) +} + +func TestEditConfigDetectsSameContentInodeSubstitution(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + beforeText := "[web]\ntheme = \"system\"\n" + require.NoError(os.WriteFile(path, []byte(beforeText), 0o600)) + before, err := ReadConfigFile(path) + require.NoError(err) + ops := defaultConfigFileOps() + ops.beforeExchange = func() error { + if err := os.Remove(path); err != nil { + return err + } + return os.WriteFile(path, []byte(beforeText), 0o600) + } + + _, err = editConfigFile(path, before.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigConflict) + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(t, beforeText, string(got)) +} + +func TestEditConfigRetirementPreservesHardlinkedOriginal(t *testing.T) { + if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { + t.Skip("native config retirement") + } + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + path := filepath.Join(dir, "config.toml") + backup := filepath.Join(dir, "operator-backup.toml") + beforeText := []byte("[web]\ntheme = \"system\"\n") + require.NoError(os.WriteFile(path, beforeText, 0o600)) + require.NoError(os.Link(path, backup)) + before, err := ReadConfigFile(path) + require.NoError(err) + + _, err = EditConfigFile(path, before.ETag, []Edit{{Key: "web.theme", Value: "dark"}}) + require.NoError(err) + assert.Equal(beforeText, mustReadFile(t, backup)) + recoveries, globErr := filepath.Glob(filepath.Join(dir, configRetiredPrefix+"*")) + require.NoError(globErr) + require.Len(recoveries, 1) + assert.Equal(beforeText, mustReadFile(t, recoveries[0])) +} + +func TestEditConfigCandidateAbortPreservesExternalHardlink(t *testing.T) { + if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { + t.Skip("native config retirement") + } + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + path := filepath.Join(dir, "config.toml") + external := filepath.Join(dir, "candidate-backup.toml") + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + before, err := ReadConfigFile(path) + require.NoError(err) + ops := defaultConfigFileOps() + ops.beforeExchange = func() error { + candidates, globErr := filepath.Glob(filepath.Join(dir, ".config-edit-*.toml.tmp")) + require.NoError(globErr) + require.Len(candidates, 1) + require.NoError(os.Link(candidates[0], external)) + return errors.New("abort candidate publication") + } + + _, err = editConfigFile(path, before.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorContains(err, "abort candidate publication") + wantCandidate := []byte("[web]\ntheme = \"dark\"\n") + assert.Equal(wantCandidate, mustReadFile(t, external)) + recoveries, globErr := filepath.Glob(filepath.Join(dir, configRetiredPrefix+"*")) + require.NoError(globErr) + require.Len(recoveries, 1) + assert.Equal(wantCandidate, mustReadFile(t, recoveries[0])) +} + +func TestEditConfigClassifiesDeletionRaceAsConflict(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + before, err := ReadConfigFile(path) + require.NoError(err) + ops := defaultConfigFileOps() + ops.beforeExchange = func() error { return os.Remove(path) } + + _, err = editConfigFile(path, before.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigConflict) + _, statErr := os.Stat(path) + assert.ErrorIs(statErr, fs.ErrNotExist) +} + +func TestEditConfigMissingFileRejectsParentDirectorySwap(t *testing.T) { + if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { + t.Skip("handle-relative missing-file publication is supported on Darwin and Linux") + } + require := require.New(t) + assert := assert.New(t) + root := t.TempDir() + parent := filepath.Join(root, "active") + displacedParent := filepath.Join(root, "original") + require.NoError(os.Mkdir(parent, 0o700)) + path := filepath.Join(parent, "config.toml") + snapshot, err := ReadConfigFile(path) + require.NoError(err) + assert.False(snapshot.Exists) + unsafeContent := []byte("# unrelated same-owner file\n") + attackerCandidate := []byte("do not remove\n") + var substitutedCandidate string + ops := defaultConfigFileOps() + ops.beforeExchange = func() error { + candidates, globErr := filepath.Glob(filepath.Join(parent, ".config-edit-*.toml.tmp")) + if globErr != nil { + return globErr + } + if len(candidates) != 1 { + return fmt.Errorf("expected one candidate, got %d", len(candidates)) + } + candidateName := filepath.Base(candidates[0]) + if renameErr := os.Rename(parent, displacedParent); renameErr != nil { + return renameErr + } + if mkdirErr := os.Mkdir(parent, 0o700); mkdirErr != nil { + return mkdirErr + } + substitutedCandidate = filepath.Join(parent, candidateName) + if writeErr := os.WriteFile(substitutedCandidate, attackerCandidate, 0o600); writeErr != nil { + return writeErr + } + return os.WriteFile(path, unsafeContent, 0o600) + } + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigConflict) + assert.Equal(unsafeContent, mustReadFile(t, path)) + assert.Equal(attackerCandidate, mustReadFile(t, substitutedCandidate)) + _, originalErr := os.Stat(filepath.Join(displacedParent, "config.toml")) + require.ErrorIs(originalErr, fs.ErrNotExist) + originalCandidates, globErr := filepath.Glob(filepath.Join(displacedParent, ".config-edit-*.toml.tmp")) + require.NoError(globErr) + assert.Empty(originalCandidates) +} + +func TestEditConfigDetectsSymlinkRetargetRace(t *testing.T) { + if runtime.GOOS == windowsOS { + t.Skip("symlink semantics differ on Windows") + } + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + original := filepath.Join(dir, "original.toml") + replacement := filepath.Join(dir, "replacement.toml") + link := filepath.Join(dir, "config.toml") + originalText := "[web]\ntheme = \"system\"\n" + replacementText := "[web]\ntheme = \"light\"\n" + require.NoError(os.WriteFile(original, []byte(originalText), 0o600)) + require.NoError(os.WriteFile(replacement, []byte(replacementText), 0o600)) + require.NoError(os.Symlink(filepath.Base(original), link)) + before, err := ReadConfigFile(link) + require.NoError(err) + ops := defaultConfigFileOps() + ops.beforeExchange = func() error { + if err := os.Remove(link); err != nil { + return err + } + return os.Symlink(filepath.Base(replacement), link) + } + + _, err = editConfigFile(link, before.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigConflict) + originalAfter, readErr := os.ReadFile(original) + require.NoError(readErr) + assert.Equal(originalText, string(originalAfter)) + replacementAfter, readErr := os.ReadFile(replacement) + require.NoError(readErr) + assert.Equal(replacementText, string(replacementAfter)) +} + +func TestEditConfigReleasesAuthorityAfterSuccessfulConflictRollback(t *testing.T) { + if runtime.GOOS == windowsOS { + t.Skip("symlink semantics differ on Windows") + } + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + original := filepath.Join(dir, "original.toml") + replacementTarget := filepath.Join(dir, "replacement.toml") + link := filepath.Join(dir, "config.toml") + originalText := "[web]\ntheme = \"system\"\n" + replacementText := "[web]\ntheme = \"light\"\n" + require.NoError(os.WriteFile(original, []byte(originalText), 0o600)) + require.NoError(os.WriteFile(replacementTarget, []byte(replacementText), 0o600)) + require.NoError(os.Symlink(filepath.Base(original), link)) + snapshot, err := ReadConfigFile(link) + require.NoError(err) + releases := 0 + ops := defaultConfigFileOps() + nativeReplace := ops.replace + ops.replace = func(candidatePath, targetPath string, candidateBefore, targetBefore ConfigFile) (configReplacement, error) { + replacement, replaceErr := nativeReplace(candidatePath, targetPath, candidateBefore, targetBefore) + if replaceErr != nil { + return replacement, replaceErr + } + nativeRelease := replacement.release + replacement.release = func() error { + releases++ + return nativeRelease() + } + require.NoError(os.Remove(link)) + require.NoError(os.Symlink(filepath.Base(replacementTarget), link)) + return replacement, nil + } + + _, err = editConfigFile(link, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigConflict) + assert.Equal(1, releases) + assert.Equal(originalText, string(mustReadFile(t, original))) + assert.Equal(replacementText, string(mustReadFile(t, replacementTarget))) + candidates, globErr := filepath.Glob(filepath.Join(dir, ".config-edit-*.toml.tmp")) + require.NoError(globErr) + assert.Empty(candidates) +} + +func TestEditConfigFailsClosedWithoutConditionalReplacement(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + beforeText := "[web]\ntheme = \"system\"\n" + require.NoError(os.WriteFile(path, []byte(beforeText), 0o600)) + before, err := ReadConfigFile(path) + require.NoError(err) + ops := defaultConfigFileOps() + ops.replace = func(_, _ string, _, _ ConfigFile) (configReplacement, error) { + return configReplacement{}, ErrAtomicReplaceUnsupported + } + + _, err = editConfigFile(path, before.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrAtomicReplaceUnsupported) + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(t, beforeText, string(got)) +} + +func TestSyncConfigDirectoryPropagatesFailures(t *testing.T) { + tests := []struct { + name string + open func(string) (syncDirectoryHandle, error) + message string + }{ + { + name: "open", + open: func(string) (syncDirectoryHandle, error) { + return nil, errors.New("open failed") + }, + message: "open config directory", + }, + { + name: "sync", + open: func(string) (syncDirectoryHandle, error) { + return &failingDirectoryHandle{syncErr: errors.New("sync failed")}, nil + }, + message: "sync config directory", + }, + { + name: "close", + open: func(string) (syncDirectoryHandle, error) { + return &failingDirectoryHandle{closeErr: errors.New("close failed")}, nil + }, + message: "close config directory", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := syncConfigDirectory(t.TempDir(), tt.open) + require.Error(t, err) + assert.Contains(t, err.Error(), tt.message) + }) + } +} + +func TestEditConfigRollsBackExistingFileWhenFirstDirectorySyncFails(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + beforeText := "[web]\ntheme = \"system\"\n" + require.NoError(os.WriteFile(path, []byte(beforeText), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + opener := &sequenceDirectoryOpener{handles: []syncDirectoryHandle{ + &failingDirectoryHandle{syncErr: errors.New("durability unavailable")}, + &failingDirectoryHandle{}, + }} + ops := defaultConfigFileOps() + ops.openDirectory = opener.open + overridePinnedReplacementSync(&ops, opener.open) + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.Error(err) + require.NotErrorIs(err, ErrConfigChanged) + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(t, beforeText, string(got)) +} + +func TestEditConfigReportsChangedWhenRollbackDirectorySyncFails(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + beforeText := "[web]\ntheme = \"system\"\n" + require.NoError(os.WriteFile(path, []byte(beforeText), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + opener := &sequenceDirectoryOpener{handles: []syncDirectoryHandle{ + &failingDirectoryHandle{syncErr: errors.New("publication sync unavailable")}, + &failingDirectoryHandle{syncErr: errors.New("rollback sync unavailable")}, + }} + ops := defaultConfigFileOps() + ops.openDirectory = opener.open + overridePinnedReplacementSync(&ops, opener.open) + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigChanged) + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(t, beforeText, string(got)) +} + +func TestEditConfigPreservesDisplacedArtifactWhenConflictRollbackFails(t *testing.T) { + require := require.New(t) + dir := t.TempDir() + path := filepath.Join(dir, "config.toml") + beforeText := "[web]\ntheme = \"system\"\n" + require.NoError(os.WriteFile(path, []byte(beforeText), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + ops := defaultConfigFileOps() + nativeReplace := ops.replace + ops.replace = func(candidatePath, targetPath string, candidateBefore, targetBefore ConfigFile) (configReplacement, error) { + replacement, replaceErr := nativeReplace(candidatePath, targetPath, candidateBefore, targetBefore) + if replaceErr != nil { + return configReplacement{}, replaceErr + } + // Change the displaced artifact after publication so verification must + // enter rollback, then force rollback to retain every recovery file. + require.NoError(os.Remove(replacement.displacedPath)) + require.NoError(os.WriteFile(replacement.displacedPath, []byte(beforeText), 0o600)) + replacement.rollbackPublished = func(ConfigFile) error { return errors.New("injected rollback failure") } + return replacement, nil + } + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigChanged) + artifacts, globErr := filepath.Glob(filepath.Join(dir, ".config-edit-*.toml.tmp")) + require.NoError(globErr) + require.Len(artifacts, 1, "the displaced operator file must remain recoverable") + recovered, readErr := os.ReadFile(artifacts[0]) + require.NoError(readErr) + assert.Equal(t, beforeText, string(recovered)) + assert.Contains(t, err.Error(), artifacts[0]) +} + +func TestConditionalReplaceRollbackPreservesLaterWriter(t *testing.T) { + require := require.New(t) + dir := t.TempDir() + target := filepath.Join(dir, "config.toml") + beforeText := []byte("[web]\ntheme = \"system\"\n") + candidateText := []byte("[web]\ntheme = \"dark\"\n") + laterText := []byte("# later writer\n[web]\ntheme = \"light\"\n") + require.NoError(os.WriteFile(target, beforeText, 0o600)) + before, err := readConfigFileForEdit(target) + require.NoError(err) + t.Cleanup(func() { require.NoError(before.retained.Close()) }) + candidate := filepath.Join(filepath.Dir(before.Path), "candidate.toml") + require.NoError(os.WriteFile(candidate, candidateText, 0o600)) + + replace := func(candidatePath, targetPath string, candidateBefore, targetBefore ConfigFile) (configReplacement, error) { + replacement, replaceErr := beginConfigReplacement(candidatePath, targetPath, candidateBefore, targetBefore) + if replaceErr != nil { + return replacement, replaceErr + } + require.NoError(os.Remove(replacement.displacedPath)) + require.NoError(os.WriteFile(replacement.displacedPath, + []byte("# intervening writer\n[web]\ntheme = \"system\"\n"), 0o600)) + nativeRollback := replacement.rollbackPublished + replacement.rollbackPublished = func(expected ConfigFile) error { + // Install a later writer after conditionalReplace has verified the + // published candidate and immediately before native rollback. + require.NoError(os.Remove(target)) + require.NoError(os.WriteFile(target, laterText, 0o600)) + return nativeRollback(expected) + } + return replacement, nil + } + + replacement, err := conditionalReplace(target, candidate, before, replace, ReadConfigFile) + require.ErrorIs(err, ErrConfigChanged) + require.ErrorIs(err, ErrConfigConflict) + assert.Equal(t, laterText, mustReadFile(t, target)) + assert.FileExists(t, replacement.displacedPath) + assert.Contains(t, err.Error(), replacement.displacedPath) +} + +func mustReadFile(t *testing.T, path string) []byte { + t.Helper() + content, err := os.ReadFile(path) + require.NoError(t, err) + return content +} + +func mustConfigIdentity(t *testing.T, path string) string { + t.Helper() + snapshot, err := readPhysicalConfigSnapshot(path) + require.NoError(t, err) + return snapshot.identity +} + +func TestEditConfigReportsChangedWhenExistingCleanupFails(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + ops := defaultConfigFileOps() + nativeReplace := ops.replace + ops.replace = func(candidatePath, targetPath string, candidateBefore, targetBefore ConfigFile) (configReplacement, error) { + replacement, replaceErr := nativeReplace(candidatePath, targetPath, candidateBefore, targetBefore) + if replaceErr == nil { + replacement.cleanupDisplaced = func() error { return errors.New("cleanup unavailable") } + } + return replacement, replaceErr + } + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigChanged) + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(t, "[web]\ntheme = \"dark\"\n", string(got)) +} + +func TestEditConfigDeferredCandidateCleanupPreservesSubstitute(t *testing.T) { + if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { + t.Skip("conditional Unix cleanup") + } + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + substitute := []byte("candidate substitute\n") + var candidatePath string + ops := defaultConfigFileOps() + ops.beforeExchange = func() error { + candidates, globErr := filepath.Glob(filepath.Join(filepath.Dir(path), ".config-edit-*.toml.tmp")) + require.NoError(globErr) + require.Len(candidates, 1) + candidatePath = candidates[0] + require.NoError(os.Remove(candidatePath)) + require.NoError(os.WriteFile(candidatePath, substitute, 0o600)) + return errors.New("abort before publication") + } + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.Error(err) + assert.Equal(t, substitute, mustReadFile(t, candidatePath)) + assert.Equal(t, "[web]\ntheme = \"system\"\n", string(mustReadFile(t, path))) +} + +func TestEditConfigDisplacedCleanupPreservesSubstitute(t *testing.T) { + if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { + t.Skip("conditional Unix cleanup") + } + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + substitute := []byte("displaced substitute\n") + var displacedPath string + ops := defaultConfigFileOps() + ops.beforeExistingCleanup = func(replacement configReplacement) error { + displacedPath = replacement.displacedPath + require.NoError(os.Remove(displacedPath)) + require.NoError(os.WriteFile(displacedPath, substitute, 0o600)) + return nil + } + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigChanged) + require.ErrorIs(err, ErrConfigConflict) + assert.Equal(t, substitute, mustReadFile(t, displacedPath)) + assert.Equal(t, "[web]\ntheme = \"dark\"\n", string(mustReadFile(t, path))) +} + +func TestEditConfigMissingPublicationConsumesCandidate(t *testing.T) { + if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { + t.Skip("native no-replace publication") + } + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + path := filepath.Join(dir, "config.toml") + snapshot, err := ReadConfigFile(path) + require.NoError(err) + cleanupCalled := false + ops := defaultConfigFileOps() + ops.beforeMissingCleanup = func(configPublication) error { + cleanupCalled = true + return nil + } + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.NoError(err) + assert.False(cleanupCalled) + assert.Equal("[web]\ntheme = \"dark\"\n", string(mustReadFile(t, path))) + candidates, globErr := filepath.Glob(filepath.Join(dir, ".config-edit-*")) + require.NoError(globErr) + assert.Empty(candidates) +} + +func TestEditConfigCreationRollbackPreservesLaterWriter(t *testing.T) { + if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { + t.Skip("conditional Unix cleanup") + } + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + snapshot, err := ReadConfigFile(path) + require.NoError(err) + later := []byte("later creation writer\n") + ops := defaultConfigFileOps() + nativePublish := ops.publishNew + ops.publishNew = func(candidatePath string, retained *os.File, before ConfigFile) (configPublication, error) { + publication, publishErr := nativePublish(candidatePath, retained, before) + if publishErr != nil { + return publication, publishErr + } + nativeSync := publication.syncDirectory + calls := 0 + publication.syncDirectory = func() error { + calls++ + if calls == 1 { + require.NoError(nativeSync()) + require.NoError(os.Remove(path)) + require.NoError(os.WriteFile(path, later, 0o600)) + return errors.New("force creation rollback") + } + return nativeSync() + } + return publication, nil + } + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigChanged) + require.ErrorIs(err, ErrConfigConflict) + assert.Equal(t, later, mustReadFile(t, path)) +} + +func TestEditConfigMissingPublicationHasNoCleanupStep(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + snapshot, err := ReadConfigFile(path) + require.NoError(err) + ops := defaultConfigFileOps() + nativePublish := ops.publishNew + ops.publishNew = func(candidatePath string, retained *os.File, before ConfigFile) (configPublication, error) { + publication, publishErr := nativePublish(candidatePath, retained, before) + if publishErr == nil { + publication.cleanupCandidate = func() error { return errors.New("cleanup unavailable") } + } + return publication, publishErr + } + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.NoError(err) + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(t, "[web]\ntheme = \"dark\"\n", string(got)) +} + +func TestEditConfigRollsBackMissingFileWhenFirstDirectorySyncFails(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + snapshot, err := ReadConfigFile(path) + require.NoError(err) + opener := &sequenceDirectoryOpener{handles: []syncDirectoryHandle{ + &failingDirectoryHandle{syncErr: errors.New("durability unavailable")}, + &failingDirectoryHandle{}, + }} + ops := defaultConfigFileOps() + ops.openDirectory = opener.open + overridePinnedPublicationSync(&ops, opener.open) + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.Error(err) + require.NotErrorIs(err, ErrConfigChanged) + _, statErr := os.Stat(path) + assert.ErrorIs(t, statErr, fs.ErrNotExist) +} + +func TestEditConfigReportsChangedWhenMissingRollbackDirectorySyncFails(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + snapshot, err := ReadConfigFile(path) + require.NoError(err) + opener := &sequenceDirectoryOpener{handles: []syncDirectoryHandle{ + &failingDirectoryHandle{syncErr: errors.New("publication sync unavailable")}, + &failingDirectoryHandle{syncErr: errors.New("rollback sync unavailable")}, + }} + ops := defaultConfigFileOps() + ops.openDirectory = opener.open + overridePinnedPublicationSync(&ops, opener.open) + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigChanged) + _, statErr := os.Stat(path) + assert.ErrorIs(t, statErr, fs.ErrNotExist) +} + +func TestEditConfigReportsChangedWhenFinalDirectorySyncFails(t *testing.T) { + for _, existing := range []bool{false, true} { + name := "missing" + if existing { + name = "existing" + } + t.Run(name, func(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + if existing { + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + } + snapshot, err := ReadConfigFile(path) + require.NoError(err) + opener := &sequenceDirectoryOpener{handles: []syncDirectoryHandle{ + &failingDirectoryHandle{}, + &failingDirectoryHandle{syncErr: errors.New("final sync unavailable")}, + }} + ops := defaultConfigFileOps() + ops.openDirectory = opener.open + if existing { + overridePinnedReplacementSync(&ops, opener.open) + } else { + overridePinnedPublicationSync(&ops, opener.open) + } + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigChanged) + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(t, "[web]\ntheme = \"dark\"\n", string(got)) + }) + } +} + +func TestEditConfigReportsChangedWhenFinalSnapshotReadFails(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + ops := defaultConfigFileOps() + reads := 0 + ops.read = func(readPath string) (ConfigFile, error) { + reads++ + if reads >= 3 { + return ConfigFile{}, errors.New("injected final read failure") + } + return ReadConfigFile(readPath) + } + + _, err = editConfigFile(path, snapshot.ETag, []Edit{{Key: "web.theme", Value: "dark"}}, ops) + require.ErrorIs(err, ErrConfigChanged) + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(t, "[web]\ntheme = \"dark\"\n", string(got)) +} + +func TestResolveConfigTargetFailsClosedWhenOwnershipCannotBeVerified(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + target := filepath.Join(dir, "target.toml") + link := filepath.Join(dir, "config.toml") + require.NoError(os.WriteFile(target, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + require.NoError(os.Symlink(filepath.Base(target), link)) + + resolved, mode, exists, err := resolveConfigTargetWithOwner(link, func(fs.FileInfo) (uint64, bool) { + return 0, false + }) + require.ErrorIs(err, ErrUnsafeConfigTarget) + assert.Empty(resolved) + assert.Zero(mode) + assert.False(exists) +} + +func TestResolveConfigTargetVerifiesParentDirectorySymlinkOwnership(t *testing.T) { + if runtime.GOOS == windowsOS { + t.Skip("Unix ownership semantics") + } + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + realDir := filepath.Join(dir, "real") + require.NoError(os.Mkdir(realDir, 0o700)) + require.NoError(os.WriteFile(filepath.Join(realDir, "config.toml"), []byte("[web]\n"), 0o600)) + parentLink := filepath.Join(dir, "linked") + require.NoError(os.Symlink("real", parentLink)) + euid, supported := effectiveUserID() + require.True(supported) + + resolved, mode, exists, err := resolveConfigTargetWithOwner(filepath.Join(parentLink, "config.toml"), func(info fs.FileInfo) (uint64, bool) { + if info.Name() == "linked" { + return euid + 1, true + } + return euid, true + }) + require.ErrorIs(err, ErrUnsafeConfigTarget) + assert.Empty(resolved) + assert.Zero(mode) + assert.False(exists) +} + +func TestResolveConfigTargetAllowsVerifiedRootOwnedSystemHop(t *testing.T) { + if runtime.GOOS == windowsOS { + t.Skip("Unix ownership semantics") + } + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + realDir := filepath.Join(dir, "real") + require.NoError(os.Mkdir(realDir, 0o700)) + require.NoError(os.WriteFile(filepath.Join(realDir, "config.toml"), []byte("[web]\n"), 0o600)) + parentLink := filepath.Join(dir, "linked") + require.NoError(os.Symlink("real", parentLink)) + euid, supported := effectiveUserID() + require.True(supported) + checked := false + + _, _, exists, err := resolveConfigTargetWithOwner(filepath.Join(parentLink, "config.toml"), func(info fs.FileInfo) (uint64, bool) { + if info.Name() == "linked" { + checked = true + return 0, true + } + return euid, true + }) + require.NoError(err) + assert.True(checked) + assert.True(exists) +} + +func TestResolveConfigTargetRejectsIntermediateSymlinkSwapDuringInspection(t *testing.T) { + if runtime.GOOS == windowsOS || runtime.GOOS == "darwin" || runtime.GOOS == "linux" { + t.Skip("path-based fallback is not used on platforms with pinned resolvers") + } + require := require.New(t) + dir := t.TempDir() + firstDir := filepath.Join(dir, "first") + secondDir := filepath.Join(dir, "second") + require.NoError(os.Mkdir(firstDir, 0o700)) + require.NoError(os.Mkdir(secondDir, 0o700)) + first := filepath.Join(firstDir, "config.toml") + second := filepath.Join(secondDir, "config.toml") + require.NoError(os.WriteFile(first, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + require.NoError(os.WriteFile(second, []byte("[web]\ntheme = \"light\"\n"), 0o600)) + link := filepath.Join(dir, "active") + require.NoError(os.Symlink("first", link)) + euid, supported := effectiveUserID() + require.True(supported) + + _, err := resolveOwnedSymlinksWithReadlink(filepath.Join(link, "config.toml"), func(fs.FileInfo) (uint64, bool) { + return euid, true + }, func(path string) (string, error) { + if filepath.Base(path) == "active" { + require.NoError(os.Remove(link)) + require.NoError(os.Symlink("second", link)) + } + return os.Readlink(path) + }) + require.ErrorIs(err, ErrUnsafeConfigTarget) + assert.Equal(t, "[web]\ntheme = \"system\"\n", string(mustReadFile(t, first))) + assert.Equal(t, "[web]\ntheme = \"light\"\n", string(mustReadFile(t, second))) +} + +func TestFallbackResolverRejectsRemovedSymlinkAfterReadlink(t *testing.T) { + if runtime.GOOS == windowsOS { + t.Skip("Unix symlink semantics") + } + require := require.New(t) + dir := t.TempDir() + targetDir := filepath.Join(dir, "target") + require.NoError(os.Mkdir(targetDir, 0o700)) + require.NoError(os.WriteFile(filepath.Join(targetDir, "config.toml"), []byte("[web]\n"), 0o600)) + link := filepath.Join(dir, "active") + require.NoError(os.Symlink("target", link)) + euid, supported := effectiveUserID() + require.True(supported) + + _, err := resolveOwnedSymlinksWithReadlink(filepath.Join(link, "config.toml"), func(fs.FileInfo) (uint64, bool) { + return euid, true + }, func(path string) (string, error) { + target, err := os.Readlink(path) + if filepath.Base(path) == "active" { + require.NoError(os.Remove(link)) + } + return target, err + }) + require.ErrorIs(err, ErrUnsafeConfigTarget) +} + +func TestPinnedResolverCannotBeRedirectedByIntermediateSymlinkSwap(t *testing.T) { + if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { + t.Skip("pinned resolver is supported on Darwin and Linux") + } + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + firstDir := filepath.Join(dir, "first") + secondDir := filepath.Join(dir, "second") + require.NoError(os.Mkdir(firstDir, 0o700)) + require.NoError(os.Mkdir(secondDir, 0o700)) + first := filepath.Join(firstDir, "config.toml") + second := filepath.Join(secondDir, "config.toml") + require.NoError(os.WriteFile(first, []byte("[web]\ntheme = \"system\"\n"), 0o600)) + require.NoError(os.WriteFile(second, []byte("[web]\ntheme = \"light\"\n"), 0o600)) + link := filepath.Join(dir, "active") + require.NoError(os.Symlink("first", link)) + euid, supported := effectiveUserID() + require.True(supported) + swapped := false + + resolved, err := resolveOwnedSymlinksPinned(filepath.Join(link, "config.toml"), func(fs.FileInfo) (uint64, bool) { + return euid, true + }, func(file *os.File) { + if filepath.Base(file.Name()) != "active" || swapped { + return + } + swapped = true + require.NoError(os.Remove(link)) + require.NoError(os.Symlink("second", link)) + }) + if err == nil { + assert.Equal(first, resolved) + } + assert.True(swapped) + assert.Equal("[web]\ntheme = \"system\"\n", string(mustReadFile(t, first))) + assert.Equal("[web]\ntheme = \"light\"\n", string(mustReadFile(t, second))) +} + +func TestEditConfigValidatesCompleteCandidateBeforeReplacement(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + before := "[analytics]\nengine = \"auto\"\n[server]\nbind_addr = \"127.0.0.1\"\n" + require.NoError(os.WriteFile(path, []byte(before), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + + _, err = EditConfigFile(path, snapshot.ETag, []Edit{{Key: "analytics.engine", Value: "invalid"}}) + require.Error(err) + assert.Contains(err.Error(), "invalid [analytics] engine") + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(before, string(got)) +} + +func TestEditConfigValidatesEnabledVectorCandidate(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + before := "[vector]\nenabled = false\n" + require.NoError(os.WriteFile(path, []byte(before), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + + _, err = EditConfigFile(path, snapshot.ETag, []Edit{{Key: "vector.enabled", Value: true}}) + require.Error(err) + assert.Contains(err.Error(), "vector config") + got, readErr := os.ReadFile(path) + require.NoError(readErr) + assert.Equal(before, string(got)) +} + +func TestEditConfigPreservesModeAndExistingSymlink(t *testing.T) { + if runtime.GOOS == windowsOS { + t.Skip("symlink permission semantics differ on Windows") + } + assert := assert.New(t) + require := require.New(t) + dir := t.TempDir() + target := filepath.Join(dir, "real-config.toml") + link := filepath.Join(dir, "config.toml") + intermediate := filepath.Join(dir, "config-link.toml") + require.NoError(os.WriteFile(target, []byte("[web]\ndensity = \"compact\"\n"), 0o640)) + require.NoError(os.Chmod(target, 0o640)) + require.NoError(os.Symlink(filepath.Base(target), intermediate)) + require.NoError(os.Symlink(filepath.Base(intermediate), link)) + snapshot, err := ReadConfigFile(link) + require.NoError(err) + beforeInfo, err := os.Stat(target) + require.NoError(err) + beforeOwner, ok := fileOwner(beforeInfo) + require.True(ok) + + _, err = EditConfigFile(link, snapshot.ETag, []Edit{{Key: "web.density", Value: "comfortable"}}) + require.NoError(err) + linkInfo, err := os.Lstat(link) + require.NoError(err) + assert.NotZero(linkInfo.Mode() & os.ModeSymlink) + targetInfo, err := os.Stat(target) + require.NoError(err) + assert.Equal(os.FileMode(0o640), targetInfo.Mode().Perm()) + afterOwner, ok := fileOwner(targetInfo) + require.True(ok) + assert.Equal(beforeOwner, afterOwner) + got, err := os.ReadFile(target) + require.NoError(err) + assert.Equal("[web]\ndensity = \"comfortable\"\n", string(got)) +} + +func TestReadConfigFileRejectsUnsafeTargets(t *testing.T) { + if runtime.GOOS == windowsOS { + t.Skip("symlink semantics differ on Windows") + } + dir := t.TempDir() + + t.Run("dangling symlink", func(t *testing.T) { + link := filepath.Join(dir, "dangling.toml") + require.NoError(t, os.Symlink("missing.toml", link)) + _, err := ReadConfigFile(link) + require.Error(t, err) + assert.ErrorIs(t, err, ErrUnsafeConfigTarget) + }) + + t.Run("non regular target", func(t *testing.T) { + link := filepath.Join(dir, "directory.toml") + require.NoError(t, os.Symlink(".", link)) + _, err := ReadConfigFile(link) + require.ErrorIs(t, err, ErrUnsafeConfigTarget) + }) +} + +func TestLoadConfigFileParsesSnapshotBytes(t *testing.T) { + require := require.New(t) + path := filepath.Join(t.TempDir(), "config.toml") + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"light\"\n"), 0o600)) + snapshot, err := ReadConfigFile(path) + require.NoError(err) + require.NoError(os.WriteFile(path, []byte("[web]\ntheme = \"dark\"\n"), 0o600)) + + loaded, err := LoadConfigFile(snapshot, "") + require.NoError(err) + assert.Equal(t, "light", loaded.Web.Theme) +} + +func TestLoadConfigFileUsesLogicalSymlinkPathForRelativeDefaults(t *testing.T) { + if runtime.GOOS == windowsOS { + t.Skip("symlink creation requires optional Windows privileges") + } + assert := assert.New(t) + require := require.New(t) + root := t.TempDir() + logicalDir := filepath.Join(root, "logical") + physicalDir := filepath.Join(root, "physical") + require.NoError(os.Mkdir(logicalDir, 0o700)) + require.NoError(os.Mkdir(physicalDir, 0o700)) + target := filepath.Join(physicalDir, "config.toml") + require.NoError(os.WriteFile(target, []byte("[web]\ntheme = \"dark\"\n"), 0o600)) + link := filepath.Join(logicalDir, "config.toml") + require.NoError(os.Symlink(target, link)) + + snapshot, err := ReadConfigFile(link) + require.NoError(err) + assert.Equal(link, snapshot.LogicalPath) + resolvedTarget, err := filepath.EvalSymlinks(target) + require.NoError(err) + assert.Equal(resolvedTarget, snapshot.Path) + fromSnapshot, err := LoadConfigFile(snapshot, "") + require.NoError(err) + fromDaemon, err := Load(link, "") + require.NoError(err) + assert.Equal(fromDaemon.HomeDir, fromSnapshot.HomeDir) + assert.Equal(logicalDir, fromSnapshot.HomeDir) +} diff --git a/internal/explorecatalog/grouping.go b/internal/explorecatalog/grouping.go new file mode 100644 index 000000000..d6d721361 --- /dev/null +++ b/internal/explorecatalog/grouping.go @@ -0,0 +1,39 @@ +// Package explorecatalog owns dependency-neutral analytical request catalogs +// shared by persistence and transport layers. +package explorecatalog + +const ( + GroupSource = "source" + GroupParticipant = "participant" + GroupDomain = "domain" + GroupMessageType = "message_type" + GroupKind = "kind" + GroupYear = "year" + GroupMonth = "month" +) + +var groupingDimensions = [...]string{ + GroupSource, + GroupParticipant, + GroupDomain, + GroupMessageType, + GroupKind, + GroupYear, + GroupMonth, +} + +// GroupingDimensions returns every grouping value accepted by the analytical +// server contract. The returned slice cannot mutate the catalog. +func GroupingDimensions() []string { + return append([]string(nil), groupingDimensions[:]...) +} + +// IsGroupingDimension reports whether value is a canonical server grouping. +func IsGroupingDimension(value string) bool { + for _, dimension := range groupingDimensions { + if value == dimension { + return true + } + } + return false +} diff --git a/internal/identityops/identityops.go b/internal/identityops/identityops.go index e3652c0a4..bdda0f023 100644 --- a/internal/identityops/identityops.go +++ b/internal/identityops/identityops.go @@ -36,6 +36,12 @@ type AddResult struct { Identifier string `json:"identifier"` Signal string `json:"signal"` Outcome string `json:"outcome"` + // CacheState reports whether the synchronous identity-dataset cache + // refresh that follows the mutation succeeded ("ready") or failed + // ("stale"). Set by the API layer after Add returns; empty when the + // caller does not support cache refresh (e.g. non-daemon callers of + // this package). + CacheState string `json:"cache_state,omitempty" enum:"ready,stale"` } type RemoveRequest struct { @@ -48,6 +54,12 @@ type RemoveResult struct { Identifier string `json:"identifier"` Removed int64 `json:"removed"` NoIdentity bool `json:"no_identity,omitempty"` + // CacheState reports whether the synchronous identity-dataset cache + // refresh that follows the mutation succeeded ("ready") or failed + // ("stale"). Set by the API layer after Remove returns; empty when the + // caller does not support cache refresh (e.g. non-daemon callers of + // this package). + CacheState string `json:"cache_state,omitempty" enum:"ready,stale"` } func Add(st Store, req AddRequest) (AddResult, error) { diff --git a/internal/jsonexact/jsonexact.go b/internal/jsonexact/jsonexact.go new file mode 100644 index 000000000..db9b1aeb6 --- /dev/null +++ b/internal/jsonexact/jsonexact.go @@ -0,0 +1,91 @@ +// Package jsonexact validates that JSON object keys use the exact spellings +// declared by a Go type's json tags. +package jsonexact + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "reflect" + "strings" +) + +// Validate rejects unknown or case-variant object keys recursively before the +// standard library's case-insensitive struct decoding can accept them. Value +// type validation remains the responsibility of the caller's typed decoder. +func Validate(data []byte, target any) error { + targetType := reflect.TypeOf(target) + if targetType == nil { + return errors.New("JSON exact-key target must not be nil") + } + return validate(data, targetType, "$", map[reflect.Type]map[string]reflect.Type{}) +} + +func validate( + data []byte, targetType reflect.Type, path string, + fieldCache map[reflect.Type]map[string]reflect.Type, +) error { + for targetType.Kind() == reflect.Pointer { + targetType = targetType.Elem() + } + if bytes.Equal(bytes.TrimSpace(data), []byte("null")) { + return nil + } + + switch targetType.Kind() { + case reflect.Struct: + var object map[string]json.RawMessage + if err := json.Unmarshal(data, &object); err != nil { + return fmt.Errorf("decode object at %s: %w", path, err) + } + fields := jsonFields(targetType, fieldCache) + for name, value := range object { + fieldType, ok := fields[name] + if !ok { + return fmt.Errorf("unknown field %q at %s", name, path) + } + if err := validate(value, fieldType, path+"."+name, fieldCache); err != nil { + return err + } + } + case reflect.Slice, reflect.Array: + var items []json.RawMessage + if err := json.Unmarshal(data, &items); err != nil { + return fmt.Errorf("decode array at %s: %w", path, err) + } + for i, item := range items { + if err := validate(item, targetType.Elem(), fmt.Sprintf("%s[%d]", path, i), fieldCache); err != nil { + return err + } + } + default: + // Scalar values have no object keys. The caller's typed decoder + // validates their JSON representation and Go type. + } + return nil +} + +func jsonFields( + targetType reflect.Type, cache map[reflect.Type]map[string]reflect.Type, +) map[string]reflect.Type { + if fields, ok := cache[targetType]; ok { + return fields + } + fields := make(map[string]reflect.Type, targetType.NumField()) + for field := range targetType.Fields() { + if !field.IsExported() { + continue + } + name := strings.Split(field.Tag.Get("json"), ",")[0] + switch name { + case "-": + continue + case "": + name = field.Name + } + fields[name] = field.Type + } + cache[targetType] = fields + return fields +} diff --git a/internal/query/benchmark_test.go b/internal/query/benchmark_test.go index f447d6ba6..eb1b52fb7 100644 --- a/internal/query/benchmark_test.go +++ b/internal/query/benchmark_test.go @@ -3,30 +3,38 @@ package query import ( "context" "database/sql" + "encoding/json" "fmt" "os" "path/filepath" "testing" + "time" _ "github.com/duckdb/duckdb-go/v2" + "github.com/stretchr/testify/require" "go.kenn.io/msgvault/internal/search" ) -const benchMessageCount = 100_000 +const ( + benchChatMessageCount = 100_000 + benchAuxMessageCount = 4 + benchMessageCount = benchChatMessageCount + benchAuxMessageCount +) -// buildBenchData generates a 100K-message Parquet dataset directly via +// buildBenchData generates a mixed 100K+-message Parquet dataset directly via // DuckDB SQL (no Go-side row generation). This produces realistic -// cardinality: 500 participants across 50 domains, 10 labels, -// varied subjects/snippets, and 20% attachment rate. -func buildBenchData(b *testing.B) *DuckDBEngine { - b.Helper() +// cardinality: 100 chat conversations containing 100K raw fragments plus +// email, calendar, meeting-note, and file-bearing records, 500 participants +// across 50 domains, 10 labels, varied subjects/snippets, and 20% attachment +// rate. +func buildBenchData(tb testing.TB) *DuckDBEngine { + tb.Helper() + requirements := require.New(tb) - tmpDir := b.TempDir() + tmpDir := tb.TempDir() db, err := sql.Open("duckdb", "") - if err != nil { - b.Fatalf("open duckdb: %v", err) - } + requirements.NoError(err, "open duckdb") defer func() { _ = db.Close() }() // Seed tables as DuckDB views, then COPY to Parquet. @@ -34,7 +42,7 @@ func buildBenchData(b *testing.B) *DuckDBEngine { -- Sources CREATE TABLE bench_sources AS SELECT 1::BIGINT AS id, - 'bench@gmail.com' AS identifier, + 'bench@example.com' AS account_email, 'gmail' AS source_type; -- 500 participants across 50 domains @@ -65,6 +73,12 @@ func buildBenchData(b *testing.B) *DuckDBEngine { FROM generate_series(1, 500) t(i) JOIN bench_domains d ON d.idx = ((i - 1) %% 50) + 1; + CREATE TABLE bench_participant_identifiers AS + SELECT id AS participant_id, 'email' AS identifier_type, + email_address AS identifier_value, email_address AS display_value, + true AS is_primary + FROM bench_participants; + -- 10 labels CREATE TABLE bench_labels AS SELECT * FROM (VALUES @@ -89,13 +103,13 @@ func buildBenchData(b *testing.B) *DuckDBEngine { ('Customer feedback analysis report') ) AS t(template); - -- 100K messages spanning 2020-2025 + -- 100K chat fragments plus mixed durable archive items. CREATE TABLE bench_messages AS SELECT i::BIGINT AS id, 1::BIGINT AS source_id, 'msg' || i AS source_message_id, - (200 + (i %% 5000))::BIGINT AS conversation_id, + CASE WHEN i <= %d THEN (200 + ((i - 1) %% 100))::BIGINT ELSE (10000 + i)::BIGINT END AS conversation_id, CASE (i %% 10) WHEN 0 THEN 'Q' || (i/10000+1) || ' budget review meeting notes' WHEN 1 THEN 'Re: Project alpha deployment plan #' || i @@ -114,8 +128,14 @@ func buildBenchData(b *testing.B) *DuckDBEngine { (i %% 5 = 0)::BOOLEAN AS has_attachments, NULL::TIMESTAMP AS deleted_from_source_at, (i %% 5 = 0)::INTEGER AS attachment_count, - NULL::BIGINT AS sender_id, - 'email' AS message_type, + CASE WHEN i <= %d THEN ((i %% 500) + 1)::BIGINT ELSE NULL::BIGINT END AS sender_id, + CASE + WHEN i <= %d THEN 'imessage' + WHEN i %% 4 = 0 THEN 'email' + WHEN i %% 4 = 1 THEN 'calendar_event' + WHEN i %% 4 = 2 THEN 'meeting_note' + ELSE 'file_item' + END AS message_type, EXTRACT(YEAR FROM TIMESTAMP '2020-01-01' + INTERVAL (i * 30) MINUTE)::INTEGER AS year, EXTRACT(MONTH FROM TIMESTAMP '2020-01-01' + INTERVAL (i * 30) MINUTE)::INTEGER AS month FROM generate_series(1, %d) t(i); @@ -160,9 +180,11 @@ func buildBenchData(b *testing.B) *DuckDBEngine { -- attachments on ~20%% of messages CREATE TABLE bench_attachments AS SELECT + row_number() OVER (ORDER BY m.id)::BIGINT AS attachment_id, m.id AS message_id, (1000 + m.id * 10)::BIGINT AS size, - 'file' || m.id || '.pdf' AS filename + 'file' || m.id || '.pdf' AS filename, + 'application/pdf' AS mime_type FROM bench_messages m WHERE m.id %% 5 = 0; @@ -171,13 +193,18 @@ func buildBenchData(b *testing.B) *DuckDBEngine { SELECT DISTINCT conversation_id AS id, 'thread' || conversation_id AS source_conversation_id, - '' AS title + CASE WHEN conversation_id BETWEEN 200 AND 299 THEN 'Synthetic chat ' || conversation_id ELSE '' END AS title, + CASE WHEN conversation_id BETWEEN 200 AND 299 THEN 'direct_chat' ELSE 'email' END AS conversation_type FROM bench_messages; - `, benchMessageCount) - if _, err := db.Exec(seedSQL); err != nil { - b.Fatalf("seed bench data: %v", err) - } + CREATE TABLE bench_conversation_participants AS + SELECT id AS conversation_id, (((id - 200) %% 500) + 1)::BIGINT AS participant_id + FROM bench_conversations + WHERE id BETWEEN 200 AND 299; + `, benchChatMessageCount, benchChatMessageCount, benchChatMessageCount, benchMessageCount) + + _, err = db.Exec(seedSQL) + requirements.NoError(err, "seed bench data") // Write Parquet files in the layout the engine expects. type tableSpec struct { @@ -187,39 +214,45 @@ func buildBenchData(b *testing.B) *DuckDBEngine { } tables := []tableSpec{ - {"SELECT id, identifier, source_type FROM bench_sources", + {"SELECT id, account_email, source_type FROM bench_sources", "sources", "sources.parquet"}, {"SELECT id, email_address, domain, display_name, phone_number FROM bench_participants", "participants", "participants.parquet"}, + {"SELECT participant_id, identifier_type, identifier_value, display_value, is_primary FROM bench_participant_identifiers", + "participant_identifiers", "participant_identifiers.parquet"}, {"SELECT message_id, participant_id, recipient_type, display_name FROM bench_recipients", "message_recipients", "message_recipients.parquet"}, {"SELECT id, name FROM bench_labels", "labels", "labels.parquet"}, {"SELECT message_id, label_id FROM bench_message_labels", "message_labels", "message_labels.parquet"}, - {"SELECT message_id, size, filename FROM bench_attachments", + {"SELECT attachment_id, message_id, size, filename, mime_type FROM bench_attachments", "attachments", "attachments.parquet"}, - {"SELECT id, source_conversation_id, title FROM bench_conversations", + {"SELECT id, source_conversation_id, title, conversation_type FROM bench_conversations", "conversations", "conversations.parquet"}, + {"SELECT conversation_id, participant_id FROM bench_conversation_participants", + "conversation_participants", "conversation_participants.parquet"}, + // No confirmed identities or linked participants in the benchmark + // fixture; write empty, schema-bearing files so RequiredParquetDirs + // stays complete. + {"SELECT 0::BIGINT AS source_id, 0::BIGINT AS participant_id WHERE false", + datasetOwnerParticipants, datasetOwnerParticipants + ".parquet"}, + {"SELECT 0::BIGINT AS participant_id, 0::BIGINT AS canonical_id WHERE false", + datasetParticipantClusters, datasetParticipantClusters + ".parquet"}, } for _, t := range tables { dir := filepath.Join(tmpDir, t.subdir) - if err := os.MkdirAll(dir, 0755); err != nil { - b.Fatalf("mkdir %s: %v", dir, err) - } + requirements.NoError(os.MkdirAll(dir, 0o755), "mkdir %s", dir) path := escapePath(filepath.Join(dir, t.file)) q := fmt.Sprintf("COPY (%s) TO '%s' (FORMAT PARQUET)", t.query, path) - if _, err := db.Exec(q); err != nil { - b.Fatalf("write parquet %s: %v", t.file, err) - } + _, err = db.Exec(q) + requirements.NoError(err, "write parquet %s", t.file) } // Messages are hive-partitioned by year. msgDir := filepath.Join(tmpDir, "messages") - if err := os.MkdirAll(msgDir, 0755); err != nil { - b.Fatalf("mkdir messages: %v", err) - } + requirements.NoError(os.MkdirAll(msgDir, 0o755), "mkdir messages") msgPath := escapePath(msgDir) msgCopy := fmt.Sprintf(` COPY ( @@ -230,15 +263,23 @@ func buildBenchData(b *testing.B) *DuckDBEngine { FROM bench_messages ) TO '%s' (FORMAT PARQUET, PARTITION_BY (year), OVERWRITE_OR_IGNORE) `, msgPath) - if _, err := db.Exec(msgCopy); err != nil { - b.Fatalf("write messages parquet: %v", err) - } + _, err = db.Exec(msgCopy) + requirements.NoError(err, "write messages parquet") + fingerprint, err := CacheDatasetFingerprint(tmpDir) + requirements.NoError(err, "fingerprint benchmark cache") + state, err := json.Marshal(CacheSyncState{ + LastMessageID: benchMessageCount, + LastSyncAt: time.Date(2026, 1, 3, 12, 0, 0, 0, time.UTC), + PublishedAt: time.Date(2026, 1, 3, 12, 1, 0, 0, time.UTC), + SchemaVersion: CacheSchemaVersion, + DatasetFingerprint: fingerprint, + }) + requirements.NoError(err, "marshal benchmark cache state") + requirements.NoError(os.WriteFile(CacheStatePath(tmpDir), state, 0o600), "write benchmark cache state") engine, err := NewDuckDBEngine(tmpDir, "", nil) - if err != nil { - b.Fatalf("NewDuckDBEngine: %v", err) - } - b.Cleanup(func() { _ = engine.Close() }) + requirements.NoError(err, "NewDuckDBEngine") + tb.Cleanup(func() { requirements.NoError(engine.Close()) }) return engine } @@ -252,9 +293,7 @@ func BenchmarkSearchFast(b *testing.B) { for b.Loop() { _, err := engine.SearchFast(ctx, q, MessageFilter{}, 50, 0) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -264,9 +303,7 @@ func BenchmarkSearchFast(b *testing.B) { for b.Loop() { _, err := engine.SearchFast(ctx, q, MessageFilter{}, 50, 0) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -276,9 +313,7 @@ func BenchmarkSearchFast(b *testing.B) { for b.Loop() { _, err := engine.SearchFast(ctx, q, MessageFilter{}, 50, 0) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -288,9 +323,7 @@ func BenchmarkSearchFast(b *testing.B) { for b.Loop() { _, err := engine.SearchFast(ctx, q, MessageFilter{}, 50, 0) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -303,9 +336,7 @@ func BenchmarkSearchFast(b *testing.B) { for b.Loop() { _, err := engine.SearchFast(ctx, q, filter, 50, 0) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) } @@ -321,9 +352,7 @@ func BenchmarkSearchFastWithStats(b *testing.B) { _, err := engine.SearchFastWithStats(ctx, q, "budget", MessageFilter{}, ViewSenders, 50, 0) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -334,9 +363,7 @@ func BenchmarkSearchFastWithStats(b *testing.B) { _, err := engine.SearchFastWithStats(ctx, q, "quarterly report financials", MessageFilter{}, ViewSenders, 50, 0) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) } @@ -350,9 +377,7 @@ func BenchmarkAggregate(b *testing.B) { b.ResetTimer() for b.Loop() { _, err := engine.Aggregate(ctx, ViewSenders, opts) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -360,9 +385,7 @@ func BenchmarkAggregate(b *testing.B) { b.ResetTimer() for b.Loop() { _, err := engine.Aggregate(ctx, ViewDomains, opts) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -370,9 +393,7 @@ func BenchmarkAggregate(b *testing.B) { b.ResetTimer() for b.Loop() { _, err := engine.Aggregate(ctx, ViewLabels, opts) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -380,9 +401,7 @@ func BenchmarkAggregate(b *testing.B) { b.ResetTimer() for b.Loop() { _, err := engine.Aggregate(ctx, ViewTime, opts) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -394,9 +413,7 @@ func BenchmarkAggregate(b *testing.B) { b.ResetTimer() for b.Loop() { _, err := engine.Aggregate(ctx, ViewSenders, opts) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) } @@ -409,9 +426,7 @@ func BenchmarkGetTotalStats(b *testing.B) { b.ResetTimer() for b.Loop() { _, err := engine.GetTotalStats(ctx, StatsOptions{}) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -420,9 +435,7 @@ func BenchmarkGetTotalStats(b *testing.B) { b.ResetTimer() for b.Loop() { _, err := engine.GetTotalStats(ctx, opts) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) } @@ -435,9 +448,7 @@ func BenchmarkListMessages(b *testing.B) { b.ResetTimer() for b.Loop() { _, err := engine.ListMessages(ctx, MessageFilter{}) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -446,9 +457,7 @@ func BenchmarkListMessages(b *testing.B) { b.ResetTimer() for b.Loop() { _, err := engine.ListMessages(ctx, filter) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -457,9 +466,7 @@ func BenchmarkListMessages(b *testing.B) { b.ResetTimer() for b.Loop() { _, err := engine.ListMessages(ctx, filter) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -468,9 +475,7 @@ func BenchmarkListMessages(b *testing.B) { b.ResetTimer() for b.Loop() { _, err := engine.ListMessages(ctx, filter) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) } @@ -486,9 +491,7 @@ func BenchmarkSubAggregate(b *testing.B) { for b.Loop() { _, err := engine.SubAggregate(ctx, filter, ViewLabels, opts) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) @@ -498,9 +501,7 @@ func BenchmarkSubAggregate(b *testing.B) { for b.Loop() { _, err := engine.SubAggregate(ctx, filter, ViewSenders, opts) - if err != nil { - b.Fatal(err) - } + require.NoError(b, err) } }) } diff --git a/internal/query/cache_lock.go b/internal/query/cache_lock.go index cee8704cf..58034a762 100644 --- a/internal/query/cache_lock.go +++ b/internal/query/cache_lock.go @@ -58,7 +58,7 @@ func AcquireReadyCacheReadLock(ctx context.Context, analyticsDir string) (func() } if readiness != CacheReady { release() - return nil, fmt.Errorf("%w: cache is %s", ErrCacheUnavailable, readiness) + return nil, &CacheUnavailableError{Readiness: readiness} } return release, nil } diff --git a/internal/query/cache_state.go b/internal/query/cache_state.go index a5dfc4046..d231a702b 100644 --- a/internal/query/cache_state.go +++ b/internal/query/cache_state.go @@ -1,15 +1,25 @@ package query import ( + "crypto/sha256" "encoding/json" "errors" "fmt" "os" "path/filepath" + "sort" "strings" "time" ) +// CacheSchemaVersion is the sole schema compatibility version shared by the +// cache publisher and analytical readers. Bumped to 14 because ffe9904a +// widened owner_participants/is_from_me matching from email-only to +// identifier-type-aware (phone, chat handle, etc.): caches built under v13 +// before that change baked the narrower email-only derivation, and without +// this bump they would never be flagged stale by schema-version comparison. +const CacheSchemaVersion = 14 + // CacheSyncState is the commit marker written after a complete analytics // cache publication. SQLite remains authoritative; these watermarks only // describe the Parquet snapshot that readers may use. @@ -22,6 +32,18 @@ type CacheSyncState struct { LastCacheUpdateCount int64 `json:"last_cache_update_count,omitempty"` LastFailedSyncRunCount int64 `json:"last_failed_sync_run_count,omitempty"` LastFailedSyncRunIDSum int64 `json:"last_failed_sync_run_id_sum,omitempty"` + IdentityRevision int64 `json:"identity_revision,omitempty"` + // AccountIdentityRevision tracks identity mutations that invalidate + // baked message data — confirming or removing a "me" address, and + // participant merges (which repoint messages.sender_id) — separately + // from IdentityRevision (which also covers plain participant + // link/unlink). These changes invalidate the message-baked is_from_me + // flag, which the lightweight identity-only refresh does not re-derive, + // so this field must only advance on a full rebuild — see + // cacheops.RefreshIdentityDatasets. + AccountIdentityRevision int64 `json:"account_identity_revision,omitempty"` + PublishedAt time.Time `json:"published_at"` + DatasetFingerprint string `json:"dataset_fingerprint"` } type CacheReadiness string @@ -30,6 +52,8 @@ const ( CacheAbsent CacheReadiness = "absent" CacheReady CacheReadiness = "ready" CacheInterrupted CacheReadiness = "interrupted" + CacheStaleSchema CacheReadiness = "stale_schema" + CacheDrifted CacheReadiness = "drifted" ) var ( @@ -37,6 +61,37 @@ var ( errInvalidCacheState = errors.New("invalid analytics cache state") ) +// CacheUnavailableError preserves the named readiness reason while remaining +// compatible with errors.Is(err, ErrCacheUnavailable). +type CacheUnavailableError struct { + Readiness CacheReadiness +} + +func (e *CacheUnavailableError) Error() string { + return fmt.Sprintf("%s: cache is %s", ErrCacheUnavailable, e.Readiness) +} + +func (e *CacheUnavailableError) Unwrap() error { return ErrCacheUnavailable } + +// Revision identifies one committed cache publication. It intentionally uses +// only commit-marker fields, never ambient filesystem state. +func (s CacheSyncState) Revision() string { + payload := fmt.Sprintf("v=%d|message=%d|watermark=%s|run=%d|add=%d|update=%d|fail_count=%d|fail_sum=%d|identity=%d|account_identity=%d|published=%s", + s.SchemaVersion, + s.LastMessageID, + s.LastSyncAt.UTC().Format(time.RFC3339Nano), + s.LastCompletedSyncRunID, + s.LastCacheAdditionCount, + s.LastCacheUpdateCount, + s.LastFailedSyncRunCount, + s.LastFailedSyncRunIDSum, + s.IdentityRevision, + s.AccountIdentityRevision, + s.PublishedAt.UTC().Format(time.RFC3339Nano), + ) + return fmt.Sprintf("cache-%x", sha256.Sum256([]byte(payload))) +} + func CacheStatePath(analyticsDir string) string { return filepath.Join(analyticsDir, "_last_sync.json") } @@ -81,10 +136,26 @@ func InspectCacheReadiness(analyticsDir string) (CacheReadiness, error) { state, err := ReadCacheSyncState(analyticsDir) switch { case err == nil: - if complete && !state.LastSyncAt.IsZero() { - return CacheReady, nil + if state.LastSyncAt.IsZero() { + return CacheInterrupted, nil } - return CacheInterrupted, nil + if state.SchemaVersion != CacheSchemaVersion { + return CacheStaleSchema, nil + } + if !complete { + return CacheInterrupted, nil + } + if state.PublishedAt.IsZero() || state.DatasetFingerprint == "" { + return CacheInterrupted, nil + } + fingerprint, fingerprintErr := CacheDatasetFingerprint(analyticsDir) + if fingerprintErr != nil { + return "", fingerprintErr + } + if fingerprint != state.DatasetFingerprint { + return CacheDrifted, nil + } + return CacheReady, nil case errors.Is(err, os.ErrNotExist): if !anyParquet { return CacheAbsent, nil @@ -97,6 +168,39 @@ func InspectCacheReadiness(analyticsDir string) (CacheReadiness, error) { } } +// CacheDatasetFingerprint records the committed dataset file set. It covers +// names, sizes, and modification times for every Parquet file without reading +// archive content into memory. +func CacheDatasetFingerprint(analyticsDir string) (string, error) { + var records []string + for _, dataset := range RequiredParquetDirs { + root := filepath.Join(analyticsDir, dataset) + err := filepath.WalkDir(root, func(path string, entry os.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if entry.IsDir() || !strings.EqualFold(filepath.Ext(entry.Name()), ".parquet") { + return nil + } + info, err := entry.Info() + if err != nil { + return fmt.Errorf("read analytics dataset file info: %w", err) + } + rel, err := filepath.Rel(analyticsDir, path) + if err != nil { + return err + } + records = append(records, fmt.Sprintf("%s|%d|%d", filepath.ToSlash(rel), info.Size(), info.ModTime().UnixNano())) + return nil + }) + if err != nil { + return "", fmt.Errorf("fingerprint analytics dataset %s: %w", dataset, err) + } + } + sort.Strings(records) + return fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(strings.Join(records, "\n")))), nil +} + func datasetHasParquet(analyticsDir, dataset string) (bool, error) { datasetDir := filepath.Join(analyticsDir, dataset) entries, err := os.ReadDir(datasetDir) diff --git a/internal/query/cache_state_test.go b/internal/query/cache_state_test.go index 75ef6c464..91d0ab63f 100644 --- a/internal/query/cache_state_test.go +++ b/internal/query/cache_state_test.go @@ -15,12 +15,17 @@ import ( func TestAcquireReadyCacheReadLockRejectsAbsentCache(t *testing.T) { _, err := AcquireReadyCacheReadLock(context.Background(), t.TempDir()) require.ErrorIs(t, err, ErrCacheUnavailable) + var unavailable *CacheUnavailableError + require.ErrorAs(t, err, &unavailable) + assert.Equal(t, CacheAbsent, unavailable.Readiness) } func TestInspectCacheReadiness(t *testing.T) { validState := CacheSyncState{ LastMessageID: 1, LastSyncAt: time.Date(2026, 7, 15, 12, 0, 0, 0, time.UTC), + PublishedAt: time.Date(2026, 7, 15, 12, 1, 0, 0, time.UTC), + SchemaVersion: CacheSchemaVersion, } tests := []struct { @@ -109,6 +114,90 @@ func TestInspectCacheReadiness(t *testing.T) { } } +func TestInspectCacheReadinessNamesStaleSchemaAndDrift(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + dir := completeReadinessCache(t) + state, err := ReadCacheSyncState(dir) + require.NoError(err) + + state.SchemaVersion = CacheSchemaVersion - 1 + writeReadinessState(t, dir, state) + readiness, err := InspectCacheReadiness(dir) + require.NoError(err) + assert.Equal(CacheStaleSchema, readiness) + + state.SchemaVersion = CacheSchemaVersion + writeReadinessState(t, dir, state) + require.NoError(os.WriteFile(filepath.Join(dir, datasetSources, "drift.parquet"), []byte("drift"), 0o600)) + readiness, err = InspectCacheReadiness(dir) + require.NoError(err) + assert.Equal(CacheDrifted, readiness) +} + +func TestCacheSchemaVersionRequiresParticipantIdentifierPublication(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + assertions.Equal(14, CacheSchemaVersion) + + dir := completeReadinessCache(t) + state, err := ReadCacheSyncState(dir) + requirements.NoError(err) + state.SchemaVersion = 11 + writeReadinessState(t, dir, state) + + readiness, err := InspectCacheReadiness(dir) + requirements.NoError(err) + assertions.Equal(CacheStaleSchema, readiness) +} + +func TestInspectCacheReadinessPrefersStaleSchemaWhenNewDatasetIsMissing(t *testing.T) { + require := require.New(t) + dir := completeReadinessCache(t) + state, err := ReadCacheSyncState(dir) + require.NoError(err) + state.SchemaVersion = CacheSchemaVersion - 1 + require.NoError(os.RemoveAll(filepath.Join(dir, datasetParticipantIdentifiers))) + writeReadinessState(t, dir, state) + + readiness, err := InspectCacheReadiness(dir) + require.NoError(err) + assert.Equal(t, CacheStaleSchema, readiness) +} + +func TestCacheRevisionUsesOnlyCommittedStateWatermarks(t *testing.T) { + assert := assert.New(t) + state := CacheSyncState{ + SchemaVersion: CacheSchemaVersion, + LastMessageID: 41, + LastCompletedSyncRunID: 5, + LastCacheAdditionCount: 37, + LastCacheUpdateCount: 3, + LastFailedSyncRunCount: 2, + LastFailedSyncRunIDSum: 19, + IdentityRevision: 7, + PublishedAt: time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC), + } + revision := state.Revision() + require.NotEmpty(t, revision) + + changed := state + changed.LastCacheUpdateCount++ + assert.NotEqual(revision, changed.Revision()) + changed = state + changed.LastFailedSyncRunIDSum++ + assert.NotEqual(revision, changed.Revision()) + changed = state + changed.IdentityRevision++ + assert.NotEqual(revision, changed.Revision()) + changed = state + changed.PublishedAt = changed.PublishedAt.Add(time.Second) + assert.NotEqual(revision, changed.Revision()) + changed = state + changed.DatasetFingerprint = "filesystem-only" + assert.Equal(revision, changed.Revision(), "revision inputs are committed state watermarks, not ambient filesystem state") +} + func TestInspectCacheReadinessReturnsFilesystemErrors(t *testing.T) { dir := t.TempDir() analyticsPath := filepath.Join(dir, "analytics") @@ -128,6 +217,12 @@ func completeReadinessCache(t *testing.T) string { func writeReadinessState(t *testing.T, dir string, state CacheSyncState) { t.Helper() + if state.DatasetFingerprint == "" { + fingerprint, err := CacheDatasetFingerprint(dir) + if err == nil { + state.DatasetFingerprint = fingerprint + } + } data, err := json.Marshal(state) require.NoError(t, err) require.NoError(t, os.WriteFile(CacheStatePath(dir), data, 0o600)) diff --git a/internal/query/dialect.go b/internal/query/dialect.go index 50682b8d9..7ef116361 100644 --- a/internal/query/dialect.go +++ b/internal/query/dialect.go @@ -152,7 +152,7 @@ func (SQLiteQueryDialect) TimeTruncExpression(column string, granularity string) switch granularity { case "year": return fmt.Sprintf("strftime('%%Y', %s)", column) - case "month": + case timeGranularityMonth: return fmt.Sprintf("strftime('%%Y-%%m', %s)", column) case "day": return fmt.Sprintf("strftime('%%Y-%%m-%%d', %s)", column) @@ -253,7 +253,7 @@ func (PostgreSQLQueryDialect) TimeTruncExpression(column string, granularity str switch granularity { case "year": return fmt.Sprintf("to_char(%s AT TIME ZONE 'UTC', 'YYYY')", column) - case "month": + case timeGranularityMonth: return fmt.Sprintf("to_char(%s AT TIME ZONE 'UTC', 'YYYY-MM')", column) case "day": return fmt.Sprintf("to_char(%s AT TIME ZONE 'UTC', 'YYYY-MM-DD')", column) diff --git a/internal/query/duckdb.go b/internal/query/duckdb.go index f7afe7263..49f03ab9f 100644 --- a/internal/query/duckdb.go +++ b/internal/query/duckdb.go @@ -188,7 +188,7 @@ func NewDuckDBEngine(analyticsDir string, sqlitePath string, sqliteDB *sql.DB, o {datasetParticipants, "phone_number"}, {datasetMessages, "attachment_count"}, {datasetMessages, "sender_id"}, - {datasetMessages, "message_type"}, + {datasetMessages, messageTypeDimension}, {datasetConversations, "title"}, {datasetConversations, "conversation_type"}, {"sources", "source_type"}, @@ -468,7 +468,7 @@ func (e *DuckDBEngine) parquetCTEs() string { } else { msgExtra = append(msgExtra, "NULL::BIGINT AS sender_id") } - if e.hasCol(datasetMessages, "message_type") { + if e.hasCol(datasetMessages, messageTypeDimension) { msgReplace = append(msgReplace, "COALESCE(CAST(message_type AS VARCHAR), '') AS message_type") } else { msgExtra = append(msgExtra, "'' AS message_type") @@ -478,6 +478,11 @@ func (e *DuckDBEngine) parquetCTEs() string { } else { msgExtra = append(msgExtra, "NULL::TIMESTAMP AS deleted_at") } + if e.hasCol(datasetMessages, "is_from_me") { + msgReplace = append(msgReplace, "COALESCE(TRY_CAST(is_from_me AS BOOLEAN), false) AS is_from_me") + } else { + msgExtra = append(msgExtra, "false AS is_from_me") + } msgCTE := fmt.Sprintf("SELECT * REPLACE (\n\t\t\t\t%s\n\t\t\t)", strings.Join(msgReplace, ",\n\t\t\t\t")) if len(msgExtra) > 0 { msgCTE += ", " + strings.Join(msgExtra, ", ") @@ -2364,11 +2369,15 @@ var RequiredParquetDirs = []string{ datasetMessages, "sources", datasetParticipants, + datasetParticipantIdentifiers, "message_recipients", "labels", "message_labels", "attachments", datasetConversations, + datasetConversationParticipants, + datasetOwnerParticipants, + datasetParticipantClusters, } // SearchFast searches message metadata in Parquet files (no body text). diff --git a/internal/query/duckdb_cache_drift_test.go b/internal/query/duckdb_cache_drift_test.go index 5908a4e46..c4c6d1e50 100644 --- a/internal/query/duckdb_cache_drift_test.go +++ b/internal/query/duckdb_cache_drift_test.go @@ -22,7 +22,8 @@ import ( // // Binder Error: Column "message_type" in REPLACE list not found in FROM clause // -// The engine must detect the cache change and re-probe instead of crashing. +// The engine must detect out-of-band drift and refuse the cache instead of +// reading an uncommitted file set or falling back to SQLite. func TestDuckDBEngine_CacheRebuiltUnderneath(t *testing.T) { require := require.New(t) assert := assert.New(t) @@ -34,15 +35,15 @@ func TestDuckDBEngine_CacheRebuiltUnderneath(t *testing.T) { pb := newParquetBuilder(t). addTable("messages", "messages/year=2024", "data.parquet", newMessagesCols, ` - (1::BIGINT, 1::BIGINT, 'm1', 100::BIGINT, 'Hello SOFRA', 'snip', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2024, 1) + (1::BIGINT, 1::BIGINT, 'm1', 100::BIGINT, 'Hello SOFRA', 'snip', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 2024, 1) `). addTable("sources", "sources", "sources.parquet", sourcesCols, `(1::BIGINT, 'test@gmail.com', 'gmail')`). addTable("participants", "participants", "participants.parquet", participantsCols, `(1::BIGINT, 'alice@test.com', 'test.com', 'Alice', '')`). addTable("message_recipients", "message_recipients", "message_recipients.parquet", messageRecipientsCols, `(1::BIGINT, 1::BIGINT, 'from', 'Alice')`). addEmptyTable("labels", "labels", "labels.parquet", labelsCols, `(1::BIGINT, 'x')`). addEmptyTable("message_labels", "message_labels", "message_labels.parquet", messageLabelsCols, `(1::BIGINT, 1::BIGINT)`). - addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 100::BIGINT, 'x')`). - addTable("conversations", "conversations", "conversations.parquet", conversationsCols, `(100::BIGINT, 'thread100', '')`) + addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 1::BIGINT, 100::BIGINT, 'x', '')`). + addTable("conversations", "conversations", "conversations.parquet", conversationsCols, `(100::BIGINT, 'thread100', '', 'email')`) analyticsDir, cleanup := pb.build() t.Cleanup(cleanup) @@ -67,18 +68,11 @@ func TestDuckDBEngine_CacheRebuiltUnderneath(t *testing.T) { (1::BIGINT, 1::BIGINT, 'm1', 100::BIGINT, 'Hello SOFRA', 'snip', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, NULL::TIMESTAMP, 2024, 1) `) - // Must re-probe and succeed rather than fail with the REPLACE binder error. - res, err = engine.SearchFast(ctx, search.Parse("SOFRA"), MessageFilter{}, 10, 0) - require.NoError(err, "SearchFast after cache rebuilt underneath engine") - require.Len(res, 1) - assert.Equal("Hello SOFRA", res[0].Subject) - assert.False(engine.hasCol("messages", "message_type"), - "message_type should be re-probed as absent after the rebuild") - - // Aggregate (the other reported-broken path) must also recover. - agg, err := engine.Aggregate(ctx, ViewSenders, DefaultAggregateOptions()) - require.NoError(err, "Aggregate after cache rebuilt underneath engine") - require.Len(agg, 1) + _, err = engine.SearchFast(ctx, search.Parse("SOFRA"), MessageFilter{}, 10, 0) + require.ErrorIs(err, ErrCacheUnavailable) + var unavailable *CacheUnavailableError + require.ErrorAs(err, &unavailable) + assert.Equal(CacheDrifted, unavailable.Readiness) } func TestDuckDBEngineRejectsInterruptedCache(t *testing.T) { @@ -101,21 +95,21 @@ func TestDuckDBEngineRejectsInterruptedCache(t *testing.T) { require.ErrorIs(err, ErrCacheUnavailable) } -func TestDuckDBEngine_SearchFastWithStatsRebuildsCacheWhenParquetChanges(t *testing.T) { +func TestDuckDBEngine_SearchFastWithStatsRejectsMessageParquetDrift(t *testing.T) { require := require.New(t) assert := assert.New(t) pb := newParquetBuilder(t). addTable("messages", "messages/year=2024", "data.parquet", messagesCols, ` - (1::BIGINT, 1::BIGINT, 'm1', 100::BIGINT, 'Hello SOFRA', 'snip', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2024, 1) + (1::BIGINT, 1::BIGINT, 'm1', 100::BIGINT, 'Hello SOFRA', 'snip', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 2024, 1) `). addTable("sources", "sources", "sources.parquet", sourcesCols, `(1::BIGINT, 'test@gmail.com', 'gmail')`). addTable("participants", "participants", "participants.parquet", participantsCols, `(1::BIGINT, 'alice@test.com', 'test.com', 'Alice', '')`). addTable("message_recipients", "message_recipients", "message_recipients.parquet", messageRecipientsCols, `(1::BIGINT, 1::BIGINT, 'from', 'Alice')`). addEmptyTable("labels", "labels", "labels.parquet", labelsCols, `(1::BIGINT, 'x')`). addEmptyTable("message_labels", "message_labels", "message_labels.parquet", messageLabelsCols, `(1::BIGINT, 1::BIGINT)`). - addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 100::BIGINT, 'x')`). - addTable("conversations", "conversations", "conversations.parquet", conversationsCols, `(100::BIGINT, 'thread100', '')`) + addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 1::BIGINT, 100::BIGINT, 'x', '')`). + addTable("conversations", "conversations", "conversations.parquet", conversationsCols, `(100::BIGINT, 'thread100', '', 'email')`) analyticsDir, cleanup := pb.build() t.Cleanup(cleanup) @@ -136,35 +130,32 @@ func TestDuckDBEngine_SearchFastWithStatsRebuildsCacheWhenParquetChanges(t *test msgPath := filepath.Join(analyticsDir, "messages", "year=2024", "data.parquet") rewriteParquetForTest(t, msgPath, messagesCols, ` - (1::BIGINT, 1::BIGINT, 'm1', 100::BIGINT, 'Hello SOFRA', 'snip', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2024, 1), - (2::BIGINT, 1::BIGINT, 'm2', 101::BIGINT, 'Another SOFRA', 'snip', TIMESTAMP '2024-01-16 10:00:00', 2000::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2024, 1) + (1::BIGINT, 1::BIGINT, 'm1', 100::BIGINT, 'Hello SOFRA', 'snip', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 2024, 1), + (2::BIGINT, 1::BIGINT, 'm2', 101::BIGINT, 'Another SOFRA', 'snip', TIMESTAMP '2024-01-16 10:00:00', 2000::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 2024, 1) `) - second, err := engine.SearchFastWithStats(ctx, q, "SOFRA", MessageFilter{}, ViewSenders, 10, 0) - require.NoError(err, "SearchFastWithStats after rebuild") - require.Len(second.Messages, 2) - assert.Equal(int64(2), second.TotalCount) - require.NotNil(second.Stats) - assert.Equal(int64(2), second.Stats.MessageCount) - assert.Equal("Another SOFRA", second.Messages[0].Subject) - assert.Equal("Hello SOFRA", second.Messages[1].Subject) + _, err = engine.SearchFastWithStats(ctx, q, "SOFRA", MessageFilter{}, ViewSenders, 10, 0) + require.ErrorIs(err, ErrCacheUnavailable) + var unavailable *CacheUnavailableError + require.ErrorAs(err, &unavailable) + assert.Equal(CacheDrifted, unavailable.Readiness) } -func TestDuckDBEngine_SearchFastWithStatsRebuildsStatsWhenAttachmentsChange(t *testing.T) { +func TestDuckDBEngine_SearchFastWithStatsRejectsAttachmentParquetDrift(t *testing.T) { require := require.New(t) assert := assert.New(t) pb := newParquetBuilder(t). addTable("messages", "messages/year=2024", "data.parquet", messagesCols, ` - (1::BIGINT, 1::BIGINT, 'm1', 100::BIGINT, 'Hello SOFRA', 'snip', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2024, 1) + (1::BIGINT, 1::BIGINT, 'm1', 100::BIGINT, 'Hello SOFRA', 'snip', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 2024, 1) `). addTable("sources", "sources", "sources.parquet", sourcesCols, `(1::BIGINT, 'test@gmail.com', 'gmail')`). addTable("participants", "participants", "participants.parquet", participantsCols, `(1::BIGINT, 'alice@test.com', 'test.com', 'Alice', '')`). addTable("message_recipients", "message_recipients", "message_recipients.parquet", messageRecipientsCols, `(1::BIGINT, 1::BIGINT, 'from', 'Alice')`). addEmptyTable("labels", "labels", "labels.parquet", labelsCols, `(1::BIGINT, 'x')`). addEmptyTable("message_labels", "message_labels", "message_labels.parquet", messageLabelsCols, `(1::BIGINT, 1::BIGINT)`). - addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 100::BIGINT, 'x')`). - addTable("conversations", "conversations", "conversations.parquet", conversationsCols, `(100::BIGINT, 'thread100', '')`) + addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 1::BIGINT, 100::BIGINT, 'x', '')`). + addTable("conversations", "conversations", "conversations.parquet", conversationsCols, `(100::BIGINT, 'thread100', '', 'email')`) analyticsDir, cleanup := pb.build() t.Cleanup(cleanup) @@ -184,15 +175,13 @@ func TestDuckDBEngine_SearchFastWithStatsRebuildsStatsWhenAttachmentsChange(t *t require.Equal(0, first.Messages[0].AttachmentCount) attPath := filepath.Join(analyticsDir, "attachments", "attachments.parquet") - rewriteParquetForTest(t, attPath, attachmentsCols, `(1::BIGINT, 123::BIGINT, 'file.pdf')`) - - second, err := engine.SearchFastWithStats(ctx, q, "SOFRA", MessageFilter{}, ViewSenders, 10, 0) - require.NoError(err, "SearchFastWithStats after attachments rebuild") - require.NotNil(second.Stats) - require.Len(second.Messages, 1) - assert.Equal(int64(1), second.Stats.AttachmentCount) - assert.Equal(int64(123), second.Stats.AttachmentSize) - assert.Equal(1, second.Messages[0].AttachmentCount) + rewriteParquetForTest(t, attPath, attachmentsCols, `(1::BIGINT, 1::BIGINT, 123::BIGINT, 'file.pdf', 'application/pdf')`) + + _, err = engine.SearchFastWithStats(ctx, q, "SOFRA", MessageFilter{}, ViewSenders, 10, 0) + require.ErrorIs(err, ErrCacheUnavailable) + var unavailable *CacheUnavailableError + require.ErrorAs(err, &unavailable) + assert.Equal(CacheDrifted, unavailable.Readiness) } func TestDuckDBEngine_CacheFingerprintCoversRequiredParquetDirs(t *testing.T) { diff --git a/internal/query/duckdb_test.go b/internal/query/duckdb_test.go index f1e9be530..dc68b444c 100644 --- a/internal/query/duckdb_test.go +++ b/internal/query/duckdb_test.go @@ -2864,8 +2864,8 @@ func TestDuckDBEngine_AggregateByRecipientName_EmptyStringFallback(t *testing.T) // Build Parquet data with empty-string and whitespace display_names on recipients engine := createEngineFromBuilder(t, newParquetBuilder(t). addTable("messages", "messages/year=2024", "data.parquet", messagesCols, ` - (1::BIGINT, 1::BIGINT, 'msg1', 100::BIGINT, 'Hello', 'Snippet', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2024, 1), - (2::BIGINT, 1::BIGINT, 'msg2', 101::BIGINT, 'World', 'Snippet', TIMESTAMP '2024-01-16 10:00:00', 1000::BIGINT, false, 0, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2024, 1) + (1::BIGINT, 1::BIGINT, 'msg1', 100::BIGINT, 'Hello', 'Snippet', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0, NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 2024, 1), + (2::BIGINT, 1::BIGINT, 'msg2', 101::BIGINT, 'World', 'Snippet', TIMESTAMP '2024-01-16 10:00:00', 1000::BIGINT, false, 0, NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 2024, 1) `). addTable("sources", "sources", "sources.parquet", sourcesCols, ` (1::BIGINT, 'test@gmail.com', 'gmail') @@ -2883,10 +2883,10 @@ func TestDuckDBEngine_AggregateByRecipientName_EmptyStringFallback(t *testing.T) `). addEmptyTable("labels", "labels", "labels.parquet", labelsCols, `(1::BIGINT, 'x')`). addEmptyTable("message_labels", "message_labels", "message_labels.parquet", messageLabelsCols, `(1::BIGINT, 1::BIGINT)`). - addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 100::BIGINT, 'x')`). + addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 1::BIGINT, 100::BIGINT, 'x', '')`). addTable("conversations", "conversations", "conversations.parquet", conversationsCols, ` - (100::BIGINT, 'thread100', ''), - (101::BIGINT, 'thread101', '') + (100::BIGINT, 'thread100', '', 'email'), + (101::BIGINT, 'thread101', '', 'email') `)) ctx := context.Background() @@ -2912,8 +2912,8 @@ func TestDuckDBEngine_ListMessages_MatchEmptyRecipientName(t *testing.T) { // Build Parquet data with a message that has no recipients engine := createEngineFromBuilder(t, newParquetBuilder(t). addTable("messages", "messages/year=2024", "data.parquet", messagesCols, ` - (1::BIGINT, 1::BIGINT, 'msg1', 100::BIGINT, 'Has Recipient', 'Snippet', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2024, 1), - (2::BIGINT, 1::BIGINT, 'msg2', 101::BIGINT, 'No Recipient', 'Snippet', TIMESTAMP '2024-01-16 10:00:00', 1000::BIGINT, false, 0, NULL::TIMESTAMP, NULL::BIGINT, 'email', 2024, 1) + (1::BIGINT, 1::BIGINT, 'msg1', 100::BIGINT, 'Has Recipient', 'Snippet', TIMESTAMP '2024-01-15 10:00:00', 1000::BIGINT, false, 0, NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 2024, 1), + (2::BIGINT, 1::BIGINT, 'msg2', 101::BIGINT, 'No Recipient', 'Snippet', TIMESTAMP '2024-01-16 10:00:00', 1000::BIGINT, false, 0, NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 2024, 1) `). addTable("sources", "sources", "sources.parquet", sourcesCols, ` (1::BIGINT, 'test@gmail.com', 'gmail') @@ -2928,10 +2928,10 @@ func TestDuckDBEngine_ListMessages_MatchEmptyRecipientName(t *testing.T) { `). addEmptyTable("labels", "labels", "labels.parquet", labelsCols, `(1::BIGINT, 'x')`). addEmptyTable("message_labels", "message_labels", "message_labels.parquet", messageLabelsCols, `(1::BIGINT, 1::BIGINT)`). - addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 100::BIGINT, 'x')`). + addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 1::BIGINT, 100::BIGINT, 'x', '')`). addTable("conversations", "conversations", "conversations.parquet", conversationsCols, ` - (100::BIGINT, 'thread100', ''), - (101::BIGINT, 'thread101', '') + (100::BIGINT, 'thread100', '', 'email'), + (101::BIGINT, 'thread101', '', 'email') `)) ctx := context.Background() @@ -3540,8 +3540,8 @@ func TestDuckDBEngine_VARCHARParquetColumns(t *testing.T) { // string, to reproduce type mismatches in COALESCE, JOINs, and TRY_CAST paths. engine := createEngineFromBuilder(t, newParquetBuilder(t). addTable("messages", "messages/year=2024", "data.parquet", messagesCols, ` - (1::BIGINT, 1::BIGINT, 'msg1', '100', 'Hello World', 'snippet1', TIMESTAMP '2024-01-15 10:00:00', '1000', '0', '0', NULL::TIMESTAMP, NULL::BIGINT, 'email', 2024, 1), - (2::BIGINT, 1::BIGINT, 'msg2', '101', 'Goodbye', 'snippet2', TIMESTAMP '2024-01-16 10:00:00', '2000', '1', '0', NULL::TIMESTAMP, NULL::BIGINT, 'email', 2024, 1) + (1::BIGINT, 1::BIGINT, 'msg1', '100', 'Hello World', 'snippet1', TIMESTAMP '2024-01-15 10:00:00', '1000', '0', '0', NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 2024, 1), + (2::BIGINT, 1::BIGINT, 'msg2', '101', 'Goodbye', 'snippet2', TIMESTAMP '2024-01-16 10:00:00', '2000', '1', '0', NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 2024, 1) `). addTable("sources", "sources", "sources.parquet", sourcesCols, ` (1::BIGINT, 'test@gmail.com', 'gmail') @@ -3555,10 +3555,10 @@ func TestDuckDBEngine_VARCHARParquetColumns(t *testing.T) { `). addEmptyTable("labels", "labels", "labels.parquet", labelsCols, `(1::BIGINT, 'x')`). addEmptyTable("message_labels", "message_labels", "message_labels.parquet", messageLabelsCols, `(1::BIGINT, 1::BIGINT)`). - addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, '100', 'x')`). + addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 1::BIGINT, '100', 'x', '')`). addTable("conversations", "conversations", "conversations.parquet", conversationsCols, ` - (100::BIGINT, 'thread100', ''), - (101::BIGINT, 'thread101', '') + (100::BIGINT, 'thread100', '', 'email'), + (101::BIGINT, 'thread101', '', 'email') `)) ctx := context.Background() @@ -3878,8 +3878,9 @@ func TestDuckDBEngine_HideDeletedFromSource(t *testing.T) { // TestDuckDBEngine_StaleParquetSchema verifies that a DuckDB engine can query // Parquet files written BEFORE PR #160 added phone_number, attachment_count, -// sender_id, and message_type columns. The engine should synthesise sensible -// defaults instead of failing with a binder error. +// sender_id, and message_type columns, and before later cache versions added +// deleted_at and is_from_me. The engine should synthesise sensible defaults +// instead of failing with a binder error. func TestDuckDBEngine_StaleParquetSchema(t *testing.T) { // Old-style column definitions (pre-WhatsApp). const oldMessagesCols = "id, source_id, source_message_id, conversation_id, subject, snippet, sent_at, size_estimate, has_attachments, deleted_from_source_at, year, month" @@ -3903,7 +3904,7 @@ func TestDuckDBEngine_StaleParquetSchema(t *testing.T) { `). addEmptyTable("labels", "labels", "labels.parquet", labelsCols, `(1::BIGINT, 'x')`). addEmptyTable("message_labels", "message_labels", "message_labels.parquet", messageLabelsCols, `(1::BIGINT, 1::BIGINT)`). - addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 100::BIGINT, 'x')`). + addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, `(1::BIGINT, 1::BIGINT, 100::BIGINT, 'x', '')`). addTable("conversations", "conversations", "conversations.parquet", oldConversationsCols, ` (100::BIGINT, 'thread100'), (101::BIGINT, 'thread101') @@ -3957,19 +3958,35 @@ func TestDuckDBEngine_StaleParquetSchema(t *testing.T) { assert.Equal(t, int64(2), stats.MessageCount) }) - // Verify that optionalCols correctly detected the missing columns. + // Verify that optionalCols correctly detected the missing columns, + // including deleted_at and is_from_me (added for pre-v13 caches, i.e. + // caches written before participant links / is_from_me existed). t.Run("ProbeDetectedMissing", func(t *testing.T) { for _, col := range []struct{ table, col string }{ {"participants", "phone_number"}, {"messages", "attachment_count"}, {"messages", "sender_id"}, {"messages", "message_type"}, + {"messages", "deleted_at"}, + {"messages", "is_from_me"}, {"conversations", "title"}, } { assert.False(t, engine.hasCol(col.table, col.col), "expected %s.%s to be detected as missing", col.table, col.col) } }) + + // Verify that parquetCTEs() defaults is_from_me to false (rather than + // erroring) when the column is entirely absent from the Parquet file, + // as happens with a pre-v13 cache never rebuilt since is_from_me was + // added. + t.Run("IsFromMeDefaultsFalseWhenColumnAbsent", func(t *testing.T) { + query := fmt.Sprintf("WITH %s SELECT is_from_me FROM msg WHERE id = 1", engine.parquetCTEs()) + var isFromMe bool + err := engine.db.QueryRowContext(ctx, query).Scan(&isFromMe) + require.NoError(t, err, "query is_from_me with stale Parquet schema") + assert.False(t, isFromMe, "is_from_me should default to false when the column is absent") + }) } func TestDuckDBEngine_GetGmailIDsByMessageIDs(t *testing.T) { diff --git a/internal/query/engine.go b/internal/query/engine.go index 14fae1893..750a97bca 100644 --- a/internal/query/engine.go +++ b/internal/query/engine.go @@ -102,6 +102,41 @@ type Engine interface { Close() error } +// Explorer is intentionally separate from Engine: only the committed +// DuckDB/Parquet analytical read model implements it. Transactional engines +// must never become a modality-specific fallback for exploration. +type Explorer interface { + Explore(ctx context.Context, request ExploreRequest) (*ExploreResponse, error) + ExploreCoverage(ctx context.Context, request ExploreCoverageRequest) (*ExploreCoverageResponse, error) + ExploreGroups(ctx context.Context, request ExploreGroupRequest) (*ExploreGroupResponse, error) + ExploreSelectionStats(ctx context.Context, request ExploreSelectionRequest) (*ExploreSelectionStats, error) + ExploreFiles(ctx context.Context, request ExploreFilesRequest) (*ExploreFilesResponse, error) + ExploreMatchCounts(ctx context.Context, request ExploreMatchCountsRequest) (*ExploreMatchCountsResponse, error) +} + +// FileSearcher is separate from Engine so Files can only be backed by the +// committed analytical cache; transactional engines never become a fallback. +type FileSearcher interface { + SearchFiles(ctx context.Context, request FileSearchRequest) (*FileSearchResponse, error) +} + +// FileGrouper is separate from Explorer so grouped Files cannot silently fall +// back to message-level analytical groups. +type FileGrouper interface { + GroupFiles(ctx context.Context, request FileGroupRequest) (*ExploreGroupResponse, error) +} + +// PeopleAnalyzer is separate from Engine so identity analytics can only be +// served from a committed canonical cache snapshot. +type PeopleAnalyzer interface { + SearchPeople(ctx context.Context, request PersonSearchRequest) (*PersonSearchResponse, error) + GetPerson(ctx context.Context, id int64, analyticalContext Context, clusterMemberIDs []int64) (*PersonSummary, error) + GetPersonSummary(ctx context.Context, id int64, explore ExploreRequest) (*PersonSearchResponse, error) + SearchDomains(ctx context.Context, request DomainSearchRequest) (*DomainSearchResponse, error) + GetDomain(ctx context.Context, domain string, analyticalContext Context) (*DomainSummary, error) + GetDomainSummary(ctx context.Context, domain string, explore ExploreRequest) (*DomainSearchResponse, error) +} + // MessageBodySearcher is an optional capability for exact full-text search of // message bodies. It is deliberately separate from Engine so generic Search // retains its composite subject/body/participant semantics. diff --git a/internal/query/explore.go b/internal/query/explore.go new file mode 100644 index 000000000..798c6dcef --- /dev/null +++ b/internal/query/explore.go @@ -0,0 +1,488 @@ +package query + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "strings" +) + +var ErrInvalidExploreRequest = errors.New("invalid exploration request") + +const defaultExploreLimit = 100 +const maxExploreLimit = 1000 + +// Explore projects the committed analytical cache into modality-neutral row +// units. It never consults the transactional database. +func (e *DuckDBEngine) Explore(ctx context.Context, request ExploreRequest) (*ExploreResponse, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + + if request.Page.Offset < 0 || request.Page.Limit < 0 || request.Page.Limit > maxExploreLimit { + return nil, fmt.Errorf("%w: page is outside the supported range", ErrInvalidExploreRequest) + } + if request.Context.Deletion != DeletionAny && + request.Context.Deletion != DeletionActive && + request.Context.Deletion != DeletionDeleted { + return nil, fmt.Errorf("%w: unknown deletion filter %q", ErrInvalidExploreRequest, request.Context.Deletion) + } + if len(request.Grouping) > 0 { + return nil, fmt.Errorf("%w: grouped exploration is not available in the entry-row query", ErrInvalidExploreRequest) + } + if request.Presentation != PresentationDefault && request.Presentation != PresentationTable { + return nil, fmt.Errorf("%w: unsupported presentation %q", ErrInvalidExploreRequest, request.Presentation) + } + if len(request.Sort) > 1 || (len(request.Sort) == 1 && + (request.Sort[0].Field != "sent_at" || request.Sort[0].Direction != sortDirectionDesc)) { + return nil, fmt.Errorf("%w: only sent_at descending sort is supported", ErrInvalidExploreRequest) + } + searchProvenance, err := validateResolvedSearch(request.Search) + if err != nil { + return nil, err + } + + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + conditions, args := buildExploreConditions(request) + candidateRankExpression, candidateRankArgs := buildExploreCandidateRank(request.Search) + queryText := buildExploreSQL(conditions, candidateRankExpression, e.parquetPath(datasetParticipantClusters), e.parquetPath(datasetOwnerParticipants)) + args = append(args, candidateRankArgs...) + limit := request.Page.Limit + if limit == 0 { + limit = defaultExploreLimit + } + args = append(args, limit, request.Page.Offset) + + rows, err := e.db.QueryContext(ctx, queryText, args...) + if err != nil { + return nil, fmt.Errorf("explore analytical entries: %w", err) + } + defer func() { _ = rows.Close() }() + + response := &ExploreResponse{ + Rows: make([]EntryRow, 0), + CacheRevision: state.Revision(), + SearchProvenance: searchProvenance, + } + for rows.Next() { + var row EntryRow + var anchorID, conversationID, strongestMatchedMessageID, counterpartParticipantID sql.NullInt64 + var participantIDsJSON, participantLabelsJSON string + if err := rows.Scan( + &row.Key, &row.Kind, &anchorID, &conversationID, &row.OccurredAt, + &row.SourceID, &row.SourceType, &row.SourceIdentifier, + &row.MessageType, &row.ConversationType, &row.Title, &row.Preview, + &participantIDsJSON, &participantLabelsJSON, &strongestMatchedMessageID, &row.MessageCount, + &row.HasAttachments, &row.AttachmentCount, &row.AttachmentSize, + &row.DeletedFromSource, &response.TotalCount, &counterpartParticipantID, + ); err != nil { + return nil, fmt.Errorf("scan analytical entry: %w", err) + } + if row.Title == row.Preview { + row.Title = FlattenSnippet(row.Title) + } + row.Preview = FlattenSnippet(row.Preview) + if anchorID.Valid { + row.AnchorMessageID = &anchorID.Int64 + } + if conversationID.Valid { + row.ConversationID = &conversationID.Int64 + } + if strongestMatchedMessageID.Valid { + row.StrongestMatchedMessageID = &strongestMatchedMessageID.Int64 + } + if counterpartParticipantID.Valid { + row.CounterpartParticipantID = &counterpartParticipantID.Int64 + } + if err := json.Unmarshal([]byte(participantIDsJSON), &row.ParticipantIDs); err != nil { + return nil, fmt.Errorf("decode analytical participant IDs: %w", err) + } + if err := json.Unmarshal([]byte(participantLabelsJSON), &row.ParticipantLabels); err != nil { + return nil, fmt.Errorf("decode analytical participant labels: %w", err) + } + response.Rows = append(response.Rows, row) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate analytical entries: %w", err) + } + if len(response.Rows) == 0 && request.Page.Offset > 0 { + countArgs := args[:len(args)-2] + if err := e.db.QueryRowContext(ctx, buildExploreCountSQL(conditions, candidateRankExpression), countArgs...).Scan(&response.TotalCount); err != nil { + return nil, fmt.Errorf("count analytical entries beyond page: %w", err) + } + } + return response, nil +} + +const ( + defaultExploreCoverageBatchSize = 256 + maxExploreCoverageBatchSize = 512 +) + +// ExploreCoverage returns one bounded page of exact live message IDs in the +// committed analytical context. It deliberately does not aggregate chat +// messages into row units: each message is independently eligible for an +// embedding. +func (e *DuckDBEngine) ExploreCoverage(ctx context.Context, request ExploreCoverageRequest) (*ExploreCoverageResponse, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + if request.Context.Deletion != DeletionAny && + request.Context.Deletion != DeletionActive && + request.Context.Deletion != DeletionDeleted { + return nil, fmt.Errorf("%w: unknown deletion filter %q", ErrInvalidExploreRequest, request.Context.Deletion) + } + limit := request.Limit + if limit == 0 { + limit = defaultExploreCoverageBatchSize + } + if limit < 1 || limit > maxExploreCoverageBatchSize { + return nil, fmt.Errorf("%w: coverage limit must be between 1 and %d", ErrInvalidExploreRequest, maxExploreCoverageBatchSize) + } + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + conditions, args := buildExploreConditions(ExploreRequest{Context: request.Context}) + conditions += " AND NOT internally_deleted AND NOT deleted_from_source AND message_id > ?" + args = append(args, request.AfterMessageID, limit+1) + rows, err := e.db.QueryContext(ctx, + "SELECT message_id FROM analytical_entries WHERE "+conditions+" ORDER BY message_id LIMIT ?", args...) + if err != nil { + return nil, fmt.Errorf("resolve analytical coverage context: %w", err) + } + defer func() { _ = rows.Close() }() + response := &ExploreCoverageResponse{MessageIDs: make([]int64, 0), CacheRevision: state.Revision()} + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, fmt.Errorf("scan analytical coverage message: %w", err) + } + response.MessageIDs = append(response.MessageIDs, id) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate analytical coverage messages: %w", err) + } + if len(response.MessageIDs) > limit { + response.MessageIDs = response.MessageIDs[:limit] + response.NextAfterMessageID = new(response.MessageIDs[len(response.MessageIDs)-1]) + } + return response, nil +} + +func buildExploreCandidateRank(search SearchSpec) (string, []any) { + if search.Mode != SearchSemantic && search.Mode != SearchHybrid { + return "NULL::BIGINT", nil + } + parts := make([]string, len(search.CandidateMessageIDs)) + args := make([]any, len(search.CandidateMessageIDs)) + for i, messageID := range search.CandidateMessageIDs { + parts[i] = fmt.Sprintf("WHEN ? THEN %d", i) + args[i] = messageID + } + if len(parts) == 0 { + return "NULL::BIGINT", nil + } + return "CASE message_id " + strings.Join(parts, " ") + " ELSE NULL END", args +} + +func validateResolvedSearch(search SearchSpec) (SearchProvenance, error) { + if search.Mode == SearchNone { + if search.Query != "" || search.CandidateMessageIDs != nil || + search.LexicalIndexRevision != "" || search.VectorGeneration != nil { + return SearchProvenance{}, fmt.Errorf("%w: no-search mode contains search state", ErrInvalidExploreRequest) + } + return SearchProvenance{}, nil + } + if search.CandidateMessageIDs == nil { + return SearchProvenance{}, fmt.Errorf("%w: %s search candidates are unresolved", ErrInvalidExploreRequest, search.Mode) + } + switch search.Mode { + case SearchFullText: + if search.LexicalIndexRevision == "" { + return SearchProvenance{}, fmt.Errorf("%w: full-text lexical index revision is unresolved", ErrInvalidExploreRequest) + } + if search.VectorGeneration != nil { + return SearchProvenance{}, fmt.Errorf("%w: full-text search contains vector provenance", ErrInvalidExploreRequest) + } + return SearchProvenance{LexicalIndexRevision: search.LexicalIndexRevision}, nil + case SearchSemantic: + if search.VectorGeneration == nil { + return SearchProvenance{}, fmt.Errorf("%w: semantic vector generation is unresolved", ErrInvalidExploreRequest) + } + if search.LexicalIndexRevision != "" { + return SearchProvenance{}, fmt.Errorf("%w: semantic search contains lexical provenance", ErrInvalidExploreRequest) + } + return SearchProvenance{VectorGeneration: search.VectorGeneration}, nil + case SearchHybrid: + if search.LexicalIndexRevision == "" { + return SearchProvenance{}, fmt.Errorf("%w: hybrid lexical index revision is unresolved", ErrInvalidExploreRequest) + } + if search.VectorGeneration == nil { + return SearchProvenance{}, fmt.Errorf("%w: hybrid vector generation is unresolved", ErrInvalidExploreRequest) + } + return SearchProvenance{ + LexicalIndexRevision: search.LexicalIndexRevision, + VectorGeneration: search.VectorGeneration, + }, nil + default: + return SearchProvenance{}, fmt.Errorf("%w: unknown search mode %q", ErrInvalidExploreRequest, search.Mode) + } +} + +func buildExploreConditions(request ExploreRequest) (string, []any) { + var conditions []string + var args []any + appendIntAnyOf := func(values []int64, expression string) { + if len(values) == 0 { + return + } + parts := make([]string, len(values)) + for i, value := range values { + parts[i] = expression + args = append(args, value) + } + conditions = append(conditions, "("+strings.Join(parts, " OR ")+")") + } + appendStringAnyOf := func(values []string, expression string) { + if len(values) == 0 { + return + } + parts := make([]string, len(values)) + for i, value := range values { + parts[i] = expression + args = append(args, value) + } + conditions = append(conditions, "("+strings.Join(parts, " OR ")+")") + } + + appendIntAnyOf(request.Context.SourceIDs, "source_id = ?") + if len(request.Context.ParticipantIDs) > 0 { + parts := make([]string, len(request.Context.ParticipantIDs)) + for i := range parts { + parts[i] = "(sender_id = ? OR list_contains(participant_ids, ?) OR list_contains(conversation_participant_ids, ?))" + } + conditions = append(conditions, "("+strings.Join(parts, " OR ")+")") + for _, value := range request.Context.ParticipantIDs { + args = append(args, value, value, value) + } + } + if len(request.Context.Domains) > 0 { + parts := make([]string, len(request.Context.Domains)) + for i := range parts { + parts[i] = "(lower(sender_domain) = lower(?) OR list_contains(participant_domains, lower(?)) OR list_contains(conversation_participant_domains, lower(?)))" + } + conditions = append(conditions, "("+strings.Join(parts, " OR ")+")") + for _, value := range request.Context.Domains { + args = append(args, value, value, value) + } + } + appendStringAnyOf(request.Context.MessageTypes, "lower(message_type) = lower(?)") + if request.Context.After != nil { + conditions = append(conditions, "occurred_at >= ?") + args = append(args, request.Context.After.UTC()) + } + if request.Context.Before != nil { + conditions = append(conditions, "occurred_at < ?") + args = append(args, request.Context.Before.UTC()) + } + switch request.Context.Deletion { + case DeletionAny: + case DeletionActive: + conditions = append(conditions, "NOT deleted_from_source") + case DeletionDeleted: + conditions = append(conditions, "deleted_from_source") + } + if request.Search.CandidateMessageIDs != nil { + if len(request.Search.CandidateMessageIDs) == 0 { + conditions = append(conditions, "false") + } else { + parts := make([]string, len(request.Search.CandidateMessageIDs)) + for i, id := range request.Search.CandidateMessageIDs { + parts[i] = "?" + args = append(args, id) + } + conditions = append(conditions, "message_id IN ("+strings.Join(parts, ",")+")") + } + } + if len(conditions) == 0 { + return "true", args + } + return strings.Join(conditions, " AND "), args +} + +// buildExploreSQL builds the entry-row page query. counterpart_participant_id +// reuses the exact owner-cluster resolution buildRelationshipsSQL uses (see +// its doc comment): owners are expanded through participant_clusters so an +// owner's clustered alias is still recognized as the owner, and the smallest +// non-owner participant ID on the entry is returned. If the owner_participants +// dataset has no rows at all, the owner is unknown and the column is NULL — +// never a guess at "the other side" from participant_ids[0] alone. +func buildExploreSQL(conditions, candidateRankExpression, clustersGlob, ownersGlob string) string { + return buildExploreLogicalSQLWithCandidateRank(conditions, candidateRankExpression) + fmt.Sprintf(` +), counted AS ( + SELECT *, COUNT(*) OVER () AS total_count + FROM logical_entries +), clusters AS ( + SELECT participant_id, canonical_id FROM read_parquet('%s') +), owners AS ( + SELECT DISTINCT participant_id FROM read_parquet('%s') +), canon AS ( + SELECT p.id AS participant_id, COALESCE(c.canonical_id, p.id) AS canonical_id + FROM participants p LEFT JOIN clusters c ON c.participant_id = p.id +), owner_canon AS ( + SELECT DISTINCT cn.canonical_id FROM owners o JOIN canon cn ON cn.participant_id = o.participant_id +), owner_participant_ids AS ( + SELECT DISTINCT cn.participant_id FROM canon cn + WHERE cn.canonical_id IN (SELECT canonical_id FROM owner_canon) +) +SELECT + entry_key, + entry_kind, + anchor_message_id, + conversation_id, + occurred_at, + source_id, + source_type, + source_identifier, + message_type, + conversation_type, + title, + preview, + CAST(COALESCE(to_json(participant_ids), '[]') AS VARCHAR) AS participant_ids, + CAST(COALESCE(to_json(participant_labels), '[]') AS VARCHAR) AS participant_labels, + strongest_matched_message_id, + message_count, + has_attachments, + attachment_count, + attachment_size, + deleted_from_source, + total_count, + CASE WHEN NOT EXISTS (SELECT 1 FROM owners) THEN NULL + ELSE (SELECT MIN(pid) FROM UNNEST(participant_ids) AS u(pid) + WHERE pid NOT IN (SELECT participant_id FROM owner_participant_ids)) + END AS counterpart_participant_id +FROM counted +ORDER BY occurred_at DESC, source_id ASC, entry_key ASC +LIMIT ? OFFSET ?`, clustersGlob, ownersGlob) +} + +func buildExploreCountSQL(conditions, candidateRankExpression string) string { + return buildExploreLogicalSQLWithCandidateRank(conditions, candidateRankExpression) + ` +) +SELECT COUNT(*) FROM logical_entries` +} + +func buildExploreLogicalSQL(conditions string) string { + return buildExploreLogicalSQLWithCandidateRank(conditions, "NULL::BIGINT") +} + +// buildExploreFilteredClassifiedCTE builds the "filtered" and "classified" +// CTEs shared by every query that projects analytical_entries into +// modality-neutral rows: buildExploreLogicalSQLWithCandidateRank's +// per-conversation-lifetime chat grouping, and +// buildRelationshipTimelineSQL's per-local-day chat burst grouping. The +// returned text ends with the "classified" CTE closed, ready for a caller to +// append ", AS (" and read from "classified". +func buildExploreFilteredClassifiedCTE(conditions, candidateRankExpression string) string { + return ` +WITH filtered AS ( + SELECT * + FROM analytical_entries + WHERE ` + conditions + ` +), classified AS ( + SELECT *, + ` + candidateRankExpression + ` AS candidate_rank, + lower(message_type) IN (` + TextMessageTypeSQLList + `) + OR ( + lower(message_type) IN ('', 'chat', 'text') + AND lower(conversation_type) IN ('direct_chat', 'group_chat', 'channel', 'chat') + ) AS is_chat, + CASE + WHEN lower(message_type) = 'email' OR message_type = '' THEN 'email' + WHEN lower(message_type) = '` + messageTypeCalendar + `' THEN 'event' + WHEN lower(message_type) IN ('meeting_transcript', 'meeting_note', 'meeting_minutes') THEN 'meeting' + ELSE 'item' + END AS entry_kind + FROM filtered +)` +} + +func buildExploreLogicalSQLWithCandidateRank(conditions, candidateRankExpression string) string { + return buildExploreFilteredClassifiedCTE(conditions, candidateRankExpression) + `, logical_entries AS ( + SELECT + 'source:' || CAST(source_id AS VARCHAR) || ':message:' || COALESCE(NULLIF(source_message_id, ''), CAST(message_id AS VARCHAR)) AS entry_key, + entry_kind, + message_id AS anchor_message_id, + conversation_id, + occurred_at, + source_id, + source_type, + source_identifier, + message_type, + conversation_type, + COALESCE(NULLIF(subject, ''), NULLIF(conversation_title, ''), snippet, '') AS title, + snippet AS preview, + participant_ids, + participant_labels, + list_sort(list_distinct(list_concat(participant_domains, conversation_participant_domains))) AS participant_domains, + CASE WHEN candidate_rank IS NOT NULL THEN message_id ELSE NULL END AS strongest_matched_message_id, + 1::BIGINT AS message_count, + (size_estimate + attachment_size)::BIGINT AS estimated_bytes, + (entry_kind = 'email' AND lower(source_type) = 'gmail' AND NOT deleted_from_source + AND COALESCE(source_message_id, '') <> '') AS deletable, + has_attachments, + is_from_me, + attachment_count::BIGINT AS attachment_count, + attachment_size::BIGINT AS attachment_size, + deleted_from_source + FROM classified + WHERE NOT is_chat + + UNION ALL + + SELECT + 'source:' || CAST(source_id AS VARCHAR) || ':conversation:' || CAST(conversation_id AS VARCHAR) AS entry_key, + 'conversation' AS entry_kind, + arg_max(message_id, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS anchor_message_id, + conversation_id, + MAX(occurred_at) AS occurred_at, + source_id, + arg_max(source_type, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS source_type, + arg_max(source_identifier, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS source_identifier, + arg_max(message_type, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS message_type, + arg_max(conversation_type, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS conversation_type, + COALESCE(NULLIF(MAX(conversation_title), ''), 'Conversation') AS title, + arg_max(snippet, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS preview, + list_sort(list_distinct(flatten(list(list_concat(participant_ids, conversation_participant_ids))))) AS participant_ids, + list_sort(list_distinct(flatten(list(list_concat(participant_labels, conversation_participant_labels))))) AS participant_labels, + list_sort(list_distinct(flatten(list(list_concat(participant_domains, conversation_participant_domains))))) AS participant_domains, + arg_min(message_id, struct_pack(candidate_rank := candidate_rank, message_id := message_id)) + FILTER (WHERE candidate_rank IS NOT NULL) AS strongest_matched_message_id, + COUNT(*)::BIGINT AS message_count, + SUM(size_estimate + attachment_size)::BIGINT AS estimated_bytes, + false AS deletable, + bool_or(has_attachments) AS has_attachments, + arg_max(is_from_me, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS is_from_me, + SUM(attachment_count)::BIGINT AS attachment_count, + SUM(attachment_size)::BIGINT AS attachment_size, + bool_or(deleted_from_source) AS deleted_from_source + FROM classified + WHERE is_chat + GROUP BY source_id, conversation_id` +} diff --git a/internal/query/explore_analysis.go b/internal/query/explore_analysis.go new file mode 100644 index 000000000..4e8862348 --- /dev/null +++ b/internal/query/explore_analysis.go @@ -0,0 +1,353 @@ +package query + +import ( + "context" + "fmt" + "strings" +) + +const maxExploreFilesLimit = 100 + +func (e *DuckDBEngine) ExploreGroups(ctx context.Context, request ExploreGroupRequest) (*ExploreGroupResponse, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + if err := validateExploreAnalysisPage(request.Page, maxExploreLimit); err != nil { + return nil, err + } + provenance, err := validateResolvedSearch(request.Explore.Search) + if err != nil { + return nil, err + } + groupExpr, labelExpr, fromSuffix, whereSuffix, err := exploreGroupExpressions(request.Dimension) + if err != nil { + return nil, err + } + order, err := exploreGroupOrder(request.Sort) + if err != nil { + return nil, err + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + conditions, args := buildExploreConditions(request.Explore) + limit := request.Page.Limit + if limit == 0 { + limit = defaultExploreLimit + } + queryText := buildExploreLogicalSQL(conditions) + ` +), grouped AS ( + SELECT ` + groupExpr + ` AS group_key, ` + labelExpr + ` AS group_label, + COUNT(*)::BIGINT AS group_count, + COALESCE(SUM(estimated_bytes), 0)::BIGINT AS estimated_bytes, + MAX(occurred_at) AS latest_at + FROM logical_entries` + fromSuffix + whereSuffix + ` + GROUP BY ` + groupExpr + ` +), counted AS ( + SELECT *, COUNT(*) OVER () AS total_count FROM grouped +) +SELECT group_key, group_label, group_count, estimated_bytes, latest_at, total_count +FROM counted ORDER BY ` + order + ` LIMIT ? OFFSET ?` + args = append(args, limit, request.Page.Offset) + rows, err := e.db.QueryContext(ctx, queryText, args...) + if err != nil { + return nil, fmt.Errorf("group analytical entries: %w", err) + } + defer func() { _ = rows.Close() }() + response := &ExploreGroupResponse{ + Rows: make([]ExploreGroupRow, 0), CacheRevision: state.Revision(), SearchProvenance: provenance, + } + for rows.Next() { + var row ExploreGroupRow + if err := rows.Scan(&row.Key, &row.Label, &row.Count, &row.EstimatedBytes, &row.LatestAt, &response.TotalCount); err != nil { + return nil, fmt.Errorf("scan analytical group: %w", err) + } + response.Rows = append(response.Rows, row) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate analytical groups: %w", err) + } + return response, nil +} + +func exploreGroupExpressions(dimension string) (key, label, fromSuffix, whereSuffix string, err error) { + switch dimension { + case "source": + return "CAST(source_id AS VARCHAR)", "arg_max(source_identifier, occurred_at)", "", "", nil + case "participant": + participantLabel := `COALESCE( + NULLIF(TRIM(grouped_participant.display_name), ''), + NULLIF(TRIM(grouped_participant.phone_number), ''), + NULLIF(TRIM(grouped_participant.email_address), ''), + CAST(grouped_participant.id AS VARCHAR) + )` + return "CAST(group_value AS VARCHAR)", "arg_max(" + participantLabel + ", occurred_at)", + " CROSS JOIN UNNEST(participant_ids) AS grouped(group_value)" + + " JOIN participants AS grouped_participant ON grouped_participant.id = group_value", "", nil + case "domain": + return "group_value", "group_value", ", UNNEST(participant_domains) AS grouped(group_value)", " WHERE group_value <> ''", nil + case messageTypeDimension: + return messageTypeDimension, messageTypeDimension, "", "", nil + case "kind": + return "entry_kind", "entry_kind", "", "", nil + case "year": + return "strftime(occurred_at, '%Y')", "strftime(occurred_at, '%Y')", "", "", nil + case timeGranularityMonth: + return "strftime(occurred_at, '%Y-%m')", "strftime(occurred_at, '%Y-%m')", "", "", nil + default: + return "", "", "", "", fmt.Errorf("%w: unknown group dimension %q", ErrInvalidExploreRequest, dimension) + } +} + +func exploreGroupOrder(sort SortSpec) (string, error) { + if sort.Field == "" { + sort = SortSpec{Field: "count", Direction: sortDirectionDesc} + } + if sort.Direction != sortDirectionAsc && sort.Direction != sortDirectionDesc { + return "", fmt.Errorf("%w: unknown group sort direction %q", ErrInvalidExploreRequest, sort.Direction) + } + var column string + switch sort.Field { + case "key": + column = "group_key" + case "count": + column = "group_count" + case "estimated_bytes": + column = "estimated_bytes" + case "latest_at": + column = "latest_at" + default: + return "", fmt.Errorf("%w: unknown group sort field %q", ErrInvalidExploreRequest, sort.Field) + } + return column + " " + strings.ToUpper(sort.Direction) + ", group_key ASC", nil +} + +func (e *DuckDBEngine) ExploreSelectionStats(ctx context.Context, request ExploreSelectionRequest) (*ExploreSelectionStats, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + provenance, err := validateResolvedSearch(request.Explore.Search) + if err != nil { + return nil, err + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + conditions, args := buildExploreConditions(request.Explore) + selectionConditions := make([]string, 0, 2) + if request.IncludedKeys != nil { + if len(request.IncludedKeys) == 0 { + selectionConditions = append(selectionConditions, "false") + } else { + placeholders := make([]string, len(request.IncludedKeys)) + for i, key := range request.IncludedKeys { + placeholders[i] = "?" + args = append(args, key) + } + selectionConditions = append(selectionConditions, "entry_key IN ("+strings.Join(placeholders, ",")+")") + } + } + if len(request.ExcludedKeys) > 0 { + placeholders := make([]string, len(request.ExcludedKeys)) + for i, key := range request.ExcludedKeys { + placeholders[i] = "?" + args = append(args, key) + } + selectionConditions = append(selectionConditions, "entry_key NOT IN ("+strings.Join(placeholders, ",")+")") + } + selection := "" + if len(selectionConditions) > 0 { + selection = " WHERE " + strings.Join(selectionConditions, " AND ") + } + queryText := buildExploreLogicalSQL(conditions) + ` +) +SELECT COUNT(*)::BIGINT, COALESCE(SUM(estimated_bytes), 0)::BIGINT, + COALESCE(SUM(CASE WHEN deletable THEN 1 ELSE 0 END), 0)::BIGINT, + COALESCE(SUM(attachment_count), 0)::BIGINT, + COALESCE(SUM(CASE WHEN attachment_count > 0 THEN 1 ELSE 0 END), 0)::BIGINT, + COALESCE(SUM(CASE WHEN deletable THEN 1 ELSE 0 END), 0)::BIGINT, + CASE WHEN COUNT(*) = 1 AND MIN(entry_kind) <> 'conversation' + THEN MIN(anchor_message_id) ELSE NULL END +FROM logical_entries` + selection + response := &ExploreSelectionStats{CacheRevision: state.Revision(), SearchProvenance: provenance} + if err := e.db.QueryRowContext(ctx, queryText, args...).Scan( + &response.Count, &response.EstimatedBytes, &response.DeletableCount, &response.FileCount, + &response.ExportableCount, &response.OpenableCount, &response.RawExportMessageID, + ); err != nil { + return nil, fmt.Errorf("preflight analytical selection: %w", err) + } + if request.IncludeDeletableMessageIDs { + deletableSelection := selection + if deletableSelection == "" { + deletableSelection = " WHERE deletable" + } else { + deletableSelection += " AND deletable" + } + rows, err := e.db.QueryContext(ctx, buildExploreLogicalSQL(conditions)+`) +SELECT anchor_message_id FROM logical_entries`+deletableSelection+` +ORDER BY anchor_message_id`, args...) + if err != nil { + return nil, fmt.Errorf("resolve deletable analytical selection: %w", err) + } + defer func() { _ = rows.Close() }() + response.DeletableMessageIDs = make([]int64, 0, response.DeletableCount) + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, fmt.Errorf("scan deletable analytical selection: %w", err) + } + response.DeletableMessageIDs = append(response.DeletableMessageIDs, id) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate deletable analytical selection: %w", err) + } + } + return response, nil +} + +func (e *DuckDBEngine) ExploreFiles(ctx context.Context, request ExploreFilesRequest) (*ExploreFilesResponse, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + if err := validateExploreAnalysisPage(request.Page, maxExploreFilesLimit); err != nil { + return nil, err + } + provenance, err := validateResolvedSearch(request.Explore.Search) + if err != nil { + return nil, err + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + conditions, args := buildExploreConditions(request.Explore) + limit := request.Page.Limit + if limit == 0 { + limit = maxExploreFilesLimit + } + queryText := ` +WITH selected AS ( + SELECT * FROM analytical_entries WHERE ` + conditions + ` +), facts AS ( + SELECT + CASE WHEN lower(s.message_type) IN (` + TextMessageTypeSQLList + `) + OR (lower(s.message_type) IN ('', 'chat', 'text') AND lower(s.conversation_type) IN ('direct_chat', 'group_chat', 'channel', 'chat')) + THEN 'source:' || CAST(s.source_id AS VARCHAR) || ':conversation:' || CAST(s.conversation_id AS VARCHAR) + ELSE 'source:' || CAST(s.source_id AS VARCHAR) || ':message:' || COALESCE(NULLIF(s.source_message_id, ''), CAST(s.message_id AS VARCHAR)) END AS entry_key, + s.message_id, s.conversation_id, s.occurred_at, s.source_id, s.source_identifier, + COALESCE(NULLIF(s.subject, ''), NULLIF(s.conversation_title, ''), s.snippet, '') AS title, + a.attachment_id, a.filename, a.mime_type, a.size + FROM selected s JOIN attachments a ON a.message_id = s.message_id +), keyed AS ( + SELECT entry_key || ':file:' || CAST(attachment_id AS VARCHAR) AS file_key, + *, COUNT(*) OVER () AS total_count + FROM facts +) +SELECT attachment_id, file_key, entry_key, message_id, conversation_id, occurred_at, source_id, + source_identifier, title, filename, mime_type, size, total_count +FROM keyed ORDER BY occurred_at DESC, message_id ASC, filename ASC, size ASC, attachment_id ASC LIMIT ? OFFSET ?` + args = append(args, limit, request.Page.Offset) + rows, err := e.db.QueryContext(ctx, queryText, args...) + if err != nil { + return nil, fmt.Errorf("list analytical files: %w", err) + } + defer func() { _ = rows.Close() }() + response := &ExploreFilesResponse{ + Files: make([]ExploreFileFact, 0), CacheRevision: state.Revision(), SearchProvenance: provenance, + } + for rows.Next() { + var fact ExploreFileFact + if err := rows.Scan(&fact.ID, &fact.Key, &fact.EntryKey, &fact.MessageID, &fact.ConversationID, + &fact.OccurredAt, &fact.SourceID, &fact.SourceIdentifier, &fact.Title, &fact.Filename, + &fact.MimeType, &fact.Size, &response.TotalCount); err != nil { + return nil, fmt.Errorf("scan analytical file: %w", err) + } + response.Files = append(response.Files, fact) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate analytical files: %w", err) + } + return response, nil +} + +func validateExploreAnalysisPage(page PageSpec, maxLimit int) error { + if page.Offset < 0 || page.Limit < 0 || page.Limit > maxLimit { + return fmt.Errorf("%w: page is outside the supported range", ErrInvalidExploreRequest) + } + return nil +} + +func (e *DuckDBEngine) ExploreMatchCounts(ctx context.Context, request ExploreMatchCountsRequest) (*ExploreMatchCountsResponse, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + if len(request.RowKeys) == 0 || len(request.RowKeys) > maxExploreLimit { + return nil, fmt.Errorf("%w: match-count row keys are outside the supported range", ErrInvalidExploreRequest) + } + provenance, err := validateResolvedSearch(request.Explore.Search) + if err != nil { + return nil, err + } + if request.Explore.Search.Mode != SearchFullText { + return nil, fmt.Errorf("%w: exact match counts require full-text search", ErrInvalidExploreRequest) + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + conditions, args := buildExploreConditions(request.Explore) + placeholders := make([]string, len(request.RowKeys)) + for i, key := range request.RowKeys { + placeholders[i] = "?" + args = append(args, key) + } + queryText := buildExploreLogicalSQL(conditions) + ` +) +SELECT entry_key, message_count FROM logical_entries +WHERE entry_key IN (` + strings.Join(placeholders, ",") + `)` + rows, err := e.db.QueryContext(ctx, queryText, args...) + if err != nil { + return nil, fmt.Errorf("count analytical lexical matches: %w", err) + } + defer func() { _ = rows.Close() }() + response := &ExploreMatchCountsResponse{ + Counts: make(map[string]int64, len(request.RowKeys)), CacheRevision: state.Revision(), SearchProvenance: provenance, + } + for _, key := range request.RowKeys { + response.Counts[key] = 0 + } + for rows.Next() { + var key string + var count int64 + if err := rows.Scan(&key, &count); err != nil { + return nil, fmt.Errorf("scan analytical lexical match count: %w", err) + } + response.Counts[key] = count + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate analytical lexical match counts: %w", err) + } + return response, nil +} diff --git a/internal/query/explore_analysis_test.go b/internal/query/explore_analysis_test.go new file mode 100644 index 000000000..7f8e9041d --- /dev/null +++ b/internal/query/explore_analysis_test.go @@ -0,0 +1,217 @@ +package query + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestExploreGroupsAggregatesCompleteLogicalPopulation(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + first := b.AddSourceWithType("archive-a@example.com", "gmail") + second := b.AddSourceWithType("archive-b@example.com", "imap") + b.AddMessage(MessageOpt{SourceID: first, Subject: "One", SizeEstimate: 100}) + b.AddMessage(MessageOpt{SourceID: first, Subject: "Two", SizeEstimate: 200}) + b.AddMessage(MessageOpt{SourceID: second, Subject: "Three", SizeEstimate: 300}) + + result, err := b.BuildEngine().ExploreGroups(context.Background(), ExploreGroupRequest{ + Explore: ExploreRequest{}, Dimension: "source", + Sort: SortSpec{Field: "count", Direction: "desc"}, Page: PageSpec{Limit: 1}, + }) + requirements.NoError(err) + requirements.Len(result.Rows, 1) + assertions.Equal(int64(2), result.TotalCount) + assertions.Equal("1", result.Rows[0].Key) + assertions.Equal(int64(2), result.Rows[0].Count) + assertions.Equal(int64(300), result.Rows[0].EstimatedBytes) + assertions.NotEmpty(result.CacheRevision) +} + +func TestExploreGroupsParticipantLabelsUseDurableIdentityPrecedence(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + alice := b.AddParticipant("alice@example.com", "example.com", "Alice Example") + phone := b.AddPhoneParticipant("+15551234567", "") + email := b.AddParticipant("email-only@example.net", "example.net", "") + stableID := b.AddParticipant("", "", "") + + first := b.AddMessage(MessageOpt{SourceID: source, SenderID: &alice, Subject: "First"}) + b.AddFrom(first, alice, "Alice alias") + b.AddTo(first, alice, "Alice duplicate membership") + second := b.AddMessage(MessageOpt{SourceID: source, Subject: "Second"}) + b.AddTo(second, alice, "") + third := b.AddMessage(MessageOpt{SourceID: source, Subject: "Phone"}) + b.AddTo(third, phone, "") + fourth := b.AddMessage(MessageOpt{SourceID: source, Subject: "Email"}) + b.AddTo(fourth, email, "") + fifth := b.AddMessage(MessageOpt{SourceID: source, Subject: "Stable ID"}) + b.AddTo(fifth, stableID, "") + + result, err := b.BuildEngine().ExploreGroups(context.Background(), ExploreGroupRequest{ + Explore: ExploreRequest{}, Dimension: "participant", + Sort: SortSpec{Field: "key", Direction: "asc"}, Page: PageSpec{Limit: 10}, + }) + requirements.NoError(err) + requirements.Len(result.Rows, 4) + assertions.Equal([]ExploreGroupRow{ + {Key: "1", Label: "Alice Example", Count: 2, LatestAt: result.Rows[0].LatestAt}, + {Key: "2", Label: "+15551234567", Count: 1, LatestAt: result.Rows[1].LatestAt}, + {Key: "3", Label: "email-only@example.net", Count: 1, LatestAt: result.Rows[2].LatestAt}, + {Key: "4", Label: "4", Count: 1, LatestAt: result.Rows[3].LatestAt}, + }, result.Rows) +} + +func TestExploreSelectionStatsCoversPredicateAndExclusions(t *testing.T) { + assertions := assert.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + first := b.AddMessage(MessageOpt{SourceID: source, Subject: "One", SizeEstimate: 100}) + b.AddAttachment(first, 10, "one.txt") + b.AddMessage(MessageOpt{SourceID: source, Subject: "Two", SizeEstimate: 200}) + + result, err := b.BuildEngine().ExploreSelectionStats(context.Background(), ExploreSelectionRequest{ + Explore: ExploreRequest{}, ExcludedKeys: []string{"source:1:message:msg1"}, + }) + require.NoError(t, err) + assertions.Equal(int64(1), result.Count) + assertions.Equal(int64(200), result.EstimatedBytes) + assertions.NotEmpty(result.CacheRevision) +} + +func TestExploreSelectionStatsCountsPerEntryActionSupport(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + gmail := b.AddSourceWithType("archive@example.com", "gmail") + imap := b.AddSourceWithType("other@example.com", "imap") + withFile := b.AddMessage(MessageOpt{SourceID: gmail, Subject: "Exportable"}) + b.AddAttachment(withFile, 10, "message.txt") + b.AddMessage(MessageOpt{SourceID: gmail, Subject: "Open only"}) + b.AddMessage(MessageOpt{SourceID: imap, Subject: "Neither"}) + + result, err := b.BuildEngine().ExploreSelectionStats(context.Background(), ExploreSelectionRequest{ + Explore: ExploreRequest{}, + }) + requirements.NoError(err) + assertions.Equal(int64(3), result.Count) + assertions.Equal(int64(1), result.ExportableCount) + assertions.Equal(int64(2), result.OpenableCount) +} + +func TestExploreSelectionStatsCanResolveExactDeletableMessageIDs(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + first := b.AddMessage(MessageOpt{SourceID: source, Subject: "One"}) + second := b.AddMessage(MessageOpt{SourceID: source, Subject: "Two"}) + + result, err := b.BuildEngine().ExploreSelectionStats(context.Background(), ExploreSelectionRequest{ + Explore: ExploreRequest{}, ExcludedKeys: []string{"source:1:message:msg1"}, + IncludeDeletableMessageIDs: true, + }) + requirements.NoError(err) + assertions.Equal(int64(1), result.Count) + assertions.Equal([]int64{second}, result.DeletableMessageIDs) + assertions.NotContains(result.DeletableMessageIDs, first) +} + +func TestExploreSelectionStatsCanResolveExactRawExportMessageID(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + first := b.AddMessage(MessageOpt{SourceID: source, Subject: "One"}) + b.AddMessage(MessageOpt{SourceID: source, Subject: "Two"}) + + result, err := b.BuildEngine().ExploreSelectionStats(context.Background(), ExploreSelectionRequest{ + Explore: ExploreRequest{}, IncludedKeys: []string{"source:1:message:msg1"}, + }) + requirements.NoError(err) + requirements.NotNil(result.RawExportMessageID) + assertions.Equal(first, *result.RawExportMessageID) + + bulk, err := b.BuildEngine().ExploreSelectionStats(context.Background(), ExploreSelectionRequest{ + Explore: ExploreRequest{}, + }) + requirements.NoError(err) + assertions.Equal(int64(2), bulk.Count) + assertions.Nil(bulk.RawExportMessageID, "bulk selections must not materialize raw message IDs") +} + +func TestExploreFilesReturnsBoundedChronologicalAttachmentFacts(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + older := b.AddMessage(MessageOpt{SourceID: source, Subject: "Older", SentAt: time.Date(2026, 7, 18, 10, 0, 0, 0, time.UTC)}) + b.AddAttachment(older, 10, "older.txt") + newer := b.AddMessage(MessageOpt{SourceID: source, Subject: "Newer", SentAt: time.Date(2026, 7, 18, 11, 0, 0, 0, time.UTC)}) + b.AddAttachmentWithMIME(77, newer, 20, "newer.pdf", "application/pdf") + + result, err := b.BuildEngine().ExploreFiles(context.Background(), ExploreFilesRequest{ + Explore: ExploreRequest{}, Page: PageSpec{Limit: 1}, + }) + requirements.NoError(err) + requirements.Len(result.Files, 1) + assertions.Equal(int64(2), result.TotalCount) + assertions.Equal("newer.pdf", result.Files[0].Filename) + assertions.Equal(int64(20), result.Files[0].Size) + assertions.Equal(int64(77), result.Files[0].ID) + assertions.Equal(newer, result.Files[0].MessageID) + assertions.NotZero(result.Files[0].ConversationID) + assertions.Equal("application/pdf", result.Files[0].MimeType) + assertions.NotEmpty(result.CacheRevision) +} + +func TestExploreFilesUsesDurableAttachmentIdentityForDuplicateMetadataPages(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + message := b.AddMessage(MessageOpt{SourceID: source, Subject: "Duplicates"}) + b.AddAttachmentWithID(41, message, 10, "same.txt") + b.AddAttachmentWithID(42, message, 10, "same.txt") + engine := b.BuildEngine() + + first, err := engine.ExploreFiles(context.Background(), ExploreFilesRequest{ + Explore: ExploreRequest{}, Page: PageSpec{Limit: 1}, + }) + requirements.NoError(err) + requirements.Len(first.Files, 1) + second, err := engine.ExploreFiles(context.Background(), ExploreFilesRequest{ + Explore: ExploreRequest{}, Page: PageSpec{Limit: 1, Offset: 1}, + }) + requirements.NoError(err) + requirements.Len(second.Files, 1) + + assertions.Equal("source:1:message:msg1:file:41", first.Files[0].Key) + assertions.Equal("source:1:message:msg1:file:42", second.Files[0].Key) +} + +func TestExploreMatchCountsBatchesExactLogicalRowCounts(t *testing.T) { + assertions := assert.New(t) + b := NewTestDataBuilder(t) + source := b.AddSourceWithType("+15550000000", messageTypeIMessage) + const conversationID = int64(700) + first := b.AddMessage(MessageOpt{SourceID: source, ConversationID: conversationID, MessageType: messageTypeIMessage, ConversationType: "direct_chat"}) + second := b.AddMessage(MessageOpt{SourceID: source, ConversationID: conversationID, MessageType: messageTypeIMessage, ConversationType: "direct_chat"}) + b.AddMessage(MessageOpt{SourceID: source, ConversationID: conversationID, MessageType: messageTypeIMessage, ConversationType: "direct_chat"}) + + result, err := b.BuildEngine().ExploreMatchCounts(context.Background(), ExploreMatchCountsRequest{ + Explore: ExploreRequest{Search: SearchSpec{ + Mode: SearchFullText, Query: "alpha", CandidateMessageIDs: []int64{first, second}, LexicalIndexRevision: "fts5:test", + }}, + RowKeys: []string{"source:1:conversation:700"}, + }) + require.NoError(t, err) + assertions.Equal(map[string]int64{"source:1:conversation:700": 2}, result.Counts) + assertions.NotEmpty(result.CacheRevision) +} diff --git a/internal/query/explore_benchmark_test.go b/internal/query/explore_benchmark_test.go new file mode 100644 index 000000000..bd42ef15c --- /dev/null +++ b/internal/query/explore_benchmark_test.go @@ -0,0 +1,76 @@ +package query + +import ( + "context" + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/require" +) + +// BenchmarkExploreLargeArchive is the scheduled/manual reference gate for the +// generated mixed archive in buildBenchData. Warm first-page and grouped-view +// operations have a 500 ms reference budget; shared CI compiles and executes a +// bounded smoke iteration instead of enforcing wall-clock timing. +// +// Broad search deliberately transfers at most 10,000 ranked message IDs from +// the authoritative search engine into DuckDB. EntryRow has no message-ID list, +// so 100,000 raw chat fragments project to bounded logical conversation rows +// without an unbounded cross-engine message-ID transfer. +func BenchmarkExploreLargeArchive(b *testing.B) { + engine := buildBenchData(b) + ctx := context.Background() + + b.Run("first_page", func(b *testing.B) { + for b.Loop() { + result, err := engine.Explore(ctx, ExploreRequest{Page: PageSpec{Limit: 50}}) + require.NoError(b, err) + require.LessOrEqual(b, len(result.Rows), 50) + } + }) + + b.Run("nested_group", func(b *testing.B) { + for b.Loop() { + result, err := engine.ExploreGroups(ctx, ExploreGroupRequest{ + Explore: ExploreRequest{Grouping: []GroupSpec{{Dimension: "source"}}}, + Dimension: "domain", Sort: SortSpec{Field: "count", Direction: "desc"}, + Page: PageSpec{Limit: 50}, + }) + require.NoError(b, err) + require.LessOrEqual(b, len(result.Rows), 50) + } + }) + + b.Run("person_files", func(b *testing.B) { + for b.Loop() { + result, err := engine.ExploreFiles(ctx, ExploreFilesRequest{ + Explore: ExploreRequest{Context: Context{ParticipantIDs: []int64{1}}}, + Page: PageSpec{Limit: 50}, + }) + require.NoError(b, err) + require.LessOrEqual(b, len(result.Files), 50) + } + }) + + b.Run("broad_chat_search_projection", func(b *testing.B) { + require.Equal(b, 10_000, MaxExploreCandidateMessageIDs, "the benchmark must track the fixed production transfer contract") + candidateIDs := make([]int64, MaxExploreCandidateMessageIDs) + for index := range candidateIDs { + candidateIDs[index] = int64(index + 1) + } + require.Len(b, candidateIDs, MaxExploreCandidateMessageIDs) + _, leaksArchiveSizedIDs := reflect.TypeFor[EntryRow]().FieldByName("MatchedMessageIDs") + require.False(b, leaksArchiveSizedIDs) + + for b.Loop() { + result, err := engine.Explore(ctx, ExploreRequest{ + Search: SearchSpec{Mode: SearchFullText, Query: "synthetic", CandidateMessageIDs: candidateIDs, + LexicalIndexRevision: fmt.Sprintf("benchmark:%d", MaxExploreCandidateMessageIDs)}, + Page: PageSpec{Limit: 50}, + }) + require.NoError(b, err) + require.LessOrEqual(b, len(result.Rows), 50) + } + }) +} diff --git a/internal/query/explore_models.go b/internal/query/explore_models.go new file mode 100644 index 000000000..ef8e650b2 --- /dev/null +++ b/internal/query/explore_models.go @@ -0,0 +1,246 @@ +package query + +import "time" + +// ExploreRequest is the canonical modality-neutral analytical request. The +// API layer validates the finite grouping/sort catalog before constructing it. +type ExploreRequest struct { + Context Context `json:"context"` + Search SearchSpec `json:"search"` + Grouping []GroupSpec `json:"grouping,omitempty"` + Presentation Presentation `json:"presentation,omitempty"` + Sort []SortSpec `json:"sort,omitempty"` + Page PageSpec `json:"page"` +} + +// Context narrows the archive before logical chat rows are aggregated. +type Context struct { + SourceIDs []int64 `json:"source_ids,omitempty"` + ParticipantIDs []int64 `json:"participant_ids,omitempty"` + Domains []string `json:"domains,omitempty"` + MessageTypes []string `json:"message_types,omitempty"` + After *time.Time `json:"after,omitempty"` + Before *time.Time `json:"before,omitempty"` + Deletion DeletionFilter `json:"deletion,omitempty"` +} + +type DeletionFilter string + +const ( + DeletionAny DeletionFilter = "" + DeletionActive DeletionFilter = "active" + DeletionDeleted DeletionFilter = "deleted" +) + +type SearchMode string + +const ( + // MaxExploreCandidateMessageIDs is the shared cross-engine transfer ceiling + // for ranked search candidates projected by DuckDB. + MaxExploreCandidateMessageIDs = 10_000 + + SearchNone SearchMode = "" + SearchFullText SearchMode = "full_text" + SearchSemantic SearchMode = "semantic" + SearchHybrid SearchMode = "hybrid" +) + +// SearchSpec carries candidates and index provenance resolved by the +// authoritative lexical/vector backends. DuckDB projects those candidates +// into logical entry rows; it does not scan SQLite as a fallback. +type SearchSpec struct { + Mode SearchMode `json:"mode,omitempty"` + Query string `json:"query,omitempty"` + CandidateMessageIDs []int64 `json:"-"` + // LexicalCandidateMessageIDs is the complete exact FTS membership for a + // hybrid query. CandidateMessageIDs remains the bounded fused ranking pool. + LexicalCandidateMessageIDs []int64 `json:"-"` + LexicalIndexRevision string `json:"-"` + VectorGeneration *int64 `json:"-"` + // CandidatePoolSaturated reports that the authoritative search backend + // matched more messages than could be transferred into the analytical + // engine. Consumers must not present counts over this set as exact. + CandidatePoolSaturated bool `json:"-"` +} + +type GroupSpec struct { + Dimension string `json:"dimension"` +} + +type Presentation string + +const ( + PresentationDefault Presentation = "" + PresentationTable Presentation = "table" + PresentationTimeline Presentation = "timeline" + PresentationFiles Presentation = "files" +) + +type SortSpec struct { + Field string `json:"field"` + Direction string `json:"direction"` +} + +type PageSpec struct { + Limit int `json:"limit,omitempty"` + Offset int `json:"offset,omitempty"` +} + +type EntryKind string + +const ( + EntryEmail EntryKind = "email" + EntryConversation EntryKind = "conversation" + EntryEvent EntryKind = "event" + EntryMeeting EntryKind = "meeting" + EntryItem EntryKind = "item" +) + +type MatchSummary struct { + LexicalMatchCount *int64 `json:"lexical_match_count,omitempty"` + StrongestExcerpt string `json:"strongest_excerpt,omitempty"` + SemanticScore *float64 `json:"semantic_score,omitempty"` +} + +type SearchProvenance struct { + LexicalIndexRevision string `json:"lexical_index_revision,omitempty"` + VectorGeneration *int64 `json:"vector_generation,omitempty"` +} + +// EntryRow is one logical archive row. Chat/text rows aggregate messages only +// after Context has been applied; other modalities retain durable item units. +type EntryRow struct { + Key string `json:"key"` + Kind EntryKind `json:"kind"` + AnchorMessageID *int64 `json:"anchor_message_id,omitempty"` + ConversationID *int64 `json:"conversation_id,omitempty"` + OccurredAt time.Time `json:"occurred_at"` + Match MatchSummary `json:"match"` + SourceID int64 `json:"source_id"` + SourceType string `json:"source_type"` + SourceIdentifier string `json:"source_identifier"` + MessageType string `json:"message_type"` + ConversationType string `json:"conversation_type"` + Title string `json:"title"` + Preview string `json:"preview"` + ParticipantIDs []int64 `json:"participant_ids,omitempty"` + ParticipantLabels []string `json:"participant_labels,omitempty"` + // StrongestMatchedMessageID is bounded internal semantic-ranking state. + // It is never serialized and is nil for no-search and full-text rows. + StrongestMatchedMessageID *int64 `json:"-"` + MessageCount int64 `json:"message_count"` + HasAttachments bool `json:"has_attachments"` + AttachmentCount int64 `json:"attachment_count"` + AttachmentSize int64 `json:"attachment_size"` + DeletedFromSource bool `json:"deleted_from_source"` + // CounterpartParticipantID is the smallest participant ID on the entry + // that is not the archive owner (owners resolved through the same + // cluster-aware canon the Relationships ranking uses — see + // buildExploreSQL). It is nil when the owner set is unknown (the + // owner_participants dataset has no rows) or when every participant on + // the entry is the owner: never guessed from participant_ids[0] alone. + CounterpartParticipantID *int64 `json:"counterpart_participant_id,omitempty"` +} + +type ExploreResponse struct { + Rows []EntryRow `json:"rows"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance SearchProvenance `json:"search_provenance"` +} + +// ExploreCoverageRequest selects the message-level population eligible for a +// semantic index. Coverage remains message-level because vector generations +// embed messages even when exploration later aggregates them into logical +// conversation rows. +type ExploreCoverageRequest struct { + Context Context `json:"context"` + AfterMessageID int64 `json:"after_message_id,omitempty"` + Limit int `json:"limit,omitempty"` +} + +type ExploreCoverageResponse struct { + MessageIDs []int64 `json:"message_ids"` + NextAfterMessageID *int64 `json:"next_after_message_id,omitempty"` + CacheRevision string `json:"cache_revision"` +} + +type ExploreGroupRequest struct { + Explore ExploreRequest `json:"explore"` + Dimension string `json:"dimension"` + Sort SortSpec `json:"sort"` + Page PageSpec `json:"page"` +} + +type ExploreGroupRow struct { + Key string `json:"key"` + Label string `json:"label"` + Count int64 `json:"count"` + EstimatedBytes int64 `json:"estimated_bytes"` + LatestAt time.Time `json:"latest_at"` +} + +type ExploreGroupResponse struct { + Rows []ExploreGroupRow `json:"rows"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance SearchProvenance `json:"search_provenance"` +} + +type ExploreSelectionRequest struct { + Explore ExploreRequest `json:"explore"` + IncludedKeys []string `json:"included_keys,omitempty"` + ExcludedKeys []string `json:"excluded_keys,omitempty"` + IncludeDeletableMessageIDs bool `json:"-"` +} + +type ExploreSelectionStats struct { + Count int64 `json:"count"` + EstimatedBytes int64 `json:"estimated_bytes"` + DeletableCount int64 `json:"deletable_count"` + FileCount int64 `json:"file_count"` + ExportableCount int64 `json:"exportable_count"` + OpenableCount int64 `json:"openable_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance SearchProvenance `json:"search_provenance"` + DeletableMessageIDs []int64 `json:"-"` + RawExportMessageID *int64 `json:"-"` +} + +type ExploreFilesRequest struct { + Explore ExploreRequest `json:"explore"` + Page PageSpec `json:"page"` +} + +type ExploreFileFact struct { + ID int64 `json:"id"` + Key string `json:"key"` + EntryKey string `json:"entry_key"` + MessageID int64 `json:"message_id"` + ConversationID int64 `json:"conversation_id"` + OccurredAt time.Time `json:"occurred_at"` + SourceID int64 `json:"source_id"` + SourceIdentifier string `json:"source_identifier"` + Title string `json:"title"` + Filename string `json:"filename"` + MimeType string `json:"mime_type"` + Size int64 `json:"size"` +} + +type ExploreFilesResponse struct { + Files []ExploreFileFact `json:"files"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance SearchProvenance `json:"search_provenance"` +} + +type ExploreMatchCountsRequest struct { + Explore ExploreRequest `json:"explore"` + RowKeys []string `json:"row_keys"` +} + +type ExploreMatchCountsResponse struct { + Counts map[string]int64 `json:"counts"` + CacheRevision string `json:"cache_revision"` + SearchProvenance SearchProvenance `json:"search_provenance"` +} diff --git a/internal/query/explore_test.go b/internal/query/explore_test.go new file mode 100644 index 000000000..b5fcab93a --- /dev/null +++ b/internal/query/explore_test.go @@ -0,0 +1,651 @@ +package query + +import ( + "context" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestExploreKeepsSemanticMatchStateConstantPerLogicalRow(t *testing.T) { + requirements := require.New(t) + b := NewTestDataBuilder(t) + sourceID := b.AddSourceWithType("+15550000000", messageTypeIMessage) + const conversationID = int64(700) + first := b.AddMessage(MessageOpt{ + SourceID: sourceID, ConversationID: conversationID, MessageType: messageTypeIMessage, + ConversationType: "direct_chat", SentAt: time.Date(2026, 7, 18, 10, 0, 0, 0, time.UTC), + }) + second := b.AddMessage(MessageOpt{ + SourceID: sourceID, ConversationID: conversationID, MessageType: messageTypeIMessage, + ConversationType: "direct_chat", SentAt: time.Date(2026, 7, 18, 11, 0, 0, 0, time.UTC), + }) + b.AddMessage(MessageOpt{ + SourceID: sourceID, ConversationID: conversationID, MessageType: messageTypeIMessage, + ConversationType: "direct_chat", SentAt: time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC), + }) + engine := b.BuildEngine() + generation := int64(7) + + tests := []struct { + name string + search SearchSpec + wantStrongest *int64 + }{ + {name: "no search"}, + { + name: "full text", + search: SearchSpec{Mode: SearchFullText, Query: "alpha", CandidateMessageIDs: []int64{first, second}, LexicalIndexRevision: "fts5:test"}, + }, + { + name: "semantic strongest is not chronological anchor", + search: SearchSpec{Mode: SearchSemantic, Query: "alpha", CandidateMessageIDs: []int64{first, second}, VectorGeneration: &generation}, + wantStrongest: &first, + }, + { + name: "hybrid strongest is not chronological anchor", + search: SearchSpec{ + Mode: SearchHybrid, Query: "alpha", CandidateMessageIDs: []int64{first, second}, + LexicalIndexRevision: "fts5:test", VectorGeneration: &generation, + }, + wantStrongest: &first, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := engine.Explore(context.Background(), ExploreRequest{Search: tt.search}) + requirements.NoError(err) + requirements.Len(result.Rows, 1) + assertExploreBoundedStrongestMatch(t, result.Rows[0], tt.wantStrongest) + }) + } +} + +func TestExploreTenThousandFragmentConversationKeepsConstantMatchState(t *testing.T) { + requirements := require.New(t) + b := NewTestDataBuilder(t) + sourceID := b.AddSourceWithType("+15550000000", messageTypeIMessage) + const conversationID = int64(700) + for range 10_000 { + b.AddMessage(MessageOpt{ + SourceID: sourceID, ConversationID: conversationID, MessageType: messageTypeIMessage, + ConversationType: "direct_chat", + }) + } + + result, err := b.BuildEngine().Explore(context.Background(), ExploreRequest{}) + requirements.NoError(err) + requirements.Len(result.Rows, 1) + requirements.Equal(int64(10_000), result.Rows[0].MessageCount) + assertExploreBoundedStrongestMatch(t, result.Rows[0], nil) +} + +func TestExploreCoveragePagesExactLiveMessagesWithoutDuplicates(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + b := NewTestDataBuilder(t) + selected := b.AddSource("selected@example.com") + other := b.AddSource("other@example.com") + deletedAt := time.Date(2026, 7, 19, 8, 0, 0, 0, time.UTC) + + const liveCount = 600 + for range liveCount { + b.AddMessage(MessageOpt{SourceID: selected, MessageType: "email"}) + } + b.AddMessage(MessageOpt{SourceID: selected, MessageType: "email", InternalDeletedAt: &deletedAt}) + b.AddMessage(MessageOpt{SourceID: selected, MessageType: "email", DeletedFromSourceAt: &deletedAt}) + b.AddMessage(MessageOpt{SourceID: other, MessageType: "email"}) + engine := b.BuildEngine() + + var got []int64 + var after int64 + pageCalls := 0 + for { + page, err := engine.ExploreCoverage(context.Background(), ExploreCoverageRequest{ + Context: Context{SourceIDs: []int64{selected}}, + AfterMessageID: after, + Limit: 128, + }) + require.NoError(err) + pageCalls++ + assert.LessOrEqual(len(page.MessageIDs), 128) + got = append(got, page.MessageIDs...) + if page.NextAfterMessageID == nil { + break + } + assert.Greater(*page.NextAfterMessageID, after) + after = *page.NextAfterMessageID + } + + assert.Greater(pageCalls, 1) + require.Len(got, liveCount) + for i, id := range got { + assert.Equal(int64(i+1), id) + } +} + +func TestExploreCoverageHonorsCancellation(t *testing.T) { + b := NewTestDataBuilder(t) + source := b.AddSource("selected@example.com") + b.AddMessage(MessageOpt{SourceID: source}) + engine := b.BuildEngine() + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + _, err := engine.ExploreCoverage(ctx, ExploreCoverageRequest{Limit: 10}) + require.ErrorIs(t, err, context.Canceled) +} + +func assertExploreBoundedStrongestMatch(t *testing.T, row EntryRow, want *int64) { + t.Helper() + assertions := assert.New(t) + rowType := reflect.TypeFor[EntryRow]() + _, hasArchiveSizedIDs := rowType.FieldByName("MatchedMessageIDs") + assertions.False(hasArchiveSizedIDs, "logical rows must not retain every constituent message ID") + assertions.Equal(want, row.StrongestMatchedMessageID) +} + +func TestExploreLogicalRowUnitsAndStableArchiveOrdering(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + b := NewTestDataBuilder(t) + gmail := b.AddSourceWithType("archive-a@example.com", "gmail") + maildir := b.AddSourceWithType("archive-b@example.com", "imap") + chat := b.AddSourceWithType("+15550000000", "imessage") + calendar := b.AddSourceWithType("calendar@example.com", "google_calendar") + meeting := b.AddSourceWithType("meetings@example.com", "granola") + other := b.AddSourceWithType("items@example.com", "durable_source") + alice := b.AddParticipant("alice@example.com", "example.com", "Alice") + bob := b.AddParticipant("bob@example.com", "example.com", "Bob") + phone := b.AddPhoneParticipant("+15550000001", "Test Contact") + + equalTime := time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC) + firstEmail := b.AddMessage(MessageOpt{SourceID: gmail, Subject: "Archive A", SentAt: equalTime, MessageType: "email"}) + b.AddFrom(firstEmail, alice, "Alice") + secondEmail := b.AddMessage(MessageOpt{SourceID: maildir, Subject: "Archive B", SentAt: equalTime, MessageType: "email"}) + b.AddFrom(secondEmail, bob, "Bob") + b.AddAttachment(secondEmail, 42, "agenda.pdf") + + const chatConversationID = int64(900) + for i := range 10_000 { + messageID := b.AddMessage(MessageOpt{ + SourceID: chat, ConversationID: chatConversationID, + ConversationType: "direct_chat", ConversationTitle: "Synthetic chat", + MessageType: "imessage", SenderID: &phone, + SentAt: equalTime.Add(-time.Duration(i+1) * time.Second), + }) + b.AddFrom(messageID, phone, "Test Contact") + } + + event := b.AddMessage(MessageOpt{SourceID: calendar, MessageType: "calendar_event", ConversationType: "calendar", Subject: "Planning", SentAt: equalTime.Add(-time.Hour)}) + b.AddFrom(event, alice, "Alice") + note := b.AddMessage(MessageOpt{SourceID: meeting, MessageType: "meeting_transcript", ConversationType: "meeting", Subject: "Weekly notes", SentAt: equalTime.Add(-2 * time.Hour)}) + b.AddFrom(note, bob, "Bob") + item := b.AddMessage(MessageOpt{SourceID: other, MessageType: "bookmark", ConversationType: "items", Subject: "Durable item", SentAt: equalTime.Add(-3 * time.Hour)}) + b.AddFrom(item, alice, "Alice") + deletedAt := equalTime.Add(-4 * time.Hour) + deleted := b.AddMessage(MessageOpt{SourceID: gmail, MessageType: "email", Subject: "Deleted at source", SentAt: deletedAt, DeletedFromSourceAt: &deletedAt}) + b.AddFrom(deleted, alice, "Alice") + + engine := b.BuildEngine() + response, err := engine.Explore(context.Background(), ExploreRequest{Page: PageSpec{Limit: 20}}) + require.NoError(err) + require.Len(response.Rows, 7) + assert.NotEmpty(response.CacheRevision) + assert.Equal([]EntryKind{ + EntryEmail, EntryEmail, EntryConversation, EntryEvent, EntryMeeting, EntryItem, EntryEmail, + }, entryKinds(response.Rows)) + assert.Equal("Archive A", response.Rows[0].Title) + assert.Equal("Archive B", response.Rows[1].Title) + assert.NotEqual(response.Rows[0].Key, response.Rows[1].Key, "equal timestamps across archives need distinct stable keys") + assert.Equal(int64(10_000), response.Rows[2].MessageCount) + assert.True(response.Rows[1].HasAttachments) + assert.Equal(int64(1), response.Rows[1].AttachmentCount) + assert.True(response.Rows[6].DeletedFromSource) +} + +func TestExploreFlattensTitleOnlyWhenFallenBackToSnippet(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + b := NewTestDataBuilder(t) + gmail := b.AddSourceWithType("archive-a@example.com", "gmail") + when := time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC) + withSubject := b.AddMessage(MessageOpt{ + SourceID: gmail, Subject: "Re: 2 ** 3 == 8?", Snippet: "### Meeting notes\n- Action item", SentAt: when, + }) + alice := b.AddParticipant("alice@example.com", "example.com", "Alice") + b.AddFrom(withSubject, alice, "Alice") + fallback := b.AddMessage(MessageOpt{ + SourceID: gmail, Subject: "", Snippet: "### Meeting notes\n- Action item", SentAt: when.Add(-time.Minute), + }) + b.AddFrom(fallback, alice, "Alice") + + engine := b.BuildEngine() + response, err := engine.Explore(context.Background(), ExploreRequest{Page: PageSpec{Limit: 20}}) + require.NoError(err) + require.Len(response.Rows, 2) + assert.Equal("Re: 2 ** 3 == 8?", response.Rows[0].Title, "a real subject with markdown-like characters must not be altered") + assert.Equal("Meeting notes Action item", response.Rows[1].Title, "a title that fell back to the snippet is still flattened") +} + +func TestExploreAppliesContextBeforeChatAggregation(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + b := NewTestDataBuilder(t) + chat := b.AddSourceWithType("+15550000000", "sms") + alice := b.AddParticipant("alice@example.com", "example.com", "Alice") + bob := b.AddParticipant("bob@other.test", "other.test", "Bob") + const conversationID = int64(901) + for i := range 3 { + sender := alice + if i == 2 { + sender = bob + } + id := b.AddMessage(MessageOpt{SourceID: chat, ConversationID: conversationID, ConversationType: "group_chat", MessageType: "sms", SenderID: &sender}) + b.AddFrom(id, sender, "") + } + + engine := b.BuildEngine() + response, err := engine.Explore(context.Background(), ExploreRequest{ + Context: Context{ParticipantIDs: []int64{alice}}, Page: PageSpec{Limit: 10}, + }) + require.NoError(err) + require.Len(response.Rows, 1) + assert.Equal(EntryConversation, response.Rows[0].Kind) + assert.Equal(int64(2), response.Rows[0].MessageCount, "chat facts must be computed after context filtering") +} + +func TestExploreKeepsDurableCallsAsIndividualItemsInsideChatConversations(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + b := NewTestDataBuilder(t) + voice := b.AddSourceWithType("voice@example.com", "google_voice") + caller := b.AddPhoneParticipant("+15550000002", "Test Caller") + const conversationID = int64(902) + for _, messageType := range []string{ + "google_voice_call", + "google_voice_voicemail", + "synctech_sms_call", + } { + b.AddMessage(MessageOpt{ + SourceID: voice, ConversationID: conversationID, + ConversationType: "direct_chat", MessageType: messageType, + SenderID: &caller, + }) + } + + response, err := b.BuildEngine().Explore(context.Background(), ExploreRequest{}) + require.NoError(err) + require.Len(response.Rows, 3, "durable call and voicemail records must not collapse into a chat row") + for _, row := range response.Rows { + assert.Equal(EntryItem, row.Kind) + assert.Equal(int64(1), row.MessageCount) + } +} + +func TestExploreAggregatesRCSFragmentsIntoOneConversation(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + b := NewTestDataBuilder(t) + rcsSource := b.AddSourceWithType("rcs@example.com", "synctech_sms") + sender := b.AddPhoneParticipant("+15550000006", "RCS Contact") + const conversationID = int64(904) + for range 3 { + b.AddMessage(MessageOpt{ + SourceID: rcsSource, ConversationID: conversationID, + ConversationType: "direct_chat", ConversationTitle: "RCS chat", + MessageType: "rcs", SenderID: &sender, + }) + } + + response, err := b.BuildEngine().Explore(context.Background(), ExploreRequest{}) + require.NoError(err) + require.Len(response.Rows, 1) + assert.Equal(EntryConversation, response.Rows[0].Kind) + assert.Equal(int64(3), response.Rows[0].MessageCount) +} + +func TestExploreIncludesDirectSendersAndConversationMembership(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + b := NewTestDataBuilder(t) + beeper := b.AddSourceWithType("beeper@example.com", "beeper") + alice := b.AddPhoneParticipant("+15550000003", "Alice") + bob := b.AddPhoneParticipant("+15550000004", "Bob") + carol := b.AddParticipant("carol@members.example", "members.example", "Carol") + const conversationID = int64(903) + for i := range 2 { + sender := alice + if i == 1 { + sender = bob + } + b.AddMessage(MessageOpt{ + SourceID: beeper, ConversationID: conversationID, + ConversationType: "direct_chat", ConversationTitle: "Beeper chat", + MessageType: "beeper", SenderID: &sender, + }) + } + b.AddConversationParticipant(conversationID, alice) + b.AddConversationParticipant(conversationID, bob) + b.AddConversationParticipant(conversationID, carol) + + response, err := b.BuildEngine().Explore(context.Background(), ExploreRequest{}) + require.NoError(err) + require.Len(response.Rows, 1) + assert.Equal([]int64{alice, bob, carol}, response.Rows[0].ParticipantIDs) + assert.Equal([]string{"Alice", "Bob", "Carol"}, response.Rows[0].ParticipantLabels) + + memberResponse, err := b.BuildEngine().Explore(context.Background(), ExploreRequest{ + Context: Context{ParticipantIDs: []int64{carol}}, + }) + require.NoError(err) + require.Len(memberResponse.Rows, 1, "conversation membership must participate in person context") + + domainResponse, err := b.BuildEngine().Explore(context.Background(), ExploreRequest{ + Context: Context{Domains: []string{"members.example"}}, + }) + require.NoError(err) + require.Len(domainResponse.Rows, 1, "conversation membership domains must participate in domain context") +} + +func TestExploreReturnsSearchIndexProvenance(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + b := NewTestDataBuilder(t) + sourceID := b.AddSource("archive@example.com") + messageID := b.AddMessage(MessageOpt{SourceID: sourceID, Subject: "Needle"}) + engine := b.BuildEngine() + generation := int64(17) + + tests := []struct { + name string + search SearchSpec + wantLexical string + wantVector *int64 + }{ + {name: "none", search: SearchSpec{}}, + { + name: "full text", + search: SearchSpec{ + Mode: SearchFullText, Query: "needle", CandidateMessageIDs: []int64{messageID}, + LexicalIndexRevision: "fts5:23", + }, + wantLexical: "fts5:23", + }, + { + name: "semantic", + search: SearchSpec{ + Mode: SearchSemantic, Query: "needle", CandidateMessageIDs: []int64{messageID}, + VectorGeneration: &generation, + }, + wantVector: &generation, + }, + { + name: "hybrid", + search: SearchSpec{ + Mode: SearchHybrid, Query: "needle", CandidateMessageIDs: []int64{messageID}, + LexicalIndexRevision: "fts5:23", VectorGeneration: &generation, + }, + wantLexical: "fts5:23", wantVector: &generation, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + response, err := engine.Explore(context.Background(), ExploreRequest{Search: tt.search}) + require.NoError(err) + assert.Equal(tt.wantLexical, response.SearchProvenance.LexicalIndexRevision) + assert.Equal(tt.wantVector, response.SearchProvenance.VectorGeneration) + }) + } +} + +func TestExploreRejectsModeInapplicableSearchFields(t *testing.T) { + b := NewTestDataBuilder(t) + sourceID := b.AddSource("archive@example.com") + messageID := b.AddMessage(MessageOpt{SourceID: sourceID, Subject: "Needle"}) + engine := b.BuildEngine() + generation := int64(17) + + tests := []struct { + name string + search SearchSpec + }{ + {name: "none query", search: SearchSpec{Query: "needle"}}, + {name: "none explicit empty candidates", search: SearchSpec{CandidateMessageIDs: []int64{}}}, + {name: "none nonempty candidates", search: SearchSpec{CandidateMessageIDs: []int64{messageID}}}, + {name: "none lexical revision", search: SearchSpec{LexicalIndexRevision: "fts5:23"}}, + {name: "none vector generation", search: SearchSpec{VectorGeneration: &generation}}, + { + name: "full text vector generation", + search: SearchSpec{ + Mode: SearchFullText, Query: "needle", CandidateMessageIDs: []int64{messageID}, + LexicalIndexRevision: "fts5:23", VectorGeneration: &generation, + }, + }, + { + name: "semantic lexical revision", + search: SearchSpec{ + Mode: SearchSemantic, Query: "needle", CandidateMessageIDs: []int64{messageID}, + LexicalIndexRevision: "fts5:23", VectorGeneration: &generation, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := engine.Explore(context.Background(), ExploreRequest{Search: tt.search}) + require.ErrorIs(t, err, ErrInvalidExploreRequest) + }) + } +} + +func TestExploreEmptyAnalyticsDirReturnsTypedAbsentCache(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + _, err := (&DuckDBEngine{}).Explore(context.Background(), ExploreRequest{}) + require.Error(err) + require.ErrorIs(err, ErrCacheUnavailable) + var unavailable *CacheUnavailableError + require.ErrorAs(err, &unavailable) + assert.Equal(CacheAbsent, unavailable.Readiness) +} + +func TestExploreRejectsUnresolvedSearchCandidatesAndProvenance(t *testing.T) { + b := NewTestDataBuilder(t) + sourceID := b.AddSource("archive@example.com") + b.AddMessage(MessageOpt{SourceID: sourceID, Subject: "Needle"}) + engine := b.BuildEngine() + generation := int64(23) + + tests := []struct { + name string + search SearchSpec + }{ + { + name: "full text candidates unresolved", + search: SearchSpec{Mode: SearchFullText, + LexicalIndexRevision: "fts5:24"}, + }, + { + name: "full text lexical revision unresolved", + search: SearchSpec{Mode: SearchFullText, + CandidateMessageIDs: []int64{}}, + }, + { + name: "semantic candidates unresolved", + search: SearchSpec{Mode: SearchSemantic, + VectorGeneration: &generation}, + }, + { + name: "semantic generation unresolved", + search: SearchSpec{Mode: SearchSemantic, + CandidateMessageIDs: []int64{}}, + }, + { + name: "hybrid candidates unresolved", + search: SearchSpec{Mode: SearchHybrid, + LexicalIndexRevision: "fts5:24", VectorGeneration: &generation}, + }, + { + name: "hybrid lexical revision unresolved", + search: SearchSpec{Mode: SearchHybrid, + CandidateMessageIDs: []int64{}, VectorGeneration: &generation}, + }, + { + name: "hybrid generation unresolved", + search: SearchSpec{Mode: SearchHybrid, + CandidateMessageIDs: []int64{}, LexicalIndexRevision: "fts5:24"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := engine.Explore(context.Background(), ExploreRequest{Search: tt.search}) + require.ErrorIs(t, err, ErrInvalidExploreRequest) + }) + } +} + +func TestExploreResolvedEmptySearchCandidatesReturnNoRows(t *testing.T) { + b := NewTestDataBuilder(t) + sourceID := b.AddSource("archive@example.com") + b.AddMessage(MessageOpt{SourceID: sourceID, Subject: "Needle"}) + engine := b.BuildEngine() + generation := int64(23) + + searches := []SearchSpec{ + {Mode: SearchFullText, CandidateMessageIDs: []int64{}, LexicalIndexRevision: "fts5:24"}, + {Mode: SearchSemantic, CandidateMessageIDs: []int64{}, VectorGeneration: &generation}, + {Mode: SearchHybrid, CandidateMessageIDs: []int64{}, LexicalIndexRevision: "fts5:24", VectorGeneration: &generation}, + } + for _, search := range searches { + response, err := engine.Explore(context.Background(), ExploreRequest{Search: search}) + require.NoError(t, err) + assert.Empty(t, response.Rows) + assert.Zero(t, response.TotalCount) + } +} + +func TestExploreRejectsUnsupportedPresentationAndSort(t *testing.T) { + b := NewTestDataBuilder(t) + sourceID := b.AddSource("archive@example.com") + b.AddMessage(MessageOpt{SourceID: sourceID}) + engine := b.BuildEngine() + + tests := []struct { + name string + request ExploreRequest + }{ + {name: "timeline presentation", request: ExploreRequest{Presentation: PresentationTimeline}}, + {name: "ascending date", request: ExploreRequest{Sort: []SortSpec{{Field: "sent_at", Direction: "asc"}}}}, + {name: "unsupported field", request: ExploreRequest{Sort: []SortSpec{{Field: "message_count", Direction: "desc"}}}}, + {name: "multiple sorts", request: ExploreRequest{Sort: []SortSpec{{Field: "sent_at", Direction: "desc"}, {Field: "source_id", Direction: "asc"}}}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := engine.Explore(context.Background(), tt.request) + require.ErrorIs(t, err, ErrInvalidExploreRequest) + }) + } +} + +func TestExploreAcceptsExplicitTableDateDescending(t *testing.T) { + b := NewTestDataBuilder(t) + sourceID := b.AddSource("archive@example.com") + b.AddMessage(MessageOpt{SourceID: sourceID}) + + response, err := b.BuildEngine().Explore(context.Background(), ExploreRequest{ + Presentation: Presentation("table"), + Sort: []SortSpec{{Field: "sent_at", Direction: "desc"}}, + }) + require.NoError(t, err) + assert.Len(t, response.Rows, 1) +} + +func TestExplorePreservesTotalCountBeyondLastPage(t *testing.T) { + b := NewTestDataBuilder(t) + sourceID := b.AddSource("archive@example.com") + b.AddMessage(MessageOpt{SourceID: sourceID, Subject: "Only row"}) + engine := b.BuildEngine() + + response, err := engine.Explore(context.Background(), ExploreRequest{Page: PageSpec{Limit: 1, Offset: 10}}) + require.NoError(t, err) + assert.Empty(t, response.Rows) + assert.Equal(t, int64(1), response.TotalCount) +} + +func entryKinds(rows []EntryRow) []EntryKind { + kinds := make([]EntryKind, len(rows)) + for i := range rows { + kinds[i] = rows[i].Kind + } + return kinds +} + +// TestExploreCounterpartParticipantIDExcludesOwner verifies +// counterpart_participant_id is the smallest NON-owner participant on the +// entry, not simply participant_ids[0]: the owner is added first (so it has +// the smallest raw ID) and would be picked by that naive heuristic. +func TestExploreCounterpartParticipantIDExcludesOwner(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + b.AddOwnerParticipant(srcID, ownerID) + counterpartA := b.AddParticipant("alice@example.com", "example.com", "Alice") + counterpartB := b.AddParticipant("bob@example.com", "example.com", "Bob") + + msgID := b.AddMessage(MessageOpt{SourceID: srcID, IsFromMe: true}) + b.AddFrom(msgID, ownerID, "Owner") + b.AddTo(msgID, counterpartA, "Alice") + b.AddTo(msgID, counterpartB, "Bob") + + response, err := b.BuildEngine().Explore(context.Background(), ExploreRequest{}) + require.NoError(err) + require.Len(response.Rows, 1) + require.NotNil(response.Rows[0].CounterpartParticipantID) + assert.Equal(min(counterpartA, counterpartB), *response.Rows[0].CounterpartParticipantID) +} + +// TestExploreCounterpartParticipantIDNilWhenOwnerOnly verifies an entry whose +// only participant is the owner (e.g. a self-addressed note) yields a nil +// counterpart rather than falling back to the owner's own ID. +func TestExploreCounterpartParticipantIDNilWhenOwnerOnly(t *testing.T) { + require := require.New(t) + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + b.AddOwnerParticipant(srcID, ownerID) + + msgID := b.AddMessage(MessageOpt{SourceID: srcID, IsFromMe: true}) + b.AddFrom(msgID, ownerID, "Owner") + b.AddTo(msgID, ownerID, "Owner") + + response, err := b.BuildEngine().Explore(context.Background(), ExploreRequest{}) + require.NoError(err) + require.Len(response.Rows, 1) + assert.Nil(t, response.Rows[0].CounterpartParticipantID) +} + +// TestExploreCounterpartParticipantIDNilWhenOwnerUnknown verifies that when +// no owner_participants rows exist at all (the owner set is unknown), the +// column is nil rather than guessing the smallest participant ID overall — +// the exact heuristic this field replaces. +func TestExploreCounterpartParticipantIDNilWhenOwnerUnknown(t *testing.T) { + require := require.New(t) + b := NewTestDataBuilder(t) + srcID := b.AddSource("archive@example.com") + senderID := b.AddParticipant("alice@example.com", "example.com", "Alice") + recipientID := b.AddParticipant("bob@example.com", "example.com", "Bob") + + msgID := b.AddMessage(MessageOpt{SourceID: srcID}) + b.AddFrom(msgID, senderID, "Alice") + b.AddTo(msgID, recipientID, "Bob") + + response, err := b.BuildEngine().Explore(context.Background(), ExploreRequest{}) + require.NoError(err) + require.Len(response.Rows, 1) + assert.Nil(t, response.Rows[0].CounterpartParticipantID) +} diff --git a/internal/query/files.go b/internal/query/files.go new file mode 100644 index 000000000..4d116b0fe --- /dev/null +++ b/internal/query/files.go @@ -0,0 +1,365 @@ +package query + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" +) + +const maxFileSearchLimit = 500 + +// FileMIMEFamily is a stable, presentation-oriented MIME grouping. +type FileMIMEFamily string + +const ( + FileMIMEImage FileMIMEFamily = "image" + FileMIMEPDF FileMIMEFamily = "pdf" + FileMIMEAudio FileMIMEFamily = "audio" + FileMIMEVideo FileMIMEFamily = "video" + FileMIMEText FileMIMEFamily = "text" + FileMIMEDocument FileMIMEFamily = "document" + FileMIMEArchive FileMIMEFamily = "archive" + FileMIMEOther FileMIMEFamily = "other" +) + +var fileMIMEFamilies = map[FileMIMEFamily]struct{}{ + FileMIMEImage: {}, FileMIMEPDF: {}, FileMIMEAudio: {}, FileMIMEVideo: {}, + FileMIMEText: {}, FileMIMEDocument: {}, FileMIMEArchive: {}, FileMIMEOther: {}, +} + +type FileSearchRequest struct { + Explore ExploreRequest `json:"explore"` + FilenameQuery string `json:"filename_query,omitempty"` + MIMEFamilies []FileMIMEFamily `json:"mime_families,omitempty"` + Sort SortSpec `json:"sort"` + Page PageSpec `json:"page"` +} + +type FileGroupRequest struct { + Explore ExploreRequest `json:"explore"` + FilenameQuery string `json:"filename_query,omitempty"` + MIMEFamilies []FileMIMEFamily `json:"mime_families,omitempty"` + Dimension string `json:"dimension"` + Sort SortSpec `json:"sort"` + Page PageSpec `json:"page"` +} + +type FileRow struct { + ID int64 `json:"id"` + Key string `json:"key"` + EntryKey string `json:"entry_key"` + MessageID int64 `json:"message_id"` + ConversationID int64 `json:"conversation_id"` + OccurredAt time.Time `json:"occurred_at"` + SourceID int64 `json:"source_id"` + SourceType string `json:"source_type"` + SourceIdentifier string `json:"source_identifier"` + ContainingTitle string `json:"containing_title"` + Filename string `json:"filename"` + MimeType string `json:"mime_type"` + MIMEFamily FileMIMEFamily `json:"mime_family"` + Size int64 `json:"size_bytes"` + ParticipantIDs []int64 `json:"participant_ids,omitempty"` + ParticipantLabels []string `json:"participant_labels,omitempty"` + ParticipantDomains []string `json:"participant_domains,omitempty"` +} + +type FileSearchResponse struct { + Files []FileRow `json:"files"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance SearchProvenance `json:"search_provenance"` +} + +// SearchFiles projects attachment facts only from the committed analytical cache. +func (e *DuckDBEngine) SearchFiles(ctx context.Context, request FileSearchRequest) (*FileSearchResponse, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + if request.Page.Offset < 0 || request.Page.Limit < 0 || request.Page.Limit > maxFileSearchLimit { + return nil, fmt.Errorf("%w: file page is outside the supported range", ErrInvalidExploreRequest) + } + provenance, err := validateResolvedSearch(request.Explore.Search) + if err != nil { + return nil, err + } + order, err := fileSearchOrder(request.Sort) + if err != nil { + return nil, err + } + if err := validateFileMIMEFamilies(request.MIMEFamilies); err != nil { + return nil, err + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + // The acquired cache read lock is the authority for both schema refresh and + // query execution. A long-running server may otherwise retain views bound to + // the previous attachment schema after an atomic cache publication. + e.ensureFreshOptionalCols() + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + population, args := filteredFilePopulationSQL(request.Explore, request.FilenameQuery, request.MIMEFamilies) + limit := request.Page.Limit + if limit == 0 { + limit = 100 + } + args = append(args, limit, request.Page.Offset) + rows, err := e.db.QueryContext(ctx, fileSearchSQL(population, order), args...) + if err != nil { + return nil, fmt.Errorf("search analytical files: %w", err) + } + defer func() { _ = rows.Close() }() + response := &FileSearchResponse{Files: make([]FileRow, 0), CacheRevision: state.Revision(), SearchProvenance: provenance} + for rows.Next() { + var row FileRow + var rawSnippet, participantIDsJSON, participantLabelsJSON, participantDomainsJSON string + if err := rows.Scan( + &row.ID, &row.Key, &row.EntryKey, &row.MessageID, &row.ConversationID, + &row.OccurredAt, &row.SourceID, &row.SourceType, &row.SourceIdentifier, + &row.ContainingTitle, &rawSnippet, &row.Filename, &row.MimeType, &row.MIMEFamily, &row.Size, + &participantIDsJSON, &participantLabelsJSON, &participantDomainsJSON, &response.TotalCount, + ); err != nil { + return nil, fmt.Errorf("scan analytical file: %w", err) + } + if err := json.Unmarshal([]byte(participantIDsJSON), &row.ParticipantIDs); err != nil { + return nil, fmt.Errorf("decode file participant IDs: %w", err) + } + if err := json.Unmarshal([]byte(participantLabelsJSON), &row.ParticipantLabels); err != nil { + return nil, fmt.Errorf("decode file participant labels: %w", err) + } + if err := json.Unmarshal([]byte(participantDomainsJSON), &row.ParticipantDomains); err != nil { + return nil, fmt.Errorf("decode file participant domains: %w", err) + } + if row.ContainingTitle == rawSnippet { + row.ContainingTitle = FlattenSnippet(row.ContainingTitle) + } + response.Files = append(response.Files, row) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate analytical files: %w", err) + } + return response, nil +} + +// GroupFiles aggregates the same filtered attachment population returned by +// SearchFiles. Counts and estimated bytes therefore describe files, not their +// containing messages. +func (e *DuckDBEngine) GroupFiles(ctx context.Context, request FileGroupRequest) (*ExploreGroupResponse, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + if err := validateExploreAnalysisPage(request.Page, maxFileSearchLimit); err != nil { + return nil, err + } + provenance, err := validateResolvedSearch(request.Explore.Search) + if err != nil { + return nil, err + } + if err := validateFileMIMEFamilies(request.MIMEFamilies); err != nil { + return nil, err + } + groupExpr, labelExpr, fromSuffix, whereSuffix, err := fileGroupExpressions(request.Dimension) + if err != nil { + return nil, err + } + order, err := exploreGroupOrder(request.Sort) + if err != nil { + return nil, err + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + e.ensureFreshOptionalCols() + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + population, args := filteredFilePopulationSQL(request.Explore, request.FilenameQuery, request.MIMEFamilies) + limit := request.Page.Limit + if limit == 0 { + limit = defaultExploreLimit + } + queryText := population + ` +, grouped AS ( + SELECT ` + groupExpr + ` AS group_key, ` + labelExpr + ` AS group_label, + COUNT(*)::BIGINT AS group_count, + COALESCE(SUM(size), 0)::BIGINT AS estimated_bytes, + MAX(occurred_at) AS latest_at + FROM file_population` + fromSuffix + whereSuffix + ` + GROUP BY ` + groupExpr + ` +), counted AS ( + SELECT *, COUNT(*) OVER () AS total_count FROM grouped +) +SELECT group_key, group_label, group_count, estimated_bytes, latest_at, total_count +FROM counted ORDER BY ` + order + ` LIMIT ? OFFSET ?` + args = append(args, limit, request.Page.Offset) + rows, err := e.db.QueryContext(ctx, queryText, args...) + if err != nil { + return nil, fmt.Errorf("group analytical files: %w", err) + } + defer func() { _ = rows.Close() }() + response := &ExploreGroupResponse{ + Rows: make([]ExploreGroupRow, 0), CacheRevision: state.Revision(), SearchProvenance: provenance, + } + for rows.Next() { + var row ExploreGroupRow + if err := rows.Scan(&row.Key, &row.Label, &row.Count, &row.EstimatedBytes, &row.LatestAt, &response.TotalCount); err != nil { + return nil, fmt.Errorf("scan analytical file group: %w", err) + } + response.Rows = append(response.Rows, row) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate analytical file groups: %w", err) + } + return response, nil +} + +func validateFileMIMEFamilies(families []FileMIMEFamily) error { + for _, family := range families { + if _, ok := fileMIMEFamilies[family]; !ok { + return fmt.Errorf("%w: unknown MIME family %q", ErrInvalidExploreRequest, family) + } + } + return nil +} + +func filteredFilePopulationSQL( + explore ExploreRequest, + filenameQuery string, + mimeFamilies []FileMIMEFamily, +) (string, []any) { + conditions, args := buildExploreConditions(explore) + fileConditions := []string{"true"} + if query := strings.TrimSpace(filenameQuery); query != "" { + fileConditions = append(fileConditions, "contains(lower(filename), lower(?))") + args = append(args, query) + } + if len(mimeFamilies) > 0 { + parts := make([]string, len(mimeFamilies)) + for i, family := range mimeFamilies { + parts[i] = "?" + args = append(args, family) + } + fileConditions = append(fileConditions, "mime_family IN ("+strings.Join(parts, ",")+")") + } + return filePopulationSQL(conditions, strings.Join(fileConditions, " AND ")), args +} + +func fileGroupExpressions(dimension string) (key, label, fromSuffix, whereSuffix string, err error) { + switch dimension { + case "source": + return "CAST(source_id AS VARCHAR)", "arg_max(source_identifier, occurred_at)", "", "", nil + case "participant": + participantLabel := `COALESCE( + NULLIF(TRIM(grouped_participant.display_name), ''), + NULLIF(TRIM(grouped_participant.phone_number), ''), + NULLIF(TRIM(grouped_participant.email_address), ''), + CAST(grouped_participant.id AS VARCHAR) + )` + return "CAST(group_value AS VARCHAR)", "arg_max(" + participantLabel + ", occurred_at)", + " CROSS JOIN UNNEST(file_participant_ids) AS grouped(group_value)" + + " JOIN participants AS grouped_participant ON grouped_participant.id = group_value", "", nil + case "domain": + return "group_value", "group_value", ", UNNEST(file_participant_domains) AS grouped(group_value)", + " WHERE group_value <> ''", nil + case messageTypeDimension: + return messageTypeDimension, messageTypeDimension, "", "", nil + case "kind": + return "'file'", "'file'", "", "", nil + case "year": + return "strftime(occurred_at, '%Y')", "strftime(occurred_at, '%Y')", "", "", nil + case timeGranularityMonth: + return "strftime(occurred_at, '%Y-%m')", "strftime(occurred_at, '%Y-%m')", "", "", nil + default: + return "", "", "", "", fmt.Errorf("%w: unknown file group dimension %q", ErrInvalidExploreRequest, dimension) + } +} + +func fileSearchOrder(sort SortSpec) (string, error) { + if sort.Field == "" { + sort = SortSpec{Field: "occurred_at", Direction: sortDirectionDesc} + } + if sort.Direction != sortDirectionAsc && sort.Direction != sortDirectionDesc { + return "", fmt.Errorf("%w: invalid file sort direction %q", ErrInvalidExploreRequest, sort.Direction) + } + direction := strings.ToUpper(sort.Direction) + switch sort.Field { + case "occurred_at": + return "occurred_at " + direction + ", message_id ASC, attachment_id ASC", nil + case "filename": + return "lower(filename) " + direction + ", filename " + direction + ", occurred_at DESC, attachment_id ASC", nil + case "size": + return "size " + direction + ", lower(filename) ASC, occurred_at DESC, attachment_id ASC", nil + default: + return "", fmt.Errorf("%w: unknown file sort field %q", ErrInvalidExploreRequest, sort.Field) + } +} + +func filePopulationSQL(exploreConditions, fileConditions string) string { + return ` +WITH selected AS ( + SELECT * FROM analytical_entries WHERE ` + exploreConditions + ` +), classified AS ( + SELECT + a.attachment_id, a.message_id, COALESCE(a.size, 0)::BIGINT AS size, + COALESCE(a.filename, '') AS filename, COALESCE(a.mime_type, '') AS mime_type, + CASE + WHEN lower(a.mime_type) LIKE 'image/%' THEN 'image' + WHEN lower(a.mime_type) = 'application/pdf' THEN 'pdf' + WHEN lower(a.mime_type) LIKE 'audio/%' THEN 'audio' + WHEN lower(a.mime_type) LIKE 'video/%' THEN 'video' + WHEN lower(a.mime_type) LIKE 'text/%' THEN 'text' + WHEN lower(a.mime_type) IN ('application/msword', 'application/rtf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.oasis.opendocument.text') THEN 'document' + WHEN lower(a.mime_type) IN ('application/zip', 'application/gzip', 'application/x-tar', + 'application/x-7z-compressed', 'application/x-rar-compressed') THEN 'archive' + ELSE 'other' + END AS mime_family, + s.* + FROM selected s JOIN attachments a ON a.message_id = s.message_id +), filtered AS ( + SELECT * FROM classified WHERE ` + fileConditions + ` +), file_population AS ( + SELECT *, + list_sort(list_distinct(list_concat(participant_ids, conversation_participant_ids))) AS file_participant_ids, + list_sort(list_distinct(list_concat(participant_domains, conversation_participant_domains))) AS file_participant_domains + FROM filtered +) +` +} + +func fileSearchSQL(population, order string) string { + return population + ` +, counted AS ( + SELECT *, COUNT(*) OVER () AS total_count FROM file_population +) +SELECT + attachment_id, + CASE WHEN lower(message_type) IN (` + TextMessageTypeSQLList + `) + OR (lower(message_type) IN ('', 'chat', 'text') AND lower(conversation_type) IN ('direct_chat', 'group_chat', 'channel', 'chat')) + THEN 'source:' || CAST(source_id AS VARCHAR) || ':conversation:' || CAST(conversation_id AS VARCHAR) || ':file:' || CAST(attachment_id AS VARCHAR) + ELSE 'source:' || CAST(source_id AS VARCHAR) || ':message:' || COALESCE(NULLIF(source_message_id, ''), CAST(message_id AS VARCHAR)) || ':file:' || CAST(attachment_id AS VARCHAR) END, + CASE WHEN lower(message_type) IN (` + TextMessageTypeSQLList + `) + OR (lower(message_type) IN ('', 'chat', 'text') AND lower(conversation_type) IN ('direct_chat', 'group_chat', 'channel', 'chat')) + THEN 'source:' || CAST(source_id AS VARCHAR) || ':conversation:' || CAST(conversation_id AS VARCHAR) + ELSE 'source:' || CAST(source_id AS VARCHAR) || ':message:' || COALESCE(NULLIF(source_message_id, ''), CAST(message_id AS VARCHAR)) END, + message_id, conversation_id, occurred_at, source_id, source_type, source_identifier, + COALESCE(NULLIF(subject, ''), NULLIF(conversation_title, ''), snippet, ''), + snippet, + filename, mime_type, mime_family, size, + CAST(COALESCE(to_json(file_participant_ids), '[]') AS VARCHAR), + CAST(COALESCE(to_json(list_sort(list_distinct(list_concat(participant_labels, conversation_participant_labels)))), '[]') AS VARCHAR), + CAST(COALESCE(to_json(file_participant_domains), '[]') AS VARCHAR), + total_count +FROM counted ORDER BY ` + order + ` LIMIT ? OFFSET ?` +} diff --git a/internal/query/files_test.go b/internal/query/files_test.go new file mode 100644 index 000000000..bae420869 --- /dev/null +++ b/internal/query/files_test.go @@ -0,0 +1,232 @@ +package query + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSearchFilesAppliesCanonicalContextAndFileFilters(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + sourceA := b.AddSourceWithType("archive-a@example.com", "gmail") + sourceB := b.AddSourceWithType("archive-b@example.com", "imap") + person := b.AddParticipant("alice@example.com", "example.com", "Alice Example") + other := b.AddParticipant("bob@example.net", "example.net", "Bob Example") + inside := time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC) + after := inside.Add(-time.Hour) + before := inside.Add(time.Hour) + first := b.AddMessage(MessageOpt{SourceID: sourceA, ConversationID: 71, Subject: "Containing item", SentAt: inside}) + b.AddFrom(first, person, "Alice") + b.AddAttachmentWithMIME(41, first, 2048, "Quarterly Report.PDF", "application/pdf") + second := b.AddMessage(MessageOpt{SourceID: sourceA, ConversationID: 72, Subject: "Wrong person", SentAt: inside}) + b.AddFrom(second, other, "Bob") + b.AddAttachmentWithMIME(42, second, 1024, "quarterly-report.pdf", "application/pdf") + third := b.AddMessage(MessageOpt{SourceID: sourceB, ConversationID: 73, Subject: "Wrong source", SentAt: inside}) + b.AddFrom(third, person, "Alice") + b.AddAttachmentWithMIME(43, third, 512, "quarterly-report.pdf", "application/pdf") + + result, err := b.BuildEngine().SearchFiles(context.Background(), FileSearchRequest{ + Explore: ExploreRequest{Context: Context{ + SourceIDs: []int64{sourceA}, ParticipantIDs: []int64{person}, Domains: []string{"example.com"}, + After: &after, Before: &before, + }}, + FilenameQuery: "report", MIMEFamilies: []FileMIMEFamily{FileMIMEPDF}, + Sort: SortSpec{Field: "filename", Direction: "asc"}, Page: PageSpec{Limit: 25}, + }) + requirements.NoError(err) + requirements.Len(result.Files, 1) + file := result.Files[0] + assertions.Equal(int64(41), file.ID) + assertions.Equal("source:1:message:msg1:file:41", file.Key) + assertions.Equal("source:1:message:msg1", file.EntryKey) + assertions.Equal(int64(71), file.ConversationID) + assertions.Equal("Containing item", file.ContainingTitle) + assertions.Equal("application/pdf", file.MimeType) + assertions.Equal(FileMIMEPDF, file.MIMEFamily) + assertions.Equal([]int64{person}, file.ParticipantIDs) + assertions.Equal([]string{"Alice Example"}, file.ParticipantLabels) + assertions.Equal(int64(1), result.TotalCount) + assertions.NotEmpty(result.CacheRevision) +} + +func TestSearchFilesFlattensSnippetMarkupInContainingTitle(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + source := b.AddSourceWithType("meeting@example.com", "meeting") + when := time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC) + message := b.AddMessage(MessageOpt{ + SourceID: source, Subject: "", Snippet: "### Meeting notes\n- Action item", SentAt: when, + }) + b.AddAttachmentWithMIME(61, message, 1024, "notes.pdf", "application/pdf") + + result, err := b.BuildEngine().SearchFiles(context.Background(), FileSearchRequest{ + Sort: SortSpec{Field: "filename", Direction: "asc"}, Page: PageSpec{Limit: 25}, + }) + requirements.NoError(err) + requirements.Len(result.Files, 1) + assertions.Equal("Meeting notes Action item", result.Files[0].ContainingTitle) +} + +func TestSearchFilesPreservesSubjectContainingTitleWithMarkdownLikeCharacters(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + when := time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC) + message := b.AddMessage(MessageOpt{ + SourceID: source, Subject: "Re: 2 ** 3 == 8?", Snippet: "### Meeting notes\n- Action item", SentAt: when, + }) + b.AddAttachmentWithMIME(62, message, 1024, "notes.pdf", "application/pdf") + + result, err := b.BuildEngine().SearchFiles(context.Background(), FileSearchRequest{ + Sort: SortSpec{Field: "filename", Direction: "asc"}, Page: PageSpec{Limit: 25}, + }) + requirements.NoError(err) + requirements.Len(result.Files, 1) + assertions.Equal("Re: 2 ** 3 == 8?", result.Files[0].ContainingTitle) +} + +func TestSearchFilesUsesStableDateNameAndSizeSorts(t *testing.T) { + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + when := time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC) + message := b.AddMessage(MessageOpt{SourceID: source, Subject: "Duplicates", SentAt: when}) + b.AddAttachmentWithMIME(51, message, 20, "same.png", "image/png") + b.AddAttachmentWithMIME(52, message, 10, "same.png", "image/png") + b.AddAttachmentWithMIME(53, message, 10, "alpha.png", "image/png") + engine := b.BuildEngine() + + tests := []struct { + name string + sort SortSpec + wantFirst int64 + }{ + {name: "date", sort: SortSpec{Field: "occurred_at", Direction: "desc"}, wantFirst: 51}, + {name: "name", sort: SortSpec{Field: "filename", Direction: "asc"}, wantFirst: 53}, + {name: "size", sort: SortSpec{Field: "size", Direction: "asc"}, wantFirst: 53}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + result, err := engine.SearchFiles(context.Background(), FileSearchRequest{ + Sort: test.sort, Page: PageSpec{Limit: 1}, + }) + requirements.NoError(err) + requirements.Len(result.Files, 1) + assertions.Equal(test.wantFirst, result.Files[0].ID) + assertions.Equal(int64(3), result.TotalCount) + + second, secondErr := engine.SearchFiles(context.Background(), FileSearchRequest{ + Sort: test.sort, Page: PageSpec{Limit: 1, Offset: 1}, + }) + requirements.NoError(secondErr) + requirements.Len(second.Files, 1) + assertions.NotEqual(result.Files[0].Key, second.Files[0].Key) + }) + } +} + +func TestSearchFilesNamesUnavailableCache(t *testing.T) { + engine, err := NewDuckDBEngine("", "", nil) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, engine.Close()) }) + + _, err = engine.SearchFiles(context.Background(), FileSearchRequest{}) + var unavailable *CacheUnavailableError + require.ErrorAs(t, err, &unavailable) + assert.Equal(t, CacheAbsent, unavailable.Readiness) +} + +func TestGroupFilesUsesExactFilteredFilePopulation(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + sourceA := b.AddSourceWithType("archive-a@example.com", "gmail") + sourceB := b.AddSourceWithType("archive-b@example.com", "imap") + inside := time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC) + first := b.AddMessage(MessageOpt{SourceID: sourceA, Subject: "First", SentAt: inside}) + b.AddAttachmentWithMIME(101, first, 100, "invoice-one.pdf", "application/pdf") + b.AddAttachmentWithMIME(102, first, 25, "invoice-image.png", "image/png") + second := b.AddMessage(MessageOpt{SourceID: sourceA, Subject: "Second", SentAt: inside.Add(-time.Hour)}) + b.AddAttachmentWithMIME(103, second, 200, "invoice-two.pdf", "application/pdf") + third := b.AddMessage(MessageOpt{SourceID: sourceB, Subject: "Other source", SentAt: inside}) + b.AddAttachmentWithMIME(104, third, 400, "invoice-three.pdf", "application/pdf") + + engine := b.BuildEngine() + request := FileGroupRequest{ + Explore: ExploreRequest{Context: Context{SourceIDs: []int64{sourceA}}}, + FilenameQuery: "invoice", MIMEFamilies: []FileMIMEFamily{FileMIMEPDF}, + Dimension: "source", Sort: SortSpec{Field: "count", Direction: "desc"}, + Page: PageSpec{Limit: 10}, + } + grouped, err := engine.GroupFiles(context.Background(), request) + requirements.NoError(err) + requirements.Equal(int64(1), grouped.TotalCount) + requirements.Len(grouped.Rows, 1) + assertions.Equal("1", grouped.Rows[0].Key) + assertions.Equal(int64(2), grouped.Rows[0].Count) + assertions.Equal(int64(300), grouped.Rows[0].EstimatedBytes) + assertions.NotEmpty(grouped.CacheRevision) + + files, err := engine.SearchFiles(context.Background(), FileSearchRequest{ + Explore: request.Explore, FilenameQuery: request.FilenameQuery, MIMEFamilies: request.MIMEFamilies, + Sort: SortSpec{Field: "occurred_at", Direction: "desc"}, Page: PageSpec{Limit: 10}, + }) + requirements.NoError(err) + assertions.Equal(files.TotalCount, grouped.Rows[0].Count, "group count must equal filtered Files rows") +} + +func TestGroupFilesDeduplicatesParticipantAndDomainMembershipPerFile(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + alice := b.AddParticipant("alice@example.com", "example.com", "Alice Example") + bob := b.AddParticipant("bob@example.com", "example.com", "Bob Example") + message := b.AddMessage(MessageOpt{SourceID: source, ConversationID: 77, SenderID: &alice, Subject: "Participants"}) + b.AddFrom(message, alice, "Alice duplicate sender") + b.AddTo(message, alice, "Alice duplicate recipient") + b.AddTo(message, bob, "Bob") + b.AddConversationParticipant(77, alice) + b.AddConversationParticipant(77, bob) + b.AddAttachmentWithMIME(201, message, 125, "people.pdf", "application/pdf") + + engine := b.BuildEngine() + participants, err := engine.GroupFiles(context.Background(), FileGroupRequest{ + Dimension: "participant", Sort: SortSpec{Field: "key", Direction: "asc"}, Page: PageSpec{Limit: 10}, + }) + requirements.NoError(err) + requirements.Len(participants.Rows, 2) + assertions.Equal([]ExploreGroupRow{ + {Key: "1", Label: "Alice Example", Count: 1, EstimatedBytes: 125, LatestAt: participants.Rows[0].LatestAt}, + {Key: "2", Label: "Bob Example", Count: 1, EstimatedBytes: 125, LatestAt: participants.Rows[1].LatestAt}, + }, participants.Rows) + + domains, err := engine.GroupFiles(context.Background(), FileGroupRequest{ + Dimension: "domain", Sort: SortSpec{Field: "key", Direction: "asc"}, Page: PageSpec{Limit: 1}, + }) + requirements.NoError(err) + requirements.Len(domains.Rows, 1) + assertions.Equal(int64(1), domains.TotalCount) + assertions.Equal("example.com", domains.Rows[0].Key) + assertions.Equal(int64(1), domains.Rows[0].Count) + assertions.Equal(int64(125), domains.Rows[0].EstimatedBytes) +} + +func TestGroupFilesNamesUnavailableCache(t *testing.T) { + engine, err := NewDuckDBEngine("", "", nil) + require.NoError(t, err) + t.Cleanup(func() { assert.NoError(t, engine.Close()) }) + + _, err = engine.GroupFiles(context.Background(), FileGroupRequest{}) + var unavailable *CacheUnavailableError + require.ErrorAs(t, err, &unavailable) + assert.Equal(t, CacheAbsent, unavailable.Readiness) +} diff --git a/internal/query/message_types_test.go b/internal/query/message_types_test.go index 2da5b14cf..1cffdecaa 100644 --- a/internal/query/message_types_test.go +++ b/internal/query/message_types_test.go @@ -7,7 +7,7 @@ import ( ) func TestIsKnownMessageType(t *testing.T) { - known := []string{"email", "calendar_event", "sms", "mms", "whatsapp", "imessage", "teams"} + known := []string{"email", "calendar_event", "sms", "mms", "rcs", "whatsapp", "imessage", "teams"} for _, mt := range known { assert.True(t, IsKnownMessageType(mt), "%q should be known", mt) } diff --git a/internal/query/mixed_archive_fixture_test.go b/internal/query/mixed_archive_fixture_test.go new file mode 100644 index 000000000..84546d6a9 --- /dev/null +++ b/internal/query/mixed_archive_fixture_test.go @@ -0,0 +1,61 @@ +package query + +import ( + "context" + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +type mixedArchiveBrowserFixture struct { + RawChatMessageCount int64 `json:"rawChatMessageCount"` + ChatConversationCount int `json:"chatConversationCount"` + LogicalRows []EntryRow `json:"logicalRows"` + FirstPage *ExploreResponse `json:"firstPage"` +} + +func TestWriteMixedArchiveBrowserFixture(t *testing.T) { + require := require.New(t) + fixture := buildMixedArchiveBrowserFixture(t) + require.Equal(int64(100_000), fixture.RawChatMessageCount) + require.Equal(100, fixture.ChatConversationCount) + require.Len(fixture.FirstPage.Rows, 50) + require.Equal(int64(len(fixture.LogicalRows)), fixture.FirstPage.TotalCount) + + kinds := make(map[EntryKind]int) + for _, row := range fixture.LogicalRows { + kinds[row.Kind]++ + } + require.Equal(100, kinds[EntryConversation]) + require.Positive(kinds[EntryEmail]) + require.Positive(kinds[EntryEvent]) + require.Positive(kinds[EntryMeeting]) + require.Positive(kinds[EntryItem]) + + if output := os.Getenv("MSGVAULT_MIXED_ARCHIVE_FIXTURE"); output != "" { + require.NoError(writeMixedArchiveBrowserFixture(output, fixture)) + } +} + +func buildMixedArchiveBrowserFixture(t *testing.T) mixedArchiveBrowserFixture { + t.Helper() + engine := buildBenchData(t) + all, err := engine.Explore(context.Background(), ExploreRequest{Page: PageSpec{Limit: 500}}) + require.NoError(t, err) + first, err := engine.Explore(context.Background(), ExploreRequest{Page: PageSpec{Limit: 50}}) + require.NoError(t, err) + return mixedArchiveBrowserFixture{ + RawChatMessageCount: benchChatMessageCount, ChatConversationCount: 100, + LogicalRows: all.Rows, FirstPage: first, + } +} + +func writeMixedArchiveBrowserFixture(path string, fixture mixedArchiveBrowserFixture) error { + encoded, err := json.MarshalIndent(fixture, "", " ") + if err != nil { + return err + } + return os.WriteFile(path, encoded, 0o600) +} diff --git a/internal/query/people.go b/internal/query/people.go new file mode 100644 index 000000000..a580a542a --- /dev/null +++ b/internal/query/people.go @@ -0,0 +1,404 @@ +package query + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" +) + +const maxPeopleSearchLimit = 500 + +// PersonIdentifier is explicit stored identity evidence. Provenance names the +// canonical read model; it does not imply that two values are interchangeable. +// ParticipantID names which raw participant this evidence was stored against +// — for a linked cluster's person detail, identifiers span every member, so +// this is the only field that tells the caller which chip belongs to which +// member (see PersonSummary.Cluster). +type PersonIdentifier struct { + Type string `json:"type"` + Value string `json:"value"` + DisplayValue string `json:"display_value,omitempty"` + IsPrimary bool `json:"is_primary"` + Provenance string `json:"provenance"` + ParticipantID int64 `json:"participant_id"` +} + +// PersonClusterEdge is one participant_links edge within a person's cluster, +// as recorded by the store (the query layer has no edge data of its own — +// see internal/store/participant_links.go's LinkEdge). +type PersonClusterEdge struct { + ParticipantA int64 `json:"participant_a"` + ParticipantB int64 `json:"participant_b"` +} + +// PersonCluster describes the identity-link cluster a person detail belongs +// to. CanonicalID is the cluster's smallest member ID, matching the store's +// link-graph convention (see ParticipantClusters/rebuildClusterAsStar in +// internal/store/participant_links.go). Populated only when the requested +// participant is linked to at least one other participant; a nil Cluster on +// PersonSummary means the participant is unlinked. +type PersonCluster struct { + CanonicalID int64 `json:"canonical_id"` + MemberIDs []int64 `json:"member_ids"` + Edges []PersonClusterEdge `json:"edges"` +} + +type SourceCount struct { + SourceType string `json:"source_type"` + Count int64 `json:"count"` +} + +type PersonSearchRequest struct { + Explore ExploreRequest `json:"explore"` + Query string `json:"query,omitempty"` + Sort SortSpec `json:"sort"` + Page PageSpec `json:"page"` +} + +type PersonSummary struct { + ID int64 `json:"id"` + DisplayLabel string `json:"display_label"` + DisplayName string `json:"display_name,omitempty"` + PartialLabel bool `json:"partial_label"` + Identifiers []PersonIdentifier `json:"identifiers"` + ActivityCount int64 `json:"activity_count"` + FileCount int64 `json:"file_count"` + SourceCounts []SourceCount `json:"source_counts"` + FirstAt time.Time `json:"first_at"` + LastAt time.Time `json:"last_at"` + CacheRevision string `json:"cache_revision"` + Cluster *PersonCluster `json:"cluster,omitempty"` +} + +type PersonSearchResponse struct { + Rows []PersonSummary `json:"rows"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance SearchProvenance `json:"search_provenance"` +} + +type DomainSearchRequest struct { + Explore ExploreRequest `json:"explore"` + Query string `json:"query,omitempty"` + Sort SortSpec `json:"sort"` + Page PageSpec `json:"page"` +} + +type DomainSummary struct { + Domain string `json:"domain"` + ActivityCount int64 `json:"activity_count"` + PersonCount int64 `json:"person_count"` + FileCount int64 `json:"file_count"` + SourceCounts []SourceCount `json:"source_counts"` + FirstAt time.Time `json:"first_at"` + LastAt time.Time `json:"last_at"` + CacheRevision string `json:"cache_revision"` +} + +type DomainSearchResponse struct { + Rows []DomainSummary `json:"rows"` + TotalCount int64 `json:"total_count"` + CacheRevision string `json:"cache_revision"` + SearchProvenance SearchProvenance `json:"search_provenance"` +} + +func (e *DuckDBEngine) SearchPeople(ctx context.Context, request PersonSearchRequest) (*PersonSearchResponse, error) { + return e.searchPeople(ctx, request, nil, nil) +} + +// GetPerson returns one participant's analytical summary. clusterMemberIDs, +// when it has two or more entries, widens the returned Identifiers to span +// every listed participant (each tagged with its own ParticipantID) instead +// of just id — the caller (the person-detail HTTP handler) resolves cluster +// membership from the store and passes it in, keeping this query layer free +// of a store dependency. A nil/single-element list leaves Identifiers scoped +// to id alone, matching the pre-cluster-aware behavior. +func (e *DuckDBEngine) GetPerson(ctx context.Context, id int64, analyticalContext Context, clusterMemberIDs []int64) (*PersonSummary, error) { + if id < 1 { + return nil, fmt.Errorf("%w: person ID must be positive", ErrInvalidExploreRequest) + } + result, err := e.searchPeople(ctx, PersonSearchRequest{Explore: ExploreRequest{Context: analyticalContext}, Page: PageSpec{Limit: 1}}, &id, clusterMemberIDs) + if err != nil || len(result.Rows) == 0 { + return nil, err + } + return &result.Rows[0], nil +} + +// GetPersonSummary returns contextual metrics for one durable identity. Unlike +// GetPerson, its population is the exact resolved canonical Explore predicate. +func (e *DuckDBEngine) GetPersonSummary(ctx context.Context, id int64, explore ExploreRequest) (*PersonSearchResponse, error) { + if id < 1 { + return nil, fmt.Errorf("%w: person ID must be positive", ErrInvalidExploreRequest) + } + return e.searchPeople(ctx, PersonSearchRequest{Explore: explore, Page: PageSpec{Limit: 1}}, &id, nil) +} + +func (e *DuckDBEngine) searchPeople( + ctx context.Context, request PersonSearchRequest, exactID *int64, identifierScopeIDs []int64, +) (*PersonSearchResponse, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + if err := validateIdentityRequest(request.Explore.Context, request.Page); err != nil { + return nil, err + } + provenance, err := validateResolvedSearch(request.Explore.Search) + if err != nil { + return nil, err + } + order, err := identitySearchOrder(request.Sort, "display_label", "person_id") + if err != nil { + return nil, err + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + conditions, args := buildExploreConditions(request.Explore) + personWhere := []string{"true"} + if exactID != nil { + personWhere = append(personWhere, "person_id = ?") + args = append(args, *exactID) + } + if searchText := strings.TrimSpace(request.Query); searchText != "" { + personWhere = append(personWhere, `(contains(lower(display_name), lower(?)) + OR contains(lower(email_address), lower(?)) OR contains(lower(phone_number), lower(?)) + OR EXISTS (SELECT 1 FROM participant_identifiers pi + WHERE pi.participant_id = person_id AND (contains(lower(pi.identifier_value), lower(?)) + OR contains(lower(pi.display_value), lower(?)))))`) + args = append(args, searchText, searchText, searchText, searchText, searchText) + } + // identifierFilter scopes the per-row identifiers subquery below. Absent + // a caller-supplied cluster (identifierScopeIDs has fewer than two + // entries — the common listing/search case), it stays scoped to the + // row's own person_id. GetPerson passes every cluster member so a + // linked participant's identifiers span the whole cluster instead of + // just the requested ID. + identifierFilter := "pi.participant_id = counted.person_id" + if len(identifierScopeIDs) > 1 { + placeholders := make([]string, len(identifierScopeIDs)) + for i, memberID := range identifierScopeIDs { + placeholders[i] = "?" + args = append(args, memberID) + } + identifierFilter = "pi.participant_id IN (" + strings.Join(placeholders, ",") + ")" + } + limit := request.Page.Limit + if limit == 0 { + limit = defaultExploreLimit + } + args = append(args, limit, request.Page.Offset) + queryText := buildExploreLogicalSQL(conditions) + ` +), person_entries AS ( + SELECT grouped.person_id, logical_entries.* + FROM logical_entries CROSS JOIN UNNEST(participant_ids) AS grouped(person_id) +), person_population AS ( + SELECT p.id AS person_id, COALESCE(p.display_name, '') AS display_name, + COALESCE(p.email_address, '') AS email_address, COALESCE(p.phone_number, '') AS phone_number, + COALESCE(NULLIF(TRIM(p.display_name), ''), NULLIF(TRIM(p.phone_number), ''), + NULLIF(TRIM(p.email_address), ''), + (SELECT COALESCE(NULLIF(TRIM(pi.display_value), ''), pi.identifier_value) + FROM participant_identifiers pi WHERE pi.participant_id = p.id + ORDER BY pi.is_primary DESC, pi.identifier_type, pi.identifier_value LIMIT 1), + 'Unknown person #' || CAST(p.id AS VARCHAR)) AS display_label, + COUNT(*)::BIGINT AS activity_count, COALESCE(SUM(pe.attachment_count), 0)::BIGINT AS file_count, + MIN(pe.occurred_at) AS first_at, MAX(pe.occurred_at) AS last_at + FROM person_entries pe JOIN participants p ON p.id = pe.person_id + GROUP BY p.id, p.display_name, p.email_address, p.phone_number +), filtered_people AS ( + SELECT * FROM person_population WHERE ` + strings.Join(personWhere, " AND ") + ` +), counted AS ( + SELECT *, COUNT(*) OVER () AS total_count FROM filtered_people +) +SELECT person_id, display_label, display_name, + (TRIM(display_name) = '') AS partial_label, + COALESCE(CAST((SELECT to_json(list(struct_pack( + type := pi.identifier_type, value := pi.identifier_value, display_value := pi.display_value, + is_primary := pi.is_primary, provenance := 'participant_identifiers', participant_id := pi.participant_id) + ORDER BY pi.is_primary DESC, pi.identifier_type, pi.identifier_value)) + FROM participant_identifiers pi WHERE ` + identifierFilter + `) AS VARCHAR), '[]'), + activity_count, file_count, + COALESCE(CAST((SELECT to_json(list(struct_pack(source_type := source_type, count := source_count) + ORDER BY source_type)) FROM (SELECT source_type, COUNT(*)::BIGINT AS source_count + FROM person_entries pe WHERE pe.person_id = counted.person_id GROUP BY source_type)) AS VARCHAR), '[]'), + first_at, last_at, total_count +FROM counted ORDER BY ` + order + ` LIMIT ? OFFSET ?` + rows, err := e.db.QueryContext(ctx, queryText, args...) + if err != nil { + return nil, fmt.Errorf("search analytical people: %w", err) + } + defer func() { _ = rows.Close() }() + response := &PersonSearchResponse{Rows: make([]PersonSummary, 0), CacheRevision: state.Revision(), SearchProvenance: provenance} + for rows.Next() { + var row PersonSummary + var identifiersJSON, sourceCountsJSON string + if err := rows.Scan(&row.ID, &row.DisplayLabel, &row.DisplayName, &row.PartialLabel, + &identifiersJSON, &row.ActivityCount, &row.FileCount, &sourceCountsJSON, + &row.FirstAt, &row.LastAt, &response.TotalCount); err != nil { + return nil, fmt.Errorf("scan analytical person: %w", err) + } + if err := json.Unmarshal([]byte(identifiersJSON), &row.Identifiers); err != nil { + return nil, fmt.Errorf("decode person identifiers: %w", err) + } + if err := json.Unmarshal([]byte(sourceCountsJSON), &row.SourceCounts); err != nil { + return nil, fmt.Errorf("decode person source counts: %w", err) + } + row.CacheRevision = state.Revision() + response.Rows = append(response.Rows, row) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate analytical people: %w", err) + } + return response, nil +} + +func (e *DuckDBEngine) SearchDomains(ctx context.Context, request DomainSearchRequest) (*DomainSearchResponse, error) { + return e.searchDomains(ctx, request, "") +} + +func (e *DuckDBEngine) GetDomain(ctx context.Context, domain string, analyticalContext Context) (*DomainSummary, error) { + domain = strings.ToLower(strings.TrimSpace(domain)) + if domain == "" { + return nil, fmt.Errorf("%w: domain is required", ErrInvalidExploreRequest) + } + result, err := e.searchDomains(ctx, DomainSearchRequest{Explore: ExploreRequest{Context: analyticalContext}, Page: PageSpec{Limit: 1}}, domain) + if err != nil || len(result.Rows) == 0 { + return nil, err + } + return &result.Rows[0], nil +} + +// GetDomainSummary returns contextual metrics for one exact normalized domain. +func (e *DuckDBEngine) GetDomainSummary(ctx context.Context, domain string, explore ExploreRequest) (*DomainSearchResponse, error) { + domain = strings.ToLower(strings.TrimSpace(domain)) + if domain == "" { + return nil, fmt.Errorf("%w: domain is required", ErrInvalidExploreRequest) + } + return e.searchDomains(ctx, DomainSearchRequest{Explore: explore, Page: PageSpec{Limit: 1}}, domain) +} + +func (e *DuckDBEngine) searchDomains(ctx context.Context, request DomainSearchRequest, exactDomain string) (*DomainSearchResponse, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + if err := validateIdentityRequest(request.Explore.Context, request.Page); err != nil { + return nil, err + } + provenance, err := validateResolvedSearch(request.Explore.Search) + if err != nil { + return nil, err + } + order, err := identitySearchOrder(request.Sort, "domain", "domain") + if err != nil { + return nil, err + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + conditions, args := buildExploreConditions(request.Explore) + domainWhere := []string{"domain <> ''"} + if exactDomain != "" { + domainWhere = append(domainWhere, "domain = ?") + args = append(args, exactDomain) + } + if searchText := strings.TrimSpace(request.Query); searchText != "" { + domainWhere = append(domainWhere, "contains(lower(domain), lower(?))") + args = append(args, searchText) + } + limit := request.Page.Limit + if limit == 0 { + limit = defaultExploreLimit + } + args = append(args, limit, request.Page.Offset) + queryText := buildExploreLogicalSQL(conditions) + ` +), domain_entries AS ( + SELECT lower(grouped.domain) AS domain, logical_entries.* + FROM logical_entries CROSS JOIN UNNEST(participant_domains) AS grouped(domain) +), domain_population AS ( + SELECT domain, COUNT(*)::BIGINT AS activity_count, + COALESCE(SUM(attachment_count), 0)::BIGINT AS file_count, + MIN(occurred_at) AS first_at, MAX(occurred_at) AS last_at + FROM domain_entries GROUP BY domain +), filtered_domains AS ( + SELECT * FROM domain_population WHERE ` + strings.Join(domainWhere, " AND ") + ` +), counted AS ( + SELECT *, COUNT(*) OVER () AS total_count FROM filtered_domains +) +SELECT domain, activity_count, + (SELECT COUNT(DISTINCT person_id)::BIGINT FROM ( + SELECT UNNEST(participant_ids) AS person_id FROM domain_entries de WHERE de.domain = counted.domain + ) people JOIN participants p ON p.id = people.person_id AND lower(COALESCE(p.domain, '')) = counted.domain) AS person_count, + file_count, + COALESCE(CAST((SELECT to_json(list(struct_pack(source_type := source_type, count := source_count) + ORDER BY source_type)) FROM (SELECT source_type, COUNT(*)::BIGINT AS source_count + FROM domain_entries de WHERE de.domain = counted.domain GROUP BY source_type)) AS VARCHAR), '[]'), + first_at, last_at, total_count +FROM counted ORDER BY ` + order + ` LIMIT ? OFFSET ?` + rows, err := e.db.QueryContext(ctx, queryText, args...) + if err != nil { + return nil, fmt.Errorf("search analytical domains: %w", err) + } + defer func() { _ = rows.Close() }() + response := &DomainSearchResponse{Rows: make([]DomainSummary, 0), CacheRevision: state.Revision(), SearchProvenance: provenance} + for rows.Next() { + var row DomainSummary + var sourceCountsJSON string + if err := rows.Scan(&row.Domain, &row.ActivityCount, &row.PersonCount, &row.FileCount, + &sourceCountsJSON, &row.FirstAt, &row.LastAt, &response.TotalCount); err != nil { + return nil, fmt.Errorf("scan analytical domain: %w", err) + } + if err := json.Unmarshal([]byte(sourceCountsJSON), &row.SourceCounts); err != nil { + return nil, fmt.Errorf("decode domain source counts: %w", err) + } + row.CacheRevision = state.Revision() + response.Rows = append(response.Rows, row) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate analytical domains: %w", err) + } + return response, nil +} + +func validateIdentityRequest(analyticalContext Context, page PageSpec) error { + if page.Offset < 0 || page.Limit < 0 || page.Limit > maxPeopleSearchLimit { + return fmt.Errorf("%w: identity page is outside the supported range", ErrInvalidExploreRequest) + } + if analyticalContext.Deletion != DeletionAny && analyticalContext.Deletion != DeletionActive && analyticalContext.Deletion != DeletionDeleted { + return fmt.Errorf("%w: unknown deletion filter %q", ErrInvalidExploreRequest, analyticalContext.Deletion) + } + return nil +} + +func identitySearchOrder(sort SortSpec, labelField, tieField string) (string, error) { + if sort.Field == "" { + sort = SortSpec{Field: "activity_count", Direction: sortDirectionDesc} + } + if sort.Direction != sortDirectionAsc && sort.Direction != sortDirectionDesc { + return "", fmt.Errorf("%w: unknown identity sort direction %q", ErrInvalidExploreRequest, sort.Direction) + } + var column string + switch sort.Field { + case "activity_count": + column = "activity_count" + case "latest_at": + column = "last_at" + case "display_label": + column = labelField + default: + return "", fmt.Errorf("%w: unknown identity sort field %q", ErrInvalidExploreRequest, sort.Field) + } + return column + " " + strings.ToUpper(sort.Direction) + ", " + tieField + " ASC", nil +} diff --git a/internal/query/people_test.go b/internal/query/people_test.go new file mode 100644 index 000000000..94a689827 --- /dev/null +++ b/internal/query/people_test.go @@ -0,0 +1,280 @@ +package query + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSearchPeopleKeepsSameNamePeopleSeparateAndUnifiesExplicitIdentifiers(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + emailSource := b.AddSourceWithType("archive-a@example.com", "gmail") + chatSource := b.AddSourceWithType("archive-b@example.com", "apple_messages") + first := b.AddParticipant("shared-one@example.com", "example.com", "Shared Name") + second := b.AddParticipant("shared-two@example.com", "example.com", "Shared Name") + b.AddParticipantIdentifier(first, "email", "shared-one@example.com", "shared-one@example.com", true) + b.AddParticipantIdentifier(first, "phone", "+15550100001", "+1 555 010 0001", false) + b.AddParticipantIdentifier(second, "email", "shared-two@example.com", "shared-two@example.com", true) + + oldest := time.Date(2026, 7, 10, 9, 0, 0, 0, time.UTC) + firstMail := b.AddMessage(MessageOpt{SourceID: emailSource, ConversationID: 101, Subject: "First person mail", SentAt: oldest}) + b.AddFrom(firstMail, first, "Shared Name") + b.AddAttachmentWithMIME(501, firstMail, 100, "first.pdf", "application/pdf") + secondMail := b.AddMessage(MessageOpt{SourceID: emailSource, ConversationID: 102, Subject: "Second person mail", SentAt: oldest.Add(time.Hour)}) + b.AddFrom(secondMail, second, "Shared Name") + chat := b.AddMessage(MessageOpt{SourceID: chatSource, ConversationID: 103, Subject: "", SentAt: oldest.Add(2 * time.Hour), MessageType: "imessage", ConversationType: "direct_chat"}) + b.AddFrom(chat, first, "Shared Name") + b.AddConversationParticipant(103, first) + + result, err := b.BuildEngine().SearchPeople(context.Background(), PersonSearchRequest{ + Query: "Shared Name", Sort: SortSpec{Field: "display_label", Direction: "asc"}, + Page: PageSpec{Limit: 25}, + }) + requirements.NoError(err) + requirements.Len(result.Rows, 2) + assertions.Equal(int64(2), result.TotalCount) + assertions.Equal([]int64{first, second}, []int64{result.Rows[0].ID, result.Rows[1].ID}) + assertions.Equal(int64(2), result.Rows[0].ActivityCount) + assertions.Equal(int64(1), result.Rows[0].FileCount) + assertions.Equal([]SourceCount{{SourceType: "apple_messages", Count: 1}, {SourceType: "gmail", Count: 1}}, result.Rows[0].SourceCounts) + assertions.Equal(oldest, result.Rows[0].FirstAt) + assertions.Equal(oldest.Add(2*time.Hour), result.Rows[0].LastAt) + assertions.Equal([]PersonIdentifier{ + {Type: "email", Value: "shared-one@example.com", DisplayValue: "shared-one@example.com", IsPrimary: true, Provenance: "participant_identifiers", ParticipantID: first}, + {Type: "phone", Value: "+15550100001", DisplayValue: "+1 555 010 0001", Provenance: "participant_identifiers", ParticipantID: first}, + }, result.Rows[0].Identifiers) + assertions.NotEqual(result.Rows[0].ID, result.Rows[1].ID, "display-name equality must never merge people") + assertions.NotEmpty(result.CacheRevision) +} + +func TestSearchPeopleShowsPartialIdentityLabelsHonestly(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + source := b.AddSourceWithType("archive@example.com", "beeper") + person := b.AddParticipant("", "", " ") + b.AddParticipantIdentifier(person, "matrix", "@user:example.invalid", "", true) + message := b.AddMessage(MessageOpt{SourceID: source, Subject: "Unlabeled activity", SentAt: time.Date(2026, 7, 11, 9, 0, 0, 0, time.UTC), MessageType: "matrix"}) + b.AddFrom(message, person, "") + + result, err := b.BuildEngine().SearchPeople(context.Background(), PersonSearchRequest{Page: PageSpec{Limit: 25}}) + requirements.NoError(err) + requirements.Len(result.Rows, 1) + assertions.Equal("@user:example.invalid", result.Rows[0].DisplayLabel) + assertions.True(result.Rows[0].PartialLabel) + assertions.Equal(" ", result.Rows[0].DisplayName) +} + +func TestSearchDomainsSpansSourcesAndModalitiesWithoutOrganizationClaims(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + emailSource := b.AddSourceWithType("archive-a@example.com", "gmail") + chatSource := b.AddSourceWithType("archive-b@example.com", "whatsapp") + first := b.AddParticipant("one@example.com", "example.com", "One") + second := b.AddParticipant("two@example.com", "example.com", "Two") + when := time.Date(2026, 7, 12, 9, 0, 0, 0, time.UTC) + mail := b.AddMessage(MessageOpt{SourceID: emailSource, ConversationID: 201, Subject: "Mail", SentAt: when}) + b.AddFrom(mail, first, "One") + chat := b.AddMessage(MessageOpt{SourceID: chatSource, ConversationID: 202, SentAt: when.Add(time.Hour), MessageType: "whatsapp", ConversationType: "group_chat"}) + b.AddFrom(chat, second, "Two") + b.AddConversationParticipant(202, second) + b.AddAttachmentWithMIME(601, chat, 200, "photo.jpg", "image/jpeg") + + result, err := b.BuildEngine().SearchDomains(context.Background(), DomainSearchRequest{ + Query: "EXAMPLE", Sort: SortSpec{Field: "activity_count", Direction: "desc"}, Page: PageSpec{Limit: 25}, + }) + requirements.NoError(err) + requirements.Len(result.Rows, 1) + assertions.Equal("example.com", result.Rows[0].Domain) + assertions.Equal(int64(2), result.Rows[0].ActivityCount) + assertions.Equal(int64(2), result.Rows[0].PersonCount) + assertions.Equal(int64(1), result.Rows[0].FileCount) + assertions.Equal([]SourceCount{{SourceType: "gmail", Count: 1}, {SourceType: "whatsapp", Count: 1}}, result.Rows[0].SourceCounts) + assertions.Equal(when, result.Rows[0].FirstAt) + assertions.Equal(when.Add(time.Hour), result.Rows[0].LastAt) + assertions.NotEmpty(result.CacheRevision) +} + +func TestGetPersonAndDomainAreExactAndBounded(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + person := b.AddParticipant("person@example.com", "example.com", "Person") + b.AddParticipantIdentifier(person, "email", "person@example.com", "Person ", true) + message := b.AddMessage(MessageOpt{SourceID: source, Subject: "One", SentAt: time.Date(2026, 7, 13, 9, 0, 0, 0, time.UTC)}) + b.AddFrom(message, person, "Person") + engine := b.BuildEngine() + + personResult, err := engine.GetPerson(context.Background(), person, Context{}, nil) + requirements.NoError(err) + requirements.NotNil(personResult) + assertions.Equal(person, personResult.ID) + assertions.Equal("email", personResult.Identifiers[0].Type) + + domainResult, err := engine.GetDomain(context.Background(), "EXAMPLE.COM", Context{}) + requirements.NoError(err) + requirements.NotNil(domainResult) + assertions.Equal("example.com", domainResult.Domain) + + missingPerson, err := engine.GetPerson(context.Background(), 9999, Context{}, nil) + requirements.NoError(err) + assertions.Nil(missingPerson) + missingDomain, err := engine.GetDomain(context.Background(), "missing.example", Context{}) + requirements.NoError(err) + assertions.Nil(missingDomain) +} + +func TestGetPersonWithClusterMemberIDsSpansIdentifiersAcrossCluster(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + primary := b.AddParticipant("primary@example.com", "example.com", "Primary") + secondary := b.AddParticipant("secondary@example.com", "example.com", "Secondary") + b.AddParticipantIdentifier(primary, "email", "primary@example.com", "Primary ", true) + b.AddParticipantIdentifier(secondary, "phone", "+15550100002", "+1 555 010 0002", true) + message := b.AddMessage(MessageOpt{SourceID: source, Subject: "Cluster", SentAt: time.Date(2026, 7, 16, 9, 0, 0, 0, time.UTC)}) + b.AddFrom(message, primary, "Primary") + engine := b.BuildEngine() + + solo, err := engine.GetPerson(context.Background(), primary, Context{}, nil) + requirements.NoError(err) + requirements.NotNil(solo) + requirements.Len(solo.Identifiers, 1, "without cluster member IDs, identifiers stay scoped to the requested participant") + + clustered, err := engine.GetPerson(context.Background(), primary, Context{}, []int64{primary, secondary}) + requirements.NoError(err) + requirements.NotNil(clustered) + requirements.Len(clustered.Identifiers, 2, "with cluster member IDs, identifiers span every member") + byParticipant := map[int64]PersonIdentifier{} + for _, identifier := range clustered.Identifiers { + byParticipant[identifier.ParticipantID] = identifier + } + requirements.Contains(byParticipant, primary) + requirements.Contains(byParticipant, secondary) + assertions.Equal("email", byParticipant[primary].Type) + assertions.Equal("phone", byParticipant[secondary].Type) + // Row-level metrics still reflect only the exact requested participant, + // never the whole cluster's combined activity. + assertions.Equal(primary, clustered.ID) + assertions.Equal(int64(1), clustered.ActivityCount) +} + +func TestContextualPersonAndDomainSummariesUseTheExactCanonicalPopulation(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + mail := b.AddSourceWithType("mail@example.com", "gmail") + chat := b.AddSourceWithType("chat@example.com", "matrix") + person := b.AddParticipant("person@example.com", "example.com", "Person") + firstAt := time.Date(2026, 7, 15, 9, 0, 0, 0, time.UTC) + mailMessage := b.AddMessage(MessageOpt{SourceID: mail, Subject: "Included", SentAt: firstAt}) + chatMessage := b.AddMessage(MessageOpt{SourceID: chat, Subject: "Excluded", SentAt: firstAt.Add(time.Hour)}) + b.AddFrom(mailMessage, person, "Person") + b.AddFrom(chatMessage, person, "Person") + b.AddAttachmentWithMIME(701, mailMessage, 100, "included.pdf", "application/pdf") + b.AddAttachmentWithMIME(702, chatMessage, 100, "excluded.pdf", "application/pdf") + engine := b.BuildEngine() + explore := ExploreRequest{Search: SearchSpec{ + Mode: SearchFullText, Query: "Included", CandidateMessageIDs: []int64{mailMessage}, LexicalIndexRevision: "fts5:context", + }} + + people, err := engine.GetPersonSummary(context.Background(), person, explore) + requirements.NoError(err) + requirements.Len(people.Rows, 1) + assertions.Equal(int64(1), people.Rows[0].ActivityCount) + assertions.Equal(int64(1), people.Rows[0].FileCount) + assertions.Equal(firstAt, people.Rows[0].FirstAt) + assertions.Equal([]SourceCount{{SourceType: "gmail", Count: 1}}, people.Rows[0].SourceCounts) + assertions.Equal(SearchProvenance{LexicalIndexRevision: "fts5:context"}, people.SearchProvenance) + + domains, err := engine.GetDomainSummary(context.Background(), "EXAMPLE.COM", explore) + requirements.NoError(err) + requirements.Len(domains.Rows, 1) + assertions.Equal(int64(1), domains.Rows[0].ActivityCount) + assertions.Equal(int64(1), domains.Rows[0].FileCount) + assertions.Equal(firstAt, domains.Rows[0].FirstAt) + assertions.Equal([]SourceCount{{SourceType: "gmail", Count: 1}}, domains.Rows[0].SourceCounts) + assertions.Equal(SearchProvenance{LexicalIndexRevision: "fts5:context"}, domains.SearchProvenance) +} + +func TestPeopleAndDomainSearchRejectUnboundedOrUnknownSorts(t *testing.T) { + requirements := require.New(t) + engine := NewTestDataBuilder(t).BuildEngine() + _, err := engine.SearchPeople(context.Background(), PersonSearchRequest{Page: PageSpec{Limit: 501}}) + requirements.ErrorIs(err, ErrInvalidExploreRequest) + _, err = engine.SearchDomains(context.Background(), DomainSearchRequest{Sort: SortSpec{Field: "sql", Direction: "asc"}}) + requirements.ErrorIs(err, ErrInvalidExploreRequest) +} + +func TestSearchPeopleProjectsResolvedCanonicalSearchCandidates(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + first := b.AddParticipant("first@example.com", "example.com", "Shared Name") + second := b.AddParticipant("second@example.com", "example.com", "Shared Name") + firstMessage := b.AddMessage(MessageOpt{SourceID: source, Subject: "First", SentAt: time.Date(2026, 7, 14, 9, 0, 0, 0, time.UTC)}) + secondMessage := b.AddMessage(MessageOpt{SourceID: source, Subject: "Second", SentAt: time.Date(2026, 7, 14, 10, 0, 0, 0, time.UTC)}) + b.AddFrom(firstMessage, first, "Shared Name") + b.AddFrom(secondMessage, second, "Shared Name") + engine := b.BuildEngine() + generation := int64(17) + + for _, test := range []struct { + name string + search SearchSpec + provenance SearchProvenance + }{ + {name: "full text", search: SearchSpec{Mode: SearchFullText, Query: "Second", CandidateMessageIDs: []int64{secondMessage}, LexicalIndexRevision: "fts5:test"}, provenance: SearchProvenance{LexicalIndexRevision: "fts5:test"}}, + {name: "semantic", search: SearchSpec{Mode: SearchSemantic, Query: "Second", CandidateMessageIDs: []int64{secondMessage}, VectorGeneration: &generation}, provenance: SearchProvenance{VectorGeneration: &generation}}, + {name: "hybrid", search: SearchSpec{Mode: SearchHybrid, Query: "Second", CandidateMessageIDs: []int64{secondMessage}, LexicalCandidateMessageIDs: []int64{firstMessage, secondMessage}, LexicalIndexRevision: "fts5:test", VectorGeneration: &generation}, provenance: SearchProvenance{LexicalIndexRevision: "fts5:test", VectorGeneration: &generation}}, + } { + t.Run(test.name, func(t *testing.T) { + result, err := engine.SearchPeople(context.Background(), PersonSearchRequest{ + Explore: ExploreRequest{Search: test.search}, Query: "Shared Name", Page: PageSpec{Limit: 25}, + }) + requirements.NoError(err) + requirements.Len(result.Rows, 1) + assertions.Equal(second, result.Rows[0].ID, "same-name participant outside candidates must remain excluded") + assertions.Equal(test.provenance, result.SearchProvenance) + }) + } + + empty, err := engine.SearchPeople(context.Background(), PersonSearchRequest{ + Explore: ExploreRequest{Search: SearchSpec{Mode: SearchFullText, Query: "none", CandidateMessageIDs: []int64{}, LexicalIndexRevision: "fts5:empty"}}, + Page: PageSpec{Limit: 25}, + }) + requirements.NoError(err) + assertions.Empty(empty.Rows) + assertions.Equal(int64(0), empty.TotalCount) +} + +func TestSearchDomainsProjectsResolvedCanonicalSearchCandidates(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + b := NewTestDataBuilder(t) + source := b.AddSource("archive@example.com") + first := b.AddParticipant("first@example.com", "example.com", "First") + second := b.AddParticipant("second@other.example", "other.example", "Second") + firstMessage := b.AddMessage(MessageOpt{SourceID: source, Subject: "First", SentAt: time.Date(2026, 7, 14, 9, 0, 0, 0, time.UTC)}) + secondMessage := b.AddMessage(MessageOpt{SourceID: source, Subject: "Second", SentAt: time.Date(2026, 7, 14, 10, 0, 0, 0, time.UTC)}) + b.AddFrom(firstMessage, first, "First") + b.AddFrom(secondMessage, second, "Second") + + result, err := b.BuildEngine().SearchDomains(context.Background(), DomainSearchRequest{ + Explore: ExploreRequest{Search: SearchSpec{Mode: SearchFullText, Query: "Second", CandidateMessageIDs: []int64{secondMessage}, LexicalIndexRevision: "fts5:domain"}}, + Page: PageSpec{Limit: 25}, + }) + requirements.NoError(err) + requirements.Len(result.Rows, 1) + assertions.Equal("other.example", result.Rows[0].Domain) + assertions.Equal(SearchProvenance{LexicalIndexRevision: "fts5:domain"}, result.SearchProvenance) +} diff --git a/internal/query/relationship_timeline.go b/internal/query/relationship_timeline.go new file mode 100644 index 000000000..723c26bb3 --- /dev/null +++ b/internal/query/relationship_timeline.go @@ -0,0 +1,314 @@ +package query + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + "time" +) + +const ( + defaultRelationshipTimelineLimit = 100 + maxRelationshipTimelineLimit = 500 +) + +// TimelineRow is one entry in a relationship timeline: either a single +// message (email, event, meeting) or a chat_burst summarizing every chat +// message one counterpart's cluster exchanged with the owner on a single +// local day in one (source, conversation). +// +// FirstAt is only meaningful for chat_burst rows (the earliest message in +// the burst); for single-message rows it equals OccurredAt. +type TimelineRow struct { + Key string `json:"key"` // message: | burst::: + Kind string `json:"kind"` // email | event | meeting | item | chat_burst + Title string `json:"title"` + Preview string `json:"preview"` + OccurredAt time.Time `json:"occurred_at"` // burst: latest message time + // FirstAt's omitempty never actually omits (time.Time is a struct, not a + // pointer) — kept anyway to match the task's binding contract verbatim. + FirstAt time.Time `json:"first_at,omitempty"` //nolint:modernize // see comment above + MessageCount int64 `json:"message_count"` + SourceID int64 `json:"source_id"` + ConversationID *int64 `json:"conversation_id,omitempty"` + AnchorMessageID *int64 `json:"anchor_message_id,omitempty"` + HasAttachments bool `json:"has_attachments"` +} + +// RelationshipTimelineRequest scopes and pages one counterpart's timeline. +// CanonicalID must already be resolved (see ResolveCanonicalParticipant); +// the query then expands it back out to every member of that cluster so no +// member's alias messages are missed. +type RelationshipTimelineRequest struct { + CanonicalID int64 + Timezone string // validated IANA name; "" = UTC + Context Context + Limit int + Offset int +} + +// RelationshipTimelineResponse is the ranked page plus the cache/identity +// revisions it was computed against, for cursor-drift detection. +type RelationshipTimelineResponse struct { + Rows []TimelineRow + TotalCount int64 + CacheRevision string + IdentityRevision int64 +} + +// RelationshipTimeline returns a canonical identity cluster's interactions +// as a modality-neutral timeline. Email, calendar, and meeting entries are +// one row per message; chat messages are grouped into "bursts" — one row +// per (source, conversation, local calendar day) — because a chat +// conversation can contain thousands of short back-and-forth messages that +// would otherwise drown out the rarer email/meeting entries. +// +// The local day boundary is computed by DuckDB's ICU-backed timezone() +// function (statically linked into the go-duckdb prebuilt binary — see +// TestDuckDBTimezoneConversionUsesBundledICU), not by decay-style date_diff +// arithmetic: occurred_at is a naive TIMESTAMP holding a UTC instant, so the +// column is first anchored to UTC and then re-expressed in the requested +// zone's wall-clock time before formatting the date. +func (e *DuckDBEngine) RelationshipTimeline(ctx context.Context, request RelationshipTimelineRequest) (*RelationshipTimelineResponse, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + if err := validateRelationshipTimelineRequest(request); err != nil { + return nil, err + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + + members, err := e.clusterMembers(ctx, request.CanonicalID) + if err != nil { + return nil, err + } + // Cluster membership scopes the timeline, but any caller-supplied + // participant filter (request.Context.ParticipantIDs, set via a + // "participant" filter dimension) must further restrict it rather than + // be silently overwritten — the filter is part of the cursor hash and + // the caller has no way to tell it was dropped. buildExploreConditions + // only expresses one participant-membership OR-group per Context, so + // the cluster-membership condition is built separately here and AND'd + // onto whatever buildExploreConditions produced for the caller's own + // Context (including any participant filter already in it). + conditions, args := buildExploreConditions(ExploreRequest{Context: request.Context}) + membershipCondition, membershipArgs := participantMembershipCondition(members) + if membershipCondition != "" { + if conditions == "true" { + conditions = membershipCondition + } else { + conditions += " AND " + membershipCondition + } + args = append(args, membershipArgs...) + } + + timezone := request.Timezone + if timezone == "" { + timezone = "UTC" + } + args = append(args, timezone) + limit := request.Limit + if limit == 0 { + limit = defaultRelationshipTimelineLimit + } + args = append(args, limit, request.Offset) + + rows, err := e.db.QueryContext(ctx, buildRelationshipTimelineSQL(conditions), args...) + if err != nil { + return nil, fmt.Errorf("query relationship timeline: %w", err) + } + defer func() { _ = rows.Close() }() + + response := &RelationshipTimelineResponse{ + Rows: make([]TimelineRow, 0), + CacheRevision: state.Revision(), + IdentityRevision: state.IdentityRevision, + } + for rows.Next() { + var row TimelineRow + var key string + var anchorMessageID, conversationID sql.NullInt64 + if err := rows.Scan( + &key, &row.Kind, &anchorMessageID, &conversationID, &row.OccurredAt, &row.FirstAt, + &row.SourceID, &row.Title, &row.Preview, &row.MessageCount, &row.HasAttachments, + &response.TotalCount, + ); err != nil { + return nil, fmt.Errorf("scan relationship timeline row: %w", err) + } + row.Key = key + if row.Title == row.Preview { + row.Title = FlattenSnippet(row.Title) + } + row.Preview = FlattenSnippet(row.Preview) + if anchorMessageID.Valid { + row.AnchorMessageID = &anchorMessageID.Int64 + } + if conversationID.Valid { + row.ConversationID = &conversationID.Int64 + } + response.Rows = append(response.Rows, row) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate relationship timeline: %w", err) + } + return response, nil +} + +// ResolveCanonicalParticipant maps a participant ID to its canonical +// identity cluster ID via the committed participant_clusters dataset. A +// participant absent from that dataset belongs to no recorded cluster and +// is its own single-member canonical ID. +func (e *DuckDBEngine) ResolveCanonicalParticipant(ctx context.Context, participantID int64) (int64, error) { + if e.analyticsDir == "" { + return 0, &CacheUnavailableError{Readiness: CacheAbsent} + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return 0, err + } + defer release() + + queryText := fmt.Sprintf("SELECT canonical_id FROM read_parquet('%s') WHERE participant_id = ?", e.parquetPath(datasetParticipantClusters)) + var canonicalID int64 + err = e.db.QueryRowContext(ctx, queryText, participantID).Scan(&canonicalID) + switch { + case errors.Is(err, sql.ErrNoRows): + return participantID, nil + case err != nil: + return 0, fmt.Errorf("resolve canonical participant: %w", err) + default: + return canonicalID, nil + } +} + +// clusterMembers returns every participant ID whose canonical identity is +// canonicalID, via the committed participant_clusters dataset. LinkCluster +// (and Store.ParticipantClusters, which it mirrors) writes a self-row for +// the canonical participant too, so a genuine cluster's members are found +// with no participants-table join. A canonicalID with no rows at all is a +// single-member cluster of itself (never linked). +func (e *DuckDBEngine) clusterMembers(ctx context.Context, canonicalID int64) ([]int64, error) { + queryText := fmt.Sprintf("SELECT participant_id FROM read_parquet('%s') WHERE canonical_id = ?", e.parquetPath(datasetParticipantClusters)) + rows, err := e.db.QueryContext(ctx, queryText, canonicalID) + if err != nil { + return nil, fmt.Errorf("resolve cluster members: %w", err) + } + defer func() { _ = rows.Close() }() + var members []int64 + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, fmt.Errorf("scan cluster member: %w", err) + } + members = append(members, id) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate cluster members: %w", err) + } + if len(members) == 0 { + members = []int64{canonicalID} + } + return members, nil +} + +// participantMembershipCondition builds the OR-of-any-member SQL fragment +// matching buildExploreConditions' ParticipantIDs shape (sender, entry +// participant, or conversation participant), for the cluster-membership +// scope RelationshipTimeline AND's onto — rather than substitutes for — any +// caller-supplied Context.ParticipantIDs filter. Returns ("", nil) for an +// empty member list (never happens in practice: clusterMembers always +// returns at least the canonical ID itself). +func participantMembershipCondition(members []int64) (string, []any) { + if len(members) == 0 { + return "", nil + } + parts := make([]string, len(members)) + args := make([]any, 0, len(members)*3) + for i, id := range members { + parts[i] = "(sender_id = ? OR list_contains(participant_ids, ?) OR list_contains(conversation_participant_ids, ?))" + args = append(args, id, id, id) + } + return "(" + strings.Join(parts, " OR ") + ")", args +} + +func validateRelationshipTimelineRequest(request RelationshipTimelineRequest) error { + if request.Offset < 0 || request.Limit < 0 || request.Limit > maxRelationshipTimelineLimit { + return fmt.Errorf("%w: timeline page is outside the supported range", ErrInvalidExploreRequest) + } + if request.Context.Deletion != DeletionAny && request.Context.Deletion != DeletionActive && request.Context.Deletion != DeletionDeleted { + return fmt.Errorf("%w: unknown deletion filter %q", ErrInvalidExploreRequest, request.Context.Deletion) + } + if request.Timezone != "" { + if _, err := time.LoadLocation(request.Timezone); err != nil { + return fmt.Errorf("%w: invalid timezone %q: %w", ErrInvalidExploreRequest, request.Timezone, err) + } + } + return nil +} + +// buildRelationshipTimelineSQL builds the full timeline query. It shares +// the "filtered"/"classified" CTEs with buildExploreLogicalSQLWithCandidateRank +// (see buildExploreFilteredClassifiedCTE) but defines its own final CTE: +// unlike Explore's per-conversation-lifetime chat grouping, chat messages +// here are grouped per local calendar day so a years-long conversation +// doesn't collapse into a single burst. The trailing "?" binds the IANA +// timezone name once, in the "day_bucketed" CTE; both branches read the +// precomputed local_day column rather than re-evaluating timezone() per row. +func buildRelationshipTimelineSQL(conditions string) string { + return buildExploreFilteredClassifiedCTE(conditions, "NULL::BIGINT") + ` +, day_bucketed AS ( + SELECT *, strftime(timezone(?, timezone('UTC', occurred_at)), '%Y-%m-%d') AS local_day + FROM classified +), timeline_entries AS ( + SELECT + 'message:' || CAST(message_id AS VARCHAR) AS entry_key, + entry_kind, + message_id AS anchor_message_id, + conversation_id, + occurred_at, + occurred_at AS first_at, + source_id, + COALESCE(NULLIF(subject, ''), NULLIF(conversation_title, ''), snippet, '') AS title, + snippet AS preview, + 1::BIGINT AS message_count, + has_attachments + FROM day_bucketed + WHERE NOT is_chat + + UNION ALL + + SELECT + 'burst:' || CAST(source_id AS VARCHAR) || ':' || CAST(conversation_id AS VARCHAR) || ':' || local_day AS entry_key, + 'chat_burst' AS entry_kind, + arg_max(message_id, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS anchor_message_id, + conversation_id, + MAX(occurred_at) AS occurred_at, + MIN(occurred_at) AS first_at, + source_id, + COALESCE(NULLIF(MAX(conversation_title), ''), 'Conversation') AS title, + arg_max(snippet, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS preview, + COUNT(*)::BIGINT AS message_count, + bool_or(has_attachments) AS has_attachments + FROM day_bucketed + WHERE is_chat + GROUP BY source_id, conversation_id, local_day +), counted AS ( + SELECT *, COUNT(*) OVER () AS total_count FROM timeline_entries +) +SELECT + entry_key, entry_kind, anchor_message_id, conversation_id, occurred_at, first_at, + source_id, title, preview, message_count, has_attachments, total_count +FROM counted +ORDER BY occurred_at DESC, entry_key ASC +LIMIT ? OFFSET ?` +} diff --git a/internal/query/relationship_timeline_test.go b/internal/query/relationship_timeline_test.go new file mode 100644 index 000000000..95dc91ae7 --- /dev/null +++ b/internal/query/relationship_timeline_test.go @@ -0,0 +1,321 @@ +package query + +import ( + "context" + "fmt" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestDuckDBTimezoneConversionUsesBundledICU documents (rather than +// installs) the timezone conversion this package depends on: go-duckdb's +// prebuilt binaries statically link the ICU extension, so named-timezone +// conversion works without an explicit "INSTALL icu; LOAD icu;" (unlike the +// sqlite_scanner extension in NewDuckDBEngine, which is genuinely optional). +// +// It also pins the exact conversion shape relationship_timeline.go relies +// on, and a trap this test deliberately exercises: DuckDB's session +// TimeZone setting defaults to the host's local zone (not UTC — this sandbox +// runs under America/Chicago), so any expression whose result depends on +// that ambient setting is a portability hazard across dev machines and CI. +// timezone(, ) is NOT such an expression: it names the +// target zone explicitly and returns a plain (naive) TIMESTAMP, verified +// below to be identical however the session TimeZone is set. +// +// occurred_at is a naive TIMESTAMP holding a UTC instant (see +// sqlAnalyticalEntries: "m.sent_at AS occurred_at"). Calling +// timezone(, occurred_at) directly is the wrong direction — that +// overload treats a naive TIMESTAMP argument as wall-clock time IN +// and converts it TO a UTC-based TIMESTAMPTZ, which is backwards for what +// this package needs. The correct two-step conversion first anchors the +// naive value to UTC (timezone('UTC', occurred_at) => TIMESTAMPTZ), then +// asks for its wall-clock representation in the target zone +// (timezone(, ) => naive TIMESTAMP). Getting this +// backwards was the exact off-by-one-day failure mode flagged for this task. +func TestDuckDBTimezoneConversionUsesBundledICU(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + db := NewTestDataBuilder(t).BuildEngine().db + + // Wrong direction, made session-independent by converting the resulting + // TIMESTAMPTZ back to UTC explicitly rather than formatting it directly + // (formatting a TIMESTAMPTZ renders it in the ambient session + // TimeZone, which is exactly the hazard this test is pinning down). + var wrongDirection string + require.NoError(db.QueryRowContext(context.Background(), + `SELECT strftime(timezone('UTC', timezone('America/Chicago', TIMESTAMP '2026-07-13 23:30:00')), '%Y-%m-%d %H:%M:%S')`, + ).Scan(&wrongDirection)) + // Interpreting the naive literal as America/Chicago wall-clock time and + // converting to UTC lands nearly 6 hours later on the clock, not earlier + // — proof this single-step call is the wrong direction. + assert.Equal("2026-07-14 04:30:00", wrongDirection) + + // Correct two-step direction, checked under two different ambient + // session TimeZone settings to prove it does not depend on either. + for _, sessionTZ := range []string{"UTC", "Asia/Tokyo"} { + _, err := db.ExecContext(context.Background(), fmt.Sprintf("SET TimeZone = '%s'", sessionTZ)) + require.NoError(err, "session timezone %s", sessionTZ) + + var chicagoDay, utcDay string + require.NoError(db.QueryRowContext(context.Background(), ` + SELECT + strftime(timezone('America/Chicago', timezone('UTC', TIMESTAMP '2026-07-13 23:30:00')), '%Y-%m-%d'), + strftime(timezone('UTC', timezone('UTC', TIMESTAMP '2026-07-13 23:30:00')), '%Y-%m-%d') + `).Scan(&chicagoDay, &utcDay), "session timezone %s", sessionTZ) + assert.Equal("2026-07-13", chicagoDay, "session timezone %s", sessionTZ) + assert.Equal("2026-07-13", utcDay, "session timezone %s", sessionTZ) + } +} + +// buildTimelineFixture constructs the mandatory Task 6 scenario: a cluster +// person (P, linked with alias P2) with three chat messages in one +// conversation — two at 23:00/23:30 UTC on 2026-07-13, one at 00:30 UTC the +// next day — plus one email and one meeting, all with the owner. Returns the +// engine, the canonical (cluster) ID, and the three notable message IDs. +func buildTimelineFixture(t *testing.T) (engine *DuckDBEngine, canonicalID, emailID, meetingID int64) { + t.Helper() + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + b.AddOwnerParticipant(srcID, ownerID) + + pID := b.AddParticipant("pat@chat.example", "chat.example", "Pat") + p2ID := b.AddParticipant("pat@work.example", "work.example", "Pat Work") + b.LinkCluster(pID, p2ID) + canonicalID = min(pID, p2ID) + + convID := int64(9001) + day1Late1 := time.Date(2026, 7, 13, 23, 0, 0, 0, time.UTC) + day1Late2 := time.Date(2026, 7, 13, 23, 30, 0, 0, time.UTC) + day2Early := time.Date(2026, 7, 14, 0, 30, 0, 0, time.UTC) + + first := b.AddMessage(MessageOpt{ + SourceID: srcID, ConversationID: convID, ConversationType: "direct_chat", + MessageType: "imessage", SentAt: day1Late1, Snippet: "Pat: on my way", + }) + b.AddFrom(first, pID, "Pat") + b.AddTo(first, ownerID, "Owner") + + second := b.AddMessage(MessageOpt{ + SourceID: srcID, ConversationID: convID, ConversationType: "direct_chat", + MessageType: "imessage", SentAt: day1Late2, Snippet: "Pat: almost there", + }) + b.AddFrom(second, pID, "Pat") + b.AddTo(second, ownerID, "Owner") + + third := b.AddMessage(MessageOpt{ + SourceID: srcID, ConversationID: convID, ConversationType: "direct_chat", + MessageType: "imessage", SentAt: day2Early, Snippet: "Pat: here now", + }) + b.AddFrom(third, pID, "Pat") + b.AddTo(third, ownerID, "Owner") + + emailID = b.AddMessage(MessageOpt{ + SourceID: srcID, MessageType: "email", Subject: "Re: dinner", + SentAt: time.Date(2026, 7, 12, 12, 0, 0, 0, time.UTC), + }) + b.AddFrom(emailID, pID, "Pat") + b.AddTo(emailID, ownerID, "Owner") + + meetingID = b.AddMessage(MessageOpt{ + SourceID: srcID, MessageType: "calendar_event", + SentAt: time.Date(2026, 7, 11, 9, 0, 0, 0, time.UTC), + }) + b.AddFrom(meetingID, ownerID, "Owner") + b.AddTo(meetingID, pID, "Pat") + + return b.BuildEngine(), canonicalID, emailID, meetingID +} + +// TestRelationshipTimelineBurstsChatByLocalDay is the mandatory Task 6 +// scenario. With America/Chicago the three chat messages (23:00/23:30 UTC +// on day X and 00:30 UTC on day X+1, all before 19:30 local) fall on the +// same Chicago calendar day and must collapse into one chat_burst row with +// MessageCount 3 and the correct first/last timestamps and latest-message +// preview. With UTC the same three messages split across the UTC day +// boundary into two bursts. The email and meeting are untouched, one row +// each, and rows are ordered (occurred_at DESC, key ASC). +func TestRelationshipTimelineBurstsChatByLocalDay(t *testing.T) { + engine, canonicalID, emailID, meetingID := buildTimelineFixture(t) + ctx := context.Background() + + t.Run("America/Chicago collapses all three chats into one burst", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + result, err := engine.RelationshipTimeline(ctx, RelationshipTimelineRequest{ + CanonicalID: canonicalID, Timezone: "America/Chicago", Limit: 10, + }) + require.NoError(err) + require.Len(result.Rows, 3, "chat burst + email + meeting") + + burst := result.Rows[0] + assert.Equal("chat_burst", burst.Kind) + assert.Equal(int64(3), burst.MessageCount) + assert.WithinDuration(time.Date(2026, 7, 14, 0, 30, 0, 0, time.UTC), burst.OccurredAt, 0) + assert.WithinDuration(time.Date(2026, 7, 13, 23, 0, 0, 0, time.UTC), burst.FirstAt, 0) + assert.Equal("Pat: here now", burst.Preview, "preview is the latest message in the burst") + + email := result.Rows[1] + assert.Equal("email", email.Kind) + assert.Equal(int64(1), email.MessageCount) + assert.Equal("message:"+strconv.FormatInt(emailID, 10), email.Key) + + meeting := result.Rows[2] + assert.Equal("event", meeting.Kind) + assert.Equal(int64(1), meeting.MessageCount) + assert.Equal("message:"+strconv.FormatInt(meetingID, 10), meeting.Key) + + // Ordering: occurred_at DESC, key ASC. + assert.True(email.OccurredAt.After(meeting.OccurredAt)) + assert.True(burst.OccurredAt.After(email.OccurredAt)) + }) + + t.Run("UTC splits the three chats across the day boundary into two bursts", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + result, err := engine.RelationshipTimeline(ctx, RelationshipTimelineRequest{ + CanonicalID: canonicalID, Timezone: "UTC", Limit: 10, + }) + require.NoError(err) + require.Len(result.Rows, 4, "two chat bursts + email + meeting") + + bursts := make([]TimelineRow, 0, 2) + for _, row := range result.Rows { + if row.Kind == "chat_burst" { + bursts = append(bursts, row) + } + } + require.Len(bursts, 2) + + latest, earliest := bursts[0], bursts[1] + assert.Equal(int64(1), latest.MessageCount) + assert.WithinDuration(time.Date(2026, 7, 14, 0, 30, 0, 0, time.UTC), latest.OccurredAt, 0) + assert.Equal(int64(2), earliest.MessageCount) + assert.WithinDuration(time.Date(2026, 7, 13, 23, 30, 0, 0, time.UTC), earliest.OccurredAt, 0) + assert.WithinDuration(time.Date(2026, 7, 13, 23, 0, 0, 0, time.UTC), earliest.FirstAt, 0) + }) + + t.Run("empty timezone defaults to UTC", func(t *testing.T) { + require := require.New(t) + + withDefault, err := engine.RelationshipTimeline(ctx, RelationshipTimelineRequest{CanonicalID: canonicalID, Limit: 10}) + require.NoError(err) + withUTC, err := engine.RelationshipTimeline(ctx, RelationshipTimelineRequest{CanonicalID: canonicalID, Timezone: "UTC", Limit: 10}) + require.NoError(err) + require.Len(withDefault.Rows, len(withUTC.Rows)) + }) +} + +// TestRelationshipTimelineRejectsInvalidTimezone verifies Go-side IANA +// validation (time.LoadLocation) rejects a bogus zone name before any +// DuckDB query runs, mapped to ErrInvalidExploreRequest like other explore +// validation failures. +func TestRelationshipTimelineRejectsInvalidTimezone(t *testing.T) { + require := require.New(t) + + engine, canonicalID, _, _ := buildTimelineFixture(t) + _, err := engine.RelationshipTimeline(context.Background(), RelationshipTimelineRequest{ + CanonicalID: canonicalID, Timezone: "Not/AZone", Limit: 10, + }) + require.ErrorIs(err, ErrInvalidExploreRequest) +} + +// TestRelationshipTimelineResolvesAnyMemberID verifies a raw member alias ID +// (not the canonical/minimum ID) still resolves to the full cluster's +// interactions when used as CanonicalID is not itself required — the +// engine's RelationshipTimeline takes an already-resolved canonical ID (see +// ResolveCanonicalParticipant for the alias-to-canonical resolution used by +// the API layer) but must include every member's messages. +func TestRelationshipTimelineResolvesAnyMemberID(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + engine, canonicalID, _, _ := buildTimelineFixture(t) + result, err := engine.RelationshipTimeline(context.Background(), RelationshipTimelineRequest{ + CanonicalID: canonicalID, Timezone: "America/Chicago", Limit: 10, + }) + require.NoError(err) + require.Len(result.Rows, 3) + assert.Equal(int64(3), result.TotalCount) +} + +// TestRelationshipTimelineIntersectsParticipantFilterWithClusterMembership +// verifies that cluster membership scopes the timeline AND a caller-supplied +// participant filter (Context.ParticipantIDs, set via a "participant" filter +// dimension at the API layer) further restricts it — the two conditions +// intersect rather than the cluster-membership scope silently overwriting +// the caller's filter. Pat has two entries with the owner; only one also +// involves X (cc'd). Filtering by X must return only that one entry. +func TestRelationshipTimelineIntersectsParticipantFilterWithClusterMembership(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + b.AddOwnerParticipant(srcID, ownerID) + + pID := b.AddParticipant("pat@example.com", "example.com", "Pat") + xID := b.AddParticipant("x@example.com", "example.com", "X") + + entryWithX := b.AddMessage(MessageOpt{SourceID: srcID, MessageType: "email", SentAt: time.Date(2026, 1, 10, 12, 0, 0, 0, time.UTC)}) + b.AddFrom(entryWithX, pID, "Pat") + b.AddTo(entryWithX, ownerID, "Owner") + b.AddCc(entryWithX, xID, "X") + + entryWithoutX := b.AddMessage(MessageOpt{SourceID: srcID, MessageType: "email", SentAt: time.Date(2026, 1, 9, 12, 0, 0, 0, time.UTC)}) + b.AddFrom(entryWithoutX, pID, "Pat") + b.AddTo(entryWithoutX, ownerID, "Owner") + + engine := b.BuildEngine() + + all, err := engine.RelationshipTimeline(context.Background(), RelationshipTimelineRequest{ + CanonicalID: pID, Limit: 10, + }) + require.NoError(err) + require.Len(all.Rows, 2, "both of Pat's entries are in scope without a participant filter") + + filtered, err := engine.RelationshipTimeline(context.Background(), RelationshipTimelineRequest{ + CanonicalID: pID, Limit: 10, Context: Context{ParticipantIDs: []int64{xID}}, + }) + require.NoError(err) + require.Len(filtered.Rows, 1, "only the entry that also involves X must survive the intersection") + assert.Equal(int64(1), filtered.TotalCount) + assert.Equal("message:"+strconv.FormatInt(entryWithX, 10), filtered.Rows[0].Key) +} + +// TestResolveCanonicalParticipant covers both the linked case (any member ID +// resolves to the shared canonical ID) and the miss case (an unlinked +// participant is its own single-member cluster). +func TestResolveCanonicalParticipant(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + b := NewTestDataBuilder(t) + aID := b.AddParticipant("a@example.com", "example.com", "A") + a2ID := b.AddParticipant("a2@example.com", "example.com", "A2") + b.LinkCluster(aID, a2ID) + loneID := b.AddParticipant("lone@example.com", "example.com", "Lone") + + engine := b.BuildEngine() + canonical := min(aID, a2ID) + + resolvedFromA, err := engine.ResolveCanonicalParticipant(context.Background(), aID) + require.NoError(err) + assert.Equal(canonical, resolvedFromA) + + resolvedFromA2, err := engine.ResolveCanonicalParticipant(context.Background(), a2ID) + require.NoError(err) + assert.Equal(canonical, resolvedFromA2) + + resolvedLone, err := engine.ResolveCanonicalParticipant(context.Background(), loneID) + require.NoError(err) + assert.Equal(loneID, resolvedLone, "an unlinked participant is a single-member cluster of itself") +} diff --git a/internal/query/relationships.go b/internal/query/relationships.go new file mode 100644 index 000000000..6eb864c9d --- /dev/null +++ b/internal/query/relationships.go @@ -0,0 +1,284 @@ +package query + +import ( + "context" + "encoding/json" + "fmt" + "math" + "sort" + "time" +) + +const ( + relationshipWeightSent = 2.0 + relationshipWeightMeetings = 3.0 + relationshipWeightReceived = 1.0 + relationshipBreadthStep = 0.25 + relationshipHalfLifeDays = 365.0 +) + +const ( + defaultRelationshipsLimit = 100 + maxRelationshipsLimit = 500 +) + +// RelationshipSignals holds the decayed interaction sums and raw counts that +// feed RelationshipScore. Decay is applied in SQL (see (*DuckDBEngine). +// Relationships), not here. +type RelationshipSignals struct { + SentToThem float64 `json:"sent_to_them"` + ReceivedFromThem float64 `json:"received_from_them"` + MeetingsTogether float64 `json:"meetings_together"` + SentCount int64 `json:"sent_count"` + MeetingCount int64 `json:"meeting_count"` + Modalities int `json:"modalities"` + LastInteractionAt time.Time `json:"last_interaction_at"` +} + +// RelationshipScore applies the spec's weights, decay having been applied in +// SQL: score = (2.0*sent + 3.0*meetings + 1.0*received) * (1 + 0.25*(modalities-1)). +func RelationshipScore(s RelationshipSignals) float64 { + base := relationshipWeightSent*s.SentToThem + + relationshipWeightMeetings*s.MeetingsTogether + + relationshipWeightReceived*s.ReceivedFromThem + breadth := 1.0 + if s.Modalities > 1 { + breadth = 1.0 + relationshipBreadthStep*float64(s.Modalities-1) + } + return base * breadth +} + +// RelationshipRow is one ranked counterpart: a canonical identity cluster +// scored against the archive owner's interactions with it. +type RelationshipRow struct { + CanonicalID int64 `json:"canonical_id"` + DisplayLabel string `json:"display_label"` + MemberIDs []int64 `json:"member_ids"` + Score float64 `json:"score"` + Signals RelationshipSignals `json:"signals"` + LastAt time.Time `json:"last_at"` +} + +// RelationshipsRequest scopes and pages a relationship ranking query. Now is +// injected so decay is deterministic in tests; if left zero, the engine +// defaults it to time.Now().UTC() (API callers always set it explicitly so +// results are reproducible across a paginated request). +type RelationshipsRequest struct { + Context Context + ShowAll bool + Limit int + Offset int + Now time.Time +} + +// RelationshipsResponse is the ranked page plus the cache/identity revisions +// it was computed against, for cursor-drift detection. +type RelationshipsResponse struct { + Rows []RelationshipRow + TotalCount int64 + CacheRevision string + IdentityRevision int64 +} + +// RelationshipAnalyzer is separate from Engine so relationship ranking can +// only be served from a committed canonical cache snapshot with resolved +// identity clusters. +type RelationshipAnalyzer interface { + Relationships(ctx context.Context, request RelationshipsRequest) (*RelationshipsResponse, error) + + // RelationshipTimeline returns one counterpart's interactions as a + // modality-neutral timeline, with chat messages bucketed into local-day + // bursts. See relationship_timeline.go. + RelationshipTimeline(ctx context.Context, request RelationshipTimelineRequest) (*RelationshipTimelineResponse, error) + + // ResolveCanonicalParticipant maps any participant ID to its canonical + // identity cluster ID (itself, if it belongs to no recorded cluster), + // via the committed participant_clusters dataset. Callers (e.g. the + // relationship timeline route) use this to accept any member ID in a + // URL path while scoping queries by the single canonical ID. + ResolveCanonicalParticipant(ctx context.Context, participantID int64) (int64, error) +} + +// Relationships ranks every non-owner canonical identity the archive owner +// has interacted with, by a reciprocity-weighted, time-decayed score. Owner +// clusters are excluded (you don't rank yourself); by default only +// counterparts with at least one sent message or one shared meeting are +// returned (the reciprocity gate), filtering out inbound-only newsletters. +func (e *DuckDBEngine) Relationships(ctx context.Context, request RelationshipsRequest) (*RelationshipsResponse, error) { + if e.analyticsDir == "" { + return nil, &CacheUnavailableError{Readiness: CacheAbsent} + } + if err := validateRelationshipsRequest(request); err != nil { + return nil, err + } + release, err := e.acquireQuerySlot(ctx) + if err != nil { + return nil, err + } + defer release() + state, err := ReadCacheSyncState(e.analyticsDir) + if err != nil { + return nil, fmt.Errorf("read committed cache state: %w", err) + } + + now := request.Now + if now.IsZero() { + now = time.Now().UTC() + } + conditions, args := buildExploreConditions(ExploreRequest{Context: request.Context}) + queryText := buildRelationshipsSQL(conditions, e.parquetPath(datasetParticipantClusters), e.parquetPath(datasetOwnerParticipants)) + args = append(args, math.Ln2/relationshipHalfLifeDays, now.UTC()) + + rows, err := e.db.QueryContext(ctx, queryText, args...) + if err != nil { + return nil, fmt.Errorf("query analytical relationships: %w", err) + } + defer func() { _ = rows.Close() }() + + var candidates []RelationshipRow + for rows.Next() { + var row RelationshipRow + var memberIDsJSON string + if err := rows.Scan( + &row.CanonicalID, &row.DisplayLabel, &memberIDsJSON, + &row.Signals.SentToThem, &row.Signals.SentCount, + &row.Signals.ReceivedFromThem, &row.Signals.MeetingsTogether, &row.Signals.MeetingCount, + &row.Signals.Modalities, &row.LastAt, + ); err != nil { + return nil, fmt.Errorf("scan analytical relationship: %w", err) + } + if err := json.Unmarshal([]byte(memberIDsJSON), &row.MemberIDs); err != nil { + return nil, fmt.Errorf("decode relationship member IDs: %w", err) + } + row.Signals.LastInteractionAt = row.LastAt + row.Score = RelationshipScore(row.Signals) + if !request.ShowAll && row.Signals.SentCount < 1 && row.Signals.MeetingCount < 1 { + continue + } + candidates = append(candidates, row) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate analytical relationships: %w", err) + } + + sort.SliceStable(candidates, func(i, j int) bool { + if candidates[i].Score != candidates[j].Score { + return candidates[i].Score > candidates[j].Score + } + if !candidates[i].LastAt.Equal(candidates[j].LastAt) { + return candidates[i].LastAt.After(candidates[j].LastAt) + } + return candidates[i].DisplayLabel < candidates[j].DisplayLabel + }) + + limit := request.Limit + if limit == 0 { + limit = defaultRelationshipsLimit + } + response := &RelationshipsResponse{ + Rows: make([]RelationshipRow, 0), + TotalCount: int64(len(candidates)), + CacheRevision: state.Revision(), + IdentityRevision: state.IdentityRevision, + } + if request.Offset < len(candidates) { + end := min(request.Offset+limit, len(candidates)) + response.Rows = candidates[request.Offset:end] + } + return response, nil +} + +func validateRelationshipsRequest(request RelationshipsRequest) error { + if request.Offset < 0 || request.Limit < 0 || request.Limit > maxRelationshipsLimit { + return fmt.Errorf("%w: relationships page is outside the supported range", ErrInvalidExploreRequest) + } + if request.Context.Deletion != DeletionAny && request.Context.Deletion != DeletionActive && request.Context.Deletion != DeletionDeleted { + return fmt.Errorf("%w: unknown deletion filter %q", ErrInvalidExploreRequest, request.Context.Deletion) + } + return nil +} + +// buildRelationshipsSQL builds the full relationship-ranking query. Decay is +// computed in SQL from two trailing bound parameters (decay rate, now); the +// gate, score, and sort order are applied in Go so RelationshipScore stays +// the single source of truth for the weights. +// +// Owners may themselves be clustered, so "owner_canon" resolves every owner +// participant to its cluster canonical ID and interactions exclude any +// canonical identity that appears there — you never rank yourself. +// "owner_participant_ids" expands owner_canon back out to every raw +// participant ID sharing an owner's canonical cluster (not just the raw +// owner_participants rows), so a meeting attended under an alias linked only +// via participant_clusters still counts as "together" (with_owner, computed +// once per entry before the UNNEST that fans an entry out per participant). +// +// A single logical entry can list several raw participant IDs that resolve +// to the same canonical cluster (e.g. cc'ing a contact's work and personal +// addresses); the DISTINCT in "interactions" collapses those back to one row +// per (entry, canonical_id) so aggregation doesn't double-count the entry. +// +// A meeting/event the owner did not attend contributes no signal for any +// attendee (no sent/received count, no modality — see "aggregated" below), +// so "interactions" excludes such rows entirely rather than merely zeroing +// their contribution: left in, they would still feed MAX(occurred_at) and +// inflate LastAt/LastInteractionAt with a meeting the owner never attended. +func buildRelationshipsSQL(conditions, clustersGlob, ownersGlob string) string { + return buildExploreLogicalSQL(conditions) + fmt.Sprintf(` +), clusters AS ( + SELECT participant_id, canonical_id FROM read_parquet('%s') +), owners AS ( + SELECT DISTINCT participant_id FROM read_parquet('%s') +), canon AS ( + SELECT p.id AS participant_id, COALESCE(c.canonical_id, p.id) AS canonical_id + FROM participants p LEFT JOIN clusters c ON c.participant_id = p.id +), owner_canon AS ( + SELECT DISTINCT cn.canonical_id FROM owners o JOIN canon cn ON cn.participant_id = o.participant_id +), owner_participant_ids AS ( + SELECT DISTINCT cn.participant_id FROM canon cn + WHERE cn.canonical_id IN (SELECT canonical_id FROM owner_canon) +), le_with_owner AS ( + SELECT le.*, list_has_any(le.participant_ids, (SELECT list(participant_id) FROM owner_participant_ids)) AS with_owner + FROM logical_entries le +), interactions AS ( + SELECT DISTINCT + le.entry_key, + cn.canonical_id, + le.entry_kind, + le.occurred_at, + le.is_from_me, + le.with_owner, + exp(-? * date_diff('day', le.occurred_at, ?)) AS decay + FROM le_with_owner le + CROSS JOIN UNNEST(le.participant_ids) AS pid(participant_id) + JOIN canon cn ON cn.participant_id = pid.participant_id + WHERE cn.canonical_id NOT IN (SELECT canonical_id FROM owner_canon) + AND NOT (le.entry_kind IN ('event','meeting') AND NOT le.with_owner) +), aggregated AS ( + SELECT + canonical_id, + SUM(CASE WHEN is_from_me AND entry_kind IN ('email','conversation','item') THEN decay ELSE 0 END) AS sent_decayed, + COUNT(CASE WHEN is_from_me AND entry_kind IN ('email','conversation','item') THEN 1 END) AS sent_count, + SUM(CASE WHEN NOT is_from_me AND entry_kind IN ('email','conversation','item') THEN decay ELSE 0 END) AS received_decayed, + SUM(CASE WHEN entry_kind IN ('event','meeting') AND with_owner THEN decay ELSE 0 END) AS meetings_decayed, + COUNT(CASE WHEN entry_kind IN ('event','meeting') AND with_owner THEN 1 END) AS meeting_count, + COUNT(DISTINCT CASE + WHEN entry_kind IN ('event','meeting') AND with_owner THEN 'meeting' + WHEN entry_kind IN ('event','meeting') THEN NULL + WHEN entry_kind = 'conversation' THEN 'chat' + ELSE 'email' END) AS modalities, + MAX(occurred_at) AS last_at + FROM interactions + GROUP BY canonical_id +) +SELECT + a.canonical_id, + (SELECT COALESCE(NULLIF(TRIM(p2.display_name), ''), NULLIF(TRIM(p2.phone_number), ''), NULLIF(TRIM(p2.email_address), ''), + (SELECT COALESCE(NULLIF(TRIM(pi.display_value), ''), pi.identifier_value) FROM participant_identifiers pi + WHERE pi.participant_id = p2.id ORDER BY pi.is_primary DESC, pi.identifier_type, pi.identifier_value LIMIT 1), + 'Unknown person #' || CAST(p2.id AS VARCHAR)) + FROM participants p2 WHERE p2.id = a.canonical_id) AS display_label, + CAST(COALESCE((SELECT to_json(list(cn2.participant_id ORDER BY cn2.participant_id)) + FROM canon cn2 WHERE cn2.canonical_id = a.canonical_id), '[]') AS VARCHAR) AS member_ids, + a.sent_decayed, a.sent_count, a.received_decayed, a.meetings_decayed, a.meeting_count, a.modalities, a.last_at +FROM aggregated a`, clustersGlob, ownersGlob) +} diff --git a/internal/query/relationships_score_test.go b/internal/query/relationships_score_test.go new file mode 100644 index 000000000..562e0fc99 --- /dev/null +++ b/internal/query/relationships_score_test.go @@ -0,0 +1,71 @@ +package query + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestRelationshipScore(t *testing.T) { + tests := []struct { + name string + signals RelationshipSignals + want float64 + }{ + { + name: "zero signals score zero", + signals: RelationshipSignals{}, + want: 0, + }, + { + name: "one decayed sent unit scores the sent weight", + signals: RelationshipSignals{SentToThem: 1, Modalities: 1}, + want: 2.0, + }, + { + name: "one decayed meeting unit scores the meeting weight", + signals: RelationshipSignals{MeetingsTogether: 1, Modalities: 1}, + want: 3.0, + }, + { + name: "one decayed received unit scores the received weight", + signals: RelationshipSignals{ReceivedFromThem: 1, Modalities: 1}, + want: 1.0, + }, + { + name: "combined signals sum the weighted contributions", + signals: RelationshipSignals{SentToThem: 1, MeetingsTogether: 1, ReceivedFromThem: 1, Modalities: 1}, + want: 6.0, + }, + { + name: "three modalities apply a 1.5x breadth boost", + signals: RelationshipSignals{SentToThem: 1, Modalities: 3}, + want: 3.0, + }, + { + name: "two modalities apply a 1.25x breadth boost", + signals: RelationshipSignals{SentToThem: 1, Modalities: 2}, + want: 2.5, + }, + { + name: "modalities of zero behave like one (no boost, no penalty)", + signals: RelationshipSignals{SentToThem: 1, Modalities: 0}, + want: 2.0, + }, + { + name: "counts and timestamps do not affect score", + signals: RelationshipSignals{ + SentToThem: 1, Modalities: 1, + SentCount: 1000, MeetingCount: 1000, + LastInteractionAt: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC), + }, + want: 2.0, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert.InDelta(t, tc.want, RelationshipScore(tc.signals), 1e-9) + }) + } +} diff --git a/internal/query/relationships_test.go b/internal/query/relationships_test.go new file mode 100644 index 000000000..ffde08bbb --- /dev/null +++ b/internal/query/relationships_test.go @@ -0,0 +1,278 @@ +package query + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestRelationshipsRanksByReciprocityAndGatesNewsletters builds an owner +// (O), a reciprocal counterpart (A, clustered with a second identity A2 that +// only has chat activity), and an inbound-only newsletter sender (B). It +// asserts: A ranks first with cluster-combined signals under +// canonical_id = min(A, A2); B is gated out by default; ShowAll lifts the +// gate; and the result is deterministic given an injected Now. +func TestRelationshipsRanksByReciprocityAndGatesNewsletters(t *testing.T) { + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + b.AddOwnerParticipant(srcID, ownerID) + + aID := b.AddParticipant("alice@example.com", "example.com", "Alice") + a2ID := b.AddParticipant("alice@chat.example", "chat.example", "Alice Chat") + b.LinkCluster(aID, a2ID) + + bID := b.AddParticipant("newsletter@example.com", "example.com", "Newsletter") + + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + + // Owner sent 3 messages to A. + for i := range 3 { + msgID := b.AddMessage(MessageOpt{SourceID: srcID, IsFromMe: true, SentAt: now.AddDate(0, 0, -(3 - i))}) + b.AddFrom(msgID, ownerID, "Owner") + b.AddTo(msgID, aID, "Alice") + } + + // One meeting together (owner + A). + meetingID := b.AddMessage(MessageOpt{SourceID: srcID, MessageType: "calendar_event", SentAt: now.AddDate(0, 0, -2)}) + b.AddFrom(meetingID, ownerID, "Owner") + b.AddTo(meetingID, aID, "Alice") + + // A2 (clustered with A) has chat activity with the owner. + chatID := b.AddMessage(MessageOpt{SourceID: srcID, MessageType: "imessage", SentAt: now.AddDate(0, 0, -1)}) + b.AddFrom(chatID, a2ID, "Alice Chat") + b.AddTo(chatID, ownerID, "Owner") + + // Newsletter: 50 inbound messages, zero sent/meetings. + for i := range 50 { + msgID := b.AddMessage(MessageOpt{SourceID: srcID, SentAt: now.AddDate(0, 0, -(10 + i))}) + b.AddFrom(msgID, bID, "Newsletter") + b.AddTo(msgID, ownerID, "Owner") + } + + engine := b.BuildEngine() + ctx := context.Background() + + t.Run("default gate excludes the newsletter", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + result, err := engine.Relationships(ctx, RelationshipsRequest{Now: now, Limit: 10}) + require.NoError(err) + require.Len(result.Rows, 1) + assert.Equal(int64(1), result.TotalCount) + + row := result.Rows[0] + assert.Equal(aID, row.CanonicalID) + assert.Equal([]int64{aID, a2ID}, row.MemberIDs) + assert.Equal(int64(3), row.Signals.SentCount) + assert.Equal(int64(1), row.Signals.MeetingCount) + assert.Equal(3, row.Signals.Modalities) // email + meeting + chat + assert.Positive(row.Score) + assert.WithinDuration(now.AddDate(0, 0, -1), row.LastAt, 0) + }) + + t.Run("show_all lifts the gate and includes the newsletter", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + result, err := engine.Relationships(ctx, RelationshipsRequest{Now: now, Limit: 10, ShowAll: true}) + require.NoError(err) + require.Len(result.Rows, 2) + assert.Equal(int64(2), result.TotalCount) + + byCanonicalID := make(map[int64]RelationshipRow, len(result.Rows)) + for _, row := range result.Rows { + byCanonicalID[row.CanonicalID] = row + } + require.Contains(byCanonicalID, aID) + require.Contains(byCanonicalID, bID) + newsletter := byCanonicalID[bID] + assert.Equal(int64(0), newsletter.Signals.SentCount) + assert.Equal(int64(0), newsletter.Signals.MeetingCount) + assert.Positive(newsletter.Signals.ReceivedFromThem) + }) + + t.Run("deterministic given an injected Now", func(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + first, err := engine.Relationships(ctx, RelationshipsRequest{Now: now, Limit: 10, ShowAll: true}) + require.NoError(err) + second, err := engine.Relationships(ctx, RelationshipsRequest{Now: now, Limit: 10, ShowAll: true}) + require.NoError(err) + require.Len(second.Rows, len(first.Rows)) + for i := range first.Rows { + assert.Equal(first.Rows[i].CanonicalID, second.Rows[i].CanonicalID) + // DuckDB's parallel SUM can reorder floating-point additions between + // runs, so scores may differ in the last bit; the ranking they + // produce must not. + assert.InEpsilon(first.Rows[i].Score, second.Rows[i].Score, 1e-9) + } + }) +} + +// TestRelationshipsExcludesClusteredOwners verifies that when the owner's own +// participant identity is itself linked into a cluster with another +// participant, the whole cluster is excluded from ranking (you never rank +// yourself, even under an alias). +func TestRelationshipsExcludesClusteredOwners(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + ownerAliasID := b.AddParticipant("owner@alias.example", "alias.example", "Owner Alias") + b.AddOwnerParticipant(srcID, ownerID) + b.LinkCluster(ownerID, ownerAliasID) + + otherID := b.AddParticipant("bob@example.com", "example.com", "Bob") + + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + msgID := b.AddMessage(MessageOpt{SourceID: srcID, IsFromMe: true, SentAt: now.AddDate(0, 0, -1)}) + b.AddFrom(msgID, ownerID, "Owner") + b.AddTo(msgID, otherID, "Bob") + + engine := b.BuildEngine() + result, err := engine.Relationships(context.Background(), RelationshipsRequest{Now: now, Limit: 10, ShowAll: true}) + require.NoError(err) + require.Len(result.Rows, 1) + assert.Equal(otherID, result.Rows[0].CanonicalID) +} + +// TestRelationshipsDoesNotDoubleCountClusteredRecipientsInOneEntry verifies +// that a single entry whose recipient list contains two raw participant IDs +// resolving to the same canonical cluster (e.g. cc'ing someone's work and +// personal addresses that the archive owner has linked) is counted once for +// that cluster, not once per raw ID. The clustered scenario is compared +// against a control scenario with a single, unclustered recipient sent at +// the same instant: the two must produce identical decayed sums, whatever +// the DuckDB session's date-diff decay for that instant happens to be. +func TestRelationshipsDoesNotDoubleCountClusteredRecipientsInOneEntry(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + sentAt := now.AddDate(0, 0, -1) + + control := NewTestDataBuilder(t) + controlSrcID := control.AddSource("owner@example.com") + controlOwnerID := control.AddParticipant("owner@example.com", "example.com", "Owner") + control.AddOwnerParticipant(controlSrcID, controlOwnerID) + controlAID := control.AddParticipant("alice@work.example", "work.example", "Alice Work") + controlMsgID := control.AddMessage(MessageOpt{SourceID: controlSrcID, IsFromMe: true, SentAt: sentAt}) + control.AddFrom(controlMsgID, controlOwnerID, "Owner") + control.AddTo(controlMsgID, controlAID, "Alice Work") + + controlEngine := control.BuildEngine() + controlResult, err := controlEngine.Relationships(context.Background(), RelationshipsRequest{Now: now, Limit: 10, ShowAll: true}) + require.NoError(err) + require.Len(controlResult.Rows, 1) + require.Equal(int64(1), controlResult.Rows[0].Signals.SentCount) + + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + b.AddOwnerParticipant(srcID, ownerID) + + aID := b.AddParticipant("alice@work.example", "work.example", "Alice Work") + a2ID := b.AddParticipant("alice@home.example", "home.example", "Alice Home") + b.LinkCluster(aID, a2ID) + + msgID := b.AddMessage(MessageOpt{SourceID: srcID, IsFromMe: true, SentAt: sentAt}) + b.AddFrom(msgID, ownerID, "Owner") + b.AddTo(msgID, aID, "Alice Work") + b.AddCc(msgID, a2ID, "Alice Home") + + engine := b.BuildEngine() + result, err := engine.Relationships(context.Background(), RelationshipsRequest{Now: now, Limit: 10, ShowAll: true}) + require.NoError(err) + require.Len(result.Rows, 1) + + row := result.Rows[0] + assert.Equal(aID, row.CanonicalID) + assert.Equal(int64(1), row.Signals.SentCount, "one message must count once, not once per linked recipient") + assert.InEpsilon(controlResult.Rows[0].Signals.SentToThem, row.Signals.SentToThem, 1e-9, + "a clustered duplicate recipient must not inflate the decayed sum beyond one message's decay") +} + +// TestRelationshipsWithOwnerResolvesAlias verifies that a meeting attended +// under an owner alias linked only via participant_clusters (not itself an +// owner_participants row) still counts as "together" for the other +// attendee, consistent with how owner-cluster exclusion already resolves +// aliases through canon. +func TestRelationshipsWithOwnerResolvesAlias(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + ownerAliasID := b.AddParticipant("owner@alias.example", "alias.example", "Owner Alias") + b.AddOwnerParticipant(srcID, ownerID) + b.LinkCluster(ownerID, ownerAliasID) + + otherID := b.AddParticipant("x@example.com", "example.com", "X") + + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + meetingID := b.AddMessage(MessageOpt{SourceID: srcID, MessageType: "calendar_event", SentAt: now.AddDate(0, 0, -1)}) + b.AddFrom(meetingID, ownerAliasID, "Owner Alias") + b.AddTo(meetingID, otherID, "X") + + engine := b.BuildEngine() + result, err := engine.Relationships(context.Background(), RelationshipsRequest{Now: now, Limit: 10, ShowAll: true}) + require.NoError(err) + require.Len(result.Rows, 1) + assert.Equal(otherID, result.Rows[0].CanonicalID) + assert.Equal(int64(1), result.Rows[0].Signals.MeetingCount) +} + +// TestRelationshipsOwnerAbsentMeetingContributesNoModality verifies that a +// meeting/event entry the archive owner did not attend contributes no +// signal at all: no modality (rather than being miscounted as a phantom +// "email" contact), no meeting count, and — since it is excluded from +// "interactions" entirely — no contribution to LastAt/LastInteractionAt. +// +// X has both a real signal (an email from the owner, earlier) and an +// owner-absent meeting with Y (later): X's row must reflect only the email +// in LastAt, not the later meeting. Y has nothing but the owner-absent +// meeting, so Y must not appear in the results at all — a meeting the owner +// never attended is not evidence of any relationship with the owner. +func TestRelationshipsOwnerAbsentMeetingContributesNoModality(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + b.AddOwnerParticipant(srcID, ownerID) + + xID := b.AddParticipant("x@example.com", "example.com", "X") + yID := b.AddParticipant("y@example.com", "example.com", "Y") + + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + emailID := b.AddMessage(MessageOpt{SourceID: srcID, IsFromMe: true, SentAt: now.AddDate(0, 0, -5)}) + b.AddFrom(emailID, ownerID, "Owner") + b.AddTo(emailID, xID, "X") + + meetingID := b.AddMessage(MessageOpt{SourceID: srcID, MessageType: "calendar_event", SentAt: now.AddDate(0, 0, -1)}) + b.AddFrom(meetingID, xID, "X") + b.AddTo(meetingID, yID, "Y") + + engine := b.BuildEngine() + result, err := engine.Relationships(context.Background(), RelationshipsRequest{Now: now, Limit: 10, ShowAll: true}) + require.NoError(err) + require.Len(result.Rows, 1, "Y has no signal at all and must not appear") + + row := result.Rows[0] + assert.Equal(xID, row.CanonicalID) + assert.Equal(1, row.Signals.Modalities, + "only the email counts; the owner-absent meeting must not contribute a second, phantom modality") + assert.Equal(int64(0), row.Signals.MeetingCount) + assert.WithinDuration(now.AddDate(0, 0, -5), row.LastAt, 0, + "LastAt must reflect the email, not the later owner-absent meeting") +} diff --git a/internal/query/shared.go b/internal/query/shared.go index 66d274554..ea609f86b 100644 --- a/internal/query/shared.go +++ b/internal/query/shared.go @@ -20,10 +20,20 @@ import ( // Analytics dataset names. Each is both the Parquet subdirectory under // analyticsDir and the view/probe key for that dataset's optional columns. const ( - datasetMessages = "messages" - datasetParticipants = "participants" - datasetConversations = "conversations" - messageTypeEmail = "email" + datasetMessages = "messages" + datasetSources = "sources" + datasetParticipants = "participants" + datasetParticipantIdentifiers = "participant_identifiers" + datasetConversations = "conversations" + datasetConversationParticipants = "conversation_participants" + datasetOwnerParticipants = "owner_participants" + datasetParticipantClusters = "participant_clusters" + messageTypeDimension = "message_type" + messageTypeEmail = "email" + messageTypeCalendar = "calendar_event" + timeGranularityMonth = "month" + sortDirectionAsc = "asc" + sortDirectionDesc = "desc" ) // emailOnlyFilterMsg is the SQL condition restricting to email messages with "msg." alias (DuckDB). diff --git a/internal/query/snippet.go b/internal/query/snippet.go new file mode 100644 index 000000000..b2d946e7d --- /dev/null +++ b/internal/query/snippet.go @@ -0,0 +1,32 @@ +package query + +import ( + "regexp" + "strings" +) + +var ( + // Heading markers require trailing whitespace so issue refs (#50362) + // and hashtags (#launch) survive. + snippetHeading = regexp.MustCompile(`(^|\s)#{1,6}\s+`) + snippetListMarker = regexp.MustCompile(`(?m)^\s{0,3}(?:[-*+]|\d{1,3}[.)])\s+`) + snippetBold = regexp.MustCompile(`\*\*([^\s*](?:[^*]*[^\s*])?)\*\*`) + snippetInlineCode = regexp.MustCompile("`([^`]+)`") + snippetWhitespace = regexp.MustCompile(`\s+`) +) + +// FlattenSnippet renders stored snippet markup (markdown headings, list +// markers, bold/code emphasis) as plain single-line text. Meeting importers +// persist raw body prefixes as snippets, so structural markdown otherwise +// surfaces verbatim in explore excerpts. +func FlattenSnippet(snippet string) string { + if snippet == "" { + return snippet + } + flattened := snippetHeading.ReplaceAllString(snippet, "$1") + flattened = snippetListMarker.ReplaceAllString(flattened, "") + flattened = snippetBold.ReplaceAllString(flattened, "$1") + flattened = snippetInlineCode.ReplaceAllString(flattened, "$1") + flattened = snippetWhitespace.ReplaceAllString(flattened, " ") + return strings.TrimSpace(flattened) +} diff --git a/internal/query/snippet_test.go b/internal/query/snippet_test.go new file mode 100644 index 000000000..fff35677c --- /dev/null +++ b/internal/query/snippet_test.go @@ -0,0 +1,36 @@ +package query + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFlattenSnippet(t *testing.T) { + tests := []struct { + name string + in string + want string + }{ + {"empty", "", ""}, + {"plain text unchanged", "Quarterly planning notes", "Quarterly planning notes"}, + {"leading heading", "### Architecture\nMonorepo vs. two repos", "Architecture Monorepo vs. two repos"}, + {"mid-string heading after join", "sync When: 09:00 ### Architecture: Monorepo", "sync When: 09:00 Architecture: Monorepo"}, + {"issue reference preserved", "someuser opened a new pull request, #50362", "someuser opened a new pull request, #50362"}, + {"hashtag preserved", "tagged #launch in the notes", "tagged #launch in the notes"}, + {"bullet list", "- keep module separate\n- draw a clear boundary", "keep module separate draw a clear boundary"}, + {"numbered list", "1. kickoff talk\n2) judging", "kickoff talk judging"}, + {"bold and code stripped", "**Consensus:** keep `alpha module` separate", "Consensus: keep alpha module separate"}, + {"whitespace collapsed", "line one\n\n\tline two", "line one line two"}, + {"dunder identifier unchanged", "call __init__ on the object", "call __init__ on the object"}, + {"exponent operator unchanged", "compute 2 ** 3 before summing", "compute 2 ** 3 before summing"}, + {"paired backticks stripped", "run `code` now", "run code now"}, + {"paired bold stripped", "**bold** text", "bold text"}, + {"unpaired bold left alone", "keep ** unmatched", "keep ** unmatched"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, FlattenSnippet(tt.in)) + }) + } +} diff --git a/internal/query/sqlite.go b/internal/query/sqlite.go index ef46faa6e..b4bf939f7 100644 --- a/internal/query/sqlite.go +++ b/internal/query/sqlite.go @@ -183,7 +183,7 @@ func aggDimensionForView(d Dialect, view ViewType, timeGranularity TimeGranulari case TimeYear: gran = "year" case TimeMonth: - gran = "month" + gran = timeGranularityMonth case TimeDay: gran = "day" default: @@ -552,11 +552,11 @@ func (e *SQLiteEngine) buildFilterJoinsAndConditions(filter MessageFilter, table case TimeYear: gran = "year" case TimeMonth: - gran = "month" + gran = timeGranularityMonth case TimeDay: gran = "day" default: - gran = "month" + gran = timeGranularityMonth } timeExpr := e.dialect.TimeTruncExpression(prefix+"sent_at", gran) conditions = append(conditions, timeExpr+" = ?") @@ -647,7 +647,7 @@ func sqliteMessageTypeCondition(alias string, messageTypes []string) (string, [] exact = append(exact, typ) } - col := "message_type" + col := messageTypeDimension if alias != "" { col = alias + ".message_type" } @@ -1422,11 +1422,11 @@ func (e *SQLiteEngine) GetGmailIDsByFilter(ctx context.Context, filter MessageFi case TimeYear: gran = "year" case TimeMonth: - gran = "month" + gran = timeGranularityMonth case TimeDay: gran = "day" default: - gran = "month" + gran = timeGranularityMonth } timeExpr := e.dialect.TimeTruncExpression("m.sent_at", gran) conditions = append(conditions, timeExpr+" = ?") diff --git a/internal/query/testfixtures_test.go b/internal/query/testfixtures_test.go index 165093ff3..196c0e1ee 100644 --- a/internal/query/testfixtures_test.go +++ b/internal/query/testfixtures_test.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strings" "testing" "time" @@ -21,21 +22,24 @@ import ( // MessageFixture defines a message row for Parquet test data. type MessageFixture struct { - ID int64 - SourceID int64 - SourceMessageID string - ConversationID int64 - Subject string - Snippet string - SentAt time.Time - SizeEstimate int64 - HasAttachments bool - AttachmentCount int - DeletedAt *time.Time // nil = NULL - SenderID *int64 // nil = NULL (direct sender for WhatsApp/chat messages) - MessageType string // e.g. "email", "whatsapp" - Year int - Month int + ID int64 + SourceID int64 + SourceMessageID string + ConversationID int64 + Subject string + Snippet string + SentAt time.Time + SizeEstimate int64 + HasAttachments bool + AttachmentCount int + DeletedAt *time.Time // nil = NULL + InternalDeletedAt *time.Time // nil = omitted from legacy fixtures / NULL in opted-in fixtures + DeletedFromSourceAt *time.Time // nil = NULL + SenderID *int64 // nil = NULL (direct sender for WhatsApp/chat messages) + MessageType string // e.g. "email", "whatsapp" + IsFromMe bool + Year int + Month int } // SourceFixture defines a source row for Parquet test data. @@ -54,6 +58,17 @@ type ParticipantFixture struct { PhoneNumber string // E.164 phone number (for WhatsApp/chat participants) } +// ParticipantIdentifierFixture defines explicit identity evidence for one +// durable participant. Multiple rows for the same participant do not create +// additional people. +type ParticipantIdentifierFixture struct { + ParticipantID int64 + IdentifierType string + IdentifierValue string + DisplayValue string + IsPrimary bool +} + // RecipientFixture defines a message_recipients row for Parquet test data. type RecipientFixture struct { MessageID int64 @@ -76,9 +91,11 @@ type MessageLabelFixture struct { // AttachmentFixture defines an attachment row for Parquet test data. type AttachmentFixture struct { + ID int64 MessageID int64 Size int64 Filename string + MimeType string } // ConversationFixture defines a conversation row for Parquet test data. @@ -86,6 +103,28 @@ type ConversationFixture struct { ID int64 SourceConversationID string Title string // Group/chat name (for WhatsApp/chat conversations) + ConversationType string +} + +// ConversationParticipantFixture defines durable conversation membership. +type ConversationParticipantFixture struct { + ConversationID int64 + ParticipantID int64 +} + +// OwnerParticipantFixture defines an owner_participants row for Parquet test +// data: a participant that resolves to a confirmed account identity. +type OwnerParticipantFixture struct { + SourceID int64 + ParticipantID int64 +} + +// ParticipantClusterFixture defines a participant_clusters row for Parquet +// test data: participant_id mapped to its canonical (smallest member) +// cluster ID. +type ParticipantClusterFixture struct { + ParticipantID int64 + CanonicalID int64 } // --------------------------------------------------------------------------- @@ -100,15 +139,20 @@ type TestDataBuilder struct { nextPartID int64 nextLabelID int64 nextConvID int64 - - sources []SourceFixture - messages []MessageFixture - participants []ParticipantFixture - recipients []RecipientFixture - labels []LabelFixture - msgLabels []MessageLabelFixture - attachments []AttachmentFixture - conversations []ConversationFixture + nextAttID int64 + + sources []SourceFixture + messages []MessageFixture + participants []ParticipantFixture + participantIdentifiers []ParticipantIdentifierFixture + recipients []RecipientFixture + labels []LabelFixture + msgLabels []MessageLabelFixture + attachments []AttachmentFixture + conversations []ConversationFixture + conversationParticipants []ConversationParticipantFixture + ownerParticipants []OwnerParticipantFixture + participantClusters []ParticipantClusterFixture emptyAttachments bool // if true, write empty attachments file } @@ -123,6 +167,7 @@ func NewTestDataBuilder(tb testing.TB) *TestDataBuilder { nextPartID: 1, nextLabelID: 1, nextConvID: 200, + nextAttID: 1, } } @@ -173,16 +218,21 @@ func (b *TestDataBuilder) AddLabel(name string) int64 { // MessageOpt configures a message to add. type MessageOpt struct { - Subject string - Snippet string - SentAt time.Time - SizeEstimate int64 - HasAttachments bool - DeletedAt *time.Time - SourceID int64 // defaults to 1 - ConversationID int64 // 0 = auto-assign - MessageType string // defaults to "email" - SenderID *int64 // nil = NULL (direct sender for text/chat messages) + Subject string + Snippet string + SentAt time.Time + SizeEstimate int64 + HasAttachments bool + DeletedAt *time.Time + InternalDeletedAt *time.Time + DeletedFromSourceAt *time.Time + SourceID int64 // defaults to 1 + ConversationID int64 // 0 = auto-assign + MessageType string // defaults to "email" + SenderID *int64 // nil = NULL (direct sender for text/chat messages) + IsFromMe bool + ConversationType string // defaults from MessageType + ConversationTitle string } // AddMessage adds a message and returns its ID. @@ -210,20 +260,23 @@ func (b *TestDataBuilder) AddMessage(opt MessageOpt) int64 { } b.messages = append(b.messages, MessageFixture{ - ID: id, - SourceID: srcID, - SourceMessageID: fmt.Sprintf("msg%d", id), - ConversationID: convID, - Subject: opt.Subject, - Snippet: snippet, - SentAt: sentAt, - SizeEstimate: opt.SizeEstimate, - HasAttachments: opt.HasAttachments, - DeletedAt: opt.DeletedAt, - SenderID: opt.SenderID, - MessageType: opt.MessageType, - Year: sentAt.Year(), - Month: int(sentAt.Month()), + ID: id, + SourceID: srcID, + SourceMessageID: fmt.Sprintf("msg%d", id), + ConversationID: convID, + Subject: opt.Subject, + Snippet: snippet, + SentAt: sentAt, + SizeEstimate: opt.SizeEstimate, + HasAttachments: opt.HasAttachments, + DeletedAt: opt.DeletedAt, + InternalDeletedAt: opt.InternalDeletedAt, + DeletedFromSourceAt: opt.DeletedFromSourceAt, + SenderID: opt.SenderID, + MessageType: opt.MessageType, + IsFromMe: opt.IsFromMe, + Year: sentAt.Year(), + Month: int(sentAt.Month()), }) // Track conversation if not already present @@ -235,9 +288,15 @@ func (b *TestDataBuilder) AddMessage(opt MessageOpt) int64 { } } if !convExists { + conversationType := opt.ConversationType + if conversationType == "" { + conversationType = "email" + } b.conversations = append(b.conversations, ConversationFixture{ ID: convID, SourceConversationID: fmt.Sprintf("thread%d", convID), + Title: opt.ConversationTitle, + ConversationType: conversationType, }) } @@ -267,6 +326,44 @@ func (b *TestDataBuilder) AddCc(messageID, participantID int64, displayName stri b.AddRecipient(messageID, participantID, "cc", displayName) } +// AddConversationParticipant associates a participant with a conversation. +func (b *TestDataBuilder) AddConversationParticipant(conversationID, participantID int64) { + b.conversationParticipants = append(b.conversationParticipants, ConversationParticipantFixture{ + ConversationID: conversationID, + ParticipantID: participantID, + }) +} + +// AddOwnerParticipant adds an owner_participants row: participantID resolves +// to a confirmed account identity for sourceID. +func (b *TestDataBuilder) AddOwnerParticipant(sourceID, participantID int64) { + b.ownerParticipants = append(b.ownerParticipants, OwnerParticipantFixture{ + SourceID: sourceID, ParticipantID: participantID, + }) +} + +// LinkCluster adds participant_clusters rows mapping every given participant +// ID to the smallest ID among them (the canonical cluster ID), mirroring +// Store.ParticipantClusters. Requires at least two IDs. +func (b *TestDataBuilder) LinkCluster(ids ...int64) { + b.t.Helper() + require.GreaterOrEqual(b.t, len(ids), 2, "LinkCluster: at least two participant IDs are required") + canonical := slices.Min(ids) + for _, id := range ids { + b.participantClusters = append(b.participantClusters, ParticipantClusterFixture{ + ParticipantID: id, CanonicalID: canonical, + }) + } +} + +// AddParticipantIdentifier records explicit identity evidence for a participant. +func (b *TestDataBuilder) AddParticipantIdentifier(participantID int64, identifierType, identifierValue, displayValue string, isPrimary bool) { + b.participantIdentifiers = append(b.participantIdentifiers, ParticipantIdentifierFixture{ + ParticipantID: participantID, IdentifierType: identifierType, + IdentifierValue: identifierValue, DisplayValue: displayValue, IsPrimary: isPrimary, + }) +} + // AddMessageLabel associates a message with a label. func (b *TestDataBuilder) AddMessageLabel(messageID, labelID int64) { b.msgLabels = append(b.msgLabels, MessageLabelFixture{ @@ -276,9 +373,20 @@ func (b *TestDataBuilder) AddMessageLabel(messageID, labelID int64) { // AddAttachment adds an attachment row and sets HasAttachments on the related message. func (b *TestDataBuilder) AddAttachment(messageID, size int64, filename string) { + b.AddAttachmentWithMIME(b.nextAttID, messageID, size, filename, "application/octet-stream") + b.nextAttID++ +} + +// AddAttachmentWithID adds an attachment with a chosen durable identity. +func (b *TestDataBuilder) AddAttachmentWithID(id, messageID, size int64, filename string) { + b.AddAttachmentWithMIME(id, messageID, size, filename, "application/octet-stream") +} + +// AddAttachmentWithMIME adds an attachment with a chosen durable identity and MIME type. +func (b *TestDataBuilder) AddAttachmentWithMIME(id, messageID, size int64, filename, mimeType string) { b.t.Helper() b.attachments = append(b.attachments, AttachmentFixture{ - MessageID: messageID, Size: size, Filename: filename, + ID: id, MessageID: messageID, Size: size, Filename: filename, MimeType: mimeType, }) // Ensure the related message has HasAttachments set to true. for i := range b.messages { @@ -315,9 +423,38 @@ func joinRows[T any](items []T, format func(T) string) string { // toSQL converts a MessageFixture to a SQL VALUES row string. func (m MessageFixture) toSQL() string { - deletedAt := "NULL::TIMESTAMP" - if m.DeletedAt != nil { - deletedAt = fmt.Sprintf("TIMESTAMP '%s'", m.DeletedAt.Format("2006-01-02 15:04:05")) + deletedFromSourceAt := "NULL::TIMESTAMP" + if m.DeletedFromSourceAt != nil { + deletedFromSourceAt = fmt.Sprintf("TIMESTAMP '%s'", m.DeletedFromSourceAt.Format("2006-01-02 15:04:05")) + } else if m.DeletedAt != nil { + deletedFromSourceAt = fmt.Sprintf("TIMESTAMP '%s'", m.DeletedAt.Format("2006-01-02 15:04:05")) + } + senderID := "NULL::BIGINT" + if m.SenderID != nil { + senderID = fmt.Sprintf("%d::BIGINT", *m.SenderID) + } + msgType := m.MessageType + if msgType == "" { + msgType = "email" + } + return fmt.Sprintf("(%d::BIGINT, %d::BIGINT, %s, %d::BIGINT, %s, %s, TIMESTAMP '%s', %d::BIGINT, %v, %d, %s, %s, %s, %v, %d, %d)", + m.ID, m.SourceID, sqlStr(m.SourceMessageID), m.ConversationID, + sqlStr(m.Subject), sqlStr(m.Snippet), + m.SentAt.Format("2006-01-02 15:04:05"), m.SizeEstimate, + m.HasAttachments, m.AttachmentCount, deletedFromSourceAt, senderID, sqlStr(msgType), m.IsFromMe, m.Year, m.Month, + ) +} + +func (m MessageFixture) toSQLWithInternalDeletion() string { + internalDeletedAt := "NULL::TIMESTAMP" + if m.InternalDeletedAt != nil { + internalDeletedAt = fmt.Sprintf("TIMESTAMP '%s'", m.InternalDeletedAt.Format("2006-01-02 15:04:05")) + } + deletedFromSourceAt := "NULL::TIMESTAMP" + if m.DeletedFromSourceAt != nil { + deletedFromSourceAt = fmt.Sprintf("TIMESTAMP '%s'", m.DeletedFromSourceAt.Format("2006-01-02 15:04:05")) + } else if m.DeletedAt != nil { + deletedFromSourceAt = fmt.Sprintf("TIMESTAMP '%s'", m.DeletedAt.Format("2006-01-02 15:04:05")) } senderID := "NULL::BIGINT" if m.SenderID != nil { @@ -327,11 +464,11 @@ func (m MessageFixture) toSQL() string { if msgType == "" { msgType = "email" } - return fmt.Sprintf("(%d::BIGINT, %d::BIGINT, %s, %d::BIGINT, %s, %s, TIMESTAMP '%s', %d::BIGINT, %v, %d, %s, %s, %s, %d, %d)", + return fmt.Sprintf("(%d::BIGINT, %d::BIGINT, %s, %d::BIGINT, %s, %s, TIMESTAMP '%s', %d::BIGINT, %v, %d, %s, %s, %s, %s, %v, %d, %d)", m.ID, m.SourceID, sqlStr(m.SourceMessageID), m.ConversationID, sqlStr(m.Subject), sqlStr(m.Snippet), m.SentAt.Format("2006-01-02 15:04:05"), m.SizeEstimate, - m.HasAttachments, m.AttachmentCount, deletedAt, senderID, sqlStr(msgType), m.Year, m.Month, + m.HasAttachments, m.AttachmentCount, internalDeletedAt, deletedFromSourceAt, senderID, sqlStr(msgType), m.IsFromMe, m.Year, m.Month, ) } @@ -352,6 +489,14 @@ func (b *TestDataBuilder) participantsSQL() string { }) } +func (b *TestDataBuilder) participantIdentifiersSQL() string { + return joinRows(b.participantIdentifiers, func(identifier ParticipantIdentifierFixture) string { + return fmt.Sprintf("(%d::BIGINT, %s, %s, %s, %v)", identifier.ParticipantID, + sqlStr(identifier.IdentifierType), sqlStr(identifier.IdentifierValue), + sqlStr(identifier.DisplayValue), identifier.IsPrimary) + }) +} + func (b *TestDataBuilder) recipientsSQL() string { return joinRows(b.recipients, func(r RecipientFixture) string { return fmt.Sprintf("(%d::BIGINT, %d::BIGINT, %s, %s)", @@ -373,15 +518,33 @@ func (b *TestDataBuilder) messageLabelsSQL() string { func (b *TestDataBuilder) attachmentsSQL() string { return joinRows(b.attachments, func(a AttachmentFixture) string { - return fmt.Sprintf("(%d::BIGINT, %d::BIGINT, %s)", - a.MessageID, a.Size, sqlStr(a.Filename)) + return fmt.Sprintf("(%d::BIGINT, %d::BIGINT, %d::BIGINT, %s, %s)", + a.ID, a.MessageID, a.Size, sqlStr(a.Filename), sqlStr(a.MimeType)) }) } func (b *TestDataBuilder) conversationsSQL() string { return joinRows(b.conversations, func(c ConversationFixture) string { - return fmt.Sprintf("(%d::BIGINT, %s, %s)", - c.ID, sqlStr(c.SourceConversationID), sqlStr(c.Title)) + return fmt.Sprintf("(%d::BIGINT, %s, %s, %s)", + c.ID, sqlStr(c.SourceConversationID), sqlStr(c.Title), sqlStr(c.ConversationType)) + }) +} + +func (b *TestDataBuilder) conversationParticipantsSQL() string { + return joinRows(b.conversationParticipants, func(cp ConversationParticipantFixture) string { + return fmt.Sprintf("(%d::BIGINT, %d::BIGINT)", cp.ConversationID, cp.ParticipantID) + }) +} + +func (b *TestDataBuilder) ownerParticipantsSQL() string { + return joinRows(b.ownerParticipants, func(op OwnerParticipantFixture) string { + return fmt.Sprintf("(%d::BIGINT, %d::BIGINT)", op.SourceID, op.ParticipantID) + }) +} + +func (b *TestDataBuilder) participantClustersSQL() string { + return joinRows(b.participantClusters, func(pc ParticipantClusterFixture) string { + return fmt.Sprintf("(%d::BIGINT, %d::BIGINT)", pc.ParticipantID, pc.CanonicalID) }) } @@ -391,14 +554,19 @@ func (b *TestDataBuilder) conversationsSQL() string { // column definitions (coupled to SQL generation methods above). const ( - messagesCols = "id, source_id, source_message_id, conversation_id, subject, snippet, sent_at, size_estimate, has_attachments, attachment_count, deleted_from_source_at, sender_id, message_type, year, month" - sourcesCols = "id, account_email, source_type" - participantsCols = "id, email_address, domain, display_name, phone_number" - messageRecipientsCols = "message_id, participant_id, recipient_type, display_name" - labelsCols = "id, name" - messageLabelsCols = "message_id, label_id" - attachmentsCols = "message_id, size, filename" - conversationsCols = "id, source_conversation_id, title" + messagesCols = "id, source_id, source_message_id, conversation_id, subject, snippet, sent_at, size_estimate, has_attachments, attachment_count, deleted_from_source_at, sender_id, message_type, is_from_me, year, month" + messagesColsWithDeletedAt = "id, source_id, source_message_id, conversation_id, subject, snippet, sent_at, size_estimate, has_attachments, attachment_count, deleted_at, deleted_from_source_at, sender_id, message_type, is_from_me, year, month" + sourcesCols = "id, account_email, source_type" + participantsCols = "id, email_address, domain, display_name, phone_number" + participantIdentifiersCols = "participant_id, identifier_type, identifier_value, display_value, is_primary" + messageRecipientsCols = "message_id, participant_id, recipient_type, display_name" + labelsCols = "id, name" + messageLabelsCols = "message_id, label_id" + attachmentsCols = "attachment_id, message_id, size, filename, mime_type" + conversationsCols = "id, source_conversation_id, title, conversation_type" + conversationParticipantsCols = "conversation_id, participant_id" + ownerParticipantsCols = "source_id, participant_id" + participantClustersCols = "participant_id, canonical_id" ) // Build generates Parquet files from the accumulated data and returns the @@ -418,7 +586,7 @@ func (b *TestDataBuilder) Build() (string, func()) { func (b *TestDataBuilder) addMessageTables(pb *parquetBuilder) { if len(b.messages) == 0 { pb.addEmptyTable("messages", "messages/year=0", "empty.parquet", messagesCols, - "(0::BIGINT, 0::BIGINT, '', 0::BIGINT, '', '', TIMESTAMP '1970-01-01', 0::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', 0, 0)") + "(0::BIGINT, 0::BIGINT, '', 0::BIGINT, '', '', TIMESTAMP '1970-01-01', 0::BIGINT, false, 0::INTEGER, NULL::TIMESTAMP, NULL::BIGINT, 'email', false, 0, 0)") return } byYear := map[int][]MessageFixture{} @@ -426,10 +594,16 @@ func (b *TestDataBuilder) addMessageTables(pb *parquetBuilder) { byYear[m.Year] = append(byYear[m.Year], m) } for year, msgs := range byYear { - rows := joinRows(msgs, MessageFixture.toSQL) + cols := messagesCols + rowFormatter := MessageFixture.toSQL + if slices.ContainsFunc(msgs, func(message MessageFixture) bool { return message.InternalDeletedAt != nil }) { + cols = messagesColsWithDeletedAt + rowFormatter = MessageFixture.toSQLWithInternalDeletion + } + rows := joinRows(msgs, rowFormatter) pb.addTable("messages", fmt.Sprintf("messages/year=%d", year), "data.parquet", - messagesCols, rows) + cols, rows) } } @@ -441,10 +615,14 @@ func (b *TestDataBuilder) addAuxiliaryTables(pb *parquetBuilder) { }{ {"sources", "sources", "sources.parquet", sourcesCols, "(0::BIGINT, '', 'gmail')", b.sourcesSQL(), len(b.sources) == 0}, {"participants", "participants", "participants.parquet", participantsCols, "(0::BIGINT, '', '', '', '')", b.participantsSQL(), len(b.participants) == 0}, + {"participant_identifiers", "participant_identifiers", "participant_identifiers.parquet", participantIdentifiersCols, "(0::BIGINT, '', '', '', false)", b.participantIdentifiersSQL(), len(b.participantIdentifiers) == 0}, {"message_recipients", "message_recipients", "message_recipients.parquet", messageRecipientsCols, "(0::BIGINT, 0::BIGINT, '', '')", b.recipientsSQL(), len(b.recipients) == 0}, {"labels", "labels", "labels.parquet", labelsCols, "(0::BIGINT, '')", b.labelsSQL(), len(b.labels) == 0}, {"message_labels", "message_labels", "message_labels.parquet", messageLabelsCols, "(0::BIGINT, 0::BIGINT)", b.messageLabelsSQL(), len(b.msgLabels) == 0}, - {"conversations", "conversations", "conversations.parquet", conversationsCols, "(0::BIGINT, '', '')", b.conversationsSQL(), len(b.conversations) == 0}, + {"conversations", "conversations", "conversations.parquet", conversationsCols, "(0::BIGINT, '', '', 'email')", b.conversationsSQL(), len(b.conversations) == 0}, + {"conversation_participants", "conversation_participants", "conversation_participants.parquet", conversationParticipantsCols, "(0::BIGINT, 0::BIGINT)", b.conversationParticipantsSQL(), len(b.conversationParticipants) == 0}, + {datasetOwnerParticipants, datasetOwnerParticipants, datasetOwnerParticipants + ".parquet", ownerParticipantsCols, "(0::BIGINT, 0::BIGINT)", b.ownerParticipantsSQL(), len(b.ownerParticipants) == 0}, + {datasetParticipantClusters, datasetParticipantClusters, datasetParticipantClusters + ".parquet", participantClustersCols, "(0::BIGINT, 0::BIGINT)", b.participantClustersSQL(), len(b.participantClusters) == 0}, } for _, a := range auxTables { if a.empty { @@ -461,7 +639,7 @@ func (b *TestDataBuilder) addAttachmentsTable(pb *parquetBuilder) { pb.addTable("attachments", "attachments", "attachments.parquet", attachmentsCols, b.attachmentsSQL()) } else { pb.addEmptyTable("attachments", "attachments", "attachments.parquet", attachmentsCols, - "(0::BIGINT, 0::BIGINT, '')") + "(0::BIGINT, 0::BIGINT, 0::BIGINT, '', '')") } } @@ -532,6 +710,10 @@ func (b *parquetBuilder) addEmptyTable(name, subdir, file, columns, dummyValues // analytics directory path and a cleanup function. func (b *parquetBuilder) build() (string, func()) { b.t.Helper() + b.ensureConversationParticipantsTable() + b.ensureParticipantIdentifiersTable() + b.ensureOwnerParticipantsTable() + b.ensureParticipantClustersTable() tmpDir := b.createTempDirs() @@ -543,8 +725,13 @@ func (b *parquetBuilder) build() (string, func()) { defer func() { _ = db.Close() }() b.writeParquetFiles(db, tmpDir) + fingerprint, err := CacheDatasetFingerprint(tmpDir) + require.NoError(b.t, err, "fingerprint cache fixture") stateData, err := json.Marshal(CacheSyncState{ - LastSyncAt: time.Date(2026, 7, 15, 12, 0, 0, 0, time.UTC), + LastSyncAt: time.Date(2026, 7, 15, 12, 0, 0, 0, time.UTC), + PublishedAt: time.Date(2026, 7, 15, 12, 1, 0, 0, time.UTC), + SchemaVersion: CacheSchemaVersion, + DatasetFingerprint: fingerprint, }) require.NoError(b.t, err, "marshal cache state") require.NoError(b.t, os.WriteFile(CacheStatePath(tmpDir), stateData, 0o600), @@ -553,6 +740,52 @@ func (b *parquetBuilder) build() (string, func()) { return tmpDir, func() { _ = os.RemoveAll(tmpDir) } } +func (b *parquetBuilder) ensureParticipantIdentifiersTable() { + for _, table := range b.tables { + if table.name == datasetParticipantIdentifiers { + return + } + } + b.addEmptyTable(datasetParticipantIdentifiers, datasetParticipantIdentifiers, + datasetParticipantIdentifiers+".parquet", participantIdentifiersCols, + "(0::BIGINT, '', '', '', false)") +} + +func (b *parquetBuilder) ensureOwnerParticipantsTable() { + for _, table := range b.tables { + if table.name == datasetOwnerParticipants { + return + } + } + b.addEmptyTable(datasetOwnerParticipants, datasetOwnerParticipants, + datasetOwnerParticipants+".parquet", ownerParticipantsCols, "(0::BIGINT, 0::BIGINT)") +} + +func (b *parquetBuilder) ensureParticipantClustersTable() { + for _, table := range b.tables { + if table.name == datasetParticipantClusters { + return + } + } + b.addEmptyTable(datasetParticipantClusters, datasetParticipantClusters, + datasetParticipantClusters+".parquet", participantClustersCols, "(0::BIGINT, 0::BIGINT)") +} + +func (b *parquetBuilder) ensureConversationParticipantsTable() { + for _, table := range b.tables { + if table.name == datasetConversationParticipants { + return + } + } + b.addEmptyTable( + datasetConversationParticipants, + datasetConversationParticipants, + datasetConversationParticipants+".parquet", + conversationParticipantsCols, + "(0::BIGINT, 0::BIGINT)", + ) +} + // createTempDirs creates the temp directory and all required subdirectories. func (b *parquetBuilder) createTempDirs() string { b.t.Helper() diff --git a/internal/query/text_models.go b/internal/query/text_models.go index f401b00d0..a901ac455 100644 --- a/internal/query/text_models.go +++ b/internal/query/text_models.go @@ -114,12 +114,15 @@ type TextStatsOptions struct { const ( messageTypeSMS = "sms" + messageTypeRCS = "rcs" + messageTypeIMessage = "imessage" messageTypeMeetingTranscript = "meeting_transcript" ) // TextMessageTypes lists the message_type values included in Texts mode. var TextMessageTypes = []string{ - "whatsapp", "imessage", messageTypeSMS, "mms", "google_voice_text", "teams", "discord", "beeper", + "whatsapp", messageTypeIMessage, messageTypeSMS, "mms", messageTypeRCS, + "google_voice_text", "teams", "discord", "beeper", "fbmessenger", } // TextMessageTypeSQLList renders TextMessageTypes as a quoted SQL IN-list @@ -153,12 +156,13 @@ func IsTextMessageType(mt string) bool { // values instead of silently returning no results. var KnownMessageTypes = []string{ messageTypeEmail, - "calendar_event", + messageTypeCalendar, messageTypeMeetingTranscript, messageTypeSMS, "mms", + messageTypeRCS, "whatsapp", - "imessage", + messageTypeIMessage, "teams", "discord", "fbmessenger", diff --git a/internal/query/views.go b/internal/query/views.go index 8cec98a95..b4e1cee3d 100644 --- a/internal/query/views.go +++ b/internal/query/views.go @@ -125,6 +125,7 @@ func probeAllOptionalColumns(db *sql.DB, analyticsDir string) map[string]map[str datasetMessages: probeColumns(db, msgGlob, true), datasetParticipants: probeColumns(db, tablePath(datasetParticipants), false), datasetConversations: probeColumns(db, tablePath(datasetConversations), false), + "attachments": probeColumns(db, tablePath("attachments"), false), "sources": probeColumns(db, tablePath("sources"), false), } } @@ -195,10 +196,20 @@ func createBaseViews(db *sql.DB, analyticsDir string, optCols map[string]map[str defaultExpr: "NULL::BIGINT AS sender_id", }, { - name: "message_type", + name: messageTypeDimension, replaceExpr: "COALESCE(CAST(message_type AS VARCHAR), '') AS message_type", defaultExpr: "'' AS message_type", }, + { + name: "deleted_at", + replaceExpr: "TRY_CAST(deleted_at AS TIMESTAMP) AS deleted_at", + defaultExpr: "NULL::TIMESTAMP AS deleted_at", + }, + { + name: "is_from_me", + replaceExpr: "COALESCE(TRY_CAST(is_from_me AS BOOLEAN), false) AS is_from_me", + defaultExpr: "false AS is_from_me", + }, }, }, probe: colsFor(datasetMessages), @@ -264,7 +275,13 @@ func createBaseViews(db *sql.DB, analyticsDir string, optCols map[string]map[str "CAST(size AS BIGINT) AS size", "CAST(filename AS VARCHAR) AS filename", }, + optionalCols: []optionalCol{{ + name: "mime_type", + replaceExpr: "COALESCE(CAST(mime_type AS VARCHAR), '') AS mime_type", + defaultExpr: "'' AS mime_type", + }}, }, + probe: colsFor("attachments"), }, { def: viewDef{ @@ -289,6 +306,29 @@ func createBaseViews(db *sql.DB, analyticsDir string, optCols map[string]map[str }, probe: colsFor(datasetConversations), }, + { + def: viewDef{ + name: datasetConversationParticipants, + pathPattern: tablePath(datasetConversationParticipants), + replaceCols: []string{ + "CAST(conversation_id AS BIGINT) AS conversation_id", + "CAST(participant_id AS BIGINT) AS participant_id", + }, + }, + }, + { + def: viewDef{ + name: datasetParticipantIdentifiers, + pathPattern: tablePath(datasetParticipantIdentifiers), + replaceCols: []string{ + "CAST(participant_id AS BIGINT) AS participant_id", + "CAST(identifier_type AS VARCHAR) AS identifier_type", + "CAST(identifier_value AS VARCHAR) AS identifier_value", + "COALESCE(CAST(display_value AS VARCHAR), '') AS display_value", + "COALESCE(TRY_CAST(is_primary AS BOOLEAN), false) AS is_primary", + }, + }, + }, { def: viewDef{ name: "sources", @@ -334,6 +374,7 @@ func createConvenienceViews(db *sql.DB) error { {"v_domains", sqlVDomains}, {"v_labels", sqlVLabels}, {"v_threads", sqlVThreads}, + {"analytical_entries", sqlAnalyticalEntries}, } for _, v := range views { if _, err := db.Exec(v.sql); err != nil { @@ -343,6 +384,81 @@ func createConvenienceViews(db *sql.DB) error { return nil } +// sqlAnalyticalEntries normalizes every durable message-shaped source row. +// Logical row-unit selection deliberately remains in Explore so Context can be +// applied before chat conversations are aggregated. +const sqlAnalyticalEntries = ` +CREATE OR REPLACE VIEW analytical_entries AS +WITH message_participant_links AS ( + SELECT message_id, participant_id + FROM message_recipients + UNION ALL + SELECT id AS message_id, sender_id AS participant_id + FROM messages + WHERE sender_id IS NOT NULL +), message_participant_facts AS ( + SELECT + link.message_id, + list_sort(list_distinct(list(link.participant_id))) AS participant_ids, + list_sort(list_distinct(list(COALESCE(NULLIF(p.display_name, ''), NULLIF(p.phone_number, ''), p.email_address, '')))) AS participant_labels, + list_sort(list_distinct(list(COALESCE(p.domain, '')))) AS participant_domains + FROM message_participant_links link + JOIN participants p ON p.id = link.participant_id + GROUP BY link.message_id +), conversation_participant_facts AS ( + SELECT + cp.conversation_id, + list_sort(list_distinct(list(cp.participant_id))) AS participant_ids, + list_sort(list_distinct(list(COALESCE(NULLIF(p.display_name, ''), NULLIF(p.phone_number, ''), p.email_address, '')))) AS participant_labels, + list_sort(list_distinct(list(COALESCE(p.domain, '')))) AS participant_domains + FROM conversation_participants cp + JOIN participants p ON p.id = cp.participant_id + GROUP BY cp.conversation_id +) +SELECT + m.id AS message_id, + m.source_id, + COALESCE(s.source_type, '') AS source_type, + COALESCE(s.account_email, '') AS source_identifier, + m.source_message_id, + m.conversation_id, + COALESCE(c.source_conversation_id, '') AS source_conversation_id, + COALESCE(c.conversation_type, '') AS conversation_type, + COALESCE(c.title, '') AS conversation_title, + COALESCE(m.message_type, '') AS message_type, + m.sender_id, + COALESCE(NULLIF(sender.email_address, ''), NULLIF(sender.phone_number, ''), '') AS sender_identifier, + COALESCE(NULLIF(sender.display_name, ''), NULLIF(sender.phone_number, ''), sender.email_address, '') AS sender_display, + COALESCE(sender.domain, '') AS sender_domain, + m.sent_at AS occurred_at, + COALESCE(m.subject, '') AS subject, + COALESCE(m.snippet, '') AS snippet, + m.is_from_me, + m.size_estimate, + m.deleted_at IS NOT NULL AS internally_deleted, + m.deleted_from_source_at IS NOT NULL AS deleted_from_source, + COALESCE(m.has_attachments, false) AS has_attachments, + COALESCE(att.attachment_count, 0) AS attachment_count, + COALESCE(att.attachment_size, 0) AS attachment_size, + COALESCE(recip.participant_ids, []::BIGINT[]) AS participant_ids, + COALESCE(recip.participant_labels, []::VARCHAR[]) AS participant_labels, + COALESCE(recip.participant_domains, []::VARCHAR[]) AS participant_domains, + COALESCE(conv_part.participant_ids, []::BIGINT[]) AS conversation_participant_ids, + COALESCE(conv_part.participant_labels, []::VARCHAR[]) AS conversation_participant_labels, + COALESCE(conv_part.participant_domains, []::VARCHAR[]) AS conversation_participant_domains +FROM messages m +JOIN sources s ON s.id = m.source_id +LEFT JOIN conversations c ON c.id = m.conversation_id +LEFT JOIN participants sender ON sender.id = m.sender_id +LEFT JOIN message_participant_facts recip ON recip.message_id = m.id +LEFT JOIN conversation_participant_facts conv_part ON conv_part.conversation_id = m.conversation_id +LEFT JOIN ( + SELECT message_id, COUNT(*) AS attachment_count, COALESCE(SUM(size), 0) AS attachment_size + FROM attachments + GROUP BY message_id +) att ON att.message_id = m.id +` + // sqlVMessages: messages with sender resolved via dual-path // (message_recipients for email, messages.sender_id for chat) // and labels as sorted JSON array. diff --git a/internal/store/account_identities.go b/internal/store/account_identities.go index 0a57ad594..7cb0e5101 100644 --- a/internal/store/account_identities.go +++ b/internal/store/account_identities.go @@ -6,10 +6,71 @@ import ( "errors" "fmt" "sort" + "strconv" "strings" "time" ) +const accountIdentityRevisionKey = "account_identity_revision" + +// AccountIdentityRevision returns the current account-identity revision (0 +// if never bumped). Unlike IdentityRevision (which also bumps on plain +// participant link/unlink and covers the cheap owner_participants/ +// participant_clusters refresh), this revision increments only on identity +// mutations that invalidate baked message data: AddAccountIdentity or +// RemoveAccountIdentity actually changing which participants are owners for +// a source, and participant merges (MergeParticipants, mergeParticipant) +// repointing messages.sender_id. Either invalidates the is_from_me flag +// baked into every message Parquet shard at export time, so callers use +// this revision to detect when a full cache rebuild (not the lightweight +// identity-only refresh) is required. +func (s *Store) AccountIdentityRevision() (int64, error) { + return readAccountIdentityRevision(s.db) +} + +// readAccountIdentityRevision reads the archive_metadata account-identity +// revision through q (0 if the row does not exist yet), mirroring +// readIdentityRevision in participant_links.go. +func readAccountIdentityRevision(q rowQuerier) (int64, error) { + var value string + err := q.QueryRow( + `SELECT value FROM archive_metadata WHERE key = ?`, accountIdentityRevisionKey, + ).Scan(&value) + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } + if err != nil { + return 0, fmt.Errorf("read account identity revision: %w", err) + } + revision, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return 0, fmt.Errorf("parse account identity revision %q: %w", value, err) + } + return revision, nil +} + +// bumpAccountIdentityRevision increments the account-identity revision +// inside tx, seeding the row with 0 first if it does not exist yet. Follows +// bumpIdentityRevision's approach in participant_links.go. Callers are the +// identity mutations that invalidate baked message data: AddAccountIdentity, +// RemoveAccountIdentity, and the participant-merge paths (MergeParticipants, +// mergeParticipant); none of them expose the new value, so unlike +// bumpIdentityRevision this returns only an error. +func (s *Store) bumpAccountIdentityRevision(tx *loggedTx) error { + if _, err := tx.Exec(s.dialect.InsertOrIgnore( + `INSERT OR IGNORE INTO archive_metadata (key, value) VALUES (?, '0')`), + accountIdentityRevisionKey); err != nil { + return fmt.Errorf("seed account identity revision: %w", err) + } + if _, err := tx.Exec( + `UPDATE archive_metadata SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT) + WHERE key = ?`, + accountIdentityRevisionKey); err != nil { + return fmt.Errorf("bump account identity revision: %w", err) + } + return nil +} + // AccountIdentity is one confirmed "me" address for one source. type AccountIdentity struct { SourceID int64 @@ -51,9 +112,18 @@ func looksLikeEmail(addr string) bool { // column accommodates email, phone E.164, and synthetic identifiers like // chat handles where case can be significant). // -// Concurrency: the read-modify-write runs in a writer-locked transaction. -// SQLite acquires the writer lock at BEGIN IMMEDIATE so concurrent -// callers serialize. PostgreSQL takes a row-level lock with +// Confirming a brand new (source_id, address) pair changes which +// participants are owners for the source, so it bumps both the identity +// revision (the owner_participants cache dataset depends on it) and the +// account-identity revision (the message-baked is_from_me flag depends on +// it and can only be repaired by a full cache rebuild). Merging a new +// signal into an already-confirmed address does not change that mapping, +// so it leaves both revisions untouched. +// +// Concurrency: the read-modify-write runs inside a transaction that first +// takes lockIdentityMutationTx's write lock, mirroring LinkParticipants so +// every identity mutation serializes against the others. PostgreSQL also +// takes a row-level lock on the account_identities row with // SELECT ... FOR UPDATE so the merge sees the latest committed value. // On a still-empty row two callers may both fall through INSERT — the // unique-key violation is caught by the retry loop, which then sees the @@ -87,58 +157,51 @@ func (s *Store) AddAccountIdentity(sourceID int64, address, signal string) error // (concurrent INSERT race) and busy/snapshot errors (SQLite). func (s *Store) addAccountIdentityOnce( ctx context.Context, sourceID int64, addr, signal string, match identifierMatch, -) (retErr error) { - conn, err := s.db.Conn(ctx) - if err != nil { - return fmt.Errorf("acquire connection: %w", err) - } - defer func() { _ = conn.Close() }() - - if _, err := conn.ExecContext(ctx, s.dialect.BeginWriteSQL()); err != nil { - return fmt.Errorf("begin write tx: %w", err) - } - committed := false - defer func() { - if !committed { - _, _ = conn.ExecContext(ctx, "ROLLBACK") +) error { + return s.withTx(func(tx *loggedTx) error { + if err := s.lockIdentityMutationTx(tx); err != nil { + return err } - }() - rebind := s.dialect.Rebind - whereAddr := match.WhereClause("address") - var existing string - selectSQL := `SELECT source_signal FROM account_identities - WHERE source_id = ? AND ` + whereAddr + s.dialect.SelectForUpdate() - err = conn.QueryRowContext(ctx, rebind(selectSQL), sourceID, match.BindValue()).Scan(&existing) - switch { - case errors.Is(err, sql.ErrNoRows): - if _, err := conn.ExecContext(ctx, - rebind(`INSERT INTO account_identities (source_id, address, source_signal) - VALUES (?, ?, ?)`), - sourceID, addr, signal, - ); err != nil { - return fmt.Errorf("insert account identity: %w", err) - } - case err != nil: - return fmt.Errorf("read existing source_signal: %w", err) - default: - merged := mergeSignalSet(existing, signal) - if merged != existing { - if _, err := conn.ExecContext(ctx, - rebind(`UPDATE account_identities SET source_signal = ? - WHERE source_id = ? AND `+whereAddr), - merged, sourceID, match.BindValue(), + whereAddr := match.WhereClause("address") + var existing string + selectSQL := `SELECT source_signal FROM account_identities + WHERE source_id = ? AND ` + whereAddr + s.dialect.SelectForUpdate() + err := tx.QueryRowContext(ctx, selectSQL, sourceID, match.BindValue()).Scan(&existing) + switch { + case errors.Is(err, sql.ErrNoRows): + if _, err := tx.ExecContext(ctx, + `INSERT INTO account_identities (source_id, address, source_signal) + VALUES (?, ?, ?)`, + sourceID, addr, signal, ); err != nil { - return fmt.Errorf("update source_signal: %w", err) + return fmt.Errorf("insert account identity: %w", err) + } + // A brand new (source_id, address) pair changes owner_participants; + // merging a signal into an existing row (the default case below) + // does not, so only this branch bumps the revisions. + if _, err := s.bumpIdentityRevision(tx); err != nil { + return err + } + if err := s.bumpAccountIdentityRevision(tx); err != nil { + return err + } + case err != nil: + return fmt.Errorf("read existing source_signal: %w", err) + default: + merged := mergeSignalSet(existing, signal) + if merged != existing { + if _, err := tx.ExecContext(ctx, + `UPDATE account_identities SET source_signal = ? + WHERE source_id = ? AND `+whereAddr, + merged, sourceID, match.BindValue(), + ); err != nil { + return fmt.Errorf("update source_signal: %w", err) + } } } - } - - if _, err := conn.ExecContext(ctx, "COMMIT"); err != nil { - return fmt.Errorf("commit: %w", err) - } - committed = true - return nil + return nil + }) } // mergeSignalSet returns the comma-joined sorted union of the existing @@ -202,20 +265,47 @@ func (s *Store) ListAccountIdentities(sourceID int64) ([]AccountIdentity, error) // Synthetic identifiers (Matrix MXIDs, chat handles, phone numbers) // match case-sensitively because case can be significant there. The // shape check is in looksLikeEmail. +// +// Removing a confirmed identity changes which participants are owners +// for the source, so an actual deletion bumps both the identity revision +// (the owner_participants cache dataset depends on it) and the +// account-identity revision (the message-baked is_from_me flag depends on +// it); removing an address that was never confirmed is a no-op and leaves +// both unchanged. The delete runs inside a transaction that first takes +// lockIdentityMutationTx's write lock, mirroring LinkParticipants and +// AddAccountIdentity so every identity mutation serializes against the +// others. func (s *Store) RemoveAccountIdentity(sourceID int64, address string) (int64, error) { match := newIdentifierMatch(address) - res, err := s.db.Exec( - `DELETE FROM account_identities WHERE source_id = ? AND `+match.WhereClause("address"), - sourceID, match.BindValue(), - ) - if err != nil { - return 0, fmt.Errorf("remove account identity: %w", err) - } - n, err := res.RowsAffected() + var removed int64 + err := s.withTx(func(tx *loggedTx) error { + if err := s.lockIdentityMutationTx(tx); err != nil { + return err + } + res, err := tx.Exec( + `DELETE FROM account_identities WHERE source_id = ? AND `+match.WhereClause("address"), + sourceID, match.BindValue(), + ) + if err != nil { + return fmt.Errorf("remove account identity: %w", err) + } + n, err := res.RowsAffected() + if err != nil { + return fmt.Errorf("rows affected: %w", err) + } + removed = n + if n == 0 { + return nil + } + if _, err := s.bumpIdentityRevision(tx); err != nil { + return err + } + return s.bumpAccountIdentityRevision(tx) + }) if err != nil { - return 0, fmt.Errorf("rows affected: %w", err) + return 0, err } - return n, nil + return removed, nil } // GetIdentitiesForScope returns the union of confirmed identifier addresses diff --git a/internal/store/account_identities_test.go b/internal/store/account_identities_test.go index 300acd9c9..212061956 100644 --- a/internal/store/account_identities_test.go +++ b/internal/store/account_identities_test.go @@ -206,6 +206,136 @@ func TestGetIdentitiesForScope_EmptyInput(t *testing.T) { assert.Empty(scope, "want empty scope") } +// TestAddAccountIdentity_BumpsIdentityRevisionOnNewIdentity verifies that +// confirming a brand new (source_id, address) pair bumps both the identity +// revision (since it changes which participants are owners for the source — +// the owner_participants cache dataset) and the account-identity revision +// (since it changes the message-baked is_from_me derivation, which only a +// full cache rebuild can re-derive). +func TestAddAccountIdentity_BumpsIdentityRevisionOnNewIdentity(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + f := storetest.New(t) + st := f.Store + + before, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision before") + acctBefore, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision before") + + require.NoError(st.AddAccountIdentity(f.Source.ID, "alice@example.com", "manual")) + + after, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision after") + assert.Equal(before+1, after, "adding a new identity should bump the identity revision") + acctAfter, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision after") + assert.Equal(acctBefore+1, acctAfter, "adding a new identity should bump the account identity revision") +} + +// TestAddAccountIdentity_DuplicateAddDoesNotBumpRevision guards +// idempotency: re-adding the exact same (source_id, address, signal) is a +// no-op for owner_participants, so it must not bump either revision. +func TestAddAccountIdentity_DuplicateAddDoesNotBumpRevision(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + f := storetest.New(t) + st := f.Store + + require.NoError(st.AddAccountIdentity(f.Source.ID, "alice@example.com", "manual")) + after1, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision after first add") + acctAfter1, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision after first add") + + require.NoError(st.AddAccountIdentity(f.Source.ID, "alice@example.com", "manual")) + after2, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision after duplicate add") + assert.Equal(after1, after2, "re-adding the same identity should not bump the identity revision") + acctAfter2, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision after duplicate add") + assert.Equal(acctAfter1, acctAfter2, "re-adding the same identity should not bump the account identity revision") +} + +// TestAddAccountIdentity_NewSignalOnExistingAddressDoesNotBumpRevision +// guards that merging a new signal into an already-confirmed address does +// not bump either revision: the (source_id, address) mapping that +// owner_participants derives from is unchanged, only the evidence trail is. +func TestAddAccountIdentity_NewSignalOnExistingAddressDoesNotBumpRevision(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + f := storetest.New(t) + st := f.Store + + require.NoError(st.AddAccountIdentity(f.Source.ID, "alice@example.com", "manual")) + after1, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision after first add") + acctAfter1, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision after first add") + + require.NoError(st.AddAccountIdentity(f.Source.ID, "alice@example.com", "account-identifier")) + after2, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision after signal augment") + assert.Equal(after1, after2, "adding a new signal to an existing address should not bump the identity revision") + acctAfter2, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision after signal augment") + assert.Equal(acctAfter1, acctAfter2, + "adding a new signal to an existing address should not bump the account identity revision") +} + +// TestRemoveAccountIdentity_BumpsIdentityRevisionOnHit verifies that +// removing a confirmed identity bumps both the identity revision and the +// account-identity revision, since it changes which participants are +// owners for the source and invalidates the message-baked is_from_me flag. +func TestRemoveAccountIdentity_BumpsIdentityRevisionOnHit(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + f := storetest.New(t) + st := f.Store + + require.NoError(st.AddAccountIdentity(f.Source.ID, "alice@example.com", "manual")) + before, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision before remove") + acctBefore, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision before remove") + + removed, err := st.RemoveAccountIdentity(f.Source.ID, "alice@example.com") + require.NoError(err, "RemoveAccountIdentity") + require.Equal(int64(1), removed, "removed") + + after, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision after remove") + assert.Equal(before+1, after, "removing an existing identity should bump the identity revision") + acctAfter, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision after remove") + assert.Equal(acctBefore+1, acctAfter, "removing an existing identity should bump the account identity revision") +} + +// TestRemoveAccountIdentity_MissDoesNotBumpRevision guards idempotency: +// removing an identity that does not exist must not bump either revision. +func TestRemoveAccountIdentity_MissDoesNotBumpRevision(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + f := storetest.New(t) + st := f.Store + + before, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision before") + acctBefore, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision before") + + removed, err := st.RemoveAccountIdentity(f.Source.ID, "nope@example.com") + require.NoError(err, "RemoveAccountIdentity") + assert.Equal(int64(0), removed, "removed on miss") + + after, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision after") + assert.Equal(before, after, "removing a missing identity should not bump the identity revision") + acctAfter, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision after") + assert.Equal(acctBefore, acctAfter, "removing a missing identity should not bump the account identity revision") +} + func TestRemoveAccountIdentity_Hit(t *testing.T) { require := require.New(t) assert := assert.New(t) diff --git a/internal/store/archive_identity.go b/internal/store/archive_identity.go new file mode 100644 index 000000000..85a4d7b85 --- /dev/null +++ b/internal/store/archive_identity.go @@ -0,0 +1,77 @@ +package store + +import ( + "crypto/rand" + "database/sql" + "encoding/hex" + "errors" + "fmt" +) + +const ( + archiveUIDKey = "archive_uid" + archiveRevisionV1 = "1" +) + +// ErrArchiveIdentityCorrupt means the one-time identity migration was +// recorded but its durable UID is missing. Generating a replacement would +// make external references silently point at a different archive. +var ErrArchiveIdentityCorrupt = errors.New("archive identity is corrupt") + +// ArchiveUID returns the durable identity of this archive. +func (s *Store) ArchiveUID() (string, error) { + var uid string + if err := s.db.QueryRow(`SELECT value FROM archive_metadata WHERE key = ?`, archiveUIDKey).Scan(&uid); err != nil { + return "", fmt.Errorf("read archive UID: %w", err) + } + return uid, nil +} + +// ArchiveRevision identifies the archive identity contract stored in +// archive_metadata. It is intentionally independent from both the UID and any +// external project revision so caches can compare all three values explicitly. +func (s *Store) ArchiveRevision() (string, error) { + if _, err := s.ArchiveUID(); err != nil { + return "", err + } + return archiveRevisionV1, nil +} + +func (s *Store) ensureArchiveUID() error { + return s.withTx(func(tx *loggedTx) error { + random := make([]byte, 32) + if _, err := rand.Read(random); err != nil { + return fmt.Errorf("generate archive UID: %w", err) + } + uid := hex.EncodeToString(random) + statement := s.dialect.InsertOrIgnore(` + INSERT OR IGNORE INTO archive_metadata (key, value) + SELECT ?, ? + WHERE NOT EXISTS ( + SELECT 1 FROM applied_migrations WHERE name = ? + )`) + if _, err := tx.Exec(statement, archiveUIDKey, uid, migrationArchiveIdentity); err != nil { + return fmt.Errorf("persist archive UID: %w", err) + } + + var existing string + if err := tx.QueryRow(`SELECT value FROM archive_metadata WHERE key = ?`, archiveUIDKey).Scan(&existing); err != nil { + if !errors.Is(err, sql.ErrNoRows) { + return fmt.Errorf("verify archive UID: %w", err) + } + var migrationPresent int + if countErr := tx.QueryRow(`SELECT COUNT(*) FROM applied_migrations WHERE name = ?`, migrationArchiveIdentity).Scan(&migrationPresent); countErr != nil { + return fmt.Errorf("check archive identity migration: %w", countErr) + } + if migrationPresent > 0 { + return fmt.Errorf("%w: migration ledger is present but archive UID is missing", ErrArchiveIdentityCorrupt) + } + return fmt.Errorf("verify archive UID: %w", err) + } + migrationSQL := s.dialect.InsertOrIgnore(`INSERT OR IGNORE INTO applied_migrations (name) VALUES (?)`) + if _, err := tx.Exec(migrationSQL, migrationArchiveIdentity); err != nil { + return fmt.Errorf("record archive identity migration: %w", err) + } + return nil + }) +} diff --git a/internal/store/archive_identity_dialects_test.go b/internal/store/archive_identity_dialects_test.go new file mode 100644 index 000000000..2448402ee --- /dev/null +++ b/internal/store/archive_identity_dialects_test.go @@ -0,0 +1,29 @@ +package store_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/store" + "go.kenn.io/msgvault/internal/testutil" +) + +// This test runs through the selected real store dialect. The default suite +// exercises SQLite; make test-pg exercises the same migration on PostgreSQL. +func TestArchiveIdentityMigrationExecutesOnSelectedDialect(t *testing.T) { + require := require.New(t) + st := testutil.NewTestStore(t) + uid, err := st.ArchiveUID() + require.NoError(err) + require.Len(uid, 64) + revision, err := st.ArchiveRevision() + require.NoError(err) + assert.Equal(t, "1", revision) + + _, err = st.DB().Exec(`DELETE FROM archive_metadata WHERE key = 'archive_uid'`) + require.NoError(err) + err = st.InitSchema() + require.Error(err) + assert.ErrorIs(t, err, store.ErrArchiveIdentityCorrupt) +} diff --git a/internal/store/archive_identity_test.go b/internal/store/archive_identity_test.go new file mode 100644 index 000000000..ce8e4d8f4 --- /dev/null +++ b/internal/store/archive_identity_test.go @@ -0,0 +1,135 @@ +package store + +import ( + "encoding/hex" + "path/filepath" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestArchiveUIDInitializedOnceAndStableAcrossReopen(t *testing.T) { + require := require.New(t) + dbPath := filepath.Join(t.TempDir(), "archive.db") + first, err := OpenForTest(dbPath) + require.NoError(err) + require.NoError(first.InitSchema()) + uid, err := first.ArchiveUID() + require.NoError(err) + require.Len(uid, 64) + _, err = hex.DecodeString(uid) + require.NoError(err, "archive UID must be 256 bits from crypto/rand") + require.NoError(first.Close()) + + second, err := OpenForTest(dbPath) + require.NoError(err) + t.Cleanup(func() { _ = second.Close() }) + require.NoError(second.InitSchema()) + reopenedUID, err := second.ArchiveUID() + require.NoError(err) + assert.Equal(t, uid, reopenedUID) + var rows int + require.NoError(second.DB().QueryRow(`SELECT COUNT(*) FROM archive_metadata WHERE key = 'archive_uid'`).Scan(&rows)) + assert.Equal(t, 1, rows) +} + +func TestArchiveUIDMigrationRejectsMissingIdentityAfterLedgerRecorded(t *testing.T) { + require := require.New(t) + dbPath := filepath.Join(t.TempDir(), "legacy.db") + st, err := OpenForTest(dbPath) + require.NoError(err) + t.Cleanup(func() { _ = st.Close() }) + require.NoError(st.InitSchema()) + _, err = st.DB().Exec(`DELETE FROM archive_metadata WHERE key = 'archive_uid'`) + require.NoError(err) + + err = st.InitSchema() + require.Error(err) + require.ErrorIs(err, ErrArchiveIdentityCorrupt) + + var rows int + require.NoError(st.DB().QueryRow(`SELECT COUNT(*) FROM archive_metadata WHERE key = 'archive_uid'`).Scan(&rows)) + assert.Zero(t, rows, "a missing durable identity must never be silently replaced") +} + +func TestArchiveUIDMigrationInitializesLegacyArchiveWithoutLedger(t *testing.T) { + require := require.New(t) + dbPath := filepath.Join(t.TempDir(), "legacy-unmigrated.db") + st, err := OpenForTest(dbPath) + require.NoError(err) + t.Cleanup(func() { _ = st.Close() }) + require.NoError(st.InitSchema()) + _, err = st.DB().Exec(`DELETE FROM archive_metadata WHERE key = 'archive_uid'`) + require.NoError(err) + _, err = st.DB().Exec(`DELETE FROM applied_migrations WHERE name = ?`, migrationArchiveIdentity) + require.NoError(err) + + require.NoError(st.InitSchema()) + uid, err := st.ArchiveUID() + require.NoError(err) + assert.Len(t, uid, 64) +} + +func TestArchiveUIDConcurrentInitializationConverges(t *testing.T) { + require := require.New(t) + dbPath := filepath.Join(t.TempDir(), "concurrent.db") + setup, err := OpenForTest(dbPath) + require.NoError(err) + require.NoError(setup.InitSchema()) + _, err = setup.DB().Exec(`DELETE FROM archive_metadata WHERE key = 'archive_uid'`) + require.NoError(err) + _, err = setup.DB().Exec(`DELETE FROM applied_migrations WHERE name = ?`, migrationArchiveIdentity) + require.NoError(err) + require.NoError(setup.Close()) + + first, err := OpenForTest(dbPath) + require.NoError(err) + t.Cleanup(func() { _ = first.Close() }) + second, err := OpenForTest(dbPath) + require.NoError(err) + t.Cleanup(func() { _ = second.Close() }) + + stores := []*Store{first, second} + start := make(chan struct{}) + errs := make(chan error, len(stores)) + var wg sync.WaitGroup + for _, st := range stores { + wg.Add(1) + go func(st *Store) { + defer wg.Done() + <-start + errs <- st.ensureArchiveUID() + }(st) + } + close(start) + wg.Wait() + close(errs) + for err := range errs { + require.NoError(err) + } + + firstUID, err := first.ArchiveUID() + require.NoError(err) + secondUID, err := second.ArchiveUID() + require.NoError(err) + assert.Equal(t, firstUID, secondUID) +} + +func TestArchiveUIDDiffersAcrossArchives(t *testing.T) { + require := require.New(t) + first, err := OpenForTest(filepath.Join(t.TempDir(), "first.db")) + require.NoError(err) + t.Cleanup(func() { _ = first.Close() }) + require.NoError(first.InitSchema()) + second, err := OpenForTest(filepath.Join(t.TempDir(), "second.db")) + require.NoError(err) + t.Cleanup(func() { _ = second.Close() }) + require.NoError(second.InitSchema()) + firstUID, err := first.ArchiveUID() + require.NoError(err) + secondUID, err := second.ArchiveUID() + require.NoError(err) + assert.NotEqual(t, firstUID, secondUID) +} diff --git a/internal/store/conversations.go b/internal/store/conversations.go index af98d133e..558673665 100644 --- a/internal/store/conversations.go +++ b/internal/store/conversations.go @@ -1,10 +1,291 @@ package store import ( + "context" "database/sql" + "errors" "fmt" + "strings" + "time" ) +// ConversationWindow is a bounded, stable slice of full message details. +type ConversationWindow struct { + Messages []APIMessage + AnchorPosition int64 + Total int64 +} + +// ErrConversationAnchorOutsideRange is returned by +// GetConversationWindowContext when the anchor message exists in the +// conversation but falls outside the requested [start, end) time bounds. +// Callers should surface this as a client error rather than the generic +// "anchor not found" case, since the anchor is valid but the requested +// window excludes it. +var ErrConversationAnchorOutsideRange = errors.New("conversation anchor outside range") + +// ConversationExists reports whether the containing conversation is present, +// independent of whether it currently has visible messages. +func (s *Store) ConversationExists(conversationID int64) (bool, error) { + return s.ConversationExistsContext(context.Background(), conversationID) +} + +// ConversationExistsContext is the context-aware existence check. +func (s *Store) ConversationExistsContext(ctx context.Context, conversationID int64) (bool, error) { + var exists bool + err := s.db.QueryRowContext(ctx, s.dialect.Rebind(` + SELECT EXISTS (SELECT 1 FROM conversations WHERE id = ?) + `), conversationID).Scan(&exists) + if err != nil { + return false, fmt.Errorf("check conversation: %w", err) + } + return exists, nil +} + +// conversationRangeClause builds the optional time-bound SQL fragment for the +// conversation window's ordered CTE, plus the matching bind args in the order +// they must appear (start before end). Either bound may be nil to leave that +// side unrestricted. The returned clause is empty when both bounds are nil. +func conversationRangeClause(start, end *time.Time) (clause string, args []any) { + if start != nil { + clause += " AND COALESCE(m.sent_at, m.received_at, m.internal_date) >= ?" + args = append(args, *start) + } + if end != nil { + clause += " AND COALESCE(m.sent_at, m.received_at, m.internal_date) < ?" + args = append(args, *end) + } + return clause, args +} + +// anchorTimestampInRangeContext reports whether anchorID belongs to +// conversationID (exists) and, if so, whether its timestamp +// (COALESCE(sent_at, received_at, internal_date)) falls within [start, end). +// A nil bound leaves that side unrestricted. exists is false when the anchor +// is not a live message of the conversation, mirroring the visibility rules +// GetConversationWindowContext itself applies. +func (s *Store) anchorTimestampInRangeContext( + ctx context.Context, conversationID, anchorID int64, start, end *time.Time, +) (inRange, exists bool, err error) { + query := fmt.Sprintf(` + SELECT COALESCE(m.sent_at, m.received_at, m.internal_date) + FROM messages m + WHERE m.conversation_id = ? AND m.id = ? AND %s + `, LiveMessagesWhere("m", false)) + + var ts nullableTimestamp + scanErr := s.db.QueryRowContext(ctx, s.dialect.Rebind(query), conversationID, anchorID).Scan(&ts) + if errors.Is(scanErr, sql.ErrNoRows) { + return false, false, nil + } + if scanErr != nil { + return false, false, fmt.Errorf("check conversation anchor range: %w", scanErr) + } + if !ts.Valid { + return false, true, nil + } + afterStart := start == nil || !ts.Time.Before(*start) + beforeEnd := end == nil || ts.Time.Before(*end) + return afterStart && beforeEnd, true, nil +} + +// GetConversationWindow returns full details around anchor in chronological +// (sent timestamp, message ID) order. before and after are caller-validated +// bounds; the selected anchor is always included. +func (s *Store) GetConversationWindow( + conversationID, anchorID int64, before, after int, +) (*ConversationWindow, error) { + return s.GetConversationWindowContext(context.Background(), conversationID, anchorID, before, after, nil, nil) +} + +// GetConversationWindowContext is the context-aware conversation reader. +// start and end optionally bound the window to messages with a timestamp +// (COALESCE(sent_at, received_at, internal_date)) in the half-open range +// [start, end); either or both may be nil to leave that side unbounded. +// When bounds are set, position numbering, total_count, and anchor_position +// are all computed over the bounded subset only, so before/after and the +// caller-derived HasBefore/HasAfter are relative to the range rather than +// the full conversation. If the anchor exists in the conversation but its +// timestamp falls outside the bounds, ErrConversationAnchorOutsideRange is +// returned instead of an empty window. +func (s *Store) GetConversationWindowContext( + ctx context.Context, + conversationID, anchorID int64, + before, after int, + start, end *time.Time, +) (*ConversationWindow, error) { + if start != nil || end != nil { + inRange, exists, err := s.anchorTimestampInRangeContext(ctx, conversationID, anchorID, start, end) + if err != nil { + return nil, err + } + if exists && !inRange { + return nil, ErrConversationAnchorOutsideRange + } + } + + rangeClause, rangeArgs := conversationRangeClause(start, end) + query := fmt.Sprintf(` + WITH ordered AS ( + SELECT + m.id, + ROW_NUMBER() OVER ( + ORDER BY COALESCE(m.sent_at, m.received_at, m.internal_date) ASC, m.id ASC + ) AS position, + COUNT(*) OVER () AS total_count + FROM messages m + WHERE m.conversation_id = ? AND %s%s + ), anchor AS ( + SELECT position AS anchor_position FROM ordered WHERE id = ? + ), selected AS ( + SELECT ordered.id, ordered.position, ordered.total_count, anchor.anchor_position + FROM ordered CROSS JOIN anchor + WHERE ordered.position BETWEEN anchor.anchor_position - ? AND anchor.anchor_position + ? + ) + SELECT + m.id, + m.source_id, + COALESCE(m.source_message_id, ''), + COALESCE(m.conversation_id, 0), + COALESCE(c.source_conversation_id, ''), + COALESCE(m.subject, ''), + COALESCE(m.message_type, ''), + %s, + COALESCE(m.sent_at, m.received_at, m.internal_date), + COALESCE(m.snippet, ''), + m.has_attachments, + COALESCE(m.size_estimate, 0), + m.deleted_from_source_at, + COALESCE(mb.body_text, ''), + COALESCE(mb.body_html, ''), + selected.position, + selected.total_count, + selected.anchor_position + FROM selected + JOIN messages m ON m.id = selected.id + LEFT JOIN message_recipients mr ON mr.id = ( + SELECT mr2.id FROM message_recipients mr2 + WHERE mr2.message_id = m.id AND mr2.recipient_type = 'from' + ORDER BY mr2.id LIMIT 1 + ) + LEFT JOIN participants p ON p.id = COALESCE(m.sender_id, mr.participant_id) + LEFT JOIN conversations c ON c.id = m.conversation_id + LEFT JOIN message_bodies mb ON mb.message_id = m.id + ORDER BY selected.position ASC + `, LiveMessagesWhere("m", false), rangeClause, participantSummarySenderSQL) + + args := make([]any, 0, 4+len(rangeArgs)) + args = append(args, conversationID) + args = append(args, rangeArgs...) + args = append(args, anchorID, before, after) + + rows, err := s.db.QueryContext(ctx, s.dialect.Rebind(query), args...) + if err != nil { + return nil, fmt.Errorf("get conversation window: %w", err) + } + defer func() { _ = rows.Close() }() + + window := &ConversationWindow{Messages: []APIMessage{}} + ids := make([]int64, 0, before+after+1) + for rows.Next() { + var message APIMessage + var sentAt, deletedAt nullableTimestamp + var position int64 + if err := rows.Scan( + &message.ID, + &message.SourceID, + &message.SourceMessageID, + &message.ConversationID, + &message.SourceConversationID, + &message.Subject, + &message.MessageType, + &message.From, + &message.FromEmail, + &message.FromName, + &message.FromPhone, + &sentAt, + &message.Snippet, + &message.HasAttachments, + &message.SizeEstimate, + &deletedAt, + &message.BodyText, + &message.BodyHTML, + &position, + &window.Total, + &window.AnchorPosition, + ); err != nil { + return nil, fmt.Errorf("scan conversation message: %w", err) + } + if sentAt.Valid { + message.SentAt = sentAt.Time + } + if deletedAt.Valid { + deleted := deletedAt.Time + message.DeletedAt = &deleted + } + if message.BodyText != "" { + message.Body = message.BodyText + } else { + message.Body = message.BodyHTML + } + message.Headers = map[string]string{} + window.Messages = append(window.Messages, message) + ids = append(ids, message.ID) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate conversation messages: %w", err) + } + if len(ids) == 0 { + return window, nil + } + if err := s.batchPopulateContext(ctx, window.Messages, ids); err != nil { + return nil, fmt.Errorf("populate conversation participants: %w", err) + } + if err := s.batchPopulateAttachments(ctx, window.Messages, ids); err != nil { + return nil, err + } + return window, nil +} + +func (s *Store) batchPopulateAttachments(ctx context.Context, messages []APIMessage, ids []int64) error { + placeholders := make([]string, len(ids)) + args := make([]any, len(ids)) + indexByID := make(map[int64]int, len(ids)) + for i, id := range ids { + placeholders[i] = "?" + args[i] = id + indexByID[id] = i + } + rows, err := s.db.QueryContext(ctx, s.dialect.Rebind(fmt.Sprintf(` + SELECT message_id, id, COALESCE(filename, ''), COALESCE(mime_type, ''), + COALESCE(size, 0), COALESCE(content_hash, ''), COALESCE(storage_path, '') + FROM attachments + WHERE message_id IN (%s) + ORDER BY message_id, id + `, strings.Join(placeholders, ","))), args...) + if err != nil { + return fmt.Errorf("get conversation attachments: %w", err) + } + defer func() { _ = rows.Close() }() + for rows.Next() { + var messageID int64 + var attachment APIAttachment + var storagePath sql.NullString + if err := rows.Scan(&messageID, &attachment.ID, &attachment.Filename, &attachment.MimeType, + &attachment.Size, &attachment.ContentHash, &storagePath); err != nil { + return fmt.Errorf("scan conversation attachment: %w", err) + } + if storagePath.Valid && (strings.HasPrefix(storagePath.String, "http://") || strings.HasPrefix(storagePath.String, "https://")) { + attachment.ContentHash = "" + attachment.URL = storagePath.String + } + if index, ok := indexByID[messageID]; ok { + messages[index].Attachments = append(messages[index].Attachments, attachment) + } + } + return rows.Err() +} + // SetConversationMetadata writes the conversations.metadata JSON/JSONB column. // Passing an invalid sql.NullString clears the column. func (s *Store) SetConversationMetadata(conversationID int64, metadata sql.NullString) error { diff --git a/internal/store/conversations_test.go b/internal/store/conversations_test.go index 2fec6a088..98d9101e9 100644 --- a/internal/store/conversations_test.go +++ b/internal/store/conversations_test.go @@ -1,14 +1,145 @@ package store_test import ( + "context" "database/sql" + "fmt" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/store" "go.kenn.io/msgvault/internal/testutil" ) +func TestConversationWindowHydratesParticipantsLabelsBodiesAndAttachments(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + st := testutil.NewTestStore(t) + + source, err := st.GetOrCreateSource("gmail", "archive@example.com") + require.NoError(err) + conversationID, err := st.EnsureConversation(source.ID, "portable-thread", "Portable thread") + require.NoError(err) + senderID, err := st.EnsureParticipant("alice@example.com", "Alice", "example.com") + require.NoError(err) + toID, err := st.EnsureParticipant("bob@example.com", "Bob", "example.com") + require.NoError(err) + ccID, err := st.EnsureParticipant("carol@example.com", "Carol", "example.com") + require.NoError(err) + bccID, err := st.EnsureParticipant("dave@example.com", "Dave", "example.com") + require.NoError(err) + + messageID, err := st.UpsertMessage(&store.Message{ + ConversationID: conversationID, + SourceID: source.ID, + SourceMessageID: "portable-message", + MessageType: "email", + SentAt: sql.NullTime{Time: time.Date(2026, 7, 19, 12, 0, 0, 0, time.UTC), Valid: true}, + Subject: sql.NullString{String: "Portable details", Valid: true}, + Snippet: sql.NullString{String: "Complete portable fixture", Valid: true}, + SenderID: sql.NullInt64{Int64: senderID, Valid: true}, + SizeEstimate: 128, + }) + require.NoError(err) + require.NoError(st.UpsertMessageBody(messageID, + sql.NullString{String: "Portable body", Valid: true}, + sql.NullString{String: "

Portable body

", Valid: true})) + require.NoError(st.ReplaceMessageRecipients(messageID, "from", []int64{senderID}, []string{"Alice"})) + require.NoError(st.ReplaceMessageRecipients(messageID, "to", []int64{toID}, []string{"Bob"})) + require.NoError(st.ReplaceMessageRecipients(messageID, "cc", []int64{ccID}, []string{"Carol"})) + require.NoError(st.ReplaceMessageRecipients(messageID, "bcc", []int64{bccID}, []string{"Dave"})) + labelID, err := st.EnsureLabel(source.ID, "STARRED", "Starred", "system") + require.NoError(err) + require.NoError(st.ReplaceMessageLabels(messageID, []int64{labelID})) + require.NoError(st.UpsertAttachment(messageID, "report.pdf", "application/pdf", + "", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 42)) + require.NoError(st.RecomputeMessageAttachmentStats(messageID)) + + window, err := st.GetConversationWindow(conversationID, messageID, 1, 1) + require.NoError(err) + require.Len(window.Messages, 1) + message := window.Messages[0] + assert.Equal("Alice ", message.From) + assert.Equal([]string{"Bob "}, message.To) + assert.Equal([]string{"Carol "}, message.Cc) + assert.Equal([]string{"Dave "}, message.Bcc) + assert.Equal([]string{"Starred"}, message.Labels) + assert.Equal("Portable body", message.BodyText) + assert.Equal("

Portable body

", message.BodyHTML) + require.Len(message.Attachments, 1) + assert.Equal("report.pdf", message.Attachments[0].Filename) +} + +func TestConversationWindowIncludesSourceDeletedArchiveEntries(t *testing.T) { + requirements := require.New(t) + assertions := assert.New(t) + st := testutil.NewTestStore(t) + source, err := st.GetOrCreateSource("gmail", "archive@example.com") + requirements.NoError(err) + conversationID, err := st.EnsureConversation(source.ID, "retained-thread", "Retained thread") + requirements.NoError(err) + messageID, err := st.UpsertMessage(&store.Message{ + ConversationID: conversationID, SourceID: source.ID, SourceMessageID: "retained-message", + MessageType: "email", SentAt: sql.NullTime{Time: time.Now().UTC(), Valid: true}, + }) + requirements.NoError(err) + _, err = st.DB().Exec(st.Rebind("UPDATE messages SET deleted_from_source_at = CURRENT_TIMESTAMP WHERE id = ?"), messageID) + requirements.NoError(err) + + window, err := st.GetConversationWindow(conversationID, messageID, 1, 1) + requirements.NoError(err) + requirements.NotNil(window) + requirements.Len(window.Messages, 1) + assertions.Equal(messageID, window.Messages[0].ID) +} + +func TestConversationWindowContextScopesToTimeRange(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + st := testutil.NewTestStore(t) + source, err := st.GetOrCreateSource("gmail", "archive@example.com") + require.NoError(err) + conversationID, err := st.EnsureConversation(source.ID, "ranged-thread", "Ranged thread") + require.NoError(err) + + day1 := time.Date(2026, 7, 1, 12, 0, 0, 0, time.UTC) + day2 := time.Date(2026, 7, 2, 12, 0, 0, 0, time.UTC) + sentTimes := []time.Time{ + day1, day1.Add(time.Minute), day1.Add(2 * time.Minute), + day2, day2.Add(time.Minute), day2.Add(2 * time.Minute), + } + ids := make([]int64, 0, len(sentTimes)) + for i, sentAt := range sentTimes { + id, err := st.UpsertMessage(&store.Message{ + ConversationID: conversationID, + SourceID: source.ID, + SourceMessageID: fmt.Sprintf("ranged-message-%d", i), + MessageType: "email", + SentAt: sql.NullTime{Time: sentAt, Valid: true}, + }) + require.NoError(err) + ids = append(ids, id) + } + + start := day2.Truncate(24 * time.Hour) + end := start.AddDate(0, 0, 1) + + window, err := st.GetConversationWindowContext(context.Background(), conversationID, ids[4], 25, 25, &start, &end) + require.NoError(err) + require.NotNil(window) + require.Len(window.Messages, 3) + assert.Equal(int64(3), window.Total) + assert.Equal([]int64{ids[3], ids[4], ids[5]}, []int64{ + window.Messages[0].ID, window.Messages[1].ID, window.Messages[2].ID, + }) + assert.Equal(int64(2), window.AnchorPosition) + + _, err = st.GetConversationWindowContext(context.Background(), conversationID, ids[0], 25, 25, &start, &end) + require.ErrorIs(err, store.ErrConversationAnchorOutsideRange) +} + func TestConversationMetadataGetSetAndClear(t *testing.T) { require := require.New(t) assert := assert.New(t) diff --git a/internal/store/files.go b/internal/store/files.go new file mode 100644 index 000000000..96dba1db5 --- /dev/null +++ b/internal/store/files.go @@ -0,0 +1,91 @@ +package store + +import ( + "context" + "errors" + "fmt" + "strings" +) + +// FileMetadata is the transactional authority for one attachment row and its +// containing archive identities. StoragePath is retained only for local blobs; +// URL-backed rows expose URL and deliberately clear content-hash authority. +type FileMetadata struct { + ID int64 + MessageID int64 + ConversationID int64 + Filename string + MimeType string + Size int64 + ContentHash string + StoragePath string + URL string +} + +func (s *Store) GetFileMetadata(ctx context.Context, id int64) (*FileMetadata, error) { + files, err := s.GetFileMetadataBatch(ctx, []int64{id}) + if err != nil { + return nil, err + } + file, ok := files[id] + if !ok { + return nil, nil //nolint:nilnil // not-found is a normal metadata lookup result + } + return &file, nil +} + +// GetFileMetadataBatch resolves one bounded page in one database query. +func (s *Store) GetFileMetadataBatch(ctx context.Context, ids []int64) (map[int64]FileMetadata, error) { + result := make(map[int64]FileMetadata, len(ids)) + if len(ids) == 0 { + return result, nil + } + unique := make([]int64, 0, len(ids)) + seen := make(map[int64]struct{}, len(ids)) + for _, id := range ids { + if id <= 0 { + return nil, errors.New("file ID must be positive") + } + if _, exists := seen[id]; exists { + continue + } + seen[id] = struct{}{} + unique = append(unique, id) + } + placeholders := make([]string, len(unique)) + args := make([]any, len(unique)) + for i, id := range unique { + placeholders[i] = "?" + args[i] = id + } + rows, err := s.db.QueryContext(ctx, s.dialect.Rebind(` + SELECT a.id, a.message_id, m.conversation_id, + COALESCE(a.filename, ''), COALESCE(a.mime_type, ''), COALESCE(a.size, 0), + COALESCE(a.content_hash, ''), COALESCE(a.storage_path, '') + FROM attachments a + JOIN messages m ON m.id = a.message_id + WHERE a.id IN (`+strings.Join(placeholders, ",")+`) + ORDER BY a.id`), args...) + if err != nil { + return nil, fmt.Errorf("get file metadata batch: %w", err) + } + defer func() { _ = rows.Close() }() + for rows.Next() { + var file FileMetadata + if err := rows.Scan(&file.ID, &file.MessageID, &file.ConversationID, &file.Filename, + &file.MimeType, &file.Size, &file.ContentHash, &file.StoragePath); err != nil { + return nil, fmt.Errorf("scan file metadata: %w", err) + } + lowerPath := strings.ToLower(file.StoragePath) + if strings.HasPrefix(lowerPath, "http://") || strings.HasPrefix(lowerPath, "https://") { + file.URL = file.StoragePath + file.StoragePath = "" + file.ContentHash = "" + } + result[file.ID] = file + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate file metadata: %w", err) + } + return result, nil +} diff --git a/internal/store/files_test.go b/internal/store/files_test.go new file mode 100644 index 000000000..4463e453a --- /dev/null +++ b/internal/store/files_test.go @@ -0,0 +1,59 @@ +package store_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/testutil/storetest" +) + +func TestGetFileMetadataBatchUsesTransactionalAttachmentAuthority(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + f := storetest.New(t) + localMessageID := f.CreateMessage("file-local") + requirements.NoError(f.Store.UpsertAttachment( + localMessageID, "report.pdf", "application/pdf", "aa/report", "localhash", 2048, + )) + urlMessageID := f.CreateMessage("file-url") + _, err := f.Store.DB().Exec(f.Store.Rebind(` + INSERT INTO attachments + (message_id, filename, mime_type, storage_path, content_hash, size, created_at) + VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)`), + urlMessageID, "reference.html", "text/html", "https://example.com/reference", "stalehash", 31, + ) + requirements.NoError(err) + + rows, err := f.Store.DB().Query(f.Store.Rebind(` + SELECT id FROM attachments WHERE message_id IN (?, ?) ORDER BY id`), localMessageID, urlMessageID) + requirements.NoError(err) + defer func() { requirements.NoError(rows.Close()) }() + var ids []int64 + for rows.Next() { + var id int64 + requirements.NoError(rows.Scan(&id)) + ids = append(ids, id) + } + requirements.NoError(rows.Err()) + requirements.Len(ids, 2) + + files, err := f.Store.GetFileMetadataBatch(t.Context(), []int64{ids[1], ids[0], ids[1]}) + requirements.NoError(err) + requirements.Len(files, 2) + assertions.Equal(localMessageID, files[ids[0]].MessageID) + assertions.Equal(f.ConvID, files[ids[0]].ConversationID) + assertions.Equal("localhash", files[ids[0]].ContentHash) + assertions.Equal("aa/report", files[ids[0]].StoragePath) + assertions.Empty(files[ids[0]].URL) + assertions.Equal("https://example.com/reference", files[ids[1]].URL) + assertions.Empty(files[ids[1]].ContentHash) + assertions.Empty(files[ids[1]].StoragePath) +} + +func TestGetFileMetadataReturnsNotFoundWithoutError(t *testing.T) { + f := storetest.New(t) + file, err := f.Store.GetFileMetadata(t.Context(), 999999) + require.NoError(t, err) + assert.Nil(t, file) +} diff --git a/internal/store/messages.go b/internal/store/messages.go index 21f2f84d9..43ca4a9fc 100644 --- a/internal/store/messages.go +++ b/internal/store/messages.go @@ -2118,6 +2118,27 @@ func (s *Store) MergeParticipants(oldID, newID int64) error { if _, err := tx.Exec(`UPDATE participant_identifiers SET participant_id = ? WHERE participant_id = ?`, newID, oldID); err != nil { return err } + // Repoint (and, if needed, restructure) any link edges referencing + // oldID before the delete below drops them via ON DELETE CASCADE. + if err := s.rewriteLinksForMerge(tx, oldID, newID); err != nil { + return err + } + // Bump unconditionally, even when the merge touched no link edges: + // absorbing a participant whose email matches a confirmed account + // identity changes owner_participants' content (the baked dataset + // still lists the deleted oldID), and the refresh this triggers is + // cheap and idempotent, so it is not worth tracking that case + // separately from the link-touching one. + if _, err := s.bumpIdentityRevision(tx); err != nil { + return err + } + // Also bump the account-identity revision: the merge repoints + // messages.sender_id, so a merge involving the sender of any + // message with a baked is_from_me leaves that flag stale in the + // message Parquet shards, which only a full rebuild re-derives. + if err := s.bumpAccountIdentityRevision(tx); err != nil { + return err + } _, err := tx.Exec(`DELETE FROM participants WHERE id = ?`, oldID) return err }) diff --git a/internal/store/migrate_legacy_identity.go b/internal/store/migrate_legacy_identity.go index 89393e8a5..a3cac733d 100644 --- a/internal/store/migrate_legacy_identity.go +++ b/internal/store/migrate_legacy_identity.go @@ -113,6 +113,7 @@ func (s *Store) MigrateLegacyIdentityConfig(addresses []string) (applied, deferr return fmt.Errorf("check migration %q in tx: %w", migrationLegacyIdentity, err) } + insertedAny := false for _, src := range sources { // Legacy [identity] block holds email-shaped addresses only. // Skip non-email source types (whatsapp, imessage, sms, @@ -144,6 +145,11 @@ func (s *Store) MigrateLegacyIdentityConfig(addresses []string) (applied, deferr if txErr != nil { return fmt.Errorf("insert identity (source=%d, addr=%s): %w", src.ID, addr, txErr) } + // A brand new (source_id, address) pair changes + // owner_participants and the is_from_me derivation for + // this source, exactly like AddAccountIdentity's insert + // branch — see the matching comment there. + insertedAny = true case qerr != nil: return fmt.Errorf("read existing identity (source=%d, addr=%s): %w", src.ID, addr, qerr) default: @@ -163,6 +169,22 @@ func (s *Store) MigrateLegacyIdentityConfig(addresses []string) (applied, deferr } } + // A daemon that started before this migration ran must not keep + // serving a cache with is_from_me/owner_participants baked from + // before these identities existed, so bump both revisions exactly + // like AddAccountIdentity's insert path does — but only when this + // call actually inserted a row; a re-run that only merged a signal + // into an already-migrated address changes nothing those datasets + // depend on. + if insertedAny { + if _, err := s.bumpIdentityRevision(tx); err != nil { + return err + } + if err := s.bumpAccountIdentityRevision(tx); err != nil { + return err + } + } + _, txErr := tx.Exec( s.dialect.InsertOrIgnore(`INSERT OR IGNORE INTO applied_migrations (name) VALUES (?)`), migrationLegacyIdentity, diff --git a/internal/store/migrate_legacy_identity_test.go b/internal/store/migrate_legacy_identity_test.go index 5e9600bf1..b42db875d 100644 --- a/internal/store/migrate_legacy_identity_test.go +++ b/internal/store/migrate_legacy_identity_test.go @@ -39,6 +39,50 @@ func TestMigrateLegacyIdentityConfig_Basic(t *testing.T) { } } +// TestMigrateLegacyIdentityConfig_BumpsRevisionsOnlyWhenItInserts verifies +// that the legacy-config startup migration bumps both the identity revision +// and the account-identity revision when it actually inserts confirmed +// account_identities rows (so a daemon that started before the migration ran +// invalidates its cached owner_participants/is_from_me), and that the +// idempotent no-op re-run leaves both revisions unchanged. +func TestMigrateLegacyIdentityConfig_BumpsRevisionsOnlyWhenItInserts(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + f := storetest.New(t) + st := f.Store + + identityRevBefore, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision before migration") + acctRevBefore, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision before migration") + + applied, _, _, _, err := st.MigrateLegacyIdentityConfig([]string{"alice@example.com"}) //nolint:dogsled // 5-return migration; test needs only applied+err + require.NoError(err, "MigrateLegacyIdentityConfig") + require.True(applied, "applied should be true on first run") + + identityRevAfter, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision after migration") + assert.Equal(identityRevBefore+1, identityRevAfter, + "an actual insert must bump the identity revision") + acctRevAfter, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision after migration") + assert.Equal(acctRevBefore+1, acctRevAfter, + "an actual insert must bump the account identity revision") + + // Re-running is a no-op (already applied): neither revision should move. + _, _, _, _, err = st.MigrateLegacyIdentityConfig([]string{"alice@example.com"}) //nolint:dogsled // 5-return migration; test needs only err + require.NoError(err, "second MigrateLegacyIdentityConfig call") + + identityRevSecond, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision after second call") + assert.Equal(identityRevAfter, identityRevSecond, + "a no-op re-run must not bump the identity revision") + acctRevSecond, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision after second call") + assert.Equal(acctRevAfter, acctRevSecond, + "a no-op re-run must not bump the account identity revision") +} + func TestMigrateLegacyIdentityConfig_MergesExistingSignal(t *testing.T) { require := require.New(t) f := storetest.New(t) diff --git a/internal/store/migrate_phone_unique.go b/internal/store/migrate_phone_unique.go index 5a85e5c19..208515e2f 100644 --- a/internal/store/migrate_phone_unique.go +++ b/internal/store/migrate_phone_unique.go @@ -121,7 +121,7 @@ func (s *Store) dedupeParticipantsByPhone(ctx context.Context, tx *loggedTx) err winner := ids[0] losers := ids[1:] for _, loser := range losers { - if err := mergeParticipant(ctx, tx, winner, loser); err != nil { + if err := s.mergeParticipant(ctx, tx, winner, loser); err != nil { return err } } @@ -132,7 +132,7 @@ func (s *Store) dedupeParticipantsByPhone(ctx context.Context, tx *loggedTx) err // mergeParticipant moves every reference from loser onto winner, // deleting any rows whose new (winner-scoped) key would clash with an // existing row, then deletes loser from participants. -func mergeParticipant(ctx context.Context, tx *loggedTx, winner, loser int64) error { +func (s *Store) mergeParticipant(ctx context.Context, tx *loggedTx, winner, loser int64) error { // (1) message_recipients UNIQUE(message_id, participant_id, recipient_type) if _, err := tx.ExecContext(ctx, ` DELETE FROM message_recipients @@ -227,7 +227,25 @@ func mergeParticipant(ctx context.Context, tx *loggedTx, winner, loser int64) er return fmt.Errorf("repoint participant_identifiers (loser=%d, winner=%d): %w", loser, winner, err) } - // (6) Finally drop the loser. participant_identifiers cascades; the + // (6) Repoint (and, if needed, restructure) any link edges referencing + // loser before the delete below drops them via ON DELETE CASCADE. + if err := s.rewriteLinksForMerge(tx, loser, winner); err != nil { + return fmt.Errorf("rewrite participant links (loser=%d, winner=%d): %w", loser, winner, err) + } + // Bump unconditionally, even when the merge touched no link edges: see + // the matching comment in MergeParticipants (messages.go). + if _, err := s.bumpIdentityRevision(tx); err != nil { + return fmt.Errorf("bump identity revision (loser=%d, winner=%d): %w", loser, winner, err) + } + // Also bump the account-identity revision: the merge repoints + // messages.sender_id, so a merge involving the sender of any message + // with a baked is_from_me leaves that flag stale in the message + // Parquet shards, which only a full rebuild re-derives. + if err := s.bumpAccountIdentityRevision(tx); err != nil { + return fmt.Errorf("bump account identity revision (loser=%d, winner=%d): %w", loser, winner, err) + } + + // (7) Finally drop the loser. participant_identifiers cascades; the // other FKs are already cleared by the repoints above. if _, err := tx.ExecContext(ctx, `DELETE FROM participants WHERE id = ?`, loser); err != nil { return fmt.Errorf("delete loser participant id=%d: %w", loser, err) diff --git a/internal/store/migrate_phone_unique_test.go b/internal/store/migrate_phone_unique_test.go index 3f4503ae6..b288c5f40 100644 --- a/internal/store/migrate_phone_unique_test.go +++ b/internal/store/migrate_phone_unique_test.go @@ -206,3 +206,73 @@ func TestEnsureParticipantsPhoneUniqueIndex_LegacyNonUnique(t *testing.T) { require.NoError(err, "EnsureParticipantByPhone") assert.Equal(winner, gotID, "EnsureParticipantByPhone returned id %d, want winner %d (ON CONFLICT must find the unique index)", gotID, winner) } + +// TestEnsureParticipantsPhoneUniqueIndex_RewritesLinkEdges covers the +// migration's underlying mergeParticipant path (used by +// dedupeParticipantsByPhone), which is a second, tx-level merge +// implementation distinct from the public MergeParticipants. It must be +// link-aware the same way: a link edge on the loser participant must +// survive the merge by repointing onto the winner rather than being +// silently dropped by participant_links' ON DELETE CASCADE, and the +// identity revision must bump since the merge changed the link graph. +func TestEnsureParticipantsPhoneUniqueIndex_RewritesLinkEdges(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + dbPath := filepath.Join(t.TempDir(), "phone_unique_links.db") + st, err := Open(dbPath) + require.NoError(err, "Open") + t.Cleanup(func() { _ = st.Close() }) + require.NoError(st.InitSchema(), "InitSchema") + + _, err = st.db.Exec(`DELETE FROM applied_migrations WHERE name = ?`, migrationPhoneUniqueIndex) + require.NoError(err, "clear migration sentinel") + _, err = st.db.Exec(`DROP INDEX IF EXISTS idx_participants_phone`) + require.NoError(err, "drop unique idx") + _, err = st.db.Exec(` + CREATE INDEX idx_participants_phone ON participants(phone_number) + WHERE phone_number IS NOT NULL + `) + require.NoError(err, "create legacy non-unique idx") + + insertParticipant := func(phone, displayName string) int64 { + t.Helper() + var id int64 + err := st.db.QueryRow(` + INSERT INTO participants (phone_number, display_name, created_at, updated_at) + VALUES (?, ?, datetime('now'), datetime('now')) + RETURNING id + `, phone, displayName).Scan(&id) + require.NoError(err, "insert participant %s", phone) + return id + } + winner := insertParticipant("+15555551234", "Alice") + loser := insertParticipant("+15555551234", "Alice (dup)") + third, err := st.EnsureParticipant("carol@example.com", "Carol", "example.com") + require.NoError(err, "EnsureParticipant third") + + // Link the loser (not the winner) to a third participant, mirroring a + // user having asserted "loser and carol are the same person" before an + // unrelated phone-based dedupe absorbs loser into winner. + _, err = st.LinkParticipants(loser, third) + require.NoError(err, "LinkParticipants loser-third") + revBeforeMerge, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision before merge") + acctRevBeforeMerge, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision before merge") + + require.NoError(st.ensureParticipantsPhoneUniqueIndex(), "ensureParticipantsPhoneUniqueIndex") + + revAfter, err := st.IdentityRevision() + require.NoError(err, "IdentityRevision after merge") + assert.Equal(revBeforeMerge+1, revAfter, "dedupe merge must bump identity revision when it touches links") + acctRevAfter, err := st.AccountIdentityRevision() + require.NoError(err, "AccountIdentityRevision after merge") + assert.Equal(acctRevBeforeMerge+1, acctRevAfter, + "dedupe merge must bump the account identity revision (it repoints messages.sender_id)") + + clusters, err := st.ParticipantClusters() + require.NoError(err, "ParticipantClusters") + canonical := min(winner, third) + assert.Equal(map[int64]int64{winner: canonical, third: canonical}, clusters, + "link must repoint from loser to winner") +} diff --git a/internal/store/migrations.go b/internal/store/migrations.go index 986e96afc..0c9956df1 100644 --- a/internal/store/migrations.go +++ b/internal/store/migrations.go @@ -12,6 +12,7 @@ import ( const ( migrationAttachmentsContentHashUnique = "attachments_content_hash_unique_index" migrationMessagesLastModifiedBackfill = "messages_last_modified_backfill" + migrationArchiveIdentity = "archive_identity_v1" ) // IsMigrationApplied reports whether the named one-time data migration diff --git a/internal/store/migrations_test.go b/internal/store/migrations_test.go index 1093af4e8..71d1c0955 100644 --- a/internal/store/migrations_test.go +++ b/internal/store/migrations_test.go @@ -37,3 +37,18 @@ func TestMarkMigrationApplied_Idempotent(t *testing.T) { require.NoError(t, err, "IsMigrationApplied") assert.True(t, applied, "migration should be marked as applied after two calls") } + +func TestArchiveIdentityMigrationIsRecorded(t *testing.T) { + f := storetest.New(t) + + applied, err := f.Store.IsMigrationApplied("archive_identity_v1") + require.NoError(t, err) + assert.True(t, applied, "archive identity initialization must be a recorded migration") +} + +func TestArchiveIdentityExistsInConfiguredDialect(t *testing.T) { + f := storetest.New(t) + uid, err := f.Store.ArchiveUID() + require.NoError(t, err) + assert.Len(t, uid, 64) +} diff --git a/internal/store/participant_links.go b/internal/store/participant_links.go new file mode 100644 index 000000000..9f5805126 --- /dev/null +++ b/internal/store/participant_links.go @@ -0,0 +1,503 @@ +package store + +import ( + "database/sql" + "errors" + "fmt" + "slices" + "strconv" + "strings" +) + +// ErrAlreadyLinked is returned by LinkParticipants when the requested edge +// is redundant: the two participants are already connected through other +// links, so adding this edge would create a cycle rather than growing the +// forest. +var ErrAlreadyLinked = errors.New("participants are already linked through other identities") + +// ErrParticipantNotFound is returned (wrapped, with the offending IDs in the +// message) by Link/UnlinkParticipants when one or both participant IDs do +// not exist. Callers distinguish it from internal errors via errors.Is so a +// missing row maps to a 400, not a 500. +var ErrParticipantNotFound = errors.New("participant not found") + +// ErrInvalidParticipantID is returned (wrapped) by LinkParticipants when the +// two IDs fail the self-link/positive-ID shape check, before any database +// access. Distinguished from internal errors via errors.Is, same as +// ErrParticipantNotFound. +var ErrInvalidParticipantID = errors.New("invalid participant id") + +const identityRevisionKey = "identity_revision" + +// linkEdge is one row of participant_links, always normalized so a < b. +type linkEdge struct{ a, b int64 } + +// edgeRows is satisfied by both *sql.Rows (non-transactional queries) and +// *loggedRows (queries issued through a *loggedTx), letting loadLinkEdges +// and loadLinkEdgesTx share one scan routine. +type edgeRows interface { + Next() bool + Scan(dest ...any) error + Close() error + Err() error +} + +// scanLinkEdges drains rows into a slice of edges, closing rows before +// returning. +func scanLinkEdges(rows edgeRows) ([]linkEdge, error) { + defer func() { _ = rows.Close() }() + var edges []linkEdge + for rows.Next() { + var e linkEdge + if err := rows.Scan(&e.a, &e.b); err != nil { + return nil, fmt.Errorf("scan participant link: %w", err) + } + edges = append(edges, e) + } + return edges, rows.Err() +} + +// loadLinkEdges reads every participant_links row outside of a transaction. +// Used by the read-only cluster resolvers. +func (s *Store) loadLinkEdges() ([]linkEdge, error) { + rows, err := s.db.Query(`SELECT participant_a, participant_b FROM participant_links`) + if err != nil { + return nil, fmt.Errorf("query participant links: %w", err) + } + return scanLinkEdges(rows) +} + +// loadLinkEdgesTx reads every participant_links row within tx. Used by +// Link/UnlinkParticipants so the redundant-edge check sees a consistent +// snapshot with the write that follows it. +func (s *Store) loadLinkEdgesTx(tx *loggedTx) ([]linkEdge, error) { + rows, err := tx.Query(`SELECT participant_a, participant_b FROM participant_links`) + if err != nil { + return nil, fmt.Errorf("query participant links: %w", err) + } + return scanLinkEdges(rows) +} + +// buildAdjacency turns an edge list into an undirected adjacency map. +func buildAdjacency(edges []linkEdge) map[int64][]int64 { + adj := make(map[int64][]int64, 2*len(edges)) + for _, e := range edges { + adj[e.a] = append(adj[e.a], e.b) + adj[e.b] = append(adj[e.b], e.a) + } + return adj +} + +// componentOf returns the connected component containing id (including id +// itself), found by breadth-first traversal of edges. An id with no edges +// returns the single-element set {id}. +func componentOf(id int64, edges []linkEdge) map[int64]struct{} { + adj := buildAdjacency(edges) + visited := map[int64]struct{}{id: {}} + queue := []int64{id} + for len(queue) > 0 { + cur := queue[0] + queue = queue[1:] + for _, next := range adj[cur] { + if _, seen := visited[next]; !seen { + visited[next] = struct{}{} + queue = append(queue, next) + } + } + } + return visited +} + +// normalizeEdge orders a pair so the smaller ID comes first, matching the +// participant_links CHECK (participant_a < participant_b) constraint. +func normalizeEdge(a, b int64) (int64, int64) { + if a > b { + return b, a + } + return a, b +} + +// rowQuerier is satisfied by both *loggedDB and *loggedTx, letting +// readIdentityRevision serve both the non-transactional IdentityRevision +// and the in-transaction currentIdentityRevisionTx from one implementation. +type rowQuerier interface { + QueryRow(query string, args ...any) *sql.Row +} + +// readIdentityRevision reads the archive_metadata identity revision +// through q (0 if the row does not exist yet). +func readIdentityRevision(q rowQuerier) (int64, error) { + var value string + err := q.QueryRow( + `SELECT value FROM archive_metadata WHERE key = ?`, identityRevisionKey, + ).Scan(&value) + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } + if err != nil { + return 0, fmt.Errorf("read identity revision: %w", err) + } + revision, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return 0, fmt.Errorf("parse identity revision %q: %w", value, err) + } + return revision, nil +} + +// IdentityRevision returns the current identity revision (0 if never +// bumped). The revision increments on every link/unlink so callers can +// cheaply detect whether cached cluster data is stale. +func (s *Store) IdentityRevision() (int64, error) { + return readIdentityRevision(s.db) +} + +// currentIdentityRevisionTx reads the revision inside tx without bumping +// it, for idempotent Link/Unlink calls that made no change. +func (s *Store) currentIdentityRevisionTx(tx *loggedTx) (int64, error) { + return readIdentityRevision(tx) +} + +// bumpIdentityRevision increments the revision inside tx and returns the +// new value, seeding the row with 0 first if it does not exist yet. +func (s *Store) bumpIdentityRevision(tx *loggedTx) (int64, error) { + if _, err := tx.Exec(s.dialect.InsertOrIgnore( + `INSERT OR IGNORE INTO archive_metadata (key, value) VALUES (?, '0')`), + identityRevisionKey); err != nil { + return 0, fmt.Errorf("seed identity revision: %w", err) + } + var revision int64 + if err := tx.QueryRow( + `UPDATE archive_metadata SET value = CAST(CAST(value AS INTEGER) + 1 AS TEXT) + WHERE key = ? RETURNING CAST(value AS INTEGER)`, + identityRevisionKey).Scan(&revision); err != nil { + return 0, fmt.Errorf("bump identity revision: %w", err) + } + return revision, nil +} + +// lockIdentityMutationTx seeds the identity revision row if it does not +// exist yet, then takes a write lock on it. Link/UnlinkParticipants must +// call this before reading the edge snapshot: without it, two concurrent +// calls that each connect previously-disjoint clusters (e.g. link(2,3) +// racing link(1,4) where {1,2} and {3,4} already exist) could both read a +// stale snapshot, both pass the connectivity check, and both commit, +// producing a cycle and breaking the forest invariant documented in +// schema.sql. On SQLite the UPDATE forces the transaction to acquire the +// RESERVED (write) lock immediately, so the edge read that follows is +// serialized against other writers. On PostgreSQL the UPDATE takes a row +// lock on the identity-revision row, so concurrent link/unlink +// transactions queue on it. +func (s *Store) lockIdentityMutationTx(tx *loggedTx) error { + if _, err := tx.Exec(s.dialect.InsertOrIgnore( + `INSERT OR IGNORE INTO archive_metadata (key, value) VALUES (?, '0')`), + identityRevisionKey); err != nil { + return fmt.Errorf("seed identity revision: %w", err) + } + if _, err := tx.Exec( + `UPDATE archive_metadata SET value = value WHERE key = ?`, + identityRevisionKey); err != nil { + return fmt.Errorf("lock identity revision: %w", err) + } + return nil +} + +// verifyParticipantsExistTx returns a clear ErrParticipantNotFound (wrapped) +// error if either lo or hi is not a participants row, instead of letting the +// caller hit an opaque foreign key violation from the INSERT (LinkParticipants) +// or silently no-op on a nonexistent pair (UnlinkParticipants). +func (s *Store) verifyParticipantsExistTx(tx *loggedTx, lo, hi int64) error { + var count int + if err := tx.QueryRow( + `SELECT COUNT(*) FROM participants WHERE id IN (?, ?)`, lo, hi, + ).Scan(&count); err != nil { + return fmt.Errorf("verify participants exist: %w", err) + } + if count != 2 { + return fmt.Errorf("participant ids must exist (got %d, %d): %w", lo, hi, ErrParticipantNotFound) + } + return nil +} + +// LinkParticipants asserts a and b are the same person. Returns +// ErrInvalidParticipantID (wrapped) for a self-link or non-positive ID, and +// ErrParticipantNotFound (wrapped) if either ID is not a participants row. +// Idempotent for the exact existing edge; returns ErrAlreadyLinked for a new +// redundant edge between participants already connected indirectly. Returns +// the identity revision after the call. +func (s *Store) LinkParticipants(a, b int64) (int64, error) { + if a == b || a <= 0 || b <= 0 { + return 0, fmt.Errorf("link participants: ids must be distinct positive IDs (got %d, %d): %w", + a, b, ErrInvalidParticipantID) + } + lo, hi := normalizeEdge(a, b) + + var revision int64 + err := s.withTx(func(tx *loggedTx) error { + if err := s.lockIdentityMutationTx(tx); err != nil { + return err + } + if err := s.verifyParticipantsExistTx(tx, lo, hi); err != nil { + return err + } + edges, err := s.loadLinkEdgesTx(tx) + if err != nil { + return err + } + for _, e := range edges { + if e.a == lo && e.b == hi { + revision, err = s.currentIdentityRevisionTx(tx) + return err + } + } + if _, connected := componentOf(lo, edges)[hi]; connected { + return ErrAlreadyLinked + } + if _, err := tx.Exec( + `INSERT INTO participant_links (participant_a, participant_b) VALUES (?, ?)`, + lo, hi); err != nil { + return fmt.Errorf("insert participant link: %w", err) + } + revision, err = s.bumpIdentityRevision(tx) + return err + }) + return revision, err +} + +// UnlinkParticipants removes the edge between a and b, if present. Returns +// ErrParticipantNotFound (wrapped) if either ID is not a participants row. +// Idempotent: unlinking a pair with no edge is a no-op that returns the +// current revision unchanged. Returns the identity revision after the call. +func (s *Store) UnlinkParticipants(a, b int64) (int64, error) { + lo, hi := normalizeEdge(a, b) + + var revision int64 + err := s.withTx(func(tx *loggedTx) error { + if err := s.lockIdentityMutationTx(tx); err != nil { + return err + } + if err := s.verifyParticipantsExistTx(tx, lo, hi); err != nil { + return err + } + res, err := tx.Exec( + `DELETE FROM participant_links WHERE participant_a = ? AND participant_b = ?`, + lo, hi) + if err != nil { + return fmt.Errorf("delete participant link: %w", err) + } + n, err := res.RowsAffected() + if err != nil { + return fmt.Errorf("rows affected: %w", err) + } + if n == 0 { + revision, err = s.currentIdentityRevisionTx(tx) + return err + } + revision, err = s.bumpIdentityRevision(tx) + return err + }) + return revision, err +} + +// rewriteLinksForMerge repoints link edges from loser to winner when a +// participant merge (MergeParticipants, mergeParticipant) absorbs loser into +// winner. Must run inside the merge's own transaction, before the final +// `DELETE FROM participants WHERE id = ?`: participant_links has an ON +// DELETE CASCADE FK, and letting that cascade fire first would silently +// drop the user's links instead of repointing them. +// +// A plain repoint is not enough: contracting the two endpoints of a path can +// create a cycle. Links a-x, x-y, y-b form a path; merging b into a +// collapses the path's endpoints together, and repointing y-b to y-a alone +// would yield the cycle a-x-y-a. So instead of repointing in place, this +// rebuilds the entire affected cluster as a canonical star rooted at its +// smallest member ID, which is always cycle-free. +// +// Both callers (MergeParticipants, mergeParticipant) bump the identity +// revision unconditionally after calling this, regardless of whether it +// touched any edge: a merge can change owner_participants even when it +// touches no link edge, so there is no return value for them to condition +// on. +func (s *Store) rewriteLinksForMerge(tx *loggedTx, loser, winner int64) error { + if err := s.lockIdentityMutationTx(tx); err != nil { + return err + } + edges, err := s.loadLinkEdgesTx(tx) + if err != nil { + return err + } + if !linksReference(edges, loser, winner) { + return nil + } + + members := mergedClusterMembers(loser, winner, edges) + if len(members) < 2 { + // loser and winner were only ever linked to each other: now that + // they are literally the same row, the edge is redundant and + // simply disappears rather than being replaced by a self-loop. + return deleteMergeEdge(tx, loser, winner) + } + return rebuildClusterAsStar(tx, members) +} + +// linksReference reports whether any edge in edges has loser or winner as +// an endpoint. +func linksReference(edges []linkEdge, loser, winner int64) bool { + for _, e := range edges { + if e.a == loser || e.b == loser || e.a == winner || e.b == winner { + return true + } + } + return false +} + +// mergedClusterMembers returns the node set of the cluster that must be +// rebuilt after the merge: the combined reach of loser's and winner's +// components before contraction, with loser replaced by winner. +func mergedClusterMembers(loser, winner int64, edges []linkEdge) map[int64]struct{} { + members := componentOf(loser, edges) + for m := range componentOf(winner, edges) { + members[m] = struct{}{} + } + delete(members, loser) + members[winner] = struct{}{} + return members +} + +// deleteMergeEdge removes the edge between loser and winner. Used when they +// were only ever linked to each other, so the merge leaves no cluster to +// rebuild. +func deleteMergeEdge(tx *loggedTx, loser, winner int64) error { + if _, err := tx.Exec( + `DELETE FROM participant_links WHERE participant_a IN (?, ?) OR participant_b IN (?, ?)`, + loser, winner, loser, winner, + ); err != nil { + return fmt.Errorf("delete direct merge link: %w", err) + } + return nil +} + +// rebuildClusterAsStar deletes every link edge touching a member of the +// cluster, then reinserts a canonical star rooted at the smallest member +// ID — the one shape vertex contraction can never turn into a cycle. +func rebuildClusterAsStar(tx *loggedTx, members map[int64]struct{}) error { + ids := make([]int64, 0, len(members)) + for m := range members { + ids = append(ids, m) + } + slices.Sort(ids) + + if err := deleteClusterEdges(tx, ids); err != nil { + return err + } + + canonical := ids[0] + for _, other := range ids[1:] { + lo, hi := normalizeEdge(canonical, other) + if _, err := tx.Exec( + `INSERT INTO participant_links (participant_a, participant_b) VALUES (?, ?)`, + lo, hi, + ); err != nil { + return fmt.Errorf("insert canonical star link: %w", err) + } + } + return nil +} + +// deleteClusterEdges removes every link edge with an endpoint in ids. Any +// edge touching a merge's loser has its other endpoint in ids (that +// endpoint is in componentOf(loser) by construction), so this also removes +// the loser's edges without the loser needing to appear in ids. +func deleteClusterEdges(tx *loggedTx, ids []int64) error { + placeholders := strings.TrimSuffix(strings.Repeat("?,", len(ids)), ",") + args := make([]any, 0, 2*len(ids)) + for range 2 { + for _, id := range ids { + args = append(args, id) + } + } + if _, err := tx.Exec(fmt.Sprintf( + `DELETE FROM participant_links WHERE participant_a IN (%s) OR participant_b IN (%s)`, + placeholders, placeholders, + ), args...); err != nil { + return fmt.Errorf("delete affected cluster links: %w", err) + } + return nil +} + +// ParticipantClusters returns participant_id → canonical cluster ID +// (the smallest member ID) for every participant that appears in a link +// edge. Unlinked participants are their own cluster and are not returned. +func (s *Store) ParticipantClusters() (map[int64]int64, error) { + edges, err := s.loadLinkEdges() + if err != nil { + return nil, err + } + adj := buildAdjacency(edges) + + ids := make([]int64, 0, len(adj)) + for id := range adj { + ids = append(ids, id) + } + slices.Sort(ids) + + clusters := make(map[int64]int64, len(adj)) + visited := make(map[int64]struct{}, len(adj)) + for _, id := range ids { + if _, seen := visited[id]; seen { + continue + } + // Ascending traversal guarantees id is the smallest unvisited + // node overall, and therefore the smallest member of its own + // component: any smaller member would already be visited, + // either as an earlier component's root or reached via BFS + // from one. + for member := range componentOf(id, edges) { + clusters[member] = id + visited[member] = struct{}{} + } + } + return clusters, nil +} + +// ClusterMembers returns all participant IDs in the cluster containing id +// (including id itself), sorted ascending. Single-element for unlinked ids. +func (s *Store) ClusterMembers(id int64) ([]int64, error) { + edges, err := s.loadLinkEdges() + if err != nil { + return nil, err + } + component := componentOf(id, edges) + members := make([]int64, 0, len(component)) + for member := range component { + members = append(members, member) + } + slices.Sort(members) + return members, nil +} + +// LinkEdge is one participant_links row, exposed to API callers (the +// person-detail HTTP handler) that need the literal edges of a cluster, not +// just its membership — e.g. to render a per-chip unlink affordance that +// calls UnlinkParticipants with an exact existing edge. +type LinkEdge struct { + A int64 `json:"participant_a"` + B int64 `json:"participant_b"` +} + +// ClusterEdges returns every link edge in the connected component containing +// id (including id itself), normalized a b { + return b, a + } + return a, b +} + +// TestMergeParticipantsRewritesLinkEdges covers the simple case: the loser +// of a merge has a link edge to a third participant. After the merge, that +// edge must be repointed onto the winner instead of being dropped by the +// participant_links ON DELETE CASCADE, and both the identity revision and +// the account-identity revision must bump: the merge repoints +// messages.sender_id, which can leave a stale is_from_me baked into message +// Parquet shards, so the staleness check must force a full rebuild (not the +// cheap identity-only refresh) — see cache_staleness.go. +func TestMergeParticipantsRewritesLinkEdges(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + f := storetest.New(t) + a := f.EnsureParticipant("a@example.com", "A", "example.com") // a < b < c + b := f.EnsureParticipant("b@example.com", "B", "example.com") + c := f.EnsureParticipant("c@example.com", "C", "example.com") + + _, err := f.Store.LinkParticipants(b, c) + require.NoError(err) + + revBefore, err := f.Store.IdentityRevision() + require.NoError(err) + acctRevBefore, err := f.Store.AccountIdentityRevision() + require.NoError(err) + + require.NoError(f.Store.MergeParticipants(b, a)) // b (loser) merges into a (winner) + + revAfter, err := f.Store.IdentityRevision() + require.NoError(err) + assert.Equal(revBefore+1, revAfter, "identity revision must bump when a merge touches links") + acctRevAfter, err := f.Store.AccountIdentityRevision() + require.NoError(err) + assert.Equal(acctRevBefore+1, acctRevAfter, "account identity revision must bump on every merge") + + clusters, err := f.Store.ParticipantClusters() + require.NoError(err) + assert.Equal(map[int64]int64{a: a, c: a}, clusters, "edge must repoint from b to a") + + var loserCount int + require.NoError(f.Store.DB().QueryRow(f.Store.Rebind(`SELECT COUNT(*) FROM participants WHERE id = ?`), b). + Scan(&loserCount)) + assert.Equal(0, loserCount, "merged-away participant must be gone") +} + +// TestMergeParticipantsPathContractionKeepsForest covers the case the spec +// calls out explicitly: contracting the endpoints of a path can create a +// cycle. Links a-x, x-y, y-b form a path; merging b into a contracts the +// path's endpoints together, which would yield cycle a-x-y-a if the edges +// were merely repointed without rebuilding the cluster as a star. +func TestMergeParticipantsPathContractionKeepsForest(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + f := storetest.New(t) + a := f.EnsureParticipant("a@example.com", "A", "example.com") // a < x < y < b + x := f.EnsureParticipant("x@example.com", "X", "example.com") + y := f.EnsureParticipant("y@example.com", "Y", "example.com") + b := f.EnsureParticipant("b@example.com", "B", "example.com") + + _, err := f.Store.LinkParticipants(a, x) + require.NoError(err) + _, err = f.Store.LinkParticipants(x, y) + require.NoError(err) + _, err = f.Store.LinkParticipants(y, b) + require.NoError(err) + + require.NoError(f.Store.MergeParticipants(b, a)) // b (loser) merges into a (winner) + + // Cluster {a, x, y} must remain intact and rooted at a (the smallest + // surviving member). + members, err := f.Store.ClusterMembers(a) + require.NoError(err) + assert.Equal([]int64{a, x, y}, members) + + // Exactly 2 edges, forming a star: no self-edges, no duplicate edges. + var edgeCount int + require.NoError(f.Store.DB().QueryRow(f.Store.Rebind( + `SELECT COUNT(*) FROM participant_links WHERE participant_a IN (?, ?, ?) OR participant_b IN (?, ?, ?)`), + a, x, y, a, x, y).Scan(&edgeCount)) + assert.Equal(2, edgeCount, "3-node cluster must have exactly 2 edges (a star, not a cycle)") + + // Unlinking any single edge splits exactly one member off. + _, err = f.Store.UnlinkParticipants(a, x) + require.NoError(err) + xMembers, err := f.Store.ClusterMembers(x) + require.NoError(err) + assert.Equal([]int64{x}, xMembers, "unlinking a-x must split x off on its own") + aMembers, err := f.Store.ClusterMembers(a) + require.NoError(err) + assert.Equal([]int64{a, y}, aMembers, "unlinking a-x must leave a and y still linked") +} + +// TestMergeParticipantsWithoutLinksStillBumpsRevisionButRewritesNoLinks +// covers the common case: neither side of the merge has ever been linked. +// The merge must still bump the identity revision unconditionally (a merge +// can change owner_participants' content even without touching the link +// graph, per Finding 2 of the relationships-backend review), but since +// there was no link edge to repoint, participant_links must remain empty. +// It must also bump the account-identity revision unconditionally: the +// merge repoints messages.sender_id regardless of whether a link edge +// existed, so a stale is_from_me can only be repaired by a full rebuild. +func TestMergeParticipantsWithoutLinksStillBumpsRevisionButRewritesNoLinks(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + f := storetest.New(t) + a := f.EnsureParticipant("a@example.com", "A", "example.com") + b := f.EnsureParticipant("b@example.com", "B", "example.com") + + revBefore, err := f.Store.IdentityRevision() + require.NoError(err) + acctRevBefore, err := f.Store.AccountIdentityRevision() + require.NoError(err) + + require.NoError(f.Store.MergeParticipants(b, a)) + + revAfter, err := f.Store.IdentityRevision() + require.NoError(err) + assert.Equal(revBefore+1, revAfter, "merge must bump the identity revision unconditionally") + acctRevAfter, err := f.Store.AccountIdentityRevision() + require.NoError(err) + assert.Equal(acctRevBefore+1, acctRevAfter, "merge must bump the account identity revision unconditionally") + + var edgeCount int + require.NoError(f.Store.DB().QueryRow(`SELECT COUNT(*) FROM participant_links`).Scan(&edgeCount)) + assert.Equal(0, edgeCount, "merge without pre-existing links must not create any link edge") +} + +// TestLinkParticipants_ConcurrentDisjointClusters covers the race that +// lockIdentityMutationTx serializes against: two LinkParticipants calls +// that each try to connect two already-linked, previously-disjoint +// clusters (e.g. link(b,c) racing link(a,d) where {a,b} and {c,d} already +// exist). Without a lock taken before the edge snapshot read, both calls +// could see the pre-merge snapshot, both pass the connectivity check, and +// both commit a new edge — producing a cycle and breaking the +// participant_links forest invariant. With the lock, exactly one call +// merges the clusters and the other observes the merge and returns +// ErrAlreadyLinked. +// +// This runs against the file-backed store from storetest.New (real +// separate connections, real SQLite/PostgreSQL locking), following the +// pattern of TestEnsureParticipant_Concurrent. +func TestLinkParticipants_ConcurrentDisjointClusters(t *testing.T) { + require := require.New(t) + f := storetest.New(t) + + const groups = 10 + type group struct{ a, b, c, d int64 } + groupIDs := make([]group, groups) + for i := range groups { + a := f.EnsureParticipant(fmt.Sprintf("a%d@example.com", i), "A", "example.com") + b := f.EnsureParticipant(fmt.Sprintf("b%d@example.com", i), "B", "example.com") + c := f.EnsureParticipant(fmt.Sprintf("c%d@example.com", i), "C", "example.com") + d := f.EnsureParticipant(fmt.Sprintf("d%d@example.com", i), "D", "example.com") + _, err := f.Store.LinkParticipants(a, b) + require.NoError(err) + _, err = f.Store.LinkParticipants(c, d) + require.NoError(err) + groupIDs[i] = group{a: a, b: b, c: c, d: d} + } + + var wg sync.WaitGroup + errs1 := make([]error, groups) + errs2 := make([]error, groups) + for i, g := range groupIDs { + wg.Add(2) + go func(idx int, g group) { + defer wg.Done() + _, err := f.Store.LinkParticipants(g.b, g.c) + errs1[idx] = err + }(i, g) + go func(idx int, g group) { + defer wg.Done() + _, err := f.Store.LinkParticipants(g.a, g.d) + errs2[idx] = err + }(i, g) + } + wg.Wait() + + for i, g := range groupIDs { + // Exactly one of the two merge attempts must succeed and the + // other must observe the merge as redundant. Two successes + // would mean both edges landed, creating a 4-cycle. + succeeded := 0 + for _, err := range []error{errs1[i], errs2[i]} { + switch { + case err == nil: + succeeded++ + case errors.Is(err, store.ErrAlreadyLinked): + // expected outcome for the loser + default: + require.NoError(err, "group %d: unexpected error", i) + } + } + assert.Equal(t, 1, succeeded, "group %d: exactly one link call should succeed", i) + + members, err := f.Store.ClusterMembers(g.a) + require.NoError(err) + assert.ElementsMatch(t, []int64{g.a, g.b, g.c, g.d}, members, + "group %d: all four participants must end up in one cluster", i) + + var edgeCount int + require.NoError(f.Store.DB().QueryRow( + f.Store.Rebind(`SELECT COUNT(*) FROM participant_links + WHERE participant_a IN (?, ?, ?, ?) AND participant_b IN (?, ?, ?, ?)`), + g.a, g.b, g.c, g.d, g.a, g.b, g.c, g.d, + ).Scan(&edgeCount), "count group %d edges", i) + assert.Equal(t, 3, edgeCount, "group %d: 4-node cluster must have exactly 3 edges (a tree, not a cycle)", i) + } +} diff --git a/internal/store/saved_views.go b/internal/store/saved_views.go new file mode 100644 index 000000000..a207c4fe3 --- /dev/null +++ b/internal/store/saved_views.go @@ -0,0 +1,320 @@ +package store + +import ( + "bytes" + "context" + "database/sql" + "encoding/json" + "errors" + "fmt" + "io" + "strings" + "time" + + "go.kenn.io/msgvault/internal/explorecatalog" + "go.kenn.io/msgvault/internal/jsonexact" +) + +const CurrentSavedViewSchemaVersion = 1 + +var ( + ErrSavedViewNotFound = errors.New("saved view not found") + ErrSavedViewNameConflict = errors.New("saved view name already exists") + ErrSavedViewRevisionConflict = errors.New("saved view revision conflict") + ErrSavedViewInvalidState = errors.New("invalid saved view canonical state") + ErrSavedViewUnsupportedSchemaVersion = errors.New("unsupported saved view schema version") +) + +// SavedViewStateEnvelope is the version-1 canonical analytical definition. +// Every nested definition is closed and typed so result rows, bulk selection, +// and other transient workspace state cannot hide below an allowed field. +type SavedViewStateEnvelope struct { + Query string `json:"query,omitempty"` + SearchMode string `json:"search_mode,omitempty"` + Filters []SavedViewFilter `json:"filters,omitempty"` + Grouping []string `json:"grouping,omitempty"` + Presentation string `json:"presentation,omitempty"` + Sort []SavedViewSort `json:"sort,omitempty"` + Columns []string `json:"columns,omitempty"` + InspectorPinned bool `json:"inspector_pinned,omitempty"` +} + +// SavedViewFilter is a normalized version-1 analytical predicate. Values are +// strings deliberately: exact numeric identifiers remain lossless in both Go +// and JavaScript clients, including values above JavaScript's safe integer +// range. +type SavedViewFilter struct { + Field string `json:"field"` + Operator string `json:"operator"` + Values []string `json:"values" doc:"Exact filter values; numeric identifiers use decimal strings"` +} + +type SavedViewSort struct { + Field string `json:"field"` + Direction string `json:"direction" enum:"asc,desc"` +} + +type SavedView struct { + ID int64 `json:"id"` + Name string `json:"name"` + Description *string `json:"description,omitempty"` + CanonicalState json.RawMessage `json:"canonical_state"` + SchemaVersion int `json:"schema_version"` + Revision int64 `json:"revision"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type SavedViewInput struct { + Name string + Description *string + CanonicalState json.RawMessage + SchemaVersion int +} + +func (s *Store) CreateSavedView(ctx context.Context, input SavedViewInput) (*SavedView, error) { + validated, err := validateSavedViewInput(input) + if err != nil { + return nil, err + } + query := fmt.Sprintf(` + INSERT INTO saved_views (name, description, canonical_state, schema_version) + VALUES (?, ?, %s, ?) + RETURNING id, name, description, canonical_state, schema_version, + revision, created_at, updated_at + `, s.dialect.JSONBindExpr()) + view, err := scanSavedView(s.db.QueryRowContext(ctx, query, + validated.Name, validated.Description, string(validated.CanonicalState), validated.SchemaVersion)) + if err != nil { + if s.dialect.IsConflictError(err) { + return nil, ErrSavedViewNameConflict + } + return nil, fmt.Errorf("create saved view: %w", err) + } + return view, nil +} + +func (s *Store) ListSavedViews(ctx context.Context) ([]SavedView, error) { + rows, err := s.db.QueryContext(ctx, ` + SELECT id, name, description, canonical_state, schema_version, + revision, created_at, updated_at + FROM saved_views + ORDER BY LOWER(name), id + `) + if err != nil { + return nil, fmt.Errorf("list saved views: %w", err) + } + defer func() { _ = rows.Close() }() + + views := make([]SavedView, 0) + for rows.Next() { + view, scanErr := scanSavedView(rows) + if scanErr != nil { + return nil, fmt.Errorf("scan saved view: %w", scanErr) + } + views = append(views, *view) + } + if err := rows.Err(); err != nil { + return nil, fmt.Errorf("iterate saved views: %w", err) + } + return views, nil +} + +func (s *Store) GetSavedView(ctx context.Context, id int64) (*SavedView, error) { + view, err := scanSavedView(s.db.QueryRowContext(ctx, ` + SELECT id, name, description, canonical_state, schema_version, + revision, created_at, updated_at + FROM saved_views + WHERE id = ? + `, id)) + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrSavedViewNotFound + } + if err != nil { + return nil, fmt.Errorf("get saved view %d: %w", id, err) + } + return view, nil +} + +func (s *Store) UpdateSavedView( + ctx context.Context, id, expectedRevision int64, input SavedViewInput, +) (*SavedView, error) { + validated, err := validateSavedViewInput(input) + if err != nil { + return nil, err + } + query := fmt.Sprintf(` + UPDATE saved_views + SET name = ?, description = ?, canonical_state = %s, schema_version = ?, + revision = revision + 1, updated_at = %s + WHERE id = ? AND revision = ? + RETURNING id, name, description, canonical_state, schema_version, + revision, created_at, updated_at + `, s.dialect.JSONBindExpr(), s.dialect.Now()) + view, err := scanSavedView(s.db.QueryRowContext(ctx, query, + validated.Name, validated.Description, string(validated.CanonicalState), validated.SchemaVersion, + id, expectedRevision)) + if err == nil { + return view, nil + } + if s.dialect.IsConflictError(err) { + return nil, ErrSavedViewNameConflict + } + if !errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("update saved view %d: %w", id, err) + } + return nil, s.savedViewCASMiss(ctx, id) +} + +func (s *Store) DeleteSavedView(ctx context.Context, id, expectedRevision int64) error { + result, err := s.db.ExecContext(ctx, + `DELETE FROM saved_views WHERE id = ? AND revision = ?`, id, expectedRevision) + if err != nil { + return fmt.Errorf("delete saved view %d: %w", id, err) + } + deleted, err := result.RowsAffected() + if err != nil { + return fmt.Errorf("read saved view delete result: %w", err) + } + if deleted == 1 { + return nil + } + return s.savedViewCASMiss(ctx, id) +} + +func (s *Store) savedViewCASMiss(ctx context.Context, id int64) error { + var revision int64 + err := s.db.QueryRowContext(ctx, `SELECT revision FROM saved_views WHERE id = ?`, id).Scan(&revision) + if errors.Is(err, sql.ErrNoRows) { + return ErrSavedViewNotFound + } + if err != nil { + return fmt.Errorf("check saved view %d after revision miss: %w", id, err) + } + return ErrSavedViewRevisionConflict +} + +func validateSavedViewInput(input SavedViewInput) (SavedViewInput, error) { + input.Name = strings.TrimSpace(input.Name) + if input.Name == "" { + return SavedViewInput{}, fmt.Errorf("%w: name is required", ErrSavedViewInvalidState) + } + if input.SchemaVersion != CurrentSavedViewSchemaVersion { + return SavedViewInput{}, fmt.Errorf("%w: got %d, support %d", + ErrSavedViewUnsupportedSchemaVersion, input.SchemaVersion, CurrentSavedViewSchemaVersion) + } + + trimmedState := bytes.TrimSpace(input.CanonicalState) + if len(trimmedState) == 0 || bytes.Equal(trimmedState, []byte("null")) { + return SavedViewInput{}, fmt.Errorf("%w: canonical state must be a JSON object", ErrSavedViewInvalidState) + } + if err := jsonexact.Validate(trimmedState, SavedViewStateEnvelope{}); err != nil { + return SavedViewInput{}, fmt.Errorf("%w: %w", ErrSavedViewInvalidState, err) + } + + decoder := json.NewDecoder(bytes.NewReader(trimmedState)) + decoder.DisallowUnknownFields() + decoder.UseNumber() + var envelope SavedViewStateEnvelope + if err := decoder.Decode(&envelope); err != nil { + return SavedViewInput{}, fmt.Errorf("%w: %w", ErrSavedViewInvalidState, err) + } + if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + return SavedViewInput{}, fmt.Errorf("%w: canonical state must contain one JSON object", ErrSavedViewInvalidState) + } + var fields map[string]json.RawMessage + if err := json.Unmarshal(trimmedState, &fields); err != nil || fields == nil { + return SavedViewInput{}, fmt.Errorf("%w: canonical state must be a JSON object", ErrSavedViewInvalidState) + } + for _, name := range []string{ + "query", "search_mode", "filters", "grouping", "presentation", "sort", "columns", "inspector_pinned", + } { + if value, present := fields[name]; present && bytes.Equal(bytes.TrimSpace(value), []byte("null")) { + return SavedViewInput{}, fmt.Errorf("%w: %s must not be null", ErrSavedViewInvalidState, name) + } + } + if rawFilters, present := fields["filters"]; present { + var filters []struct { + Values []json.RawMessage `json:"values"` + } + if err := json.Unmarshal(rawFilters, &filters); err == nil { + for filterIndex, filter := range filters { + for valueIndex, value := range filter.Values { + if bytes.Equal(bytes.TrimSpace(value), []byte("null")) { + return SavedViewInput{}, fmt.Errorf( + "%w: filters[%d].values[%d] must not be null", + ErrSavedViewInvalidState, filterIndex, valueIndex, + ) + } + } + } + } + } + if err := validateSavedViewEnvelope(envelope); err != nil { + return SavedViewInput{}, fmt.Errorf("%w: %w", ErrSavedViewInvalidState, err) + } + canonical, err := json.Marshal(envelope) + if err != nil { + return SavedViewInput{}, fmt.Errorf("%w: %w", ErrSavedViewInvalidState, err) + } + input.CanonicalState = canonical + return input, nil +} + +func validateSavedViewEnvelope(envelope SavedViewStateEnvelope) error { + for i, filter := range envelope.Filters { + if strings.TrimSpace(filter.Field) == "" { + return fmt.Errorf("filters[%d].field is required", i) + } + if strings.TrimSpace(filter.Operator) == "" { + return fmt.Errorf("filters[%d].operator is required", i) + } + if filter.Values == nil { + return fmt.Errorf("filters[%d].values is required", i) + } + } + for i, field := range envelope.Grouping { + if !explorecatalog.IsGroupingDimension(field) { + return fmt.Errorf("grouping[%d] is not a supported analytical dimension", i) + } + } + if envelope.Presentation != "" && + envelope.Presentation != "table" && + envelope.Presentation != "timeline" && + envelope.Presentation != "files" { + return errors.New("presentation must be table, timeline, or files") + } + for i, sort := range envelope.Sort { + if strings.TrimSpace(sort.Field) == "" { + return fmt.Errorf("sort[%d].field is required", i) + } + if sort.Direction != "asc" && sort.Direction != "desc" { + return fmt.Errorf("sort[%d].direction must be asc or desc", i) + } + } + for i, column := range envelope.Columns { + if strings.TrimSpace(column) == "" { + return fmt.Errorf("columns[%d] must not be empty", i) + } + } + return nil +} + +func scanSavedView(row scanner) (*SavedView, error) { + var ( + view SavedView + description sql.NullString + state []byte + ) + if err := row.Scan( + &view.ID, &view.Name, &description, &state, &view.SchemaVersion, + &view.Revision, &view.CreatedAt, &view.UpdatedAt, + ); err != nil { + return nil, err + } + if description.Valid { + view.Description = &description.String + } + view.CanonicalState = json.RawMessage(append([]byte(nil), state...)) + return &view, nil +} diff --git a/internal/store/saved_views_pg_test.go b/internal/store/saved_views_pg_test.go new file mode 100644 index 000000000..b74392663 --- /dev/null +++ b/internal/store/saved_views_pg_test.go @@ -0,0 +1,43 @@ +package store_test + +import ( + "os" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/testutil" +) + +func TestSavedViewsPostgreSQLSchemaUsesIdentityJSONBAndTimestamps(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + testDB := os.Getenv("MSGVAULT_TEST_DB") + if !strings.HasPrefix(testDB, "postgres://") && !strings.HasPrefix(testDB, "postgresql://") { + t.Skip("PostgreSQL-only saved views schema assertion") + } + + st := testutil.NewTestStore(t) + rows, err := st.DB().Query(` + SELECT column_name, data_type, is_identity + FROM information_schema.columns + WHERE table_schema = current_schema() AND table_name = 'saved_views' + `) + requirements.NoError(err) + defer func() { requirements.NoError(rows.Close()) }() + + types := map[string]string{} + identities := map[string]string{} + for rows.Next() { + var name, dataType, identity string + requirements.NoError(rows.Scan(&name, &dataType, &identity)) + types[name] = dataType + identities[name] = identity + } + requirements.NoError(rows.Err()) + assertions.Equal("jsonb", types["canonical_state"]) + assertions.Equal("timestamp with time zone", types["created_at"]) + assertions.Equal("timestamp with time zone", types["updated_at"]) + assertions.Equal("YES", identities["id"]) +} diff --git a/internal/store/saved_views_test.go b/internal/store/saved_views_test.go new file mode 100644 index 000000000..121e99fa0 --- /dev/null +++ b/internal/store/saved_views_test.go @@ -0,0 +1,212 @@ +package store_test + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/store" + "go.kenn.io/msgvault/internal/testutil" +) + +var savedViewState = json.RawMessage(`{ + "query":"invoice", + "search_mode":"full_text", + "filters":[{"field":"source_id","operator":"in","values":["1"]}], + "grouping":["source"], + "presentation":"table", + "sort":[{"field":"count","direction":"desc"}], + "columns":["sender"], + "inspector_pinned":true +}`) + +func savedViewInput(name string) store.SavedViewInput { + description := "Quarterly review" + return store.SavedViewInput{ + Name: name, + Description: &description, + CanonicalState: savedViewState, + SchemaVersion: store.CurrentSavedViewSchemaVersion, + } +} + +func TestSavedViewsSQLiteSchema(t *testing.T) { + requirements := require.New(t) + st := testutil.NewSQLiteTestStore(t) + + rows, err := st.DB().Query(`PRAGMA table_info('saved_views')`) + requirements.NoError(err) + defer func() { requirements.NoError(rows.Close()) }() + + columns := make(map[string]bool) + for rows.Next() { + var cid, notNull, primaryKey int + var name, columnType string + var defaultValue any + requirements.NoError(rows.Scan(&cid, &name, &columnType, ¬Null, &defaultValue, &primaryKey)) + columns[name] = true + } + requirements.NoError(rows.Err()) + assert.Equal(t, map[string]bool{ + "id": true, "name": true, "description": true, "canonical_state": true, + "schema_version": true, "revision": true, "created_at": true, "updated_at": true, + }, columns) +} + +func TestSavedViewsCRUDAndStableListOrder(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + ctx := context.Background() + st := testutil.NewTestStore(t) + + zebra, err := st.CreateSavedView(ctx, savedViewInput("Zebra")) + requirements.NoError(err) + requirements.NotZero(zebra.ID) + assertions.Equal(int64(1), zebra.Revision) + assertions.False(zebra.CreatedAt.IsZero()) + assertions.Equal(zebra.CreatedAt, zebra.UpdatedAt) + assertions.JSONEq(string(savedViewState), string(zebra.CanonicalState)) + + alpha, err := st.CreateSavedView(ctx, savedViewInput("alpha")) + requirements.NoError(err) + + views, err := st.ListSavedViews(ctx) + requirements.NoError(err) + requirements.Len(views, 2) + assertions.Equal([]int64{alpha.ID, zebra.ID}, []int64{views[0].ID, views[1].ID}) + + got, err := st.GetSavedView(ctx, zebra.ID) + requirements.NoError(err) + assertions.Equal(zebra, got) + + updatedInput := savedViewInput("Beta") + updated, err := st.UpdateSavedView(ctx, zebra.ID, zebra.Revision, updatedInput) + requirements.NoError(err) + assertions.Equal(int64(2), updated.Revision) + assertions.Equal("Beta", updated.Name) + assertions.Equal(zebra.CreatedAt, updated.CreatedAt) + assertions.False(updated.UpdatedAt.Before(updated.CreatedAt)) + + requirements.NoError(st.DeleteSavedView(ctx, updated.ID, updated.Revision)) + _, err = st.GetSavedView(ctx, updated.ID) + assertions.ErrorIs(err, store.ErrSavedViewNotFound) +} + +func TestSavedViewsRejectDuplicateNamesAndStaleRevisions(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + ctx := context.Background() + st := testutil.NewTestStore(t) + created, err := st.CreateSavedView(ctx, savedViewInput("Invoices")) + requirements.NoError(err) + + _, err = st.CreateSavedView(ctx, savedViewInput("Invoices")) + requirements.ErrorIs(err, store.ErrSavedViewNameConflict) + + updated, err := st.UpdateSavedView(ctx, created.ID, created.Revision, savedViewInput("Invoices 2026")) + requirements.NoError(err) + _, err = st.UpdateSavedView(ctx, created.ID, created.Revision, savedViewInput("Stale edit")) + requirements.ErrorIs(err, store.ErrSavedViewRevisionConflict) + requirements.ErrorIs(st.DeleteSavedView(ctx, created.ID, created.Revision), store.ErrSavedViewRevisionConflict) + requirements.NoError(st.DeleteSavedView(ctx, updated.ID, updated.Revision)) + assertions.ErrorIs(st.DeleteSavedView(ctx, updated.ID, updated.Revision), store.ErrSavedViewNotFound) +} + +func TestSavedViewsValidateCanonicalStateBeforePersistence(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + ctx := context.Background() + st := testutil.NewTestStore(t) + + tests := []struct { + name string + version int + state string + wantErr error + }{ + {name: "malformed JSON", version: 1, state: `{`, wantErr: store.ErrSavedViewInvalidState}, + {name: "unsupported schema", version: 99, state: `{}`, wantErr: store.ErrSavedViewUnsupportedSchemaVersion}, + {name: "result rows", version: 1, state: `{"results":[{"id":1}]}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "selection", version: 1, state: `{"selection":[1]}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "nested result rows", version: 1, state: `{"filters":{"results":[{"id":1}]}}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "nested selection", version: 1, state: `{"presentation":{"selection":[1]}}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "result rows in typed filter", version: 1, state: `{"filters":[{"field":"sender","operator":"eq","values":["alice@example.com"],"results":[{"id":1}]}]}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "selection in typed sort", version: 1, state: `{"sort":[{"field":"sent_at","direction":"desc","selection":[1]}]}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "wrong nested filter type", version: 1, state: `{"filters":[{"field":17,"operator":"eq","values":["x"]}]}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "wrong nested sort type", version: 1, state: `{"sort":[{"field":"sent_at","direction":["desc"]}]}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "null filters", version: 1, state: `{"filters":null}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "null filter values", version: 1, state: `{"filters":[{"field":"source_id","operator":"eq","values":null}]}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "null filter value element", version: 1, state: `{"filters":[{"field":"source_id","operator":"eq","values":[null]}]}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "null presentation", version: 1, state: `{"presentation":null}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "null canonical state", version: 1, state: `null`, wantErr: store.ErrSavedViewInvalidState}, + {name: "mixed-case top-level field", version: 1, state: `{"Filters":null}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "uppercase top-level field", version: 1, state: `{"QUERY":"invoice"}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "mixed-case nested filter field", version: 1, state: `{"filters":[{"Field":"sender","operator":"eq","values":["alice@example.com"]}]}`, wantErr: store.ErrSavedViewInvalidState}, + {name: "mixed-case nested sort field", version: 1, state: `{"sort":[{"field":"sent_at","Direction":"desc"}]}`, wantErr: store.ErrSavedViewInvalidState}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + input := savedViewInput(tt.name) + input.SchemaVersion = tt.version + input.CanonicalState = json.RawMessage(tt.state) + _, err := st.CreateSavedView(ctx, input) + assertions.ErrorIs(err, tt.wantErr) + }) + } + + views, err := st.ListSavedViews(ctx) + requirements.NoError(err) + assertions.Empty(views) +} + +func TestSavedViewsPersistOnlyServerGroupingDimensions(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + ctx := context.Background() + st := testutil.NewTestStore(t) + supported := []string{"source", "participant", "domain", "message_type", "kind", "year", "month"} + + for _, dimension := range supported { + t.Run("accept "+dimension, func(t *testing.T) { + subAssertions := assert.New(t) + subRequirements := require.New(t) + input := savedViewInput("Group by " + dimension) + input.CanonicalState = json.RawMessage(`{"grouping":["` + dimension + `"]}`) + created, err := st.CreateSavedView(ctx, input) + subRequirements.NoError(err) + subAssertions.JSONEq(`{"grouping":["`+dimension+`"]}`, string(created.CanonicalState)) + }) + } + + invalid := savedViewInput("Unsupported grouping") + invalid.CanonicalState = json.RawMessage(`{"grouping":["sender"]}`) + _, err := st.CreateSavedView(ctx, invalid) + requirements.ErrorIs(err, store.ErrSavedViewInvalidState) + + created, err := st.CreateSavedView(ctx, store.SavedViewInput{ + Name: "Valid before update", CanonicalState: json.RawMessage(`{"grouping":["source"]}`), + SchemaVersion: store.CurrentSavedViewSchemaVersion, + }) + requirements.NoError(err) + invalid.Name = "Invalid update" + _, err = st.UpdateSavedView(ctx, created.ID, created.Revision, invalid) + requirements.ErrorIs(err, store.ErrSavedViewInvalidState) + unchanged, err := st.GetSavedView(ctx, created.ID) + requirements.NoError(err) + assertions.Equal(int64(1), unchanged.Revision) + assertions.JSONEq(`{"grouping":["source"]}`, string(unchanged.CanonicalState)) +} + +func TestSavedViewsPreserveLargeNumericFilterIDs(t *testing.T) { + ctx := context.Background() + st := testutil.NewTestStore(t) + input := savedViewInput("Large source ID") + input.CanonicalState = json.RawMessage(`{"filters":[{"field":"source_id","operator":"eq","values":["9223372036854775807"]}]}`) + + created, err := st.CreateSavedView(ctx, input) + require.NoError(t, err) + assert.Contains(t, string(created.CanonicalState), "9223372036854775807", + "canonicalization must not round 64-bit identifiers") +} diff --git a/internal/store/schema.sql b/internal/store/schema.sql index 606843fd6..5811af5b4 100644 --- a/internal/store/schema.sql +++ b/internal/store/schema.sql @@ -1,6 +1,11 @@ -- msgvault unified schema -- Supports: Gmail, Apple Messages, Google Messages, WhatsApp +CREATE TABLE IF NOT EXISTS archive_metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); + -- ============================================================================ -- SOURCES & IDENTITY -- ============================================================================ @@ -504,6 +509,20 @@ CREATE TABLE IF NOT EXISTS collection_sources ( CREATE INDEX IF NOT EXISTS idx_collection_sources_source_id ON collection_sources(source_id); +-- Daemon-owned analytical Saved Views. Canonical state contains only the +-- query/view definition; result rows and transient selection remain client +-- state and are never persisted here. +CREATE TABLE IF NOT EXISTS saved_views ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + description TEXT, + canonical_state JSON NOT NULL, + schema_version INTEGER NOT NULL, + revision INTEGER NOT NULL DEFAULT 1, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + -- ============================================================================ -- ACCOUNT IDENTITIES -- ============================================================================ @@ -522,6 +541,22 @@ CREATE TABLE IF NOT EXISTS account_identities ( CREATE INDEX IF NOT EXISTS idx_account_identities_address ON account_identities(address); +-- User-asserted identity links between participants. Edges are normalized +-- (participant_a < participant_b) and the graph is kept a forest: every +-- edge joins two previously distinct clusters, so deleting an edge +-- deterministically splits one cluster in two. Connected components resolve +-- to a canonical cluster (smallest member ID) at read time. +CREATE TABLE IF NOT EXISTS participant_links ( + participant_a INTEGER NOT NULL REFERENCES participants(id) ON DELETE CASCADE, + participant_b INTEGER NOT NULL REFERENCES participants(id) ON DELETE CASCADE, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (participant_a, participant_b), + CHECK (participant_a < participant_b) +); + +CREATE INDEX IF NOT EXISTS idx_participant_links_b + ON participant_links(participant_b); + -- ============================================================================ -- APPLIED MIGRATIONS -- ============================================================================ diff --git a/internal/store/schema_pg.sql b/internal/store/schema_pg.sql index 004b13c63..03862d2d0 100644 --- a/internal/store/schema_pg.sql +++ b/internal/store/schema_pg.sql @@ -1,6 +1,11 @@ -- msgvault PostgreSQL schema -- Native PostgreSQL types and identity columns, parallel to schema.sql. +CREATE TABLE IF NOT EXISTS archive_metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); + -- ============================================================================ -- SOURCES & IDENTITY -- ============================================================================ @@ -344,6 +349,20 @@ CREATE TABLE IF NOT EXISTS collection_sources ( CREATE INDEX IF NOT EXISTS idx_collection_sources_source_id ON collection_sources(source_id); +-- Daemon-owned analytical Saved Views. Canonical state contains only the +-- query/view definition; result rows and transient selection remain client +-- state and are never persisted here. +CREATE TABLE IF NOT EXISTS saved_views ( + id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + name TEXT NOT NULL UNIQUE, + description TEXT, + canonical_state JSONB NOT NULL, + schema_version INTEGER NOT NULL, + revision BIGINT NOT NULL DEFAULT 1, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); + -- Confirmed per-account "me" identities used by sent-message detection -- in dedup. Identity is account-scoped: an address confirmed for one -- source does not imply it is "me" in any other source. @@ -355,6 +374,22 @@ CREATE TABLE IF NOT EXISTS account_identities ( PRIMARY KEY (source_id, address) ); +-- User-asserted identity links between participants. Edges are normalized +-- (participant_a < participant_b) and the graph is kept a forest: every +-- edge joins two previously distinct clusters, so deleting an edge +-- deterministically splits one cluster in two. Connected components resolve +-- to a canonical cluster (smallest member ID) at read time. +CREATE TABLE IF NOT EXISTS participant_links ( + participant_a BIGINT NOT NULL REFERENCES participants(id) ON DELETE CASCADE, + participant_b BIGINT NOT NULL REFERENCES participants(id) ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (participant_a, participant_b), + CHECK (participant_a < participant_b) +); + +CREATE INDEX IF NOT EXISTS idx_participant_links_b + ON participant_links(participant_b); + -- Marks one-time data migrations that have already run. Schema DDL is -- idempotent via IF NOT EXISTS; this table is for *data* migrations -- (e.g. moving legacy config into per-account records) that must run diff --git a/internal/store/schema_sqlite.sql b/internal/store/schema_sqlite.sql index c21a08036..74ed09f48 100644 --- a/internal/store/schema_sqlite.sql +++ b/internal/store/schema_sqlite.sql @@ -1,5 +1,10 @@ -- SQLite-specific extensions (FTS5 for full-text search) +CREATE TABLE IF NOT EXISTS archive_metadata ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); + -- Full-text search index for messages -- This is a standalone FTS table (not contentless) that stores its own copy -- of searchable text. Updates are managed via Store.upsert_fts(). diff --git a/internal/store/store.go b/internal/store/store.go index 89e296d0e..5529032e5 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -697,7 +697,6 @@ func (s *Store) InitSchema() error { return fmt.Errorf("execute %s: %w", filename, err) } } - // Legacy databases may hold duplicate (message_id, content_hash) // attachment rows from the old SELECT-then-INSERT UpsertAttachment. // Dedupe before creating the partial unique index that enforces @@ -979,6 +978,9 @@ func (s *Store) InitSchema() error { // through so the rest of schema init still runs. } } + if err := s.ensureArchiveUID(); err != nil { + return err + } // Probe availability through the dialect so it works uniformly for // backends that carry FTS inside their main schema. diff --git a/internal/taskclient/capabilities.go b/internal/taskclient/capabilities.go new file mode 100644 index 000000000..ad8a9f337 --- /dev/null +++ b/internal/taskclient/capabilities.go @@ -0,0 +1,195 @@ +package taskclient + +import ( + "context" + "errors" + "net/http" + "strings" + "time" +) + +type Capabilities struct { + ProtocolVersion string `json:"protocol_version"` + RevisionReads bool `json:"revision_reads"` + ConditionalMutation bool `json:"conditional_metadata_mutation"` + ConflictResponses bool `json:"conflict_responses"` + IdempotentCreation bool `json:"idempotent_task_creation"` + ProjectOperations bool `json:"project_operations"` + MetadataOperations bool `json:"metadata_operations"` +} + +func (c Capabilities) Compatible() bool { + return c.ProtocolVersion == ProtocolVersion && + c.RevisionReads && + c.ConditionalMutation && + c.ConflictResponses && + c.IdempotentCreation && + c.ProjectOperations && + c.MetadataOperations +} + +type State string + +const ( + StateDisabled State = "disabled" + StateNotFound State = "not_found" + StateAuthenticationRequired State = "authentication_required" + StateUnreachable State = "unreachable" + StateIncompatible State = "incompatible" + StateWrongProject State = "wrong_project" + StateReady State = "ready" +) + +type Status struct { + State State `json:"state"` + Project string `json:"project,omitempty"` + Message string `json:"message"` + SecurityNote string `json:"security_note,omitempty"` +} + +type IntegrationConfig struct { + Enabled bool + Endpoint string + APIKey string + DefaultProject string + DescriptorPath string + Timeout time.Duration + MaxResponseBytes int64 + HTTPClient *http.Client + // platformSecurityCheck is a package-local test seam. Production callers + // always use the platform check selected by Discover. + platformSecurityCheck func() error +} + +func Evaluate(ctx context.Context, cfg IntegrationConfig) Status { + project := strings.TrimSpace(cfg.DefaultProject) + if !cfg.Enabled { + return status(StateDisabled, project, "Task integration is disabled.") + } + var client *Client + var err error + if strings.TrimSpace(cfg.Endpoint) == "" { + client, err = Discover(ctx, DiscoveryOptions{ + DescriptorPath: cfg.DescriptorPath, + APIKey: cfg.APIKey, + Timeout: cfg.Timeout, + MaxResponseBytes: cfg.MaxResponseBytes, + HTTPClient: cfg.HTTPClient, + platformSecurityCheck: cfg.platformSecurityCheck, + }) + } else { + client, err = New(ClientOptions{ + Endpoint: cfg.Endpoint, + APIKey: cfg.APIKey, + Timeout: cfg.Timeout, + MaxResponseBytes: cfg.MaxResponseBytes, + HTTPClient: cfg.HTTPClient, + }) + } + if err != nil { + return statusForError(err, project) + } + return evaluateClient(ctx, client, project) +} + +// Connect returns an authenticated client only after the required capability +// and configured-project checks succeed. Callers use this same gate for every +// mutation so a previously-ready service cannot silently downgrade. +func Connect(ctx context.Context, cfg IntegrationConfig) (*Client, Project, error) { + if !cfg.Enabled { + return nil, Project{}, ErrNotFound + } + var client *Client + var err error + if strings.TrimSpace(cfg.Endpoint) == "" { + client, err = Discover(ctx, DiscoveryOptions{ + DescriptorPath: cfg.DescriptorPath, APIKey: cfg.APIKey, Timeout: cfg.Timeout, + MaxResponseBytes: cfg.MaxResponseBytes, HTTPClient: cfg.HTTPClient, + platformSecurityCheck: cfg.platformSecurityCheck, + }) + } else { + client, err = New(ClientOptions{Endpoint: cfg.Endpoint, APIKey: cfg.APIKey, Timeout: cfg.Timeout, + MaxResponseBytes: cfg.MaxResponseBytes, HTTPClient: cfg.HTTPClient}) + } + if err != nil { + return nil, Project{}, err + } + capabilities, err := client.Capabilities(ctx) + if err != nil { + return nil, Project{}, err + } + if !capabilities.Compatible() { + return nil, Project{}, ErrIncompatible + } + projectName := strings.TrimSpace(cfg.DefaultProject) + if projectName == "" { + return nil, Project{}, ErrWrongProject + } + project, err := client.ResolveProject(ctx, projectName) + if err != nil { + return nil, Project{}, err + } + return client, project, nil +} + +func evaluateClient(ctx context.Context, client *Client, project string) Status { + capabilities, err := client.Capabilities(ctx) + if err != nil { + if errors.Is(err, ErrNotFound) { + return withClientSecurityNote(status(StateIncompatible, project, "Task service is reachable but does not implement the required capability protocol."), client) + } + return withClientSecurityNote(statusForError(err, project), client) + } + if !capabilities.Compatible() { + return withClientSecurityNote(status(StateIncompatible, project, "Task service is reachable but lacks required revision, conditional mutation, conflict, idempotency, project, or metadata capabilities."), client) + } + if project == "" { + return withClientSecurityNote(status(StateWrongProject, project, "The configured task project is empty or unavailable."), client) + } + if _, err := client.ResolveProject(ctx, project); err != nil { + if errors.Is(err, ErrWrongProject) { + return withClientSecurityNote(status(StateWrongProject, project, "The resolved task project did not match the configured project."), client) + } + if errors.Is(err, ErrNotFound) { + return withClientSecurityNote(status(StateWrongProject, project, "The configured task project was not found."), client) + } + return withClientSecurityNote(statusForError(err, project), client) + } + return withClientSecurityNote(status(StateReady, project, "Task integration is ready."), client) +} + +func withClientSecurityNote(result Status, client *Client) Status { + if note := client.SecurityNote(); note != "" { + result.SecurityNote = note + } + return result +} + +func statusForError(err error, project string) Status { + switch { + case errors.Is(err, ErrDescriptorFileSecurityLimit): + result := status(StateIncompatible, project, "This platform cannot safely use local task descriptor discovery.") + result.SecurityNote = "Secure descriptor and token-file ownership, no-follow opening, and handle validation are unavailable; discovery was rejected." + return result + case errors.Is(err, ErrUnixSocketSecurityLimit): + result := status(StateIncompatible, project, "This platform cannot safely use the configured Unix socket endpoint.") + result.SecurityNote = "Required peer credentials and socket ownership/mode enforcement are unavailable; the endpoint was rejected." + return result + case errors.Is(err, ErrPlatformSecurityLimit): + result := status(StateIncompatible, project, "This platform cannot prove the required local task integration security.") + result.SecurityNote = "The operation was rejected because required platform security checks are unavailable." + return result + case errors.Is(err, ErrNotFound): + return status(StateNotFound, project, "No compatible local task service was discovered.") + case errors.Is(err, ErrAuthenticationRequired): + return status(StateAuthenticationRequired, project, "Task service authentication is required.") + case errors.Is(err, ErrIncompatible), errors.Is(err, ErrInsecureDescriptor), errors.Is(err, ErrInsecureEndpoint), errors.Is(err, ErrInvalidResponse), errors.Is(err, ErrResponseTooLarge), errors.Is(err, ErrRedirect): + return status(StateIncompatible, project, "Task service is reachable or configured but incompatible with the required secure protocol.") + default: + return status(StateUnreachable, project, "Task service is unavailable.") + } +} + +func status(state State, project, message string) Status { + return Status{State: state, Project: project, Message: message} +} diff --git a/internal/taskclient/capabilities_test.go b/internal/taskclient/capabilities_test.go new file mode 100644 index 000000000..d76d13bdf --- /dev/null +++ b/internal/taskclient/capabilities_test.go @@ -0,0 +1,298 @@ +package taskclient + +import ( + "context" + "net/http" + "net/http/httptest" + "path/filepath" + "runtime" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEvaluateStatusStates(t *testing.T) { + t.Run("disabled", func(t *testing.T) { + status := Evaluate(context.Background(), IntegrationConfig{}) + assert.Equal(t, StateDisabled, status.State) + }) + + t.Run("not found", func(t *testing.T) { + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + DescriptorPath: filepath.Join(t.TempDir(), "missing.json"), + DefaultProject: "test-project", + }) + assert.Equal(t, StateNotFound, status.State) + }) + + t.Run("authentication required", func(t *testing.T) { + path := writeDescriptor(t, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-test", + Endpoint: "http://127.0.0.1:32145", + }, 0o600) + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + DescriptorPath: path, + DefaultProject: "test-project", + }) + assert.Equal(t, StateAuthenticationRequired, status.State) + }) + + t.Run("unreachable", func(t *testing.T) { + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + Endpoint: "http://127.0.0.1:1", + APIKey: "test-key", + DefaultProject: "test-project", + }) + assert.Equal(t, StateUnreachable, status.State) + }) + + t.Run("stale Unix descriptor is unreachable", func(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Unix socket discovery is covered by Unix test lanes") + } + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + DescriptorPath: writeStaleUnixDescriptor(t), + DefaultProject: "test-project", + }) + assert.Equal(t, StateUnreachable, status.State) + }) + + t.Run("reachable but incompatible", func(t *testing.T) { + server := capabilityServer(t, Capabilities{ + ProtocolVersion: ProtocolVersion, + RevisionReads: true, + ProjectOperations: true, + MetadataOperations: true, + ConditionalMutation: true, + ConflictResponses: true, + IdempotentCreation: false, + }, http.StatusOK) + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + Endpoint: server.URL, + APIKey: "test-key", + DefaultProject: "test-project", + }) + assert.Equal(t, StateIncompatible, status.State) + }) + + t.Run("reachable without capability protocol is incompatible", func(t *testing.T) { + server := capabilityServer(t, Capabilities{}, http.StatusNotFound) + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + Endpoint: server.URL, + APIKey: "test-key", + DefaultProject: "test-project", + }) + assert.Equal(t, StateIncompatible, status.State) + }) + + for _, responseStatus := range []int{http.StatusMethodNotAllowed, http.StatusNotImplemented} { + t.Run("reached unsupported "+http.StatusText(responseStatus), func(t *testing.T) { + server := capabilityServer(t, Capabilities{}, responseStatus) + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + Endpoint: server.URL, + APIKey: "test-key", + DefaultProject: "test-project", + }) + assert.Equal(t, StateIncompatible, status.State) + }) + } + + t.Run("live without CAS is incompatible", func(t *testing.T) { + capabilities := compatibleCapabilities() + capabilities.ConditionalMutation = false + server := capabilityServer(t, capabilities, http.StatusOK) + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + Endpoint: server.URL, + APIKey: "test-key", + DefaultProject: "test-project", + }) + assert.Equal(t, StateIncompatible, status.State) + }) + + t.Run("wrong project", func(t *testing.T) { + server := taskProtocolServer(t, compatibleCapabilities(), http.StatusNotFound) + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + Endpoint: server.URL, + APIKey: "test-key", + DefaultProject: "missing-project", + }) + assert.Equal(t, StateWrongProject, status.State) + }) + + t.Run("well shaped unrelated project", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v1/capabilities": + writeTestJSON(t, w, compatibleCapabilities()) + case "/api/v1/projects/test-project": + writeTestJSON(t, w, Project{ID: "project-other-id", Name: "other-project", Revision: `"p1"`}) + default: + http.NotFound(w, r) + } + })) + t.Cleanup(server.Close) + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + Endpoint: server.URL, + APIKey: "test-key", + DefaultProject: "test-project", + }) + assert.Equal(t, StateWrongProject, status.State) + assert.Contains(t, status.Message, "did not match") + }) + + t.Run("ready", func(t *testing.T) { + server := taskProtocolServer(t, compatibleCapabilities(), http.StatusOK) + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + Endpoint: server.URL, + APIKey: "test-key", + DefaultProject: "test-project", + }) + assert.Equal(t, StateReady, status.State) + assert.Equal(t, "test-project", status.Project) + }) +} + +func compatibleCapabilities() Capabilities { + return Capabilities{ + ProtocolVersion: ProtocolVersion, + RevisionReads: true, + ProjectOperations: true, + MetadataOperations: true, + ConditionalMutation: true, + ConflictResponses: true, + IdempotentCreation: true, + } +} + +func capabilityServer(t *testing.T, capabilities Capabilities, status int) *httptest.Server { + t.Helper() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/v1/capabilities", r.URL.Path) + w.WriteHeader(status) + if status == http.StatusOK { + writeTestJSON(t, w, capabilities) + } + })) + t.Cleanup(server.Close) + return server +} + +func taskProtocolServer(t *testing.T, capabilities Capabilities, projectStatus int) *httptest.Server { + t.Helper() + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v1/capabilities": + writeTestJSON(t, w, capabilities) + case "/api/v1/projects/test-project", "/api/v1/projects/missing-project": + if projectStatus == http.StatusOK { + w.Header().Set("ETag", `"project-revision"`) + writeTestJSON(t, w, Project{ID: "project-test", Name: "test-project"}) + } else { + w.WriteHeader(projectStatus) + } + default: + http.NotFound(w, r) + } + })) + t.Cleanup(server.Close) + return server +} + +func TestCapabilitiesRequireAllSafetyGuarantees(t *testing.T) { + base := compatibleCapabilities() + tests := []struct { + name string + mutate func(*Capabilities) + }{ + {"revision reads", func(c *Capabilities) { c.RevisionReads = false }}, + {"conditional mutation", func(c *Capabilities) { c.ConditionalMutation = false }}, + {"conflict responses", func(c *Capabilities) { c.ConflictResponses = false }}, + {"idempotent creation", func(c *Capabilities) { c.IdempotentCreation = false }}, + {"project operations", func(c *Capabilities) { c.ProjectOperations = false }}, + {"metadata operations", func(c *Capabilities) { c.MetadataOperations = false }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + candidate := base + tt.mutate(&candidate) + assert.False(t, candidate.Compatible()) + }) + } + require.True(t, base.Compatible()) +} + +func TestEvaluateClientPropagatesSecurityNote(t *testing.T) { + const note = "peer credentials unavailable; private socket path checks enforced" + + t.Run("wrong project", func(t *testing.T) { + server := taskProtocolServer(t, compatibleCapabilities(), http.StatusNotFound) + client := newLoopbackClient(t, server.URL, "note-test-key", nil) + client.securityNote = note + + status := evaluateClient(context.Background(), client, "missing-project") + + assert.Equal(t, StateWrongProject, status.State) + assert.Equal(t, note, status.SecurityNote) + }) + + t.Run("post-discovery authentication error", func(t *testing.T) { + server := capabilityServer(t, Capabilities{}, http.StatusUnauthorized) + client := newLoopbackClient(t, server.URL, "note-test-key", nil) + client.securityNote = note + + status := evaluateClient(context.Background(), client, "test-project") + + assert.Equal(t, StateAuthenticationRequired, status.State) + assert.Equal(t, note, status.SecurityNote) + }) +} + +func TestPlatformSecurityLimitStatusContext(t *testing.T) { + t.Run("descriptor discovery", func(t *testing.T) { + assertions := assert.New(t) + descriptorPath := writeDescriptor(t, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-test", + Endpoint: "http://127.0.0.1:32145", + }, 0o600) + status := Evaluate(context.Background(), IntegrationConfig{ + Enabled: true, + APIKey: "must-not-leak-test-key", + DefaultProject: "test-project", + DescriptorPath: descriptorPath, + platformSecurityCheck: func() error { + return ErrDescriptorFileSecurityLimit + }, + }) + + assertions.Equal(StateIncompatible, status.State) + assertions.Contains(status.Message, "descriptor") + assertions.Contains(status.SecurityNote, "ownership") + assertions.Contains(status.SecurityNote, "no-follow") + assertions.NotContains(status.Message+status.SecurityNote, "Unix socket") + assertions.NotContains(status.Message+status.SecurityNote, "must-not-leak-test-key") + }) + + t.Run("explicit Unix socket", func(t *testing.T) { + assertions := assert.New(t) + status := statusForError(ErrUnixSocketSecurityLimit, "test-project") + + assertions.Equal(StateIncompatible, status.State) + assertions.Contains(status.Message, "Unix socket") + assertions.Contains(status.SecurityNote, "peer credentials") + assertions.NotContains(status.Message+status.SecurityNote, "descriptor") + }) +} diff --git a/internal/taskclient/client.go b/internal/taskclient/client.go new file mode 100644 index 000000000..0299e2cb8 --- /dev/null +++ b/internal/taskclient/client.go @@ -0,0 +1,521 @@ +// Package taskclient implements the narrow, provider-neutral protocol used by +// the optional server-side task integration. +package taskclient + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "net/url" + "slices" + "strconv" + "strings" + "time" +) + +const ( + // ProtocolVersion is the only task-integration protocol understood by this + // client. Descriptor and capability responses must match it exactly. + ProtocolVersion = "1" + + DefaultRequestTimeout = 5 * time.Second + DefaultMaxResponseSize = int64(1 << 20) +) + +var ( + ErrAuthenticationRequired = errors.New("task integration authentication required") + ErrConflict = errors.New("task integration revision conflict") + ErrIdempotencyKeyRequired = errors.New("task integration idempotency key required") + ErrIncompatible = errors.New("task integration incompatible") + ErrInsecureDescriptor = errors.New("task integration descriptor is insecure") + ErrInsecureEndpoint = errors.New("task integration endpoint is insecure") + ErrInvalidResponse = errors.New("task integration response is invalid") + ErrNotFound = errors.New("task integration not found") + ErrPlatformSecurityLimit = errors.New("task integration platform security limitation") + ErrDescriptorFileSecurityLimit = fmt.Errorf( + "descriptor file security unavailable: %w", ErrPlatformSecurityLimit, + ) + ErrUnixSocketSecurityLimit = fmt.Errorf( + "unix socket security unavailable: %w", ErrPlatformSecurityLimit, + ) + ErrRedirect = errors.New("task integration redirect rejected") + ErrRequestRejected = errors.New("task integration request rejected") + ErrResponseTooLarge = errors.New("task integration response is too large") + ErrRevisionRequired = errors.New("task integration revision required") + ErrUnreachable = errors.New("task integration unreachable") + ErrWrongProject = errors.New("task integration project mismatch") +) + +type EndpointKind string + +const ( + EndpointHTTPS EndpointKind = "https" + EndpointLoopbackHTTP EndpointKind = "loopback_http" + EndpointUnix EndpointKind = "unix" +) + +type ClientOptions struct { + Endpoint string + APIKey string + Timeout time.Duration + MaxResponseBytes int64 + HTTPClient *http.Client +} + +// Client is a bounded protocol client. Credentials remain private to this +// server-side object and are never exposed by its public accessors. +type Client struct { + baseURL *url.URL + apiKey string + endpointKind EndpointKind + instanceID string + securityNote string + httpClient *http.Client + maxResponseBytes int64 +} + +type httpStatusError struct { + statusCode int + classification error +} + +func (e *httpStatusError) Error() string { + return fmt.Sprintf("%s: HTTP status %d", e.classification, e.statusCode) +} + +func (e *httpStatusError) Unwrap() error { return e.classification } + +type Project struct { + ID string `json:"id"` + Name string `json:"name"` + Revision string `json:"revision,omitempty"` +} + +type Task struct { + ID string `json:"id"` + Project string `json:"project"` + Title string `json:"title"` + Notes string `json:"notes,omitempty"` + Revision string `json:"revision"` + Metadata map[string]any `json:"metadata,omitempty"` +} + +type TaskCreate struct { + Title string `json:"title"` + Notes string `json:"notes,omitempty"` + Priority string `json:"priority,omitempty"` + Labels []string `json:"labels,omitempty"` + Metadata map[string]any `json:"metadata,omitempty"` +} + +type TaskList struct { + Tasks []Task `json:"tasks"` + NextCursor string `json:"next_cursor,omitempty"` +} + +type metadataMutation struct { + Metadata map[string]any `json:"metadata"` +} + +func New(options ClientOptions) (*Client, error) { + endpoint := strings.TrimSpace(options.Endpoint) + u, kind, socketPath, err := validateEndpoint(endpoint) + if err != nil { + return nil, err + } + key := strings.TrimSpace(options.APIKey) + if kind == EndpointLoopbackHTTP && key == "" { + return nil, ErrAuthenticationRequired + } + + timeout := options.Timeout + if timeout <= 0 { + timeout = DefaultRequestTimeout + } + maxResponseBytes := options.MaxResponseBytes + if maxResponseBytes <= 0 { + maxResponseBytes = DefaultMaxResponseSize + } + client := http.Client{Timeout: timeout} + if options.HTTPClient != nil { + client = *options.HTTPClient + client.Timeout = timeout + } + client.CheckRedirect = func(_ *http.Request, _ []*http.Request) error { + return ErrRedirect + } + + securityNote := "" + if kind == EndpointUnix { + expectedOwner := currentUserID() + if err := validateSecureSocket(socketPath, expectedOwner); err != nil { + return nil, err + } + if !peerCredentialsSupported() { + securityNote = "peer credentials unavailable on this platform; socket path and parent ownership/mode enforced" + } + dialer := &net.Dialer{Timeout: timeout} + client.Transport = &http.Transport{ + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + conn, err := dialer.DialContext(ctx, "unix", socketPath) + if err != nil { + return nil, fmt.Errorf("%w: connect Unix socket", ErrUnreachable) + } + if err := verifyPeerCredentials(conn, expectedOwner); err != nil { + _ = conn.Close() + return nil, err + } + return conn, nil + }, + } + } + + return &Client{ + baseURL: u, + apiKey: key, + endpointKind: kind, + securityNote: securityNote, + httpClient: &client, + maxResponseBytes: maxResponseBytes, + }, nil +} + +func (c *Client) EndpointKind() EndpointKind { return c.endpointKind } +func (c *Client) InstanceID() string { return c.instanceID } +func (c *Client) SecurityNote() string { return c.securityNote } +func (c *Client) HasAuthentication() bool { return c.apiKey != "" || c.endpointKind == EndpointUnix } + +func (c *Client) Capabilities(ctx context.Context) (Capabilities, error) { + var result Capabilities + if err := c.doJSON(ctx, http.MethodGet, "/api/v1/capabilities", nil, nil, &result, http.StatusOK); err != nil { + var statusErr *httpStatusError + if errors.As(err, &statusErr) { + return Capabilities{}, &httpStatusError{ + statusCode: statusErr.statusCode, + classification: classifyCapabilityHTTPStatus(statusErr.statusCode), + } + } + return Capabilities{}, err + } + if result.ProtocolVersion == "" { + return Capabilities{}, ErrInvalidResponse + } + return result, nil +} + +func (c *Client) ResolveProject(ctx context.Context, project string) (Project, error) { + if err := validatePathSegment(project); err != nil { + return Project{}, err + } + var result Project + headers, err := c.doJSONWithHeaders(ctx, http.MethodGet, "/api/v1/projects/"+url.PathEscape(project), nil, nil, &result, http.StatusOK) + if err != nil { + return Project{}, err + } + if result.ID == "" || result.Name == "" { + return Project{}, ErrInvalidResponse + } + if result.Revision == "" { + result.Revision = headers.Get("ETag") + } + if result.Revision == "" { + return Project{}, ErrInvalidResponse + } + if result.Name != project && result.ID != project { + return Project{}, ErrWrongProject + } + return result, nil +} + +func (c *Client) CreateTask(ctx context.Context, project, idempotencyKey string, create TaskCreate) (Task, error) { + if err := validatePathSegment(project); err != nil { + return Task{}, err + } + if strings.TrimSpace(idempotencyKey) == "" { + return Task{}, ErrIdempotencyKeyRequired + } + if strings.TrimSpace(create.Title) == "" { + return Task{}, fmt.Errorf("%w: task title is required", ErrInvalidResponse) + } + headers := http.Header{"Idempotency-Key": []string{idempotencyKey}} + var result Task + responseHeaders, err := c.doJSONWithHeaders(ctx, http.MethodPost, "/api/v1/projects/"+url.PathEscape(project)+"/tasks", create, headers, &result, http.StatusOK, http.StatusCreated) + if err != nil { + return Task{}, err + } + fillRevisionFromETag(&result, responseHeaders) + return validateTask(result, project, "") +} + +func (c *Client) SearchTasks(ctx context.Context, project, query string, limit int) (TaskList, error) { + values := make(url.Values) + values.Set("q", query) + values.Set("limit", strconv.Itoa(boundedLimit(limit))) + return c.listTasks(ctx, project, values) +} + +func (c *Client) ListTasks(ctx context.Context, project string, limit int, cursor string) (TaskList, error) { + values := make(url.Values) + values.Set("limit", strconv.Itoa(boundedLimit(limit))) + if cursor != "" { + values.Set("cursor", cursor) + } + return c.listTasks(ctx, project, values) +} + +func (c *Client) listTasks(ctx context.Context, project string, query url.Values) (TaskList, error) { + if err := validatePathSegment(project); err != nil { + return TaskList{}, err + } + path := "/api/v1/projects/" + url.PathEscape(project) + "/tasks?" + query.Encode() + var result TaskList + if err := c.doJSON(ctx, http.MethodGet, path, nil, nil, &result, http.StatusOK); err != nil { + return TaskList{}, err + } + for i := range result.Tasks { + validated, err := validateTask(result.Tasks[i], project, "") + if err != nil { + return TaskList{}, err + } + result.Tasks[i] = validated + } + return result, nil +} + +func (c *Client) GetTask(ctx context.Context, project, taskID string) (Task, error) { + if err := validatePathSegment(project); err != nil { + return Task{}, err + } + if err := validatePathSegment(taskID); err != nil { + return Task{}, err + } + var result Task + path := "/api/v1/projects/" + url.PathEscape(project) + "/tasks/" + url.PathEscape(taskID) + headers, err := c.doJSONWithHeaders(ctx, http.MethodGet, path, nil, nil, &result, http.StatusOK) + if err != nil { + return Task{}, err + } + fillRevisionFromETag(&result, headers) + return validateTask(result, project, taskID) +} + +func (c *Client) MutateMetadata(ctx context.Context, project, taskID, revision string, metadata map[string]any) (Task, error) { + if err := validatePathSegment(project); err != nil { + return Task{}, err + } + if err := validatePathSegment(taskID); err != nil { + return Task{}, err + } + if strings.TrimSpace(revision) == "" { + return Task{}, ErrRevisionRequired + } + headers := http.Header{"If-Match": []string{revision}} + path := "/api/v1/projects/" + url.PathEscape(project) + "/tasks/" + url.PathEscape(taskID) + "/metadata" + var result Task + responseHeaders, err := c.doJSONWithHeaders(ctx, http.MethodPatch, path, metadataMutation{Metadata: metadata}, headers, &result, http.StatusOK) + if err != nil { + return Task{}, err + } + fillRevisionFromETag(&result, responseHeaders) + return validateTask(result, project, taskID) +} + +func (c *Client) doJSON(ctx context.Context, method, path string, requestBody any, headers http.Header, result any, successStatuses ...int) error { + _, err := c.doJSONWithHeaders(ctx, method, path, requestBody, headers, result, successStatuses...) + return err +} + +func (c *Client) doJSONWithHeaders(ctx context.Context, method, path string, requestBody any, headers http.Header, result any, successStatuses ...int) (http.Header, error) { + target := *c.baseURL + parts := strings.SplitN(path, "?", 2) + target.Path = strings.TrimRight(target.Path, "/") + parts[0] + if len(parts) == 2 { + target.RawQuery = parts[1] + } + var body io.Reader + if requestBody != nil { + encoded, err := json.Marshal(requestBody) + if err != nil { + return nil, fmt.Errorf("encode task integration request: %w", err) + } + body = bytes.NewReader(encoded) + } + req, err := http.NewRequestWithContext(ctx, method, target.String(), body) + if err != nil { + return nil, fmt.Errorf("build task integration request: %w", err) + } + if requestBody != nil { + req.Header.Set("Content-Type", "application/json") + } + req.Header.Set("Accept", "application/json") + if c.apiKey != "" { + req.Header.Set("Authorization", "Bearer "+c.apiKey) + } + for name, values := range headers { + for _, value := range values { + req.Header.Add(name, value) + } + } + + resp, err := c.httpClient.Do(req) + if err != nil { + if errors.Is(err, ErrRedirect) { + return nil, ErrRedirect + } + return nil, fmt.Errorf("%w: request failed", ErrUnreachable) + } + defer func() { _ = resp.Body.Close() }() + if !containsStatus(successStatuses, resp.StatusCode) { + return nil, &httpStatusError{ + statusCode: resp.StatusCode, + classification: classifyHTTPStatus(resp.StatusCode), + } + } + data, err := readBounded(resp.Body, c.maxResponseBytes) + if err != nil { + return nil, err + } + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(result); err != nil { + return nil, fmt.Errorf("%w: decode response", ErrInvalidResponse) + } + if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + return nil, fmt.Errorf("%w: trailing response data", ErrInvalidResponse) + } + return resp.Header.Clone(), nil +} + +func classifyCapabilityHTTPStatus(statusCode int) error { + if statusCode == http.StatusUnauthorized || statusCode == http.StatusForbidden { + return ErrAuthenticationRequired + } + if isTransientHTTPStatus(statusCode) { + return ErrUnreachable + } + return ErrIncompatible +} + +func classifyHTTPStatus(statusCode int) error { + switch statusCode { + case http.StatusUnauthorized, http.StatusForbidden: + return ErrAuthenticationRequired + case http.StatusNotFound: + return ErrNotFound + case http.StatusConflict, http.StatusPreconditionFailed: + return ErrConflict + case http.StatusBadRequest, http.StatusNotAcceptable, + http.StatusUnsupportedMediaType, http.StatusUnprocessableEntity: + return ErrRequestRejected + default: + if isTransientHTTPStatus(statusCode) { + return ErrUnreachable + } + return ErrIncompatible + } +} + +func isTransientHTTPStatus(statusCode int) bool { + if statusCode == http.StatusRequestTimeout || + statusCode == http.StatusTooEarly || + statusCode == http.StatusTooManyRequests { + return true + } + return statusCode >= http.StatusInternalServerError && + statusCode <= 599 && + statusCode != http.StatusNotImplemented +} + +func fillRevisionFromETag(task *Task, headers http.Header) { + if task.Revision == "" { + task.Revision = headers.Get("ETag") + } +} + +func readBounded(reader io.Reader, maximum int64) ([]byte, error) { + data, err := io.ReadAll(io.LimitReader(reader, maximum+1)) + if err != nil { + return nil, fmt.Errorf("%w: read response", ErrUnreachable) + } + if int64(len(data)) > maximum { + return nil, ErrResponseTooLarge + } + return data, nil +} + +func validateEndpoint(endpoint string) (*url.URL, EndpointKind, string, error) { + if endpoint == "" { + return nil, "", "", fmt.Errorf("%w: endpoint is empty", ErrInsecureEndpoint) + } + u, err := url.Parse(endpoint) + if err != nil || u.Scheme == "" || u.User != nil || u.Fragment != "" || u.RawQuery != "" { + return nil, "", "", ErrInsecureEndpoint + } + switch strings.ToLower(u.Scheme) { + case "https": + if u.Host == "" { + return nil, "", "", ErrInsecureEndpoint + } + return u, EndpointHTTPS, "", nil + case "http": + if u.Host == "" || !isLoopbackHost(u.Hostname()) { + return nil, "", "", ErrInsecureEndpoint + } + return u, EndpointLoopbackHTTP, "", nil + case "unix": + if u.Host != "" || !strings.HasPrefix(u.Path, "/") { + return nil, "", "", ErrInsecureEndpoint + } + socketPath := u.Path + return &url.URL{Scheme: "http", Host: "unix"}, EndpointUnix, socketPath, nil + default: + return nil, "", "", ErrInsecureEndpoint + } +} + +func isLoopbackHost(host string) bool { + if strings.EqualFold(host, "localhost") { + return true + } + ip := net.ParseIP(host) + return ip != nil && ip.IsLoopback() +} + +func validatePathSegment(value string) error { + if value == "" || value == "." || value == ".." || strings.ContainsAny(value, "/\\?#") { + return fmt.Errorf("%w: invalid path identity", ErrInvalidResponse) + } + return nil +} + +func validateTask(task Task, expectedProject, expectedID string) (Task, error) { + if task.ID == "" || (expectedID != "" && task.ID != expectedID) { + return Task{}, ErrInvalidResponse + } + if task.Project == "" || task.Project != expectedProject { + return Task{}, ErrInvalidResponse + } + if task.Revision == "" { + return Task{}, ErrInvalidResponse + } + return task, nil +} + +func boundedLimit(limit int) int { + if limit < 1 { + return 100 + } + if limit > 500 { + return 500 + } + return limit +} + +func containsStatus(statuses []int, candidate int) bool { + return slices.Contains(statuses, candidate) +} diff --git a/internal/taskclient/client_test.go b/internal/taskclient/client_test.go new file mode 100644 index 000000000..3ecda170c --- /dev/null +++ b/internal/taskclient/client_test.go @@ -0,0 +1,244 @@ +package taskclient + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestClientProtocolOperations(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + type observedRequest struct { + Method string + Path string + Query string + Authorization string + IdempotencyKey string + IfMatch string + } + requests := make(chan observedRequest, 8) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + requests <- observedRequest{ + Method: r.Method, + Path: r.URL.Path, + Query: r.URL.RawQuery, + Authorization: r.Header.Get("Authorization"), + IdempotencyKey: r.Header.Get("Idempotency-Key"), + IfMatch: r.Header.Get("If-Match"), + } + switch { + case r.Method == http.MethodGet && r.URL.Path == "/api/v1/projects/test-project": + w.Header().Set("ETag", `"project-revision"`) + writeTestJSON(t, w, Project{ID: "project-test", Name: "test-project"}) + case r.Method == http.MethodPost && r.URL.Path == "/api/v1/projects/test-project/tasks": + var create TaskCreate + assertions.NoError(json.NewDecoder(r.Body).Decode(&create)) + assertions.Equal("Synthetic task", create.Title) + w.WriteHeader(http.StatusCreated) + writeTestJSON(t, w, Task{ID: "task-test", Project: "test-project", Title: create.Title, Revision: `"r1"`}) + case r.Method == http.MethodGet && r.URL.Path == "/api/v1/projects/test-project/tasks": + writeTestJSON(t, w, TaskList{Tasks: []Task{{ID: "task-test", Project: "test-project", Title: "Synthetic task", Revision: `"r1"`}}}) + case r.Method == http.MethodGet && r.URL.Path == "/api/v1/projects/test-project/tasks/task-test": + w.Header().Set("ETag", `"r1"`) + writeTestJSON(t, w, Task{ID: "task-test", Project: "test-project", Title: "Synthetic task", Revision: `"r1"`}) + case r.Method == http.MethodPatch && r.URL.Path == "/api/v1/projects/test-project/tasks/task-test/metadata": + w.Header().Set("ETag", `"r2"`) + writeTestJSON(t, w, Task{ID: "task-test", Project: "test-project", Title: "Synthetic task", Revision: `"r2"`}) + default: + http.NotFound(w, r) + } + })) + t.Cleanup(server.Close) + client := newLoopbackClient(t, server.URL, "operation-test-key", nil) + ctx := context.Background() + + project, err := client.ResolveProject(ctx, "test-project") + requirements.NoError(err) + assertions.Equal("project-test", project.ID) + request := <-requests + assertions.Equal("Bearer operation-test-key", request.Authorization) + + created, err := client.CreateTask(ctx, "test-project", "retry-stable-key", TaskCreate{Title: "Synthetic task"}) + requirements.NoError(err) + assertions.Equal("task-test", created.ID) + request = <-requests + assertions.Equal("retry-stable-key", request.IdempotencyKey) + + searched, err := client.SearchTasks(ctx, "test-project", "Synthetic", 25) + requirements.NoError(err) + assertions.Len(searched.Tasks, 1) + request = <-requests + assertions.Contains(request.Query, "q=Synthetic") + assertions.Contains(request.Query, "limit=25") + + listed, err := client.ListTasks(ctx, "test-project", 10, "cursor-test") + requirements.NoError(err) + assertions.Len(listed.Tasks, 1) + request = <-requests + assertions.Contains(request.Query, "limit=10") + assertions.Contains(request.Query, "cursor=cursor-test") + + read, err := client.GetTask(ctx, "test-project", "task-test") + requirements.NoError(err) + assertions.Equal(`"r1"`, read.Revision) + <-requests + + mutated, err := client.MutateMetadata(ctx, "test-project", "task-test", `"r1"`, map[string]any{"mail_links": []any{}}) + requirements.NoError(err) + assertions.Equal(`"r2"`, mutated.Revision) + request = <-requests + assertions.Equal(`"r1"`, request.IfMatch) +} + +func TestClientRequiresMutationSafetyHeaders(t *testing.T) { + client, err := New(ClientOptions{Endpoint: "https://tasks.example.com", APIKey: "test-key"}) + require.NoError(t, err) + + _, err = client.CreateTask(context.Background(), "test-project", "", TaskCreate{Title: "Synthetic task"}) + require.ErrorIs(t, err, ErrIdempotencyKeyRequired) + + _, err = client.MutateMetadata(context.Background(), "test-project", "task-test", "", map[string]any{}) + require.ErrorIs(t, err, ErrRevisionRequired) +} + +func TestClientValidatesResponseShapesAndAuthentication(t *testing.T) { + t.Run("authentication response", func(t *testing.T) { + server := capabilityServer(t, Capabilities{}, http.StatusUnauthorized) + client := newLoopbackClient(t, server.URL, "rejected-test-key", nil) + + _, err := client.Capabilities(context.Background()) + + assert.ErrorIs(t, err, ErrAuthenticationRequired) + }) + + t.Run("missing task identity", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + writeTestJSON(t, w, Task{Title: "Missing identity"}) + })) + t.Cleanup(server.Close) + client := newLoopbackClient(t, server.URL, "shape-test-key", nil) + + _, err := client.GetTask(context.Background(), "test-project", "task-test") + + assert.ErrorIs(t, err, ErrInvalidResponse) + }) +} + +func TestClientUsesETagAsTaskRevision(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("ETag", `"etag-revision"`) + writeTestJSON(t, w, Task{ID: "task-test", Project: "test-project", Title: "Synthetic task"}) + })) + t.Cleanup(server.Close) + client := newLoopbackClient(t, server.URL, "etag-test-key", nil) + + task, err := client.GetTask(context.Background(), "test-project", "task-test") + + require.NoError(t, err) + assert.Equal(t, `"etag-revision"`, task.Revision) +} + +func TestCapabilitiesClassifyReachedHTTPResponses(t *testing.T) { + tests := []struct { + name string + statusCode int + want error + }{ + {name: "no content", statusCode: http.StatusNoContent, want: ErrIncompatible}, + {name: "non-enumerated client response", statusCode: http.StatusTeapot, want: ErrIncompatible}, + {name: "method not allowed", statusCode: http.StatusMethodNotAllowed, want: ErrIncompatible}, + {name: "not implemented", statusCode: http.StatusNotImplemented, want: ErrIncompatible}, + {name: "conflict", statusCode: http.StatusConflict, want: ErrIncompatible}, + {name: "precondition failed", statusCode: http.StatusPreconditionFailed, want: ErrIncompatible}, + {name: "unauthorized", statusCode: http.StatusUnauthorized, want: ErrAuthenticationRequired}, + {name: "forbidden", statusCode: http.StatusForbidden, want: ErrAuthenticationRequired}, + {name: "internal server error", statusCode: http.StatusInternalServerError, want: ErrUnreachable}, + {name: "service unavailable", statusCode: http.StatusServiceUnavailable, want: ErrUnreachable}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + server := capabilityServer(t, Capabilities{}, test.statusCode) + client := newLoopbackClient(t, server.URL, "protocol-test-key", nil) + + _, err := client.Capabilities(context.Background()) + + require.ErrorIs(t, err, test.want) + }) + } + + t.Run("unreachable network", func(t *testing.T) { + server := capabilityServer(t, compatibleCapabilities(), http.StatusOK) + endpoint := server.URL + server.Close() + client := newLoopbackClient(t, endpoint, "protocol-test-key", nil) + + _, err := client.Capabilities(context.Background()) + + require.ErrorIs(t, err, ErrUnreachable) + }) +} + +func TestTaskOperationsPreserveConflictResponses(t *testing.T) { + for _, statusCode := range []int{http.StatusConflict, http.StatusPreconditionFailed} { + t.Run(http.StatusText(statusCode), func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(statusCode) + })) + t.Cleanup(server.Close) + client := newLoopbackClient(t, server.URL, "conflict-test-key", nil) + + _, err := client.MutateMetadata(context.Background(), "test-project", "task-test", `"r1"`, map[string]any{}) + + require.ErrorIs(t, err, ErrConflict) + }) + } +} + +func TestTaskOperationsClassifyValidationResponsesAsRequestRejections(t *testing.T) { + for _, statusCode := range []int{http.StatusBadRequest, http.StatusUnprocessableEntity} { + t.Run(http.StatusText(statusCode), func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(statusCode) + })) + t.Cleanup(server.Close) + client := newLoopbackClient(t, server.URL, "validation-test-key", nil) + + _, err := client.CreateTask(context.Background(), "test-project", "request-id", TaskCreate{Title: "Synthetic task"}) + + require.ErrorIs(t, err, ErrRequestRejected) + assert.NotErrorIs(t, err, ErrIncompatible) + }) + } +} + +func TestResolveProjectBindsConfiguredIdentityAndRevision(t *testing.T) { + t.Run("unrelated project", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + writeTestJSON(t, w, Project{ID: "project-other-id", Name: "other-project", Revision: `"p1"`}) + })) + t.Cleanup(server.Close) + client := newLoopbackClient(t, server.URL, "project-test-key", nil) + + _, err := client.ResolveProject(context.Background(), "test-project") + + require.ErrorIs(t, err, ErrWrongProject) + }) + + t.Run("missing revision", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + writeTestJSON(t, w, Project{ID: "project-test-id", Name: "test-project"}) + })) + t.Cleanup(server.Close) + client := newLoopbackClient(t, server.URL, "project-test-key", nil) + + _, err := client.ResolveProject(context.Background(), "test-project") + + require.ErrorIs(t, err, ErrInvalidResponse) + }) +} diff --git a/internal/taskclient/discovery.go b/internal/taskclient/discovery.go new file mode 100644 index 000000000..786fc8b7f --- /dev/null +++ b/internal/taskclient/discovery.go @@ -0,0 +1,162 @@ +package taskclient + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + "time" +) + +const ( + maxDescriptorBytes = int64(64 << 10) + maxTokenBytes = int64(16 << 10) +) + +type descriptor struct { + ProtocolVersion string `json:"protocol_version"` + InstanceID string `json:"instance_id"` + Endpoint string `json:"endpoint"` + TokenFile string `json:"token_file,omitempty"` +} + +type DiscoveryOptions struct { + DescriptorPath string + APIKey string + Timeout time.Duration + MaxResponseBytes int64 + HTTPClient *http.Client + // platformSecurityCheck is a package-local test seam. Production callers + // always use descriptorFileSecurityCheck. + platformSecurityCheck func() error +} + +func Discover(_ context.Context, options DiscoveryOptions) (*Client, error) { + path := strings.TrimSpace(options.DescriptorPath) + if path == "" { + path = DefaultDescriptorPath() + } + if _, err := os.Lstat(path); errors.Is(err, os.ErrNotExist) { + return nil, ErrNotFound + } + securityCheck := options.platformSecurityCheck + if securityCheck == nil { + securityCheck = descriptorFileSecurityCheck + } + if err := securityCheck(); err != nil { + return nil, err + } + expectedOwner := currentUserID() + data, err := readSecureRegularFile(path, expectedOwner, maxDescriptorBytes) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, ErrNotFound + } + return nil, err + } + var value descriptor + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&value); err != nil { + return nil, fmt.Errorf("%w: malformed descriptor", ErrIncompatible) + } + if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) { + return nil, fmt.Errorf("%w: trailing descriptor data", ErrIncompatible) + } + if value.ProtocolVersion != ProtocolVersion || strings.TrimSpace(value.InstanceID) == "" { + return nil, fmt.Errorf("%w: unsupported descriptor protocol", ErrIncompatible) + } + + apiKey := strings.TrimSpace(options.APIKey) + _, kind, _, err := validateEndpoint(strings.TrimSpace(value.Endpoint)) + if err != nil { + return nil, err + } + if kind == EndpointLoopbackHTTP && apiKey == "" && value.TokenFile != "" { + if !filepath.IsAbs(value.TokenFile) { + return nil, fmt.Errorf("%w: token file must be absolute", ErrInsecureDescriptor) + } + token, tokenErr := readSecureRegularFile(value.TokenFile, expectedOwner, maxTokenBytes) + if tokenErr != nil { + return nil, tokenErr + } + apiKey = strings.TrimSpace(string(token)) + } + if kind == EndpointLoopbackHTTP && apiKey == "" { + return nil, ErrAuthenticationRequired + } + client, err := New(ClientOptions{ + Endpoint: value.Endpoint, + APIKey: apiKey, + Timeout: options.Timeout, + MaxResponseBytes: options.MaxResponseBytes, + HTTPClient: options.HTTPClient, + }) + if err != nil { + return nil, err + } + client.instanceID = value.InstanceID + return client, nil +} + +func DefaultDescriptorPath() string { + if runtimeDir := strings.TrimSpace(os.Getenv("XDG_RUNTIME_DIR")); runtimeDir != "" && filepath.IsAbs(runtimeDir) { + return filepath.Join(runtimeDir, "msgvault", "task-integration.json") + } + return filepath.Join(os.TempDir(), fmt.Sprintf("msgvault-%d", currentUserID()), "task-integration.json") +} + +func validateSecureRegularFile(path string, expectedOwner uint32) error { + file, err := openSecureRegularFile(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return os.ErrNotExist + } + return fmt.Errorf("%w: open secure file", ErrInsecureDescriptor) + } + defer func() { _ = file.Close() }() + info, err := file.Stat() + if err != nil { + return fmt.Errorf("%w: inspect secure file", ErrInsecureDescriptor) + } + return validateSecureFileInfo(info, expectedOwner) +} + +func readSecureRegularFile(path string, expectedOwner uint32, maximum int64) ([]byte, error) { + file, err := openSecureRegularFile(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, os.ErrNotExist + } + return nil, fmt.Errorf("%w: open secure file", ErrInsecureDescriptor) + } + defer func() { _ = file.Close() }() + info, err := file.Stat() + if err != nil { + return nil, fmt.Errorf("%w: inspect secure file", ErrInsecureDescriptor) + } + if err := validateSecureFileInfo(info, expectedOwner); err != nil { + return nil, err + } + return readBounded(file, maximum) +} + +func validateSecureFileInfo(info os.FileInfo, expectedOwner uint32) error { + if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 { + return fmt.Errorf("%w: file must be regular and non-symlinked", ErrInsecureDescriptor) + } + if info.Mode().Perm()&0o077 != 0 { + return fmt.Errorf("%w: file permissions must deny group and other access", ErrInsecureDescriptor) + } + owner, err := fileInfoOwnerID(info) + if err != nil || owner != expectedOwner { + return fmt.Errorf("%w: file owner does not match daemon user", ErrInsecureDescriptor) + } + return nil +} diff --git a/internal/taskclient/discovery_posix.go b/internal/taskclient/discovery_posix.go new file mode 100644 index 000000000..3c7c075a9 --- /dev/null +++ b/internal/taskclient/discovery_posix.go @@ -0,0 +1,71 @@ +//go:build !windows + +package taskclient + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "syscall" + + "golang.org/x/sys/unix" +) + +func currentUserID() uint32 { + // Unix effective user IDs are represented by the kernel as unsigned + // 32-bit values even though os.Geteuid exposes an int. + return uint32(os.Geteuid()) // #nosec G115 -- kernel UID width is uint32 +} + +func descriptorFileSecurityCheck() error { return nil } + +func fileOwnerID(path string) (uint32, error) { + info, err := os.Lstat(path) + if err != nil { + return 0, err + } + return fileInfoOwnerID(info) +} + +func fileInfoOwnerID(info os.FileInfo) (uint32, error) { + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return 0, errors.New("file owner unavailable") + } + return stat.Uid, nil +} + +func openSecureRegularFile(path string) (*os.File, error) { + fd, err := unix.Open(path, unix.O_RDONLY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0) + if err != nil { + return nil, fmt.Errorf("open secure file without following links: %w", err) + } + return os.NewFile(uintptr(fd), filepath.Base(path)), nil +} + +func validateSecureSocket(path string, expectedOwner uint32) error { + info, err := os.Lstat(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("%w: Unix socket is missing", ErrUnreachable) + } + return fmt.Errorf("%w: inspect Unix socket", ErrInsecureEndpoint) + } + if info.Mode()&os.ModeSymlink != 0 || info.Mode()&os.ModeSocket == 0 || info.Mode().Perm()&0o077 != 0 { + return fmt.Errorf("%w: Unix socket must be non-symlinked and private", ErrInsecureEndpoint) + } + owner, err := fileInfoOwnerID(info) + if err != nil || owner != expectedOwner { + return fmt.Errorf("%w: Unix socket owner mismatch", ErrInsecureEndpoint) + } + parent, err := os.Lstat(filepath.Dir(path)) + if err != nil || parent.Mode()&os.ModeSymlink != 0 || !parent.IsDir() || parent.Mode().Perm()&0o077 != 0 { + return fmt.Errorf("%w: Unix socket parent must be a private non-symlinked directory", ErrInsecureEndpoint) + } + parentOwner, err := fileInfoOwnerID(parent) + if err != nil || parentOwner != expectedOwner { + return fmt.Errorf("%w: Unix socket parent owner mismatch", ErrInsecureEndpoint) + } + return nil +} diff --git a/internal/taskclient/discovery_test.go b/internal/taskclient/discovery_test.go new file mode 100644 index 000000000..a99b6ace4 --- /dev/null +++ b/internal/taskclient/discovery_test.go @@ -0,0 +1,341 @@ +package taskclient + +import ( + "context" + "encoding/json" + "net" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDiscoverDescriptorSecurityAndProtocol(t *testing.T) { + t.Run("regular descriptor", func(t *testing.T) { + path := writeDescriptor(t, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-test", + Endpoint: "http://127.0.0.1:32145", + }, 0o600) + + client, err := Discover(context.Background(), DiscoveryOptions{ + DescriptorPath: path, + APIKey: "configured-test-key", + }) + + require.NoError(t, err) + assert.Equal(t, "instance-test", client.InstanceID()) + assert.Equal(t, EndpointLoopbackHTTP, client.EndpointKind()) + }) + + t.Run("symlink", func(t *testing.T) { + target := writeDescriptor(t, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-test", + Endpoint: "http://127.0.0.1:32145", + }, 0o600) + link := filepath.Join(t.TempDir(), "descriptor.json") + require.NoError(t, os.Symlink(target, link)) + + _, err := Discover(context.Background(), DiscoveryOptions{DescriptorPath: link, APIKey: "test-key"}) + + assert.ErrorIs(t, err, ErrInsecureDescriptor) + }) + + t.Run("wrong owner", func(t *testing.T) { + path := writeDescriptor(t, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-test", + Endpoint: "http://127.0.0.1:32145", + }, 0o600) + owner, err := fileOwnerID(path) + require.NoError(t, err) + + err = validateSecureRegularFile(path, owner+1) + + assert.ErrorIs(t, err, ErrInsecureDescriptor) + }) + + t.Run("permissive mode", func(t *testing.T) { + path := writeDescriptor(t, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-test", + Endpoint: "http://127.0.0.1:32145", + }, 0o644) + + _, err := Discover(context.Background(), DiscoveryOptions{DescriptorPath: path, APIKey: "test-key"}) + + assert.ErrorIs(t, err, ErrInsecureDescriptor) + }) + + t.Run("malformed protocol", func(t *testing.T) { + path := writeDescriptor(t, descriptor{ + ProtocolVersion: "999", + InstanceID: "instance-test", + Endpoint: "http://127.0.0.1:32145", + }, 0o600) + + _, err := Discover(context.Background(), DiscoveryOptions{DescriptorPath: path, APIKey: "test-key"}) + + assert.ErrorIs(t, err, ErrIncompatible) + }) +} + +func TestDiscoverFailsClosedWithoutPlatformFileSecurity(t *testing.T) { + path := writeDescriptor(t, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-test", + Endpoint: "http://127.0.0.1:32145", + }, 0o600) + + _, err := Discover(context.Background(), DiscoveryOptions{ + DescriptorPath: path, + APIKey: "test-key", + platformSecurityCheck: func() error { + return ErrPlatformSecurityLimit + }, + }) + + require.ErrorIs(t, err, ErrPlatformSecurityLimit) +} + +func TestDiscoverReportsMissingDescriptorBeforePlatformSecurityLimit(t *testing.T) { + _, err := Discover(context.Background(), DiscoveryOptions{ + DescriptorPath: filepath.Join(t.TempDir(), "missing-descriptor.json"), + platformSecurityCheck: func() error { + return ErrDescriptorFileSecurityLimit + }, + }) + + require.ErrorIs(t, err, ErrNotFound) + assert.NotErrorIs(t, err, ErrPlatformSecurityLimit) +} + +func TestDiscoverUnixSocket(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + if runtime.GOOS == "windows" { + t.Skip("Unix sockets are validated by the Unix test lane") + } + dir := t.TempDir() + requirements.NoError(os.Chmod(dir, 0o700)) + socketPath := filepath.Join(dir, "task.sock") + listener, err := net.Listen("unix", socketPath) + requirements.NoError(err) + requirements.NoError(os.Chmod(socketPath, 0o600)) + server := &http.Server{ + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assertions.Equal("/api/v1/capabilities", r.URL.Path) + writeTestJSON(t, w, compatibleCapabilities()) + }), + ReadHeaderTimeout: time.Second, + } + serveErr := make(chan error, 1) + go func() { serveErr <- server.Serve(listener) }() + t.Cleanup(func() { + requirements.NoError(server.Close()) + assertions.ErrorIs(<-serveErr, http.ErrServerClosed) + }) + descriptorPath := filepath.Join(dir, "descriptor.json") + writeDescriptorAt(t, descriptorPath, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-unix", + Endpoint: "unix://" + socketPath, + }, 0o600) + + client, err := Discover(context.Background(), DiscoveryOptions{DescriptorPath: descriptorPath}) + + requirements.NoError(err) + assertions.Equal(EndpointUnix, client.EndpointKind()) + capabilities, err := client.Capabilities(context.Background()) + requirements.NoError(err) + assertions.True(capabilities.Compatible()) + if !peerCredentialsSupported() { + assertions.Contains(client.SecurityNote(), "peer credentials unavailable") + } +} + +func TestDiscoverStaleUnixSocketIsUnreachable(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("Unix socket discovery is covered by Unix test lanes") + } + descriptorPath := writeStaleUnixDescriptor(t) + + _, err := Discover(context.Background(), DiscoveryOptions{DescriptorPath: descriptorPath}) + + require.ErrorIs(t, err, ErrUnreachable) +} + +func TestDiscoverLoopbackAuthentication(t *testing.T) { + t.Run("configured key", func(t *testing.T) { + path := writeDescriptor(t, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-loopback", + Endpoint: "http://127.0.0.1:32145", + }, 0o600) + + client, err := Discover(context.Background(), DiscoveryOptions{DescriptorPath: path, APIKey: "configured-test-key"}) + + require.NoError(t, err) + assert.True(t, client.HasAuthentication()) + }) + + t.Run("secure token file reference", func(t *testing.T) { + dir := t.TempDir() + tokenPath := filepath.Join(dir, "token") + require.NoError(t, os.WriteFile(tokenPath, []byte("descriptor-test-token\n"), 0o600)) + path := filepath.Join(dir, "descriptor.json") + writeDescriptorAt(t, path, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-loopback", + Endpoint: "http://127.0.0.1:32145", + TokenFile: tokenPath, + }, 0o600) + + client, err := Discover(context.Background(), DiscoveryOptions{DescriptorPath: path}) + + require.NoError(t, err) + assert.True(t, client.HasAuthentication()) + }) + + t.Run("missing auth", func(t *testing.T) { + path := writeDescriptor(t, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-loopback", + Endpoint: "http://127.0.0.1:32145", + }, 0o600) + + _, err := Discover(context.Background(), DiscoveryOptions{DescriptorPath: path}) + + assert.ErrorIs(t, err, ErrAuthenticationRequired) + }) +} + +func TestExplicitEndpointPolicy(t *testing.T) { + t.Run("remote HTTP rejection", func(t *testing.T) { + _, err := New(ClientOptions{Endpoint: "http://tasks.example.com", APIKey: "test-key"}) + assert.ErrorIs(t, err, ErrInsecureEndpoint) + }) + + t.Run("HTTPS acceptance", func(t *testing.T) { + client, err := New(ClientOptions{Endpoint: "https://tasks.example.com", APIKey: "test-key"}) + require.NoError(t, err) + assert.Equal(t, EndpointHTTPS, client.EndpointKind()) + }) +} + +func TestClientTransportBounds(t *testing.T) { + capabilities := compatibleCapabilities() + + t.Run("redirect rejection without credential forwarding", func(t *testing.T) { + var receivedAuthorization string + target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedAuthorization = r.Header.Get("Authorization") + writeTestJSON(t, w, capabilities) + })) + t.Cleanup(target.Close) + origin := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + http.Redirect(w, httptest.NewRequest(http.MethodGet, target.URL, nil), target.URL, http.StatusFound) + })) + t.Cleanup(origin.Close) + client := newLoopbackClient(t, origin.URL, "redirect-test-key", nil) + + _, err := client.Capabilities(context.Background()) + + require.ErrorIs(t, err, ErrRedirect) + assert.Empty(t, receivedAuthorization) + }) + + t.Run("timeout", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + select { + case <-time.After(250 * time.Millisecond): + writeTestJSON(t, w, capabilities) + case <-t.Context().Done(): + } + })) + t.Cleanup(server.Close) + client := newLoopbackClient(t, server.URL, "timeout-test-key", func(options *ClientOptions) { + options.Timeout = 20 * time.Millisecond + }) + + _, err := client.Capabilities(context.Background()) + + assert.ErrorIs(t, err, ErrUnreachable) + }) + + t.Run("oversized body", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte(`{"protocol_version":"1","padding":"` + strings.Repeat("x", 256) + `"}`)) + assert.NoError(t, err) + })) + t.Cleanup(server.Close) + client := newLoopbackClient(t, server.URL, "body-test-key", func(options *ClientOptions) { + options.MaxResponseBytes = 64 + }) + + _, err := client.Capabilities(context.Background()) + + assert.ErrorIs(t, err, ErrResponseTooLarge) + }) +} + +func writeDescriptor(t *testing.T, value descriptor, mode os.FileMode) string { + t.Helper() + path := filepath.Join(t.TempDir(), "descriptor.json") + writeDescriptorAt(t, path, value, mode) + return path +} + +func writeStaleUnixDescriptor(t *testing.T) string { + t.Helper() + // t.TempDir includes the full test name and exceeds Darwin's Unix socket + // path limit for this nested subtest, so this fixture needs a short path. + dir, err := os.MkdirTemp("", "taskclient-stale-") //nolint:usetesting + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, os.RemoveAll(dir)) }) + require.NoError(t, os.Chmod(dir, 0o700)) + socketPath := filepath.Join(dir, "stale.sock") + listener, err := net.Listen("unix", socketPath) + require.NoError(t, err) + require.NoError(t, listener.Close()) + descriptorPath := filepath.Join(dir, "descriptor.json") + writeDescriptorAt(t, descriptorPath, descriptor{ + ProtocolVersion: ProtocolVersion, + InstanceID: "instance-stale", + Endpoint: "unix://" + socketPath, + }, 0o600) + return descriptorPath +} + +func writeDescriptorAt(t *testing.T, path string, value descriptor, mode os.FileMode) { + t.Helper() + data, err := json.Marshal(value) + require.NoError(t, err) + require.NoError(t, os.WriteFile(path, data, mode)) + require.NoError(t, os.Chmod(path, mode)) +} + +func newLoopbackClient(t *testing.T, endpoint, key string, customize func(*ClientOptions)) *Client { + t.Helper() + options := ClientOptions{Endpoint: endpoint, APIKey: key} + if customize != nil { + customize(&options) + } + client, err := New(options) + require.NoError(t, err) + return client +} + +func writeTestJSON(t *testing.T, w http.ResponseWriter, value any) { + t.Helper() + w.Header().Set("Content-Type", "application/json") + require.NoError(t, json.NewEncoder(w).Encode(value)) +} diff --git a/internal/taskclient/discovery_windows.go b/internal/taskclient/discovery_windows.go new file mode 100644 index 000000000..bbaaf1d57 --- /dev/null +++ b/internal/taskclient/discovery_windows.go @@ -0,0 +1,35 @@ +//go:build windows + +package taskclient + +import ( + "errors" + "net" + "os" +) + +func currentUserID() uint32 { return 0 } + +func descriptorFileSecurityCheck() error { + return ErrDescriptorFileSecurityLimit +} + +func fileOwnerID(string) (uint32, error) { + return 0, ErrDescriptorFileSecurityLimit +} + +func fileInfoOwnerID(os.FileInfo) (uint32, error) { + return 0, errors.New("windows descriptor ownership is unavailable") +} + +func openSecureRegularFile(string) (*os.File, error) { + return nil, ErrDescriptorFileSecurityLimit +} + +func validateSecureSocket(string, uint32) error { + return ErrUnixSocketSecurityLimit +} + +func peerCredentialsSupported() bool { return false } + +func verifyPeerCredentials(net.Conn, uint32) error { return nil } diff --git a/internal/taskclient/peer_darwin.go b/internal/taskclient/peer_darwin.go new file mode 100644 index 000000000..75f5f8c30 --- /dev/null +++ b/internal/taskclient/peer_darwin.go @@ -0,0 +1,33 @@ +//go:build darwin + +package taskclient + +import ( + "fmt" + "net" + "syscall" + + "golang.org/x/sys/unix" +) + +func peerCredentialsSupported() bool { return true } + +func verifyPeerCredentials(conn net.Conn, expectedOwner uint32) error { + syscallConn, ok := conn.(syscall.Conn) + if !ok { + return fmt.Errorf("%w: Unix peer credential handle unavailable", ErrInsecureEndpoint) + } + var credential *unix.Xucred + var socketErr error + raw, err := syscallConn.SyscallConn() + if err != nil { + return fmt.Errorf("%w: Unix peer credential handle unavailable", ErrInsecureEndpoint) + } + err = raw.Control(func(fd uintptr) { + credential, socketErr = unix.GetsockoptXucred(int(fd), unix.SOL_LOCAL, unix.LOCAL_PEERCRED) + }) + if err != nil || socketErr != nil || credential == nil || credential.Uid != expectedOwner { + return fmt.Errorf("%w: Unix peer credential mismatch", ErrInsecureEndpoint) + } + return nil +} diff --git a/internal/taskclient/peer_fallback.go b/internal/taskclient/peer_fallback.go new file mode 100644 index 000000000..bc6ebbef5 --- /dev/null +++ b/internal/taskclient/peer_fallback.go @@ -0,0 +1,9 @@ +//go:build !darwin && !freebsd && !linux && !windows + +package taskclient + +import "net" + +func peerCredentialsSupported() bool { return false } + +func verifyPeerCredentials(net.Conn, uint32) error { return nil } diff --git a/internal/taskclient/peer_freebsd.go b/internal/taskclient/peer_freebsd.go new file mode 100644 index 000000000..1d056b4b5 --- /dev/null +++ b/internal/taskclient/peer_freebsd.go @@ -0,0 +1,33 @@ +//go:build freebsd + +package taskclient + +import ( + "fmt" + "net" + "syscall" + + "golang.org/x/sys/unix" +) + +func peerCredentialsSupported() bool { return true } + +func verifyPeerCredentials(conn net.Conn, expectedOwner uint32) error { + syscallConn, ok := conn.(syscall.Conn) + if !ok { + return fmt.Errorf("%w: Unix peer credential handle unavailable", ErrInsecureEndpoint) + } + var credential *unix.Xucred + var socketErr error + raw, err := syscallConn.SyscallConn() + if err != nil { + return fmt.Errorf("%w: Unix peer credential handle unavailable", ErrInsecureEndpoint) + } + err = raw.Control(func(fd uintptr) { + credential, socketErr = unix.GetsockoptXucred(int(fd), unix.SOL_LOCAL, unix.LOCAL_PEERCRED) + }) + if err != nil || socketErr != nil || credential == nil || credential.Uid != expectedOwner { + return fmt.Errorf("%w: Unix peer credential mismatch", ErrInsecureEndpoint) + } + return nil +} diff --git a/internal/taskclient/peer_linux.go b/internal/taskclient/peer_linux.go new file mode 100644 index 000000000..19d0e2586 --- /dev/null +++ b/internal/taskclient/peer_linux.go @@ -0,0 +1,33 @@ +//go:build linux + +package taskclient + +import ( + "fmt" + "net" + "syscall" + + "golang.org/x/sys/unix" +) + +func peerCredentialsSupported() bool { return true } + +func verifyPeerCredentials(conn net.Conn, expectedOwner uint32) error { + syscallConn, ok := conn.(syscall.Conn) + if !ok { + return fmt.Errorf("%w: Unix peer credential handle unavailable", ErrInsecureEndpoint) + } + var credential *unix.Ucred + var socketErr error + raw, err := syscallConn.SyscallConn() + if err != nil { + return fmt.Errorf("%w: Unix peer credential handle unavailable", ErrInsecureEndpoint) + } + err = raw.Control(func(fd uintptr) { + credential, socketErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED) + }) + if err != nil || socketErr != nil || credential == nil || credential.Uid != expectedOwner { + return fmt.Errorf("%w: Unix peer credential mismatch", ErrInsecureEndpoint) + } + return nil +} diff --git a/internal/tasklinks/index.go b/internal/tasklinks/index.go new file mode 100644 index 000000000..de463563a --- /dev/null +++ b/internal/tasklinks/index.go @@ -0,0 +1,356 @@ +package tasklinks + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "time" + + "go.kenn.io/msgvault/internal/taskclient" +) + +const ( + DefaultStaleAfter = 15 * time.Minute + HardMaxTasks = 5000 + MaxCacheFileBytes = 8 << 20 + indexFormatV1 = 1 +) + +type IndexState string + +const ( + StateReady IndexState = "ready" + StatePartial IndexState = "partial" + StateStale IndexState = "stale" + StateAuthenticationRequired IndexState = "authentication_required" + StateUnavailable IndexState = "unavailable" + StateDisabled IndexState = "disabled" + StateNotFound IndexState = "not_found" + StateWrongProject IndexState = "wrong_project" + StateIncompatible IndexState = "incompatible" +) + +const ( + ReasonSafetyLimit = "safety_limit" + ReasonInterrupted = "interrupted" + ReasonUnavailable = "daemon_unavailable" + ReasonCursorCycle = "cursor_cycle" + ReasonEmptyPage = "empty_page_with_cursor" + ReasonArchiveUID = "archive_uid_mismatch" + ReasonArchiveRevision = "archive_revision_mismatch" + ReasonProjectMismatch = "project_mismatch" + ReasonCacheFormat = "cache_format_incompatible" + ReasonPersistenceFailure = "persistence_failure" + ReasonCachePersistenceUnsupported = "cache_persistence_unsupported" + ReasonIncompatibleMetadata = "incompatible_metadata" +) + +var ErrDiskCacheSecurityUnsupported = errors.New("secure task reverse-index disk persistence is unsupported on this platform") + +type CacheIdentity struct { + Project string `json:"project"` + ArchiveUID string `json:"archive_uid"` + ArchiveRevision string `json:"archive_revision"` +} + +type TaskSummary struct { + ID string `json:"id"` + Title string `json:"title"` + Revision string `json:"revision,omitempty"` +} + +type IndexStatus struct { + State IndexState `json:"state"` + Complete bool `json:"complete"` + LastScan time.Time `json:"last_scan"` + RemoteRevision string `json:"remote_revision,omitempty"` + Reason string `json:"reason,omitempty"` +} + +type LookupResult struct { + IndexStatus + + Tasks []TaskSummary `json:"tasks"` +} + +type indexEntry struct { + Task TaskSummary `json:"task"` + Links []MailLink `json:"links"` +} + +type indexFile struct { + FormatVersion int `json:"format_version"` + Identity CacheIdentity `json:"identity"` + Status IndexStatus `json:"status"` + Entries []indexEntry `json:"entries"` +} + +type ListClient interface { + ListTasks(ctx context.Context, project string, limit int, cursor string) (taskclient.TaskList, error) +} + +type Index struct { + path string + now func() time.Time + persistencePolicy func() error + data indexFile +} + +func NewIndex(path string, now func() time.Time) *Index { + return NewIndexWithOptions(path, IndexOptions{Now: now}) +} + +type IndexOptions struct { + Now func() time.Time + PersistencePolicy func() error +} + +func NewIndexWithOptions(path string, options IndexOptions) *Index { + now := options.Now + if now == nil { + now = time.Now + } + policy := options.PersistencePolicy + if policy == nil { + policy = cachePersistencePolicy + } + return &Index{path: path, now: now, persistencePolicy: policy} +} + +func (i *Index) Load() error { + if err := i.persistencePolicy(); err != nil { + return err + } + info, err := os.Lstat(i.path) + if errors.Is(err, os.ErrNotExist) { + return nil + } + if err != nil { + return fmt.Errorf("inspect task reverse index: %w", err) + } + if err := validateCacheFile(i.path, info); err != nil { + return err + } + file, err := os.Open(i.path) + if err != nil { + return fmt.Errorf("open task reverse index: %w", err) + } + defer func() { _ = file.Close() }() + openedInfo, err := file.Stat() + if err != nil { + return fmt.Errorf("inspect opened task reverse index: %w", err) + } + if err := validateCacheFile(i.path, openedInfo); err != nil { + return err + } + if !os.SameFile(info, openedInfo) { + return errors.New("task reverse index changed while it was being opened") + } + data, err := io.ReadAll(io.LimitReader(file, MaxCacheFileBytes+1)) + if err != nil { + return fmt.Errorf("read task reverse index: %w", err) + } + if len(data) > MaxCacheFileBytes { + return fmt.Errorf("task reverse index exceeds %d bytes", MaxCacheFileBytes) + } + var decoded indexFile + if err := json.Unmarshal(data, &decoded); err != nil { + return fmt.Errorf("decode task reverse index: %w", err) + } + if decoded.FormatVersion != indexFormatV1 { + i.data = indexFile{FormatVersion: indexFormatV1, Status: IndexStatus{State: StateIncompatible, Reason: ReasonCacheFormat}} + return nil + } + i.data = decoded + return nil +} + +func (i *Index) save(data indexFile) error { + if err := i.persistencePolicy(); err != nil { + return err + } + dir := filepath.Dir(i.path) + if err := preparePrivateCacheDir(dir); err != nil { + return err + } + data.FormatVersion = indexFormatV1 + encoded, err := json.Marshal(data) + if err != nil { + return err + } + if len(encoded) > MaxCacheFileBytes { + return fmt.Errorf("task reverse index exceeds %d bytes", MaxCacheFileBytes) + } + tmp, err := os.CreateTemp(dir, ".reverse-index-*") + if err != nil { + return err + } + name := tmp.Name() + defer func() { _ = os.Remove(name) }() + if err := secureCacheFile(tmp); err != nil { + _ = tmp.Close() + return err + } + if _, err := tmp.Write(encoded); err != nil { + _ = tmp.Close() + return err + } + if err := tmp.Sync(); err != nil { + _ = tmp.Close() + return err + } + if err := tmp.Close(); err != nil { + return err + } + return os.Rename(name, i.path) +} + +func preparePrivateCacheDir(dir string) error { + info, err := os.Lstat(dir) + switch { + case errors.Is(err, os.ErrNotExist): + if err := os.MkdirAll(dir, 0o700); err != nil { + return fmt.Errorf("create task cache directory: %w", err) + } + case err != nil: + return fmt.Errorf("inspect task cache directory: %w", err) + case info.Mode()&os.ModeSymlink != 0: + return errors.New("task cache directory must not be a symlink") + case !info.IsDir(): + return errors.New("task cache path is not a directory") + } + if err := secureCacheDirectory(dir); err != nil { + return fmt.Errorf("secure task cache directory: %w", err) + } + return nil +} + +func (i *Index) Rebuild(ctx context.Context, client ListClient, identity CacheIdentity, remoteRevision string, maxTasks int) (IndexStatus, error) { + if maxTasks < 1 || maxTasks > HardMaxTasks { + maxTasks = HardMaxTasks + } + next := indexFile{FormatVersion: indexFormatV1, Identity: identity, Status: IndexStatus{State: StateReady, Complete: true, LastScan: i.now(), RemoteRevision: remoteRevision}, Entries: []indexEntry{}} + cursor := "" + seenCursors := map[string]struct{}{} + scanned := 0 + for { + if err := ctx.Err(); err != nil { + status := i.retainLastGood(StateStale, ReasonInterrupted) + return status, err + } + remaining := maxTasks - scanned + if remaining < 1 { + next.Status.State, next.Status.Complete, next.Status.Reason = StatePartial, false, ReasonSafetyLimit + break + } + limit := min(500, remaining) + page, err := client.ListTasks(ctx, identity.Project, limit, cursor) + if err != nil { + status := i.retainLastGood(StateUnavailable, ReasonUnavailable) + return status, err + } + pageTasks := page.Tasks + stopAfterPage := false + if len(pageTasks) > limit { + pageTasks = pageTasks[:limit] + next.Status.State, next.Status.Complete, next.Status.Reason = StatePartial, false, ReasonSafetyLimit + stopAfterPage = true + } + if len(pageTasks) > remaining { + pageTasks = pageTasks[:remaining] + next.Status.State, next.Status.Complete, next.Status.Reason = StatePartial, false, ReasonSafetyLimit + stopAfterPage = true + } + scanned += len(pageTasks) + for _, task := range pageTasks { + links, incompatible := indexMailLinks(task.Metadata) + if incompatible && next.Status.Reason == "" { + next.Status.State, next.Status.Complete, next.Status.Reason = StatePartial, false, ReasonIncompatibleMetadata + } + if len(links) > 0 { + next.Entries = append(next.Entries, indexEntry{Task: TaskSummary{ID: task.ID, Title: task.Title, Revision: task.Revision}, Links: links}) + } + } + if stopAfterPage || page.NextCursor == "" { + break + } + if len(page.Tasks) == 0 { + next.Status.State, next.Status.Complete, next.Status.Reason = StatePartial, false, ReasonEmptyPage + break + } + if page.NextCursor == cursor { + next.Status.State, next.Status.Complete, next.Status.Reason = StatePartial, false, ReasonCursorCycle + break + } + if _, seen := seenCursors[page.NextCursor]; seen { + next.Status.State, next.Status.Complete, next.Status.Reason = StatePartial, false, ReasonCursorCycle + break + } + seenCursors[page.NextCursor] = struct{}{} + if scanned >= maxTasks { + next.Status.State, next.Status.Complete, next.Status.Reason = StatePartial, false, ReasonSafetyLimit + break + } + cursor = page.NextCursor + } + if err := i.save(next); err != nil { + if errors.Is(err, ErrDiskCacheSecurityUnsupported) { + next.Status.Reason = ReasonCachePersistenceUnsupported + i.data = next + return next.Status, nil + } + return i.markLastGood(StateStale, ReasonPersistenceFailure), err + } + i.data = next + return next.Status, nil +} + +func (i *Index) retainLastGood(state IndexState, reason string) IndexStatus { + status := i.markLastGood(state, reason) + _ = i.save(i.data) + return status +} + +func (i *Index) markLastGood(state IndexState, reason string) IndexStatus { + i.data.Status.State = state + i.data.Status.Complete = false + i.data.Status.Reason = reason + if i.data.FormatVersion == 0 { + i.data.FormatVersion = indexFormatV1 + } + return i.data.Status +} + +func (i *Index) Lookup(expected CacheIdentity, identity MessageIdentity, authenticated bool) LookupResult { + status := i.data.Status + if i.data.Identity.Project != expected.Project { + status.State, status.Complete, status.Reason = StateWrongProject, false, ReasonProjectMismatch + return LookupResult{IndexStatus: status, Tasks: []TaskSummary{}} + } + if i.data.Identity.ArchiveUID != expected.ArchiveUID { + status.State, status.Complete, status.Reason = StateStale, false, ReasonArchiveUID + return LookupResult{IndexStatus: status, Tasks: []TaskSummary{}} + } + if i.data.Identity.ArchiveRevision != expected.ArchiveRevision { + status.State, status.Complete, status.Reason = StateStale, false, ReasonArchiveRevision + return LookupResult{IndexStatus: status, Tasks: []TaskSummary{}} + } + if !authenticated { + status.State, status.Complete, status.Reason = StateAuthenticationRequired, false, "authentication_required" + return LookupResult{IndexStatus: status, Tasks: []TaskSummary{}} + } + if status.State != StateUnavailable && !status.LastScan.IsZero() && i.now().Sub(status.LastScan) > DefaultStaleAfter { + status.State, status.Complete, status.Reason = StateStale, false, "refresh_overdue" + } + result := LookupResult{IndexStatus: status, Tasks: []TaskSummary{}} + for _, entry := range i.data.Entries { + if len(Resolve(entry.Links, identity)) > 0 { + result.Tasks = append(result.Tasks, entry.Task) + } + } + return result +} diff --git a/internal/tasklinks/index_security_unix.go b/internal/tasklinks/index_security_unix.go new file mode 100644 index 000000000..206c592c8 --- /dev/null +++ b/internal/tasklinks/index_security_unix.go @@ -0,0 +1,41 @@ +//go:build !windows + +package tasklinks + +import ( + "fmt" + "os" + "syscall" +) + +func cachePersistencePolicy() error { + return nil +} + +func secureCacheDirectory(path string) error { + if err := os.Chmod(path, 0o700); err != nil { //nolint:gosec // directories require execute permission + return fmt.Errorf("secure task cache directory: %w", err) + } + return nil +} + +func secureCacheFile(file *os.File) error { + return file.Chmod(0o600) +} + +func validateCacheFile(path string, info os.FileInfo) error { + if info.Mode()&os.ModeSymlink != 0 { + return fmt.Errorf("task reverse index %q must not be a symlink", path) + } + if !info.Mode().IsRegular() { + return fmt.Errorf("task reverse index %q is not a regular file", path) + } + if info.Mode().Perm() != 0o600 { + return fmt.Errorf("task reverse index %q must have mode 0600", path) + } + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok || stat.Uid != uint32(os.Getuid()) { //nolint:gosec // POSIX UIDs are non-negative and stored as uint32 + return fmt.Errorf("task reverse index %q is not owned by the current user", path) + } + return nil +} diff --git a/internal/tasklinks/index_security_windows.go b/internal/tasklinks/index_security_windows.go new file mode 100644 index 000000000..f3e7374b4 --- /dev/null +++ b/internal/tasklinks/index_security_windows.go @@ -0,0 +1,23 @@ +//go:build windows + +package tasklinks + +import ( + "os" +) + +func cachePersistencePolicy() error { + return ErrDiskCacheSecurityUnsupported +} + +func secureCacheDirectory(string) error { + return ErrDiskCacheSecurityUnsupported +} + +func secureCacheFile(*os.File) error { + return ErrDiskCacheSecurityUnsupported +} + +func validateCacheFile(string, os.FileInfo) error { + return ErrDiskCacheSecurityUnsupported +} diff --git a/internal/tasklinks/index_security_windows_test.go b/internal/tasklinks/index_security_windows_test.go new file mode 100644 index 000000000..3fac716d9 --- /dev/null +++ b/internal/tasklinks/index_security_windows_test.go @@ -0,0 +1,43 @@ +//go:build windows + +package tasklinks + +import ( + "context" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/taskclient" +) + +func TestWindowsDiskCacheFailsClosedWithoutConsumingOrReplacingExistingFile(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + path := filepath.Join(t.TempDir(), "reverse-index.json") + original := []byte(`{"must_not_be_consumed":true}`) + requirements.NoError(os.WriteFile(path, original, 0o600)) + idx := NewIndex(path, time.Now) + identity := MessageIdentity{ArchiveUID: "archive-a", MessageID: 42} + + requirements.ErrorIs(idx.Load(), ErrDiskCacheSecurityUnsupported) + assertions.Empty(idx.Lookup(testCacheIdentity(), identity, true).Tasks) + + status, err := idx.Rebuild(context.Background(), &listClient{pages: []taskclient.TaskList{{ + Tasks: []taskclient.Task{indexedTask("task-1", "Current-session task", identity)}, + }}}, testCacheIdentity(), "remote-1", 100) + requirements.NoError(err) + assertions.Equal(StateReady, status.State) + assertions.Equal(ReasonCachePersistenceUnsupported, status.Reason) + assertions.True(status.Complete) + currentSession := idx.Lookup(testCacheIdentity(), identity, true) + requirements.Len(currentSession.Tasks, 1) + assertions.Equal("Current-session task", currentSession.Tasks[0].Title) + + got, err := os.ReadFile(path) + requirements.NoError(err) + assertions.Equal(original, got) +} diff --git a/internal/tasklinks/index_test.go b/internal/tasklinks/index_test.go new file mode 100644 index 000000000..56aac0561 --- /dev/null +++ b/internal/tasklinks/index_test.go @@ -0,0 +1,274 @@ +package tasklinks + +import ( + "context" + "errors" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/taskclient" +) + +type listClient struct { + pages []taskclient.TaskList + errAt int + calls int + limits []int + cursors []string +} + +func (c *listClient) ListTasks(_ context.Context, _ string, limit int, cursor string) (taskclient.TaskList, error) { + c.calls++ + c.limits = append(c.limits, limit) + c.cursors = append(c.cursors, cursor) + if c.errAt == c.calls { + return taskclient.TaskList{}, errors.New("unavailable") + } + return c.pages[c.calls-1], nil +} + +func indexedTask(id, title string, identity MessageIdentity) taskclient.Task { + return taskclient.Task{ID: id, Project: "project", Title: title, Revision: "r1", Metadata: MetadataWithLink(nil, NewMailLink(identity, time.Now()))} +} + +func testCacheIdentity() CacheIdentity { + return CacheIdentity{Project: "project", ArchiveUID: "archive-a", ArchiveRevision: "archive-rev-1"} +} + +func TestReverseIndexBuildPaginationPersistenceAndArchiveChange(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + path := filepath.Join(t.TempDir(), "tasks", "reverse-index.json") + now := time.Date(2026, 7, 19, 1, 0, 0, 0, time.UTC) + idx := NewIndex(path, func() time.Time { return now }) + identity := testIdentity() + otherIdentity := identity + otherIdentity.SourceMessageID = "other" + otherIdentity.MessageID = 43 + client := &listClient{pages: []taskclient.TaskList{ + {Tasks: []taskclient.Task{indexedTask("one", "First", identity)}, NextCursor: "next"}, + {Tasks: []taskclient.Task{indexedTask("two", "Second", otherIdentity)}}, + }} + cacheIdentity := testCacheIdentity() + status, err := idx.Rebuild(context.Background(), client, cacheIdentity, "remote-1", 100) + require.NoError(err) + assert.True(status.Complete) + assert.Equal(2, client.calls) + lookup := idx.Lookup(cacheIdentity, identity, true) + require.Len(lookup.Tasks, 1) + assert.Equal("First", lookup.Tasks[0].Title) + assert.Equal(now, lookup.LastScan) + assert.Equal("remote-1", lookup.RemoteRevision) + + reloaded := NewIndex(path, func() time.Time { return now.Add(time.Hour) }) + require.NoError(reloaded.Load()) + assert.Len(reloaded.Lookup(cacheIdentity, identity, true).Tasks, 1) + + changed := &listClient{pages: []taskclient.TaskList{{}}} + changedIdentity := cacheIdentity + changedIdentity.ArchiveUID = "archive-b" + _, err = reloaded.Rebuild(context.Background(), changed, changedIdentity, "remote-2", 100) + require.NoError(err) + assert.Empty(reloaded.Lookup(changedIdentity, identity, true).Tasks, "archive identity change discards old entries") +} + +func TestReverseIndexReportsPartialStaleUnavailableAndHidesTitlesWithoutAuth(t *testing.T) { + assert := assert.New(t) + now := time.Date(2026, 7, 19, 1, 0, 0, 0, time.UTC) + idx := NewIndex(filepath.Join(t.TempDir(), "index.json"), func() time.Time { return now }) + identity := testIdentity() + partial := &listClient{pages: []taskclient.TaskList{{Tasks: []taskclient.Task{indexedTask("one", "Secret title", identity)}, NextCursor: "more"}}} + cacheIdentity := testCacheIdentity() + status, err := idx.Rebuild(context.Background(), partial, cacheIdentity, "remote-1", 1) + require.NoError(t, err) + assert.False(status.Complete) + assert.Equal(ReasonSafetyLimit, status.Reason) + + unauth := idx.Lookup(cacheIdentity, identity, false) + assert.Equal(StateAuthenticationRequired, unauth.State) + assert.Empty(unauth.Tasks) + + now = now.Add(2 * DefaultStaleAfter) + stale := idx.Lookup(cacheIdentity, identity, true) + assert.Equal(StateStale, stale.State) + assert.NotEmpty(stale.Tasks) + + unavailable := &listClient{pages: []taskclient.TaskList{{}}, errAt: 1} + status, err = idx.Rebuild(context.Background(), unavailable, cacheIdentity, "remote-2", 100) + require.Error(t, err) + assert.Equal(ReasonUnavailable, status.Reason) + failed := idx.Lookup(cacheIdentity, identity, true) + assert.NotEmpty(failed.Tasks, "last successful result is retained") + assert.Equal(StateUnavailable, failed.State) +} + +func TestReverseIndexRetainsValidLinksButMarksMixedMetadataPartial(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + idx := NewIndex(filepath.Join(t.TempDir(), "index.json"), time.Now) + identity := testIdentity() + metadata := MetadataWithLink(nil, NewMailLink(identity, time.Now())) + raw, ok := metadata[MailLinksKey].([]any) + require.True(ok) + metadata[MailLinksKey] = append(raw, "incompatible-extension") + task := taskclient.Task{ID: "mixed", Project: "project", Title: "Valid target", Revision: "r1", Metadata: metadata} + + status, err := idx.Rebuild(context.Background(), &listClient{pages: []taskclient.TaskList{{Tasks: []taskclient.Task{task}}}}, testCacheIdentity(), "remote-1", 100) + require.NoError(err) + assert.Equal(StatePartial, status.State) + assert.Equal("incompatible_metadata", status.Reason) + assert.False(status.Complete) + lookup := idx.Lookup(testCacheIdentity(), identity, true) + require.Len(lookup.Tasks, 1) + assert.Equal("Valid target", lookup.Tasks[0].Title) +} + +func TestReverseIndexCancellationRetainsLastSuccessfulSnapshot(t *testing.T) { + assert := assert.New(t) + now := time.Date(2026, 7, 19, 1, 0, 0, 0, time.UTC) + idx := NewIndex(filepath.Join(t.TempDir(), "index.json"), func() time.Time { return now }) + identity := testIdentity() + cacheIdentity := testCacheIdentity() + _, err := idx.Rebuild(context.Background(), &listClient{pages: []taskclient.TaskList{{Tasks: []taskclient.Task{indexedTask("one", "Last good", identity)}}}}, cacheIdentity, "remote-1", 100) + require.NoError(t, err) + lastGood := idx.Lookup(cacheIdentity, identity, true) + + ctx, cancel := context.WithCancel(context.Background()) + cancel() + now = now.Add(time.Minute) + status, err := idx.Rebuild(ctx, &listClient{}, cacheIdentity, "remote-2", 100) + require.ErrorIs(t, err, context.Canceled) + assert.Equal(StateStale, status.State) + assert.Equal(ReasonInterrupted, status.Reason) + assert.False(status.Complete) + retained := idx.Lookup(cacheIdentity, identity, true) + assert.Equal(lastGood.Tasks, retained.Tasks) + assert.Equal(lastGood.LastScan, retained.LastScan) + assert.Equal(lastGood.RemoteRevision, retained.RemoteRevision) +} + +func TestReverseIndexSaveFailureRetainsLastGoodAndMarksMemoryStale(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + now := time.Date(2026, 7, 19, 1, 0, 0, 0, time.UTC) + path := filepath.Join(t.TempDir(), "index.json") + idx := NewIndex(path, func() time.Time { return now }) + identity := testIdentity() + cacheIdentity := testCacheIdentity() + _, err := idx.Rebuild(context.Background(), &listClient{pages: []taskclient.TaskList{{Tasks: []taskclient.Task{indexedTask("one", "Last good", identity)}}}}, cacheIdentity, "remote-1", 100) + require.NoError(err) + lastGood := idx.Lookup(cacheIdentity, identity, true) + + require.NoError(os.Remove(path)) + require.NoError(os.Mkdir(path, 0o700), "a directory at the target forces the production atomic rename to fail") + now = now.Add(time.Minute) + status, err := idx.Rebuild(context.Background(), &listClient{pages: []taskclient.TaskList{{Tasks: []taskclient.Task{indexedTask("two", "Must not replace", identity)}}}}, cacheIdentity, "remote-2", 100) + require.Error(err) + assert.Equal(StateStale, status.State) + assert.Equal("persistence_failure", status.Reason) + assert.False(status.Complete) + + retained := idx.Lookup(cacheIdentity, identity, true) + assert.Equal(StateStale, retained.State) + assert.Equal("persistence_failure", retained.Reason) + assert.Equal(lastGood.Tasks, retained.Tasks) + assert.Equal(lastGood.LastScan, retained.LastScan) + assert.Equal(lastGood.RemoteRevision, retained.RemoteRevision) +} + +func TestReverseIndexRejectsIdentityMismatchBeforeReturningTitles(t *testing.T) { + idx := NewIndex(filepath.Join(t.TempDir(), "index.json"), time.Now) + identity := testIdentity() + cacheIdentity := testCacheIdentity() + _, err := idx.Rebuild(context.Background(), &listClient{pages: []taskclient.TaskList{{Tasks: []taskclient.Task{indexedTask("one", "Must not leak", identity)}}}}, cacheIdentity, "remote-1", 100) + require.NoError(t, err) + + tests := []struct { + name string + mutate func(*CacheIdentity) + want IndexState + }{ + {name: "project", mutate: func(got *CacheIdentity) { got.Project = "other" }, want: StateWrongProject}, + {name: "archive uid", mutate: func(got *CacheIdentity) { got.ArchiveUID = "other" }, want: StateStale}, + {name: "archive revision", mutate: func(got *CacheIdentity) { got.ArchiveRevision = "other" }, want: StateStale}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + expected := cacheIdentity + tt.mutate(&expected) + result := idx.Lookup(expected, identity, true) + assert.Equal(t, tt.want, result.State) + assert.Empty(t, result.Tasks) + }) + } +} + +func TestReverseIndexBoundsOversizedPagesAndCyclicCursors(t *testing.T) { + assert := assert.New(t) + idx := NewIndex(filepath.Join(t.TempDir(), "index.json"), time.Now) + identity := testIdentity() + cacheIdentity := testCacheIdentity() + oversized := &listClient{pages: []taskclient.TaskList{{ + Tasks: []taskclient.Task{ + indexedTask("one", "One", identity), indexedTask("two", "Two", identity), indexedTask("three", "Three", identity), + }, + NextCursor: "more", + }}} + status, err := idx.Rebuild(context.Background(), oversized, cacheIdentity, "remote-1", 2) + require.NoError(t, err) + assert.Equal(StatePartial, status.State) + assert.Equal(ReasonSafetyLimit, status.Reason) + assert.Len(idx.Lookup(cacheIdentity, identity, true).Tasks, 2) + assert.Equal([]int{2}, oversized.limits) + + cyclic := &listClient{pages: []taskclient.TaskList{ + {Tasks: []taskclient.Task{{ID: "unlinked-1", Project: "project", Title: "Unlinked", Revision: "r1"}}, NextCursor: "again"}, + {Tasks: []taskclient.Task{{ID: "unlinked-2", Project: "project", Title: "Unlinked", Revision: "r1"}}, NextCursor: "again"}, + }} + status, err = idx.Rebuild(context.Background(), cyclic, cacheIdentity, "remote-2", 100) + require.NoError(t, err) + assert.Equal(StatePartial, status.State) + assert.Equal(ReasonCursorCycle, status.Reason) + assert.Equal(2, cyclic.calls) +} + +func TestReverseIndexCacheFileSecurityAndSizeBounds(t *testing.T) { + require := require.New(t) + dir := filepath.Join(t.TempDir(), "tasks") + require.NoError(os.MkdirAll(dir, 0o755)) + path := filepath.Join(dir, "reverse-index.json") + require.NoError(os.WriteFile(path, []byte(`{}`), 0o644)) + require.NoError(os.Chmod(path, 0o644)) + idx := NewIndex(path, time.Now) + require.Error(idx.Load(), "group/world-readable caches may contain task titles") + + require.NoError(os.Remove(path)) + target := filepath.Join(dir, "target") + require.NoError(os.WriteFile(target, []byte(`{}`), 0o600)) + require.NoError(os.Symlink(target, path)) + require.Error(idx.Load(), "cache symlinks must be rejected") + + require.NoError(os.Remove(path)) + require.NoError(os.WriteFile(path, make([]byte, MaxCacheFileBytes+1), 0o600)) + require.Error(idx.Load(), "cache reads must be bounded") +} + +func TestReverseIndexSaveRepairsDirectoryAndUsesPrivateFileMode(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + dir := filepath.Join(t.TempDir(), "tasks") + require.NoError(os.MkdirAll(dir, 0o755)) + path := filepath.Join(dir, "reverse-index.json") + idx := NewIndex(path, time.Now) + require.NoError(idx.save(indexFile{Identity: testCacheIdentity()})) + dirInfo, err := os.Stat(dir) + require.NoError(err) + assert.Equal(os.FileMode(0o700), dirInfo.Mode().Perm()) + fileInfo, err := os.Stat(path) + require.NoError(err) + assert.Equal(os.FileMode(0o600), fileInfo.Mode().Perm()) +} diff --git a/internal/tasklinks/metadata.go b/internal/tasklinks/metadata.go new file mode 100644 index 000000000..cf6d18aef --- /dev/null +++ b/internal/tasklinks/metadata.go @@ -0,0 +1,326 @@ +// Package tasklinks implements provider-neutral task metadata links without +// making the disposable local cache authoritative. +package tasklinks + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "maps" + "strings" + "time" + "unicode" + "unicode/utf8" + + "go.kenn.io/msgvault/internal/taskclient" +) + +const ( + MailLinksKey = "mail_links" + MaxSnapshotFieldBytes = 512 +) + +var ErrUnsafeMailLinks = errors.New("mail_links metadata cannot be preserved safely") + +type MessageIdentity struct { + ArchiveUID string + ArchiveRevision string + MessageID int64 + ConversationID int64 + Subject string + From string + SentAt time.Time + SourceType string + SourceIdentifier string + SourceMessageID string +} + +type MailLink struct { + MessageID int64 `json:"message_id"` + ConversationID int64 `json:"conversation_id,omitempty"` + Subject string `json:"subject,omitempty"` + From string `json:"from,omitempty"` + SentAt string `json:"sent_at,omitempty"` + AddedAt string `json:"added_at"` + ArchiveUID string `json:"archive_uid,omitempty"` + SourceType string `json:"source_type,omitempty"` + SourceIdentifier string `json:"source_identifier,omitempty"` + SourceMessageID string `json:"source_message_id,omitempty"` +} + +type MatchKind string + +const ( + MatchSource MatchKind = "source" + MatchArchive MatchKind = "archive" + MatchLegacy MatchKind = "legacy" +) + +type Match struct { + Link MailLink + Kind MatchKind + RecoveredFromAnotherArchive bool +} + +func NewMailLink(identity MessageIdentity, addedAt time.Time) MailLink { + return MailLink{ + MessageID: identity.MessageID, ConversationID: identity.ConversationID, + Subject: snapshot(identity.Subject), From: snapshot(identity.From), + SentAt: identity.SentAt.UTC().Format(time.RFC3339), AddedAt: addedAt.UTC().Format(time.RFC3339), + ArchiveUID: snapshot(identity.ArchiveUID), SourceType: snapshot(identity.SourceType), + SourceIdentifier: snapshot(identity.SourceIdentifier), SourceMessageID: snapshot(identity.SourceMessageID), + } +} + +func snapshot(value string) string { + value = strings.Map(func(r rune) rune { + if unicode.IsControl(r) && r != '\t' && r != '\n' { + return -1 + } + return r + }, strings.TrimSpace(value)) + if len(value) <= MaxSnapshotFieldBytes { + return value + } + value = value[:MaxSnapshotFieldBytes] + for !utf8.ValidString(value) { + value = value[:len(value)-1] + } + return value +} + +func Resolve(links []MailLink, identity MessageIdentity) []Match { + for _, mode := range []MatchKind{MatchSource, MatchArchive, MatchLegacy} { + matches := make([]Match, 0) + for _, link := range links { + matched := false + switch mode { + case MatchSource: + matched = completeSource(link.SourceType, link.SourceIdentifier, link.SourceMessageID) && + link.SourceType == identity.SourceType && link.SourceIdentifier == identity.SourceIdentifier && link.SourceMessageID == identity.SourceMessageID + case MatchArchive: + matched = link.ArchiveUID != "" && link.ArchiveUID == identity.ArchiveUID && link.MessageID == identity.MessageID + case MatchLegacy: + matched = link.ArchiveUID == "" && !completeSource(link.SourceType, link.SourceIdentifier, link.SourceMessageID) && link.MessageID == identity.MessageID + } + if matched { + matches = append(matches, Match{Link: link, Kind: mode, RecoveredFromAnotherArchive: mode == MatchSource && link.ArchiveUID != "" && link.ArchiveUID != identity.ArchiveUID}) + } + } + if len(matches) > 0 { + return matches + } + } + return nil +} + +func matchesAnySupportedIdentity(link MailLink, identity MessageIdentity) bool { + source := completeSource(link.SourceType, link.SourceIdentifier, link.SourceMessageID) && + link.SourceType == identity.SourceType && + link.SourceIdentifier == identity.SourceIdentifier && + link.SourceMessageID == identity.SourceMessageID + archive := link.ArchiveUID != "" && link.ArchiveUID == identity.ArchiveUID && link.MessageID == identity.MessageID + legacy := link.ArchiveUID == "" && + !completeSource(link.SourceType, link.SourceIdentifier, link.SourceMessageID) && + link.MessageID == identity.MessageID + return source || archive || legacy +} + +func completeSource(sourceType, identifier, messageID string) bool { + return sourceType != "" && identifier != "" && messageID != "" +} + +func MailLinks(metadata map[string]any) []MailLink { + _, links, err := rawMailLinks(metadata) + if err != nil { + return nil + } + return links +} + +// indexMailLinks tolerates individual incompatible elements so a remote task +// with one valid link cannot disappear from the disposable reverse index. The +// boolean reports that the result is incomplete. Mutation paths intentionally +// continue to use rawMailLinks and fail closed on the same input. +func indexMailLinks(metadata map[string]any) ([]MailLink, bool) { + raw, ok := metadata[MailLinksKey] + if !ok { + return nil, false + } + data, err := json.Marshal(raw) + if err != nil { + return nil, true + } + var elements []json.RawMessage + if err := json.Unmarshal(data, &elements); err != nil { + return nil, true + } + links := make([]MailLink, 0, len(elements)) + incompatible := false + for _, element := range elements { + var object map[string]any + if err := json.Unmarshal(element, &object); err != nil || object == nil { + incompatible = true + continue + } + var link MailLink + if err := json.Unmarshal(element, &link); err != nil { + incompatible = true + continue + } + links = append(links, link) + } + return links, incompatible +} + +func rawMailLinks(metadata map[string]any) ([]any, []MailLink, error) { + raw, ok := metadata[MailLinksKey] + if !ok { + return []any{}, []MailLink{}, nil + } + data, err := json.Marshal(raw) + if err != nil { + return nil, nil, fmt.Errorf("%w: encode existing value: %w", ErrUnsafeMailLinks, err) + } + var elements []any + if err := json.Unmarshal(data, &elements); err != nil { + return nil, nil, fmt.Errorf("%w: expected an array: %w", ErrUnsafeMailLinks, err) + } + links := make([]MailLink, 0, len(elements)) + for index, element := range elements { + object, ok := element.(map[string]any) + if !ok { + return nil, nil, fmt.Errorf("%w: element %d is not an object", ErrUnsafeMailLinks, index) + } + encoded, err := json.Marshal(object) + if err != nil { + return nil, nil, fmt.Errorf("%w: encode element %d: %w", ErrUnsafeMailLinks, index, err) + } + var link MailLink + if err := json.Unmarshal(encoded, &link); err != nil { + return nil, nil, fmt.Errorf("%w: decode element %d: %w", ErrUnsafeMailLinks, index, err) + } + links = append(links, link) + } + return elements, links, nil +} + +func cloneMetadata(metadata map[string]any) map[string]any { + clone := make(map[string]any, len(metadata)+1) + maps.Copy(clone, metadata) + return clone +} + +func MetadataWithLink(metadata map[string]any, link MailLink) map[string]any { + result, err := metadataWithLink(metadata, link) + if err != nil { + return cloneMetadata(metadata) + } + return result +} + +func metadataWithLink(metadata map[string]any, link MailLink) (map[string]any, error) { + result := cloneMetadata(metadata) + raw, links, err := rawMailLinks(result) + if err != nil { + return nil, err + } + identity := MessageIdentity{ArchiveUID: link.ArchiveUID, MessageID: link.MessageID, SourceType: link.SourceType, SourceIdentifier: link.SourceIdentifier, SourceMessageID: link.SourceMessageID} + if len(Resolve(links, identity)) == 0 { + encoded, err := json.Marshal(link) + if err != nil { + return nil, fmt.Errorf("encode new mail link: %w", err) + } + var object map[string]any + if err := json.Unmarshal(encoded, &object); err != nil { + return nil, fmt.Errorf("normalize new mail link: %w", err) + } + raw = append(raw, object) + } + result[MailLinksKey] = raw + return result, nil +} + +func metadataWithoutLink(metadata map[string]any, identity MessageIdentity) (map[string]any, error) { + result := cloneMetadata(metadata) + raw, links, err := rawMailLinks(result) + if err != nil { + return nil, err + } + kept := make([]any, 0, len(raw)) + for index, link := range links { + if !matchesAnySupportedIdentity(link, identity) { + kept = append(kept, raw[index]) + } + } + result[MailLinksKey] = kept + return result, nil +} + +type MutationClient interface { + CreateTask(ctx context.Context, project, idempotencyKey string, create taskclient.TaskCreate) (taskclient.Task, error) + GetTask(ctx context.Context, project, taskID string) (taskclient.Task, error) + MutateMetadata(ctx context.Context, project, taskID, revision string, metadata map[string]any) (taskclient.Task, error) +} + +type Service struct { + Client MutationClient + Project string + Now func() time.Time +} + +func (s Service) now() time.Time { + if s.Now != nil { + return s.Now() + } + return time.Now() +} + +func (s Service) Create(ctx context.Context, key string, create taskclient.TaskCreate, identity MessageIdentity) (taskclient.Task, error) { + metadata, err := metadataWithLink(create.Metadata, NewMailLink(identity, s.now())) + if err != nil { + return taskclient.Task{}, err + } + create.Metadata = metadata + return s.Client.CreateTask(ctx, s.Project, key, create) +} + +func (s Service) Link(ctx context.Context, taskID string, identity MessageIdentity) (taskclient.Task, error) { + return s.mutate(ctx, taskID, identity, false) +} + +func (s Service) Unlink(ctx context.Context, taskID string, identity MessageIdentity) (taskclient.Task, error) { + return s.mutate(ctx, taskID, identity, true) +} + +func (s Service) mutate(ctx context.Context, taskID string, identity MessageIdentity, unlink bool) (taskclient.Task, error) { + for attempt := range 2 { + task, err := s.Client.GetTask(ctx, s.Project, taskID) + if err != nil { + return taskclient.Task{}, err + } + _, existingLinks, err := rawMailLinks(task.Metadata) + if err != nil { + return taskclient.Task{}, err + } + if !unlink && len(Resolve(existingLinks, identity)) > 0 { + return task, nil + } + metadata, err := metadataWithLink(task.Metadata, NewMailLink(identity, s.now())) + if unlink { + metadata, err = metadataWithoutLink(task.Metadata, identity) + } + if err != nil { + return taskclient.Task{}, err + } + updated, err := s.Client.MutateMetadata(ctx, s.Project, taskID, task.Revision, metadata) + if err == nil { + return updated, nil + } + if !errors.Is(err, taskclient.ErrConflict) || attempt == 1 { + return taskclient.Task{}, err + } + } + panic("unreachable") +} diff --git a/internal/tasklinks/metadata_test.go b/internal/tasklinks/metadata_test.go new file mode 100644 index 000000000..6dc9016fd --- /dev/null +++ b/internal/tasklinks/metadata_test.go @@ -0,0 +1,232 @@ +package tasklinks + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.kenn.io/msgvault/internal/taskclient" +) + +func testIdentity() MessageIdentity { + return MessageIdentity{ArchiveUID: "archive-a", MessageID: 42, ConversationID: 7, + Subject: "Synthetic subject", From: "sender@example.com", SentAt: time.Date(2026, 7, 18, 12, 0, 0, 0, time.UTC), + SourceType: "gmail", SourceIdentifier: "archive@example.com", SourceMessageID: "source-42"} +} + +func TestResolvePrecedenceAndArchiveRecovery(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + identity := testIdentity() + links := []MailLink{ + {MessageID: 42}, + {ArchiveUID: "archive-a", MessageID: 42}, + {ArchiveUID: "archive-other", MessageID: 999, SourceType: "gmail", SourceIdentifier: "archive@example.com", SourceMessageID: "source-42"}, + } + matches := Resolve(links, identity) + require.Len(matches, 1) + assert.Equal(MatchSource, matches[0].Kind) + assert.True(matches[0].RecoveredFromAnotherArchive) + + identity.SourceMessageID = "changed" + matches = Resolve(links, identity) + require.Len(matches, 1) + assert.Equal(MatchArchive, matches[0].Kind) + + identity.ArchiveUID = "rebuilt" + matches = Resolve(links, identity) + require.Len(matches, 1) + assert.Equal(MatchLegacy, matches[0].Kind) +} + +func TestSnapshotIsBoundedSanitizedAndHasNoContent(t *testing.T) { + assert := assert.New(t) + identity := testIdentity() + identity.Subject = "hello\x00\n" + string(make([]byte, 2000)) + link := NewMailLink(identity, time.Now()) + assert.NotContains(link.Subject, "\x00") + assert.LessOrEqual(len(link.Subject), MaxSnapshotFieldBytes) + encoded := MetadataWithLink(map[string]any{"unrelated": map[string]any{"kept": true}}, link) + assert.Contains(encoded, "unrelated") + assert.NotContains(encoded, "body") + assert.NotContains(encoded, "attachments") +} + +type mutationClient struct { + tasks []taskclient.Task + mutateErrs []error + mutations []map[string]any +} + +func (c *mutationClient) CreateTask(_ context.Context, project, key string, create taskclient.TaskCreate) (taskclient.Task, error) { + return taskclient.Task{ID: "task-new", Project: project, Title: create.Title, Revision: "r1", Metadata: create.Metadata}, nil +} +func (c *mutationClient) GetTask(_ context.Context, _, _ string) (taskclient.Task, error) { + t := c.tasks[0] + if len(c.tasks) > 1 { + c.tasks = c.tasks[1:] + } + return t, nil +} +func (c *mutationClient) MutateMetadata(_ context.Context, project, id, _ string, metadata map[string]any) (taskclient.Task, error) { + c.mutations = append(c.mutations, metadata) + if len(c.mutateErrs) > 0 { + err := c.mutateErrs[0] + c.mutateErrs = c.mutateErrs[1:] + if err != nil { + return taskclient.Task{}, err + } + } + return taskclient.Task{ID: id, Project: project, Title: "Task", Revision: "next", Metadata: metadata}, nil +} + +func TestMutationsAreIdempotentMergeOnlyAndRetryOnce(t *testing.T) { + assert := assert.New(t) + identity := testIdentity() + existing := taskclient.Task{ID: "task-1", Project: "project", Title: "Task", Revision: "r1", Metadata: map[string]any{"owner": "kept"}} + retried := existing + retried.Revision = "r2" + client := &mutationClient{tasks: []taskclient.Task{existing, retried}, mutateErrs: []error{taskclient.ErrConflict, nil}} + service := Service{Client: client, Project: "project"} + + linked, err := service.Link(context.Background(), "task-1", identity) + require.NoError(t, err) + assert.Equal("kept", linked.Metadata["owner"]) + assert.Len(client.mutations, 2) + assert.Equal("kept", client.mutations[0]["owner"]) + + client.tasks = []taskclient.Task{linked} + _, err = service.Link(context.Background(), "task-1", identity) + require.NoError(t, err) + assert.Len(client.mutations, 2, "duplicate link is a no-op") + + client.tasks = []taskclient.Task{linked} + unlinked, err := service.Unlink(context.Background(), "task-1", identity) + require.NoError(t, err) + assert.Equal("kept", unlinked.Metadata["owner"]) +} + +func TestUnlinkRemovesEverySupportedRepresentationOfTheMessage(t *testing.T) { + identity := testIdentity() + existing := taskclient.Task{ + ID: "task-1", Project: "project", Title: "Task", Revision: "r1", + Metadata: map[string]any{MailLinksKey: []any{ + MailLink{ArchiveUID: "archive-other", MessageID: 999, SourceType: identity.SourceType, SourceIdentifier: identity.SourceIdentifier, SourceMessageID: identity.SourceMessageID}, + MailLink{ArchiveUID: identity.ArchiveUID, MessageID: identity.MessageID}, + MailLink{MessageID: identity.MessageID}, + MailLink{MessageID: 999}, + }}, + } + client := &mutationClient{tasks: []taskclient.Task{existing}} + + unlinked, err := (Service{Client: client, Project: "project"}).Unlink(context.Background(), "task-1", identity) + + require.NoError(t, err) + links := MailLinks(unlinked.Metadata) + require.Len(t, links, 1) + assert.Equal(t, int64(999), links[0].MessageID) +} + +func TestMutationPreservesUnrelatedRawMailLinkFieldsLosslessly(t *testing.T) { + require := require.New(t) + identity := testIdentity() + originalLink := map[string]any{ + "message_id": float64(8), + "archive_uid": "another-archive", + "provider_extension": map[string]any{ + "nested": []any{"keep", float64(3)}, + }, + } + existing := taskclient.Task{ + ID: "task-1", Project: "project", Title: "Task", Revision: "r1", + Metadata: map[string]any{ + "owner": "kept", + MailLinksKey: []any{originalLink}, + }, + } + client := &mutationClient{tasks: []taskclient.Task{existing}} + _, err := (Service{Client: client, Project: "project"}).Link(context.Background(), "task-1", identity) + require.NoError(err) + require.Len(client.mutations, 1) + rawLinks, ok := client.mutations[0][MailLinksKey].([]any) + require.True(ok) + require.Len(rawLinks, 2) + assert.Equal(t, originalLink, rawLinks[0], "unrelated link objects and extension fields must be byte-semantically preserved") +} + +func TestMutationFailsClosedForUnparseableRawMailLinkElement(t *testing.T) { + existing := taskclient.Task{ + ID: "task-1", Project: "project", Title: "Task", Revision: "r1", + Metadata: map[string]any{MailLinksKey: []any{map[string]any{"message_id": float64(8)}, "opaque-extension"}}, + } + client := &mutationClient{tasks: []taskclient.Task{existing}} + _, err := (Service{Client: client, Project: "project"}).Link(context.Background(), "task-1", testIdentity()) + require.Error(t, err) + require.ErrorIs(t, err, ErrUnsafeMailLinks) + assert.Empty(t, client.mutations, "unsafe metadata must not be overwritten") +} + +func TestMutationSecondConflictIsSurfaced(t *testing.T) { + existing := taskclient.Task{ID: "task-1", Project: "project", Title: "Task", Revision: "r1"} + retried := existing + retried.Revision = "r2" + client := &mutationClient{tasks: []taskclient.Task{existing, retried}, mutateErrs: []error{taskclient.ErrConflict, taskclient.ErrConflict}} + _, err := (Service{Client: client, Project: "project"}).Link(context.Background(), "task-1", testIdentity()) + require.ErrorIs(t, err, taskclient.ErrConflict) + assert.Len(t, client.mutations, 2) +} + +func TestCreateRetryExercisesIdempotentHTTPContractWithIdenticalPayload(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + createdByKey := map[string]taskclient.Task{} + payloadByKey := map[string][]byte{} + createCalls := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(http.MethodPost, r.Method) + assert.Equal("/api/v1/projects/project/tasks", r.URL.Path) + key := r.Header.Get("Idempotency-Key") + assert.NotEmpty(key) + payload, err := io.ReadAll(r.Body) + assert.NoError(err) + if existing, ok := createdByKey[key]; ok { + if !bytes.Equal(payloadByKey[key], payload) { + w.WriteHeader(http.StatusConflict) + return + } + w.Header().Set("Content-Type", "application/json") + assert.NoError(json.NewEncoder(w).Encode(existing)) + return + } + var create taskclient.TaskCreate + assert.NoError(json.Unmarshal(payload, &create)) + createCalls++ + created := taskclient.Task{ID: "task-created", Project: "project", Title: create.Title, Revision: "r1", Metadata: create.Metadata} + createdByKey[key], payloadByKey[key] = created, append([]byte(nil), payload...) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusCreated) + assert.NoError(json.NewEncoder(w).Encode(created)) + })) + t.Cleanup(server.Close) + client, err := taskclient.New(taskclient.ClientOptions{Endpoint: server.URL, APIKey: "test-key"}) + require.NoError(err) + addedAt := time.Date(2026, 7, 19, 1, 2, 3, 0, time.UTC) + service := Service{Client: client, Project: "project", Now: func() time.Time { return addedAt }} + + first, err := service.Create(context.Background(), "stable-key", taskclient.TaskCreate{Title: "Synthetic", Priority: "high", Labels: []string{"mail"}}, testIdentity()) + require.NoError(err) + second, err := service.Create(context.Background(), "stable-key", taskclient.TaskCreate{Title: "Synthetic", Priority: "high", Labels: []string{"mail"}}, testIdentity()) + require.NoError(err) + assert.Equal(first, second) + assert.Equal(1, createCalls, "the external service must create only once for a repeated key and payload") + links := MailLinks(first.Metadata) + require.Len(links, 1) + assert.Equal(addedAt.Format(time.RFC3339), links[0].AddedAt) +} diff --git a/internal/vector/backend.go b/internal/vector/backend.go index 9bdafea90..4a768e942 100644 --- a/internal/vector/backend.go +++ b/internal/vector/backend.go @@ -235,6 +235,19 @@ type Backend interface { Close() error } +// FilteredCoverageBackend is the optional exact-coverage capability used by +// analytical search. The caller resolves the canonical filtered population in +// DuckDB; the vector backend intersects that population with one generation. +type FilteredCoverageBackend interface { + EmbeddedMessageCountForIDs(ctx context.Context, gen GenerationID, messageIDs []int64) (int64, error) +} + +// FilteredCoverageBatchSize is the maximum number of canonical message IDs a +// filtered coverage backend accepts per call. Coverage callers page at this +// boundary so neither SQL bind arrays nor cross-database JSON blobs grow with +// archive size. +const FilteredCoverageBatchSize = 256 + // FusingBackend is an optional capability implemented by backends that // can fuse FTS5 + ANN in a single SQL query. The hybrid engine checks // for this via type assertion. diff --git a/internal/vector/errors.go b/internal/vector/errors.go index 03a7ba3e0..52e0516b6 100644 --- a/internal/vector/errors.go +++ b/internal/vector/errors.go @@ -70,4 +70,8 @@ var ( // index built only for message_type=sms must not answer an unscoped // vector query over email + SMS. ErrIndexScopeMismatch = errors.New("index scope mismatch") + + // ErrCoverageBatchTooLarge rejects an analytical coverage intersection + // that would exceed the fixed backend parameter bound. + ErrCoverageBatchTooLarge = errors.New("filtered coverage batch too large") ) diff --git a/internal/vector/pgvector/backend.go b/internal/vector/pgvector/backend.go index ad54f34e2..657347b4a 100644 --- a/internal/vector/pgvector/backend.go +++ b/internal/vector/pgvector/backend.go @@ -23,6 +23,7 @@ import ( // Compile-time check that *Backend satisfies vector.Backend. var _ vector.Backend = (*Backend)(nil) var _ vector.ChunkScoringBackend = (*Backend)(nil) +var _ vector.FilteredCoverageBackend = (*Backend)(nil) // annOverFetchFactor multiplies k for the inner ANN scan so that after // GROUP BY dedup across multi-chunk messages, at least k distinct @@ -1272,6 +1273,28 @@ func (b *Backend) EmbeddedMessageCount(ctx context.Context, gen vector.Generatio return n, nil } +func (b *Backend) EmbeddedMessageCountForIDs(ctx context.Context, gen vector.GenerationID, messageIDs []int64) (int64, error) { + if len(messageIDs) == 0 { + return 0, nil + } + if len(messageIDs) > vector.FilteredCoverageBatchSize { + return 0, fmt.Errorf("%w: got %d, maximum %d", vector.ErrCoverageBatchTooLarge, len(messageIDs), vector.FilteredCoverageBatchSize) + } + var count int64 + if err := b.db.QueryRowContext(ctx, ` + SELECT COUNT(DISTINCT e.message_id) + FROM embeddings e + JOIN messages m ON m.id = e.message_id + WHERE e.generation_id = $1 + AND e.message_id = ANY($2) + AND m.embed_gen = $1 + AND `+store.LiveMessagesWhere("m", true), + int64(gen), messageIDs).Scan(&count); err != nil { + return 0, fmt.Errorf("count filtered embedded messages: %w", err) + } + return count, nil +} + // ScoreMessageChunks scores every embedded chunk of messageID in gen // against queryVec. Results are sorted by score descending (best first). func (b *Backend) ScoreMessageChunks(ctx context.Context, gen vector.GenerationID, messageID int64, queryVec []float32) ([]vector.ChunkHit, error) { diff --git a/internal/vector/pgvector/coverage_test.go b/internal/vector/pgvector/coverage_test.go index 0d4dba234..229e2f7ab 100644 --- a/internal/vector/pgvector/coverage_test.go +++ b/internal/vector/pgvector/coverage_test.go @@ -153,3 +153,41 @@ func TestCoverageSplit_ScopedEmbeddedHoldsInvariant(t *testing.T) { assert.Equal(live, embeddedCount+blank+missing, "invariant: live == embedded + blank + missing") } + +func TestFilteredCoverageRequiresLiveGenerationStampAndVector(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + ctx := context.Background() + db := openPGTestDB(t) + b, err := Open(ctx, Options{DB: db, Dimension: 8}) + require.NoError(err) + t.Cleanup(func() { _ = b.Close() }) + + _, err = db.ExecContext(ctx, ` + INSERT INTO messages (id, embed_gen, deleted_at, deleted_from_source_at) VALUES + (1, NULL, NULL, NULL), + (2, NULL, NULL, NULL), + (3, NULL, CURRENT_TIMESTAMP, NULL), + (4, NULL, NULL, CURRENT_TIMESTAMP), + (5, 999, NULL, NULL), + (6, NULL, NULL, NULL)`) + require.NoError(err) + gen, err := b.CreateGeneration(ctx, "test-model", 8, "fp") + require.NoError(err) + _, err = db.ExecContext(ctx, `UPDATE messages SET embed_gen = $1 WHERE id IN (1, 3, 4, 6)`, int64(gen)) + require.NoError(err) + chunks := make([]vector.Chunk, 0, 5) + for i, id := range []int64{1, 2, 3, 4, 5} { + v := make([]float32, 8) + v[i] = 1 + chunks = append(chunks, vector.Chunk{MessageID: id, Vector: v}) + } + require.NoError(b.Upsert(ctx, gen, chunks)) + + count, err := b.EmbeddedMessageCountForIDs(ctx, gen, []int64{1, 2, 3, 4, 5, 6}) + require.NoError(err) + assert.Equal(int64(1), count) + + _, err = b.EmbeddedMessageCountForIDs(ctx, gen, make([]int64, vector.FilteredCoverageBatchSize+1)) + assert.ErrorIs(err, vector.ErrCoverageBatchTooLarge) +} diff --git a/internal/vector/sqlitevec/backend.go b/internal/vector/sqlitevec/backend.go index 342ef5f95..bd28c2c65 100644 --- a/internal/vector/sqlitevec/backend.go +++ b/internal/vector/sqlitevec/backend.go @@ -27,6 +27,7 @@ const sqliteDatetimeFormat = "2006-01-02 15:04:05" // Compile-time check that *Backend satisfies the vector.Backend interface. var _ vector.Backend = (*Backend)(nil) var _ vector.ChunkScoringBackend = (*Backend)(nil) +var _ vector.FilteredCoverageBackend = (*Backend)(nil) // Options configures how Open establishes a Backend. type Options struct { @@ -1475,6 +1476,62 @@ func (b *Backend) EmbeddedMessageCount(ctx context.Context, gen vector.Generatio return n, nil } +// EmbeddedMessageCountForIDs intersects a DuckDB-resolved canonical context +// with the selected vector generation inside vectors.db. +func (b *Backend) EmbeddedMessageCountForIDs(ctx context.Context, gen vector.GenerationID, messageIDs []int64) (int64, error) { + if len(messageIDs) == 0 { + return 0, nil + } + if len(messageIDs) > vector.FilteredCoverageBatchSize { + return 0, fmt.Errorf("%w: got %d, maximum %d", vector.ErrCoverageBatchTooLarge, len(messageIDs), vector.FilteredCoverageBatchSize) + } + if b.mainDB == nil { + return 0, errors.New("filtered coverage requires the main message database") + } + encoded, err := json.Marshal(messageIDs) + if err != nil { + return 0, fmt.Errorf("encode coverage message ids: %w", err) + } + rows, err := b.mainDB.QueryContext(ctx, ` + SELECT id + FROM messages + WHERE id IN (SELECT value FROM json_each(?)) + AND embed_gen = ? + AND `+store.LiveMessagesWhere("", true), string(encoded), int64(gen)) + if err != nil { + return 0, fmt.Errorf("list live stamped coverage messages: %w", err) + } + defer func() { _ = rows.Close() }() + liveStampedIDs := make([]int64, 0, len(messageIDs)) + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return 0, fmt.Errorf("scan live stamped coverage message: %w", err) + } + liveStampedIDs = append(liveStampedIDs, id) + } + if err := rows.Err(); err != nil { + return 0, fmt.Errorf("iterate live stamped coverage messages: %w", err) + } + if len(liveStampedIDs) == 0 { + return 0, nil + } + encoded, err = json.Marshal(liveStampedIDs) + if err != nil { + return 0, fmt.Errorf("encode live stamped coverage ids: %w", err) + } + var count int64 + if err := b.db.QueryRowContext(ctx, ` + SELECT COUNT(DISTINCT message_id) + FROM embeddings + WHERE generation_id = ? + AND message_id IN (SELECT value FROM json_each(?))`, + int64(gen), string(encoded)).Scan(&count); err != nil { + return 0, fmt.Errorf("count filtered embedded messages: %w", err) + } + return count, nil +} + // ScoreMessageChunks scores every embedded chunk of messageID in gen // against queryVec. Results are sorted by score descending (best first). func (b *Backend) ScoreMessageChunks(ctx context.Context, gen vector.GenerationID, messageID int64, queryVec []float32) ([]vector.ChunkHit, error) { diff --git a/internal/vector/sqlitevec/coverage_test.go b/internal/vector/sqlitevec/coverage_test.go index d445fbacf..e41054f38 100644 --- a/internal/vector/sqlitevec/coverage_test.go +++ b/internal/vector/sqlitevec/coverage_test.go @@ -278,3 +278,58 @@ func TestCoverageSplit_ScopedEmbeddedHoldsInvariant(t *testing.T) { assert.Equal(live, embedded+blank+missingCount, "invariant: live == embedded + blank + missing") } + +func TestFilteredCoverageRequiresLiveGenerationStampAndVector(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + ctx := context.Background() + st := testutil.NewSQLiteTestStore(t) + b, err := Open(ctx, Options{ + Path: filepath.Join(t.TempDir(), "vectors.db"), Dimension: 8, MainDB: st.DB(), + }) + require.NoError(err) + t.Cleanup(func() { _ = b.Close() }) + + source, err := st.GetOrCreateSource("gmail", "me@example.com") + require.NoError(err) + conversationID, err := st.EnsureConversationWithType(source.ID, "coverage", "email_thread", "Coverage") + require.NoError(err) + makeMessage := func(sourceMessageID string) int64 { + id, err := st.UpsertMessage(&store.Message{ + SourceID: source.ID, SourceMessageID: sourceMessageID, + ConversationID: conversationID, MessageType: "email", + }) + require.NoError(err) + return id + } + valid := makeMessage("valid") + clearedStamp := makeMessage("cleared-stamp") + dedupLoser := makeMessage("dedup-loser") + sourceDeleted := makeMessage("source-deleted") + wrongGeneration := makeMessage("wrong-generation") + noVector := makeMessage("no-vector") + + gen, err := b.CreateGeneration(ctx, "test-model", 8, "fp") + require.NoError(err) + chunks := make([]vector.Chunk, 0, 5) + for i, id := range []int64{valid, clearedStamp, dedupLoser, sourceDeleted, wrongGeneration} { + v := make([]float32, 8) + v[i] = 1 + chunks = append(chunks, vector.Chunk{MessageID: id, Vector: v}) + } + require.NoError(b.Upsert(ctx, gen, chunks)) + require.NoError(st.SetEmbedGen(ctx, []int64{valid, dedupLoser, sourceDeleted, noVector}, int64(gen))) + require.NoError(st.SetEmbedGen(ctx, []int64{wrongGeneration}, int64(gen)+99)) + _, err = st.DB().Exec(`UPDATE messages SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?`, dedupLoser) + require.NoError(err) + _, err = st.DB().Exec(`UPDATE messages SET deleted_from_source_at = CURRENT_TIMESTAMP WHERE id = ?`, sourceDeleted) + require.NoError(err) + + count, err := b.EmbeddedMessageCountForIDs(ctx, gen, + []int64{valid, clearedStamp, dedupLoser, sourceDeleted, wrongGeneration, noVector}) + require.NoError(err) + assert.Equal(int64(1), count) + + _, err = b.EmbeddedMessageCountForIDs(ctx, gen, make([]int64, vector.FilteredCoverageBatchSize+1)) + assert.ErrorIs(err, vector.ErrCoverageBatchTooLarge) +} diff --git a/internal/web/dist/stub.html b/internal/web/dist/stub.html new file mode 100644 index 000000000..9766475a4 --- /dev/null +++ b/internal/web/dist/stub.html @@ -0,0 +1 @@ +ok diff --git a/internal/web/embed.go b/internal/web/embed.go new file mode 100644 index 000000000..3e8a35a89 --- /dev/null +++ b/internal/web/embed.go @@ -0,0 +1,24 @@ +// Package web embeds and serves the browser application bundled with msgvault. +package web + +import ( + "embed" + "fmt" + "io/fs" +) + +// distFS always contains stub.html so ordinary Go builds do not require the +// frontend toolchain. Release builds copy Vite's output beside the stub before +// compiling the binary. +// +//go:embed dist/* +var distFS embed.FS + +// Assets returns the embedded frontend distribution rooted at dist/. +func Assets() (fs.FS, error) { + assets, err := fs.Sub(distFS, "dist") + if err != nil { + return nil, fmt.Errorf("root embedded web assets: %w", err) + } + return assets, nil +} diff --git a/internal/web/handler.go b/internal/web/handler.go new file mode 100644 index 000000000..1956fe976 --- /dev/null +++ b/internal/web/handler.go @@ -0,0 +1,128 @@ +package web + +import ( + "bytes" + "encoding/json" + "io/fs" + "net/http" + "path" + "strings" + "time" +) + +const ( + shellCacheControl = "no-store, no-cache, must-revalidate, max-age=0" + immutableCache = "public, max-age=31536000, immutable" + shellCSP = "default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'" + viteManifestPath = ".vite/manifest.json" +) + +type viteManifestEntry struct { + File string `json:"file"` + CSS []string `json:"css"` + Assets []string `json:"assets"` +} + +// Handler serves the embedded browser application and delegates requests that +// do not belong to it to fallback. +func Handler(fallback http.Handler) http.Handler { + assets, err := Assets() + if err != nil { + return fallback + } + return NewHandler(assets, fallback) +} + +// NewHandler serves a frontend filesystem. Handler uses it with the embedded +// distribution; accepting an fs.FS also keeps routing behavior independently +// testable without requiring a frontend build. +func NewHandler(assets fs.FS, fallback http.Handler) http.Handler { + if fallback == nil { + fallback = http.NotFoundHandler() + } + + index, indexErr := fs.ReadFile(assets, "index.html") + immutableAssets := readImmutableAssets(assets) + files := http.FileServerFS(assets) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet && r.Method != http.MethodHead { + fallback.ServeHTTP(w, r) + return + } + + name := strings.TrimPrefix(path.Clean(r.URL.Path), "/") + if name != "." && name != "index.html" && name != viteManifestPath && fs.ValidPath(name) { + info, err := fs.Stat(assets, name) + if err == nil && !info.IsDir() { + w.Header().Set("X-Content-Type-Options", "nosniff") + if _, ok := immutableAssets[name]; ok { + w.Header().Set("Cache-Control", immutableCache) + } + files.ServeHTTP(w, r) + return + } + } + + if indexErr != nil || !isNavigationPath(r.URL.Path) { + fallback.ServeHTTP(w, r) + return + } + + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("Cache-Control", shellCacheControl) + w.Header().Set("Pragma", "no-cache") + w.Header().Set("Expires", "0") + w.Header().Set("Content-Security-Policy", shellCSP) + w.Header().Set("X-Content-Type-Options", "nosniff") + http.ServeContent(w, r, "index.html", time.Time{}, bytes.NewReader(index)) + }) +} + +func readImmutableAssets(assets fs.FS) map[string]struct{} { + content, err := fs.ReadFile(assets, viteManifestPath) + if err != nil { + return nil + } + + var manifest map[string]viteManifestEntry + if err := json.Unmarshal(content, &manifest); err != nil { + return nil + } + + immutable := make(map[string]struct{}) + add := func(name string) { + if name != "" && name != viteManifestPath && fs.ValidPath(name) { + immutable[name] = struct{}{} + } + } + for _, entry := range manifest { + add(entry.File) + for _, name := range entry.CSS { + add(name) + } + for _, name := range entry.Assets { + add(name) + } + } + return immutable +} + +func isNavigationPath(requestPath string) bool { + if requestPath == "/api" || requestPath == "/debug" { + return false + } + if requestPath == "/assets" || strings.HasPrefix(requestPath, "/assets/") { + return false + } + for _, prefix := range []string{"/api/", "/debug/", "/openapi", "/docs"} { + if strings.HasPrefix(requestPath, prefix) { + return false + } + } + + cleaned := path.Clean(requestPath) + if cleaned == "/" || cleaned == "/index.html" { + return true + } + return path.Ext(cleaned) == "" +} diff --git a/internal/web/handler_test.go b/internal/web/handler_test.go new file mode 100644 index 000000000..a0252e1cf --- /dev/null +++ b/internal/web/handler_test.go @@ -0,0 +1,308 @@ +package web + +import ( + "encoding/json" + "io/fs" + "net/http" + "net/http/httptest" + "testing" + "testing/fstest" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testIndex = `test shellapp` + +const testViteManifest = `{ + "index.html": { + "file": "assets/index-DV9xHoWC.js", + "name": "index", + "src": "index.html", + "isEntry": true, + "css": ["assets/index-DZ2EoQeD.css"], + "assets": ["assets/logo-horizontal.svg"] + }, + "src/lib/conversation.ts": { + "file": "assets/conversation-DV9xHoWC.js", + "name": "conversation", + "src": "src/lib/conversation.ts", + "isDynamicEntry": true + } +}` + +func TestWebHandlerServesExactAssetsBeforeNavigationFallback(t *testing.T) { + assets := fstest.MapFS{ + "index.html": {Data: []byte(testIndex)}, + ".vite/manifest.json": {Data: []byte(testViteManifest)}, + "assets/index-DV9xHoWC.js": {Data: []byte(`console.log("bundle")`)}, + "assets/index-DZ2EoQeD.css": {Data: []byte(`body { color: black; }`)}, + "assets/conversation-DV9xHoWC.js": {Data: []byte(`export const view = {}`)}, + "assets/app-release1.js": {Data: []byte(`console.log("release")`)}, + "assets/theme-variant2.css": {Data: []byte(`:root { color-scheme: light; }`)}, + "assets/unversioned-helper.js": {Data: []byte(`export {}`)}, + "assets/logo-horizontal.svg": {Data: []byte(``)}, + "assets/manifest-production.json": {Data: []byte(`{}`)}, + "assets/theme-darkmode.css": {Data: []byte(`:root { color-scheme: dark; }`)}, + } + handler := NewHandler(assets, jsonNotFoundHandler()) + + cases := []struct { + name string + path string + wantBody string + wantCache string + }{ + { + name: "hashed JavaScript", + path: "/assets/index-DV9xHoWC.js", + wantBody: `console.log("bundle")`, + wantCache: "public, max-age=31536000, immutable", + }, + { + name: "hashed stylesheet", + path: "/assets/index-DZ2EoQeD.css", + wantBody: `body { color: black; }`, + wantCache: "public, max-age=31536000, immutable", + }, + { + name: "hashed named chunk", + path: "/assets/conversation-DV9xHoWC.js", + wantBody: `export const view = {}`, + wantCache: "public, max-age=31536000, immutable", + }, + { + name: "plausible mutable JavaScript", + path: "/assets/app-release1.js", + wantBody: `console.log("release")`, + }, + { + name: "plausible mutable stylesheet", + path: "/assets/theme-variant2.css", + wantBody: `:root { color-scheme: light; }`, + }, + { + name: "unversioned asset", + path: "/assets/unversioned-helper.js", + wantBody: `export {}`, + }, + { + name: "manifest-declared image", + path: "/assets/logo-horizontal.svg", + wantBody: ``, + wantCache: "public, max-age=31536000, immutable", + }, + { + name: "environment-specific manifest", + path: "/assets/manifest-production.json", + wantBody: `{}`, + }, + { + name: "eight-character descriptive suffix", + path: "/assets/theme-darkmode.css", + wantBody: `:root { color-scheme: dark; }`, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert := assert.New(t) + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, tc.path, nil)) + + assert.Equal(http.StatusOK, recorder.Code) + assert.Equal(tc.wantBody, recorder.Body.String()) + assert.Equal(tc.wantCache, recorder.Header().Get("Cache-Control")) + assert.Equal("nosniff", recorder.Header().Get("X-Content-Type-Options")) + assert.Empty(recorder.Header().Get("Content-Security-Policy")) + }) + } +} + +func TestWebHandlerServesShellForSafeNavigation(t *testing.T) { + handler := NewHandler(fstest.MapFS{ + "index.html": &fstest.MapFile{Data: []byte(testIndex)}, + }, jsonNotFoundHandler()) + + for _, path := range []string{"/", "/index.html", "/not/a/real/route"} { + t.Run(path, func(t *testing.T) { + assert := assert.New(t) + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, path, nil)) + + assert.Equal(http.StatusOK, recorder.Code) + assert.Equal(testIndex, recorder.Body.String()) + assert.Equal("text/html; charset=utf-8", recorder.Header().Get("Content-Type")) + assert.Equal("no-store, no-cache, must-revalidate, max-age=0", recorder.Header().Get("Cache-Control")) + assert.Equal("no-cache", recorder.Header().Get("Pragma")) + assert.Equal("0", recorder.Header().Get("Expires")) + assert.Equal("nosniff", recorder.Header().Get("X-Content-Type-Options")) + assert.Equal( + "default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'", + recorder.Header().Get("Content-Security-Policy"), + ) + }) + } +} + +func TestWebHandlerDelegatesUnsafeFallbacks(t *testing.T) { + handler := NewHandler(fstest.MapFS{ + "index.html": &fstest.MapFile{Data: []byte(testIndex)}, + }, jsonNotFoundHandler()) + + cases := []struct { + name string + method string + path string + }{ + {name: "bare API root", method: http.MethodGet, path: "/api"}, + {name: "unknown API", method: http.MethodGet, path: "/api/v1/not-real"}, + {name: "bare debug root", method: http.MethodGet, path: "/debug"}, + {name: "debug route", method: http.MethodGet, path: "/debug/not-real"}, + {name: "OpenAPI route", method: http.MethodGet, path: "/openapi-not-real"}, + {name: "docs route", method: http.MethodGet, path: "/docs/not-real"}, + {name: "missing extensionless asset", method: http.MethodGet, path: "/assets/missing"}, + {name: "missing exact asset", method: http.MethodGet, path: "/assets/index-MISSING.js"}, + {name: "non-navigation method", method: http.MethodPost, path: "/not/a/real/route"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert := assert.New(t) + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, httptest.NewRequest(tc.method, tc.path, nil)) + + assert.Equal(http.StatusNotFound, recorder.Code) + assert.Equal("application/json", recorder.Header().Get("Content-Type")) + assert.Empty(recorder.Header().Get("Content-Security-Policy")) + var response map[string]string + require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &response)) + assert.Equal("not_found", response["error"]) + }) + } +} + +func TestWebHandlerSupportsHEADWithoutResponseBodies(t *testing.T) { + handler := NewHandler(fstest.MapFS{ + "index.html": {Data: []byte(testIndex)}, + ".vite/manifest.json": {Data: []byte(testViteManifest)}, + "assets/index-DV9xHoWC.js": {Data: []byte(`console.log("bundle")`)}, + }, jsonNotFoundHandler()) + + cases := []struct { + name string + path string + wantType string + wantCache string + wantLength string + }{ + { + name: "shell", + path: "/everything", + wantType: "text/html; charset=utf-8", + wantCache: "no-store, no-cache, must-revalidate, max-age=0", + wantLength: "82", + }, + { + name: "asset", + path: "/assets/index-DV9xHoWC.js", + wantType: "text/javascript; charset=utf-8", + wantCache: "public, max-age=31536000, immutable", + wantLength: "21", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert := assert.New(t) + recorder := httptest.NewRecorder() + handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodHead, tc.path, nil)) + + assert.Equal(http.StatusOK, recorder.Code) + assert.Empty(recorder.Body.String()) + assert.Equal(tc.wantType, recorder.Header().Get("Content-Type")) + assert.Equal(tc.wantCache, recorder.Header().Get("Cache-Control")) + assert.Equal(tc.wantLength, recorder.Header().Get("Content-Length")) + assert.Equal("nosniff", recorder.Header().Get("X-Content-Type-Options")) + }) + } +} + +func TestWebHandlerFailsConservativelyWithoutValidManifest(t *testing.T) { + for _, tc := range []struct { + name string + manifest *fstest.MapFile + }{ + {name: "missing manifest"}, + {name: "malformed manifest", manifest: &fstest.MapFile{Data: []byte(`{"index.html":`)}}, + } { + t.Run(tc.name, func(t *testing.T) { + assertions := assert.New(t) + assets := fstest.MapFS{ + "index.html": {Data: []byte(testIndex)}, + "assets/index-DV9xHoWC.js": {Data: []byte(`console.log("bundle")`)}, + } + if tc.manifest != nil { + assets[".vite/manifest.json"] = tc.manifest + } + handler := NewHandler(assets, jsonNotFoundHandler()) + + assetRecorder := httptest.NewRecorder() + handler.ServeHTTP(assetRecorder, httptest.NewRequest(http.MethodGet, "/assets/index-DV9xHoWC.js", nil)) + assertions.Equal(http.StatusOK, assetRecorder.Code) + assertions.Empty(assetRecorder.Header().Get("Cache-Control")) + + shellRecorder := httptest.NewRecorder() + handler.ServeHTTP(shellRecorder, httptest.NewRequest(http.MethodGet, "/not/a/real/route", nil)) + assertions.Equal(http.StatusOK, shellRecorder.Code) + assertions.Equal(testIndex, shellRecorder.Body.String()) + }) + } +} + +func TestWebHandlerDoesNotExposeViteManifest(t *testing.T) { + handler := NewHandler(fstest.MapFS{ + "index.html": {Data: []byte(testIndex)}, + ".vite/manifest.json": {Data: []byte(testViteManifest)}, + }, jsonNotFoundHandler()) + recorder := httptest.NewRecorder() + + handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/.vite/manifest.json", nil)) + + assert.Equal(t, http.StatusNotFound, recorder.Code) + assert.Equal(t, "application/json", recorder.Header().Get("Content-Type")) + assert.NotContains(t, recorder.Body.String(), "DV9xHoWC") +} + +func TestWebHandlerDelegatesWhenEmbeddedIndexIsAbsent(t *testing.T) { + handler := NewHandler(fstest.MapFS{ + "stub.html": &fstest.MapFile{Data: []byte("ok\n")}, + }, jsonNotFoundHandler()) + recorder := httptest.NewRecorder() + + handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) + + assert.Equal(t, http.StatusNotFound, recorder.Code) + assert.Equal(t, "application/json", recorder.Header().Get("Content-Type")) + assert.NotContains(t, recorder.Body.String(), "stub") +} + +func TestWebEmbeddedAssetsIncludeCompilationStub(t *testing.T) { + assets, err := Assets() + require.NoError(t, err) + + content, err := fs.ReadFile(assets, "stub.html") + require.NoError(t, err) + assert.Equal(t, "ok\n", string(content)) +} + +func jsonNotFoundHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + _ = json.NewEncoder(w).Encode(map[string]string{ + "error": "not_found", + "message": "route not found", + }) + }) +} diff --git a/pkg/client/client_test.go b/pkg/client/client_test.go index b840c1122..08705422c 100644 --- a/pkg/client/client_test.go +++ b/pkg/client/client_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/sha256" + "encoding/json" "fmt" "net/http" "net/http/httptest" @@ -16,6 +17,106 @@ import ( "go.kenn.io/msgvault/pkg/client/generated" ) +func TestGeneratedSavedViewStateRoundTripsCanonicalDefinition(t *testing.T) { + want := `{ + "query":"invoice", + "search_mode":"full_text", + "filters":[{"field":"source_id","operator":"eq","values":["9007199254740993"]}], + "grouping":["sender"], + "presentation":"table", + "sort":[{"field":"sent_at","direction":"desc"}], + "columns":["sender","subject"], + "inspector_pinned":true + }` + + var state generated.SavedViewStateEnvelope + require.NoError(t, json.Unmarshal([]byte(want), &state)) + got, err := json.Marshal(state) + require.NoError(t, err) + assert.JSONEq(t, want, string(got)) +} + +func TestGeneratedEnumNamesPreserveSavedViewCompatibilityAndQualifyExploration(t *testing.T) { + assertions := assert.New(t) + assertions.Equal(generated.Asc, generated.SavedViewSortDirection("asc")) + assertions.Equal(generated.Desc, generated.SavedViewSortDirection("desc")) + assertions.Equal(generated.IdentitySearchSortDirectionAsc, generated.IdentitySearchSortDirection("asc")) + assertions.Equal(generated.IdentitySearchSortDirectionDesc, generated.IdentitySearchSortDirection("desc")) + assertions.Equal(generated.Files, generated.SavedViewStateEnvelopePresentation("files")) + assertions.Equal(generated.Table, generated.SavedViewStateEnvelopePresentation("table")) + assertions.Equal(generated.Timeline, generated.SavedViewStateEnvelopePresentation("timeline")) + assertions.Equal(generated.ExploreFilterDimensionAfter, generated.ExploreFilterDimension("after")) + assertions.Equal(generated.ExploreGroupSortDirectionAsc, generated.ExploreGroupSortDirection("asc")) + assertions.Equal(generated.ExploreGroupDimensionSource, generated.ExploreGroupDimension("source")) + assertions.Equal(generated.ExploreGroupsHTTPRequestSearchModeFullText, generated.ExploreGroupsHTTPRequestSearchMode("full_text")) +} + +func TestGeneratedExploreGroupingValidatesExactlyOneDimension(t *testing.T) { + requirements := require.New(t) + valid := generated.ExploreGroupsHTTPRequest{Grouping: []generated.ExploreGroupDimension{ + generated.ExploreGroupDimensionSource, + }} + requirements.NoError(valid.Validate()) + + requirements.Error((generated.ExploreGroupsHTTPRequest{}).Validate(), "empty grouping") + requirements.Error((generated.ExploreGroupsHTTPRequest{Grouping: []generated.ExploreGroupDimension{ + generated.ExploreGroupDimensionSource, generated.ExploreGroupDimensionMonth, + }}).Validate(), "multiple grouping dimensions") + + fileValid := generated.FileGroupsHTTPRequest{Grouping: []generated.ExploreGroupDimension{ + generated.ExploreGroupDimensionSource, + }} + requirements.NoError(fileValid.Validate()) + requirements.Error((generated.FileGroupsHTTPRequest{}).Validate(), "empty file grouping") + requirements.Error((generated.FileGroupsHTTPRequest{Grouping: []generated.ExploreGroupDimension{ + generated.ExploreGroupDimensionSource, generated.ExploreGroupDimensionMonth, + }}).Validate(), "multiple file grouping dimensions") +} + +func TestGeneratedFileMetadataRequiresPresenceButAcceptsEmptyLegacyStrings(t *testing.T) { + t.Run("metadata response", func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + var present generated.FileMetadataResponse + requirements.NoError(json.Unmarshal([]byte( + `{"content_state":"metadata_only","filename":"","mime_type":""}`, + ), &present)) + requirements.NotNil(present.Filename) + requirements.NotNil(present.MimeType) + assertions.Empty(*present.Filename) + assertions.Empty(*present.MimeType) + requirements.NoError(present.Validate(), "present empty strings are legitimate legacy metadata") + + missingFilename := present + missingFilename.Filename = nil + requirements.Error(missingFilename.Validate(), "missing required filename") + missingMIME := present + missingMIME.MimeType = nil + requirements.Error(missingMIME.Validate(), "missing required MIME type") + }) + + t.Run("search row", func(t *testing.T) { + assertions := assert.New(t) + requirements := require.New(t) + var present generated.FileSearchRow + requirements.NoError(json.Unmarshal([]byte( + `{"containing_title":"item","content_state":"metadata_only","entry_key":"message:1","filename":"","key":"file:1","mime_family":"other","mime_type":"","occurred_at":"2026-07-19T12:00:00Z","source_identifier":"archive@example.com","source_type":"synthetic"}`, + ), &present)) + requirements.NotNil(present.Filename) + requirements.NotNil(present.MimeType) + assertions.Empty(*present.Filename) + assertions.Empty(*present.MimeType) + requirements.NoError(present.Validate(), "present empty strings are legitimate legacy metadata") + + missingFilename := present + missingFilename.Filename = nil + requirements.Error(missingFilename.Validate(), "missing required filename") + missingMIME := present + missingMIME.MimeType = nil + requirements.Error(missingMIME.Validate(), "missing required MIME type") + }) +} + func TestGeneratedGetAttachmentContentReturnsBinaryBytes(t *testing.T) { assert := assert.New(t) require := require.New(t) diff --git a/pkg/client/generated/client.go b/pkg/client/generated/client.go index 114f6c48d..b18fb4eea 100644 --- a/pkg/client/generated/client.go +++ b/pkg/client/generated/client.go @@ -35,6 +35,18 @@ type ClientInterface interface { DaemonPing(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*DaemonPingResponse, error) DaemonPingWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*DaemonPingResp, error) + // LogoutSession Delete the current browser session + LogoutSession(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*struct{}, error) + LogoutSessionWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*LogoutSessionResp, error) + + // GetSession Get browser authentication status + GetSession(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSessionResponse, error) + GetSessionWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSessionResp, error) + + // LoginSession Create an in-memory browser session + LoginSession(ctx context.Context, options *LoginSessionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*LoginSessionResponse, error) + LoginSessionWithResponse(ctx context.Context, options *LoginSessionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*LoginSessionResp, error) + // ListAccounts List scheduler-configured accounts (with sync schedules); use /cli/accounts for all archived sources ListAccounts(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*ListAccountsResponse, error) ListAccountsWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*ListAccountsResp, error) @@ -199,6 +211,10 @@ type ClientInterface interface { VerifyCLI(ctx context.Context, options *VerifyCLIRequestOptions, reqEditors ...runtime.RequestEditorFn) (*VerifyCLIResponse, error) VerifyCLIWithResponse(ctx context.Context, options *VerifyCLIRequestOptions, reqEditors ...runtime.RequestEditorFn) (*VerifyCLIResp, error) + // GetConversation Get a bounded containing conversation + GetConversation(ctx context.Context, options *GetConversationRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetConversationResponse, error) + GetConversationWithResponse(ctx context.Context, options *GetConversationRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetConversationResp, error) + // ListDeletions List staged deletion manifests ListDeletions(ctx context.Context, options *ListDeletionsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListDeletionsResponseJSON, error) ListDeletionsWithResponse(ctx context.Context, options *ListDeletionsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListDeletionsResp, error) @@ -211,10 +227,90 @@ type ClientInterface interface { CancelDeletion(ctx context.Context, options *CancelDeletionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CancelDeletionResponseJSON, error) CancelDeletionWithResponse(ctx context.Context, options *CancelDeletionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CancelDeletionResp, error) + // GetDeletion Inspect a staged deletion manifest + GetDeletion(ctx context.Context, options *GetDeletionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDeletionResponse, error) + GetDeletionWithResponse(ctx context.Context, options *GetDeletionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDeletionResp, error) + + // SearchDomains Search analytical domains + SearchDomains(ctx context.Context, options *SearchDomainsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchDomainsResponse, error) + SearchDomainsWithResponse(ctx context.Context, options *SearchDomainsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchDomainsResp, error) + + // GetDomain Get one analytical domain + GetDomain(ctx context.Context, options *GetDomainRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainResponse, error) + GetDomainWithResponse(ctx context.Context, options *GetDomainRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainResp, error) + + // SearchDomainFiles Search one domain's analytical files + SearchDomainFiles(ctx context.Context, options *SearchDomainFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchDomainFilesResponse, error) + SearchDomainFilesWithResponse(ctx context.Context, options *SearchDomainFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchDomainFilesResp, error) + + // GetDomainContextSummary Get one domain's contextual analytical summary + GetDomainContextSummary(ctx context.Context, options *GetDomainContextSummaryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainContextSummaryResponse, error) + GetDomainContextSummaryWithResponse(ctx context.Context, options *GetDomainContextSummaryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainContextSummaryResp, error) + + // GetDomainTimeline Get one domain's canonical activity timeline + GetDomainTimeline(ctx context.Context, options *GetDomainTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainTimelineResponse, error) + GetDomainTimelineWithResponse(ctx context.Context, options *GetDomainTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainTimelineResp, error) + + // Explore Explore canonical archive entries + Explore(ctx context.Context, options *ExploreRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ExploreResponse, error) + ExploreWithResponse(ctx context.Context, options *ExploreRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ExploreResp, error) + + // ListExploreFiles List bounded chronological attachment facts + ListExploreFiles(ctx context.Context, options *ListExploreFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListExploreFilesResponse, error) + ListExploreFilesWithResponse(ctx context.Context, options *ListExploreFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListExploreFilesResp, error) + + // ExploreGroups Group canonical archive entries + ExploreGroups(ctx context.Context, options *ExploreGroupsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ExploreGroupsResponse, error) + ExploreGroupsWithResponse(ctx context.Context, options *ExploreGroupsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ExploreGroupsResp, error) + + // CountExploreMatches Count exact lexical matches in visible rows + CountExploreMatches(ctx context.Context, options *CountExploreMatchesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CountExploreMatchesResponse, error) + CountExploreMatchesWithResponse(ctx context.Context, options *CountExploreMatchesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CountExploreMatchesResp, error) + + // PreflightExploreSelection Preflight a revision-pinned analytical selection + PreflightExploreSelection(ctx context.Context, options *PreflightExploreSelectionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PreflightExploreSelectionResponse, error) + PreflightExploreSelectionWithResponse(ctx context.Context, options *PreflightExploreSelectionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PreflightExploreSelectionResp, error) + + // GroupFiles Group analytical files + GroupFiles(ctx context.Context, options *GroupFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GroupFilesResponse, error) + GroupFilesWithResponse(ctx context.Context, options *GroupFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GroupFilesResp, error) + + // SearchFiles Search analytical files + SearchFiles(ctx context.Context, options *SearchFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchFilesResponse, error) + SearchFilesWithResponse(ctx context.Context, options *SearchFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchFilesResp, error) + + // GetFile Get authoritative file metadata + GetFile(ctx context.Context, options *GetFileRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetFileResponse, error) + GetFileWithResponse(ctx context.Context, options *GetFileRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetFileResp, error) + + // GetFileContent Download content for one authoritative file row + GetFileContent(ctx context.Context, options *GetFileContentRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetFileContentResponse, error) + GetFileContentWithResponse(ctx context.Context, options *GetFileContentRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetFileContentResp, error) + // GetHealth Get authenticated health details GetHealth(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetHealthResponse, error) GetHealthWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetHealthResp, error) + // LinkIdentityParticipants Assert two participants are the same person + LinkIdentityParticipants(ctx context.Context, options *LinkIdentityParticipantsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*LinkIdentityParticipantsResponse, error) + LinkIdentityParticipantsWithResponse(ctx context.Context, options *LinkIdentityParticipantsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*LinkIdentityParticipantsResp, error) + + // UnlinkIdentityParticipants Remove a link edge between two participants + UnlinkIdentityParticipants(ctx context.Context, options *UnlinkIdentityParticipantsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*UnlinkIdentityParticipantsResponse, error) + UnlinkIdentityParticipantsWithResponse(ctx context.Context, options *UnlinkIdentityParticipantsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*UnlinkIdentityParticipantsResp, error) + + // SearchIntegrationTasks Search tasks in the configured project + SearchIntegrationTasks(ctx context.Context, options *SearchIntegrationTasksRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchIntegrationTasksResponse, error) + SearchIntegrationTasksWithResponse(ctx context.Context, options *SearchIntegrationTasksRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchIntegrationTasksResp, error) + + // GetTaskIntegrationStatus Get task integration capability status + GetTaskIntegrationStatus(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetTaskIntegrationStatusResponse, error) + GetTaskIntegrationStatusWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetTaskIntegrationStatusResp, error) + + // TestTaskIntegration Test task integration discovery, authentication, capabilities, and project + TestTaskIntegration(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*TestTaskIntegrationResponse, error) + TestTaskIntegrationWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*TestTaskIntegrationResp, error) + // ListMessages List messages ListMessages(ctx context.Context, options *ListMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListMessagesResponse, error) ListMessagesWithResponse(ctx context.Context, options *ListMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListMessagesResp, error) @@ -235,10 +331,70 @@ type ClientInterface interface { GetMessageInlinePart(ctx context.Context, options *GetMessageInlinePartRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetMessageInlinePartResponse, error) GetMessageInlinePartWithResponse(ctx context.Context, options *GetMessageInlinePartRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetMessageInlinePartResp, error) + // ListMessageTasks List tasks linked to an archived email + ListMessageTasks(ctx context.Context, options *ListMessageTasksRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListMessageTasksResponse, error) + ListMessageTasksWithResponse(ctx context.Context, options *ListMessageTasksRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListMessageTasksResp, error) + + // CreateOrLinkMessageTask Create or link a task for an archived email + CreateOrLinkMessageTask(ctx context.Context, options *CreateOrLinkMessageTaskRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CreateOrLinkMessageTaskResponse, error) + CreateOrLinkMessageTaskWithResponse(ctx context.Context, options *CreateOrLinkMessageTaskRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CreateOrLinkMessageTaskResp, error) + + // UnlinkMessageTask Unlink a task from an archived email + UnlinkMessageTask(ctx context.Context, options *UnlinkMessageTaskRequestOptions, reqEditors ...runtime.RequestEditorFn) (*UnlinkMessageTaskResponse, error) + UnlinkMessageTaskWithResponse(ctx context.Context, options *UnlinkMessageTaskRequestOptions, reqEditors ...runtime.RequestEditorFn) (*UnlinkMessageTaskResp, error) + + // SearchPeople Search analytical people + SearchPeople(ctx context.Context, options *SearchPeopleRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchPeopleResponse, error) + SearchPeopleWithResponse(ctx context.Context, options *SearchPeopleRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchPeopleResp, error) + + // GetPerson Get one analytical person + GetPerson(ctx context.Context, options *GetPersonRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonResponse, error) + GetPersonWithResponse(ctx context.Context, options *GetPersonRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonResp, error) + + // SearchPersonFiles Search one person's analytical files + SearchPersonFiles(ctx context.Context, options *SearchPersonFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchPersonFilesResponse, error) + SearchPersonFilesWithResponse(ctx context.Context, options *SearchPersonFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchPersonFilesResp, error) + + // GetPersonContextSummary Get one person's contextual analytical summary + GetPersonContextSummary(ctx context.Context, options *GetPersonContextSummaryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonContextSummaryResponse, error) + GetPersonContextSummaryWithResponse(ctx context.Context, options *GetPersonContextSummaryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonContextSummaryResp, error) + + // GetPersonTimeline Get one person's canonical activity timeline + GetPersonTimeline(ctx context.Context, options *GetPersonTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonTimelineResponse, error) + GetPersonTimelineWithResponse(ctx context.Context, options *GetPersonTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonTimelineResp, error) + // RunQuery Run an aggregate query RunQuery(ctx context.Context, options *RunQueryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*RunQueryResponse, error) RunQueryWithResponse(ctx context.Context, options *RunQueryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*RunQueryResp, error) + // ListRelationships Rank counterparts by reciprocity-weighted interaction + ListRelationships(ctx context.Context, options *ListRelationshipsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListRelationshipsResponse, error) + ListRelationshipsWithResponse(ctx context.Context, options *ListRelationshipsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListRelationshipsResp, error) + + // GetRelationshipTimeline Get one counterpart's interaction timeline, with chat grouped into local-day bursts + GetRelationshipTimeline(ctx context.Context, options *GetRelationshipTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetRelationshipTimelineResponse, error) + GetRelationshipTimelineWithResponse(ctx context.Context, options *GetRelationshipTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetRelationshipTimelineResp, error) + + // ListSavedViews List shared analytical Saved Views + ListSavedViews(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*ListSavedViewsResponse, error) + ListSavedViewsWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*ListSavedViewsResp, error) + + // CreateSavedView Create a shared analytical Saved View + CreateSavedView(ctx context.Context, options *CreateSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CreateSavedViewResponse, error) + CreateSavedViewWithResponse(ctx context.Context, options *CreateSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CreateSavedViewResp, error) + + // DeleteSavedView Delete a shared analytical Saved View + DeleteSavedView(ctx context.Context, options *DeleteSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*struct{}, error) + DeleteSavedViewWithResponse(ctx context.Context, options *DeleteSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*DeleteSavedViewResp, error) + + // GetSavedView Get a shared analytical Saved View + GetSavedView(ctx context.Context, options *GetSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetSavedViewResponse, error) + GetSavedViewWithResponse(ctx context.Context, options *GetSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetSavedViewResp, error) + + // PatchSavedView Update a shared analytical Saved View + PatchSavedView(ctx context.Context, options *PatchSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PatchSavedViewResponse, error) + PatchSavedViewWithResponse(ctx context.Context, options *PatchSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PatchSavedViewResp, error) + // GetSchedulerStatus Get scheduler status GetSchedulerStatus(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSchedulerStatusResponse, error) GetSchedulerStatusWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSchedulerStatusResp, error) @@ -247,6 +403,10 @@ type ClientInterface interface { SearchMessages(ctx context.Context, options *SearchMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchMessagesResponse, error) SearchMessagesWithResponse(ctx context.Context, options *SearchMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchMessagesResp, error) + // GetSearchCoverage Get semantic index coverage for an analytical context + GetSearchCoverage(ctx context.Context, options *GetSearchCoverageRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetSearchCoverageResponse, error) + GetSearchCoverageWithResponse(ctx context.Context, options *GetSearchCoverageRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetSearchCoverageResp, error) + // DeepSearch Run full-text message search DeepSearch(ctx context.Context, options *DeepSearchRequestOptions, reqEditors ...runtime.RequestEditorFn) (*DeepSearchResponseJSON, error) DeepSearchWithResponse(ctx context.Context, options *DeepSearchRequestOptions, reqEditors ...runtime.RequestEditorFn) (*DeepSearchResp, error) @@ -263,6 +423,14 @@ type ClientInterface interface { FindSimilarMessages(ctx context.Context, options *FindSimilarMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*FindSimilarMessagesResponse, error) FindSimilarMessagesWithResponse(ctx context.Context, options *FindSimilarMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*FindSimilarMessagesResp, error) + // GetSettings Get browser-managed settings + GetSettings(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSettingsResponse, error) + GetSettingsWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSettingsResp, error) + + // PatchSettings Update browser-managed settings + PatchSettings(ctx context.Context, options *PatchSettingsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PatchSettingsResponse, error) + PatchSettingsWithResponse(ctx context.Context, options *PatchSettingsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PatchSettingsResp, error) + // ListSourceStatus List source sync status ListSourceStatus(ctx context.Context, options *ListSourceStatusRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListSourceStatusResponse, error) ListSourceStatusWithResponse(ctx context.Context, options *ListSourceStatusRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListSourceStatusResp, error) @@ -370,6 +538,179 @@ func (c *Client) DaemonPing(ctx context.Context, reqEditors ...runtime.RequestEd return responseParser(ctx, resp) } +// LogoutSession Delete the current browser session +func (c *Client) LogoutSession(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*struct{}, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/session", + Method: "DELETE", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*struct{}, error) { + bodyBytes := resp.Content + if resp.StatusCode != 204 { + target := new(LogoutSessionErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "LogoutSessionErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + return nil, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/session") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetSession Get browser authentication status +func (c *Client) GetSession(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSessionResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/session", + Method: "GET", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetSessionResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetSessionErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSessionErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetSessionResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSessionResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/session") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// LoginSession Create an in-memory browser session +func (c *Client) LoginSession(ctx context.Context, options *LoginSessionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*LoginSessionResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/session/login", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*LoginSessionResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(LoginSessionErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "LoginSessionErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(LoginSessionResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "LoginSessionResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/session/login") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + // ListAccounts List scheduler-configured accounts (with sync schedules); use /cli/accounts for all archived sources func (c *Client) ListAccounts(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*ListAccountsResponse, error) { var err error @@ -2843,6 +3184,69 @@ func (c *Client) VerifyCLI(ctx context.Context, options *VerifyCLIRequestOptions return responseParser(ctx, resp) } +// GetConversation Get a bounded containing conversation +func (c *Client) GetConversation(ctx context.Context, options *GetConversationRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetConversationResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/conversations/{id}", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetConversationResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetConversationErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetConversationErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetConversationResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetConversationResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/conversations/{id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + // ListDeletions List staged deletion manifests func (c *Client) ListDeletions(ctx context.Context, options *ListDeletionsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListDeletionsResponseJSON, error) { var err error @@ -3033,12 +3437,13 @@ func (c *Client) CancelDeletion(ctx context.Context, options *CancelDeletionRequ return responseParser(ctx, resp) } -// GetHealth Get authenticated health details -func (c *Client) GetHealth(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetHealthResponse, error) { +// GetDeletion Inspect a staged deletion manifest +func (c *Client) GetDeletion(ctx context.Context, options *GetDeletionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDeletionResponse, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/health", + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/deletions/{id}", Method: "GET", + Options: options, } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3046,10 +3451,10 @@ func (c *Client) GetHealth(ctx context.Context, reqEditors ...runtime.RequestEdi return nil, fmt.Errorf("error creating request: %w", err) } - responseParser := func(ctx context.Context, resp *runtime.Response) (*GetHealthResponse, error) { + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetDeletionResponse, error) { bodyBytes := resp.Content if resp.StatusCode != 200 { - target := new(GetHealthErrorResponse) + target := new(GetDeletionErrorResponse) // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { @@ -3057,7 +3462,7 @@ func (c *Client) GetHealth(ctx context.Context, reqEditors ...runtime.RequestEdi StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetHealthErrorResponse", + TargetType: "GetDeletionErrorResponse", Body: bodyBytes, Err: err, } @@ -3070,7 +3475,7 @@ func (c *Client) GetHealth(ctx context.Context, reqEditors ...runtime.RequestEdi return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), runtime.WithStatusCode(resp.StatusCode)) } - target := new(GetHealthResponse) + target := new(GetDeletionResponse) // Handle empty response body gracefully if len(bodyBytes) == 0 { return target, nil @@ -3080,7 +3485,7 @@ func (c *Client) GetHealth(ctx context.Context, reqEditors ...runtime.RequestEdi StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetHealthResponse", + TargetType: "GetDeletionResponse", Body: bodyBytes, Err: err, } @@ -3088,20 +3493,2229 @@ func (c *Client) GetHealth(ctx context.Context, reqEditors ...runtime.RequestEdi return target, nil } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/health") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/deletions/{id}") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } return responseParser(ctx, resp) } -// ListMessages List messages +// SearchDomains Search analytical domains +func (c *Client) SearchDomains(ctx context.Context, options *SearchDomainsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchDomainsResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/domains/search", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*SearchDomainsResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(SearchDomainsErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchDomainsErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(SearchDomainsResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchDomainsResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/domains/search") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetDomain Get one analytical domain +func (c *Client) GetDomain(ctx context.Context, options *GetDomainRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/domains/{domain}", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetDomainResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetDomainErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetDomainResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/domains/{domain}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// SearchDomainFiles Search one domain's analytical files +func (c *Client) SearchDomainFiles(ctx context.Context, options *SearchDomainFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchDomainFilesResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/domains/{domain}/files/search", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*SearchDomainFilesResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(SearchDomainFilesErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchDomainFilesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(SearchDomainFilesResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchDomainFilesResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/domains/{domain}/files/search") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetDomainContextSummary Get one domain's contextual analytical summary +func (c *Client) GetDomainContextSummary(ctx context.Context, options *GetDomainContextSummaryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainContextSummaryResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/domains/{domain}/summary", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetDomainContextSummaryResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetDomainContextSummaryErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainContextSummaryErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetDomainContextSummaryResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainContextSummaryResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/domains/{domain}/summary") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetDomainTimeline Get one domain's canonical activity timeline +func (c *Client) GetDomainTimeline(ctx context.Context, options *GetDomainTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainTimelineResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/domains/{domain}/timeline", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetDomainTimelineResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetDomainTimelineErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainTimelineErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetDomainTimelineResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainTimelineResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/domains/{domain}/timeline") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// Explore Explore canonical archive entries +func (c *Client) Explore(ctx context.Context, options *ExploreRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ExploreResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/explore", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*ExploreResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(ExploreErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(ExploreResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/explore") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// ListExploreFiles List bounded chronological attachment facts +func (c *Client) ListExploreFiles(ctx context.Context, options *ListExploreFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListExploreFilesResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/explore/files", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*ListExploreFilesResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(ListExploreFilesErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListExploreFilesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(ListExploreFilesResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListExploreFilesResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/explore/files") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// ExploreGroups Group canonical archive entries +func (c *Client) ExploreGroups(ctx context.Context, options *ExploreGroupsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ExploreGroupsResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/explore/groups", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*ExploreGroupsResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(ExploreGroupsErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreGroupsErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(ExploreGroupsResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreGroupsResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/explore/groups") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// CountExploreMatches Count exact lexical matches in visible rows +func (c *Client) CountExploreMatches(ctx context.Context, options *CountExploreMatchesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CountExploreMatchesResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/explore/match-counts", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*CountExploreMatchesResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(CountExploreMatchesErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CountExploreMatchesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(CountExploreMatchesResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CountExploreMatchesResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/explore/match-counts") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// PreflightExploreSelection Preflight a revision-pinned analytical selection +func (c *Client) PreflightExploreSelection(ctx context.Context, options *PreflightExploreSelectionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PreflightExploreSelectionResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/explore/preflight", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*PreflightExploreSelectionResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(PreflightExploreSelectionErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PreflightExploreSelectionErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(PreflightExploreSelectionResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PreflightExploreSelectionResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/explore/preflight") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GroupFiles Group analytical files +func (c *Client) GroupFiles(ctx context.Context, options *GroupFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GroupFilesResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/files/groups", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GroupFilesResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GroupFilesErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GroupFilesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GroupFilesResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GroupFilesResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/files/groups") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// SearchFiles Search analytical files +func (c *Client) SearchFiles(ctx context.Context, options *SearchFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchFilesResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/files/search", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*SearchFilesResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(SearchFilesErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchFilesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(SearchFilesResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchFilesResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/files/search") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetFile Get authoritative file metadata +func (c *Client) GetFile(ctx context.Context, options *GetFileRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetFileResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/files/{id}", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetFileResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetFileErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFileErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetFileResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFileResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/files/{id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetFileContent Download content for one authoritative file row +func (c *Client) GetFileContent(ctx context.Context, options *GetFileContentRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetFileContentResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/files/{id}/content", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetFileContentResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetFileContentErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFileContentErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + result := GetFileContentResponse(bodyBytes) + return &result, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/files/{id}/content") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetHealth Get authenticated health details +func (c *Client) GetHealth(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetHealthResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/health", + Method: "GET", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetHealthResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetHealthErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetHealthErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetHealthResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetHealthResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/health") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// LinkIdentityParticipants Assert two participants are the same person +func (c *Client) LinkIdentityParticipants(ctx context.Context, options *LinkIdentityParticipantsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*LinkIdentityParticipantsResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/identity/links", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*LinkIdentityParticipantsResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(LinkIdentityParticipantsErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "LinkIdentityParticipantsErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(LinkIdentityParticipantsResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "LinkIdentityParticipantsResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/identity/links") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// UnlinkIdentityParticipants Remove a link edge between two participants +func (c *Client) UnlinkIdentityParticipants(ctx context.Context, options *UnlinkIdentityParticipantsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*UnlinkIdentityParticipantsResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/identity/unlinks", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*UnlinkIdentityParticipantsResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(UnlinkIdentityParticipantsErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "UnlinkIdentityParticipantsErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(UnlinkIdentityParticipantsResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "UnlinkIdentityParticipantsResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/identity/unlinks") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// SearchIntegrationTasks Search tasks in the configured project +func (c *Client) SearchIntegrationTasks(ctx context.Context, options *SearchIntegrationTasksRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchIntegrationTasksResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/integrations/tasks/search", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*SearchIntegrationTasksResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(SearchIntegrationTasksErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchIntegrationTasksErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(SearchIntegrationTasksResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchIntegrationTasksResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/integrations/tasks/search") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetTaskIntegrationStatus Get task integration capability status +func (c *Client) GetTaskIntegrationStatus(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetTaskIntegrationStatusResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/integrations/tasks/status", + Method: "GET", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetTaskIntegrationStatusResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetTaskIntegrationStatusErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetTaskIntegrationStatusErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetTaskIntegrationStatusResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetTaskIntegrationStatusResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/integrations/tasks/status") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// TestTaskIntegration Test task integration discovery, authentication, capabilities, and project +func (c *Client) TestTaskIntegration(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*TestTaskIntegrationResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/integrations/tasks/test", + Method: "POST", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*TestTaskIntegrationResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(TestTaskIntegrationErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "TestTaskIntegrationErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(TestTaskIntegrationResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "TestTaskIntegrationResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/integrations/tasks/test") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// ListMessages List messages func (c *Client) ListMessages(ctx context.Context, options *ListMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListMessagesResponse, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages", - Method: "GET", - Options: options, + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*ListMessagesResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(ListMessagesErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListMessagesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(ListMessagesResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListMessagesResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// FilterMessages List filtered messages +func (c *Client) FilterMessages(ctx context.Context, options *FilterMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*FilterMessagesResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/filter", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*FilterMessagesResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(FilterMessagesErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "FilterMessagesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(FilterMessagesResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "FilterMessagesResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/filter") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetGmailIDsByFilter List Gmail message IDs matching a filter +func (c *Client) GetGmailIDsByFilter(ctx context.Context, options *GetGmailIDsByFilterRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetGmailIDsByFilterResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/gmail-ids", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetGmailIDsByFilterResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetGmailIDsByFilterErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetGmailIDsByFilterErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetGmailIDsByFilterResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetGmailIDsByFilterResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/gmail-ids") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetMessage Get one message +func (c *Client) GetMessage(ctx context.Context, options *GetMessageRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetMessageResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetMessageResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetMessageErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetMessageResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetMessageInlinePart Get an inline MIME part +func (c *Client) GetMessageInlinePart(ctx context.Context, options *GetMessageInlinePartRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetMessageInlinePartResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}/inline", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetMessageInlinePartResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetMessageInlinePartErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageInlinePartErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetMessageInlinePartResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageInlinePartResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}/inline") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// ListMessageTasks List tasks linked to an archived email +func (c *Client) ListMessageTasks(ctx context.Context, options *ListMessageTasksRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListMessageTasksResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}/tasks", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*ListMessageTasksResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(ListMessageTasksErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListMessageTasksErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(ListMessageTasksResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListMessageTasksResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}/tasks") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// CreateOrLinkMessageTask Create or link a task for an archived email +func (c *Client) CreateOrLinkMessageTask(ctx context.Context, options *CreateOrLinkMessageTaskRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CreateOrLinkMessageTaskResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}/tasks", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*CreateOrLinkMessageTaskResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 201 { + target := new(CreateOrLinkMessageTaskErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateOrLinkMessageTaskErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(CreateOrLinkMessageTaskResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateOrLinkMessageTaskResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}/tasks") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// UnlinkMessageTask Unlink a task from an archived email +func (c *Client) UnlinkMessageTask(ctx context.Context, options *UnlinkMessageTaskRequestOptions, reqEditors ...runtime.RequestEditorFn) (*UnlinkMessageTaskResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}/tasks/{task_id}", + Method: "DELETE", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*UnlinkMessageTaskResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(UnlinkMessageTaskErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "UnlinkMessageTaskErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(UnlinkMessageTaskResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "UnlinkMessageTaskResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}/tasks/{task_id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// SearchPeople Search analytical people +func (c *Client) SearchPeople(ctx context.Context, options *SearchPeopleRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchPeopleResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/people/search", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*SearchPeopleResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(SearchPeopleErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPeopleErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(SearchPeopleResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPeopleResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/people/search") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetPerson Get one analytical person +func (c *Client) GetPerson(ctx context.Context, options *GetPersonRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/people/{id}", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetPersonResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetPersonErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetPersonResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/people/{id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// SearchPersonFiles Search one person's analytical files +func (c *Client) SearchPersonFiles(ctx context.Context, options *SearchPersonFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchPersonFilesResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/people/{id}/files/search", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*SearchPersonFilesResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(SearchPersonFilesErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPersonFilesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(SearchPersonFilesResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPersonFilesResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/people/{id}/files/search") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetPersonContextSummary Get one person's contextual analytical summary +func (c *Client) GetPersonContextSummary(ctx context.Context, options *GetPersonContextSummaryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonContextSummaryResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/people/{id}/summary", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetPersonContextSummaryResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetPersonContextSummaryErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonContextSummaryErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetPersonContextSummaryResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonContextSummaryResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/people/{id}/summary") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetPersonTimeline Get one person's canonical activity timeline +func (c *Client) GetPersonTimeline(ctx context.Context, options *GetPersonTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonTimelineResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/people/{id}/timeline", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetPersonTimelineResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetPersonTimelineErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonTimelineErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetPersonTimelineResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonTimelineResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/people/{id}/timeline") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// RunQuery Run an aggregate query +func (c *Client) RunQuery(ctx context.Context, options *RunQueryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*RunQueryResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/query", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*RunQueryResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(RunQueryErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "RunQueryErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(RunQueryResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "RunQueryResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/query") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// ListRelationships Rank counterparts by reciprocity-weighted interaction +func (c *Client) ListRelationships(ctx context.Context, options *ListRelationshipsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListRelationshipsResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/relationships", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*ListRelationshipsResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(ListRelationshipsErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListRelationshipsErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(ListRelationshipsResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListRelationshipsResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/relationships") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// GetRelationshipTimeline Get one counterpart's interaction timeline, with chat grouped into local-day bursts +func (c *Client) GetRelationshipTimeline(ctx context.Context, options *GetRelationshipTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetRelationshipTimelineResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/relationships/{id}/timeline", + Method: "POST", + Options: options, + ContentType: "application/json", } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3109,10 +5723,10 @@ func (c *Client) ListMessages(ctx context.Context, options *ListMessagesRequestO return nil, fmt.Errorf("error creating request: %w", err) } - responseParser := func(ctx context.Context, resp *runtime.Response) (*ListMessagesResponse, error) { + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetRelationshipTimelineResponse, error) { bodyBytes := resp.Content if resp.StatusCode != 200 { - target := new(ListMessagesErrorResponse) + target := new(GetRelationshipTimelineErrorResponse) // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { @@ -3120,7 +5734,7 @@ func (c *Client) ListMessages(ctx context.Context, options *ListMessagesRequestO StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "ListMessagesErrorResponse", + TargetType: "GetRelationshipTimelineErrorResponse", Body: bodyBytes, Err: err, } @@ -3133,7 +5747,7 @@ func (c *Client) ListMessages(ctx context.Context, options *ListMessagesRequestO return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), runtime.WithStatusCode(resp.StatusCode)) } - target := new(ListMessagesResponse) + target := new(GetRelationshipTimelineResponse) // Handle empty response body gracefully if len(bodyBytes) == 0 { return target, nil @@ -3143,7 +5757,7 @@ func (c *Client) ListMessages(ctx context.Context, options *ListMessagesRequestO StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "ListMessagesResponse", + TargetType: "GetRelationshipTimelineResponse", Body: bodyBytes, Err: err, } @@ -3151,20 +5765,19 @@ func (c *Client) ListMessages(ctx context.Context, options *ListMessagesRequestO return target, nil } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/relationships/{id}/timeline") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } return responseParser(ctx, resp) } -// FilterMessages List filtered messages -func (c *Client) FilterMessages(ctx context.Context, options *FilterMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*FilterMessagesResponse, error) { +// ListSavedViews List shared analytical Saved Views +func (c *Client) ListSavedViews(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*ListSavedViewsResponse, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/filter", + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/saved-views", Method: "GET", - Options: options, } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3172,10 +5785,10 @@ func (c *Client) FilterMessages(ctx context.Context, options *FilterMessagesRequ return nil, fmt.Errorf("error creating request: %w", err) } - responseParser := func(ctx context.Context, resp *runtime.Response) (*FilterMessagesResponse, error) { + responseParser := func(ctx context.Context, resp *runtime.Response) (*ListSavedViewsResponse, error) { bodyBytes := resp.Content if resp.StatusCode != 200 { - target := new(FilterMessagesErrorResponse) + target := new(ListSavedViewsErrorResponse) // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { @@ -3183,7 +5796,7 @@ func (c *Client) FilterMessages(ctx context.Context, options *FilterMessagesRequ StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "FilterMessagesErrorResponse", + TargetType: "ListSavedViewsErrorResponse", Body: bodyBytes, Err: err, } @@ -3196,7 +5809,7 @@ func (c *Client) FilterMessages(ctx context.Context, options *FilterMessagesRequ return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), runtime.WithStatusCode(resp.StatusCode)) } - target := new(FilterMessagesResponse) + target := new(ListSavedViewsResponse) // Handle empty response body gracefully if len(bodyBytes) == 0 { return target, nil @@ -3206,7 +5819,7 @@ func (c *Client) FilterMessages(ctx context.Context, options *FilterMessagesRequ StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "FilterMessagesResponse", + TargetType: "ListSavedViewsResponse", Body: bodyBytes, Err: err, } @@ -3214,20 +5827,21 @@ func (c *Client) FilterMessages(ctx context.Context, options *FilterMessagesRequ return target, nil } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/filter") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/saved-views") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } return responseParser(ctx, resp) } -// GetGmailIDsByFilter List Gmail message IDs matching a filter -func (c *Client) GetGmailIDsByFilter(ctx context.Context, options *GetGmailIDsByFilterRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetGmailIDsByFilterResponse, error) { +// CreateSavedView Create a shared analytical Saved View +func (c *Client) CreateSavedView(ctx context.Context, options *CreateSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CreateSavedViewResponse, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/gmail-ids", - Method: "GET", - Options: options, + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/saved-views", + Method: "POST", + Options: options, + ContentType: "application/json", } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3235,10 +5849,10 @@ func (c *Client) GetGmailIDsByFilter(ctx context.Context, options *GetGmailIDsBy return nil, fmt.Errorf("error creating request: %w", err) } - responseParser := func(ctx context.Context, resp *runtime.Response) (*GetGmailIDsByFilterResponse, error) { + responseParser := func(ctx context.Context, resp *runtime.Response) (*CreateSavedViewResponse, error) { bodyBytes := resp.Content - if resp.StatusCode != 200 { - target := new(GetGmailIDsByFilterErrorResponse) + if resp.StatusCode != 201 { + target := new(CreateSavedViewErrorResponse) // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { @@ -3246,7 +5860,7 @@ func (c *Client) GetGmailIDsByFilter(ctx context.Context, options *GetGmailIDsBy StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetGmailIDsByFilterErrorResponse", + TargetType: "CreateSavedViewErrorResponse", Body: bodyBytes, Err: err, } @@ -3259,7 +5873,7 @@ func (c *Client) GetGmailIDsByFilter(ctx context.Context, options *GetGmailIDsBy return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), runtime.WithStatusCode(resp.StatusCode)) } - target := new(GetGmailIDsByFilterResponse) + target := new(CreateSavedViewResponse) // Handle empty response body gracefully if len(bodyBytes) == 0 { return target, nil @@ -3269,7 +5883,7 @@ func (c *Client) GetGmailIDsByFilter(ctx context.Context, options *GetGmailIDsBy StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetGmailIDsByFilterResponse", + TargetType: "CreateSavedViewResponse", Body: bodyBytes, Err: err, } @@ -3277,19 +5891,19 @@ func (c *Client) GetGmailIDsByFilter(ctx context.Context, options *GetGmailIDsBy return target, nil } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/gmail-ids") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/saved-views") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } return responseParser(ctx, resp) } -// GetMessage Get one message -func (c *Client) GetMessage(ctx context.Context, options *GetMessageRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetMessageResponse, error) { +// DeleteSavedView Delete a shared analytical Saved View +func (c *Client) DeleteSavedView(ctx context.Context, options *DeleteSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*struct{}, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}", - Method: "GET", + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/saved-views/{id}", + Method: "DELETE", Options: options, } @@ -3298,10 +5912,10 @@ func (c *Client) GetMessage(ctx context.Context, options *GetMessageRequestOptio return nil, fmt.Errorf("error creating request: %w", err) } - responseParser := func(ctx context.Context, resp *runtime.Response) (*GetMessageResponse, error) { + responseParser := func(ctx context.Context, resp *runtime.Response) (*struct{}, error) { bodyBytes := resp.Content - if resp.StatusCode != 200 { - target := new(GetMessageErrorResponse) + if resp.StatusCode != 204 { + target := new(DeleteSavedViewErrorResponse) // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { @@ -3309,7 +5923,7 @@ func (c *Client) GetMessage(ctx context.Context, options *GetMessageRequestOptio StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetMessageErrorResponse", + TargetType: "DeleteSavedViewErrorResponse", Body: bodyBytes, Err: err, } @@ -3322,36 +5936,21 @@ func (c *Client) GetMessage(ctx context.Context, options *GetMessageRequestOptio return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), runtime.WithStatusCode(resp.StatusCode)) } - target := new(GetMessageResponse) - // Handle empty response body gracefully - if len(bodyBytes) == 0 { - return target, nil - } - if err = json.Unmarshal(bodyBytes, target); err != nil { - return nil, &runtime.ResponseDecodeError{ - StatusCode: resp.StatusCode, - ContentType: resp.Headers.Get("Content-Type"), - ContentLength: len(bodyBytes), - TargetType: "GetMessageResponse", - Body: bodyBytes, - Err: err, - } - } - return target, nil + return nil, nil } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/saved-views/{id}") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } return responseParser(ctx, resp) } -// GetMessageInlinePart Get an inline MIME part -func (c *Client) GetMessageInlinePart(ctx context.Context, options *GetMessageInlinePartRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetMessageInlinePartResponse, error) { +// GetSavedView Get a shared analytical Saved View +func (c *Client) GetSavedView(ctx context.Context, options *GetSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetSavedViewResponse, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}/inline", + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/saved-views/{id}", Method: "GET", Options: options, } @@ -3361,10 +5960,10 @@ func (c *Client) GetMessageInlinePart(ctx context.Context, options *GetMessageIn return nil, fmt.Errorf("error creating request: %w", err) } - responseParser := func(ctx context.Context, resp *runtime.Response) (*GetMessageInlinePartResponse, error) { + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetSavedViewResponse, error) { bodyBytes := resp.Content if resp.StatusCode != 200 { - target := new(GetMessageInlinePartErrorResponse) + target := new(GetSavedViewErrorResponse) // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { @@ -3372,7 +5971,7 @@ func (c *Client) GetMessageInlinePart(ctx context.Context, options *GetMessageIn StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetMessageInlinePartErrorResponse", + TargetType: "GetSavedViewErrorResponse", Body: bodyBytes, Err: err, } @@ -3385,7 +5984,7 @@ func (c *Client) GetMessageInlinePart(ctx context.Context, options *GetMessageIn return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), runtime.WithStatusCode(resp.StatusCode)) } - target := new(GetMessageInlinePartResponse) + target := new(GetSavedViewResponse) // Handle empty response body gracefully if len(bodyBytes) == 0 { return target, nil @@ -3395,7 +5994,7 @@ func (c *Client) GetMessageInlinePart(ctx context.Context, options *GetMessageIn StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetMessageInlinePartResponse", + TargetType: "GetSavedViewResponse", Body: bodyBytes, Err: err, } @@ -3403,19 +6002,19 @@ func (c *Client) GetMessageInlinePart(ctx context.Context, options *GetMessageIn return target, nil } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}/inline") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/saved-views/{id}") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } return responseParser(ctx, resp) } -// RunQuery Run an aggregate query -func (c *Client) RunQuery(ctx context.Context, options *RunQueryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*RunQueryResponse, error) { +// PatchSavedView Update a shared analytical Saved View +func (c *Client) PatchSavedView(ctx context.Context, options *PatchSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PatchSavedViewResponse, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/query", - Method: "POST", + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/saved-views/{id}", + Method: "PATCH", Options: options, ContentType: "application/json", } @@ -3425,10 +6024,10 @@ func (c *Client) RunQuery(ctx context.Context, options *RunQueryRequestOptions, return nil, fmt.Errorf("error creating request: %w", err) } - responseParser := func(ctx context.Context, resp *runtime.Response) (*RunQueryResponse, error) { + responseParser := func(ctx context.Context, resp *runtime.Response) (*PatchSavedViewResponse, error) { bodyBytes := resp.Content if resp.StatusCode != 200 { - target := new(RunQueryErrorResponse) + target := new(PatchSavedViewErrorResponse) // Handle empty error response body gracefully - skip unmarshal if no content if len(bodyBytes) > 0 { if err = json.Unmarshal(bodyBytes, target); err != nil { @@ -3436,7 +6035,7 @@ func (c *Client) RunQuery(ctx context.Context, options *RunQueryRequestOptions, StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "RunQueryErrorResponse", + TargetType: "PatchSavedViewErrorResponse", Body: bodyBytes, Err: err, } @@ -3449,7 +6048,7 @@ func (c *Client) RunQuery(ctx context.Context, options *RunQueryRequestOptions, return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), runtime.WithStatusCode(resp.StatusCode)) } - target := new(RunQueryResponse) + target := new(PatchSavedViewResponse) // Handle empty response body gracefully if len(bodyBytes) == 0 { return target, nil @@ -3459,7 +6058,7 @@ func (c *Client) RunQuery(ctx context.Context, options *RunQueryRequestOptions, StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "RunQueryResponse", + TargetType: "PatchSavedViewResponse", Body: bodyBytes, Err: err, } @@ -3467,7 +6066,7 @@ func (c *Client) RunQuery(ctx context.Context, options *RunQueryRequestOptions, return target, nil } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/query") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/saved-views/{id}") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } @@ -3599,6 +6198,70 @@ func (c *Client) SearchMessages(ctx context.Context, options *SearchMessagesRequ return responseParser(ctx, resp) } +// GetSearchCoverage Get semantic index coverage for an analytical context +func (c *Client) GetSearchCoverage(ctx context.Context, options *GetSearchCoverageRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetSearchCoverageResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/search/coverage", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetSearchCoverageResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetSearchCoverageErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSearchCoverageErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetSearchCoverageResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSearchCoverageResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/search/coverage") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + // DeepSearch Run full-text message search func (c *Client) DeepSearch(ctx context.Context, options *DeepSearchRequestOptions, reqEditors ...runtime.RequestEditorFn) (*DeepSearchResponseJSON, error) { var err error @@ -3851,6 +6514,132 @@ func (c *Client) FindSimilarMessages(ctx context.Context, options *FindSimilarMe return responseParser(ctx, resp) } +// GetSettings Get browser-managed settings +func (c *Client) GetSettings(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSettingsResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/settings", + Method: "GET", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*GetSettingsResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(GetSettingsErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSettingsErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(GetSettingsResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSettingsResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/settings") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + +// PatchSettings Update browser-managed settings +func (c *Client) PatchSettings(ctx context.Context, options *PatchSettingsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PatchSettingsResponse, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/settings", + Method: "PATCH", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + responseParser := func(ctx context.Context, resp *runtime.Response) (*PatchSettingsResponse, error) { + bodyBytes := resp.Content + if resp.StatusCode != 200 { + target := new(PatchSettingsErrorResponse) + // Handle empty error response body gracefully - skip unmarshal if no content + if len(bodyBytes) > 0 { + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PatchSettingsErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + // Return error with (possibly empty) target + if errTarget, ok := any(*target).(error); ok { + return nil, runtime.NewClientAPIError(errTarget, runtime.WithStatusCode(resp.StatusCode)) + } + return nil, runtime.NewClientAPIError(fmt.Errorf("API error (status %d): %v", resp.StatusCode, *target), + runtime.WithStatusCode(resp.StatusCode)) + } + target := new(PatchSettingsResponse) + // Handle empty response body gracefully + if len(bodyBytes) == 0 { + return target, nil + } + if err = json.Unmarshal(bodyBytes, target); err != nil { + return nil, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PatchSettingsResponse", + Body: bodyBytes, + Err: err, + } + } + return target, nil + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/settings") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + return responseParser(ctx, resp) +} + // ListSourceStatus List source sync status func (c *Client) ListSourceStatus(ctx context.Context, options *ListSourceStatusRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListSourceStatusResponse, error) { var err error diff --git a/pkg/client/generated/client_options.go b/pkg/client/generated/client_options.go index f06885219..36ff1ee93 100644 --- a/pkg/client/generated/client_options.go +++ b/pkg/client/generated/client_options.go @@ -6,6 +6,50 @@ import ( "github.com/doordash-oss/oapi-codegen-dd/v3/pkg/runtime" ) +// LoginSessionRequestOptions is the options needed to make a request to LoginSession. +type LoginSessionRequestOptions struct { + Body *LoginSessionBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *LoginSessionRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *LoginSessionRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *LoginSessionRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *LoginSessionRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *LoginSessionRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + // AddAccountRequestOptions is the options needed to make a request to AddAccount. type AddAccountRequestOptions struct { Body *AddAccountBody @@ -1485,6 +1529,59 @@ func (o *VerifyCLIRequestOptions) GetHeader() (map[string]string, error) { return nil, nil } +// GetConversationRequestOptions is the options needed to make a request to GetConversation. +type GetConversationRequestOptions struct { + PathParams *GetConversationPath + Query *GetConversationQuery +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetConversationRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetConversationRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *GetConversationRequestOptions) GetQuery() (map[string]any, error) { + return runtime.AsMap[any](o.Query) +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetConversationRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *GetConversationRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + // ListDeletionsRequestOptions is the options needed to make a request to ListDeletions. type ListDeletionsRequestOptions struct { Query *ListDeletionsQuery @@ -1617,20 +1714,20 @@ func (o *CancelDeletionRequestOptions) GetHeader() (map[string]string, error) { return nil, nil } -// ListMessagesRequestOptions is the options needed to make a request to ListMessages. -type ListMessagesRequestOptions struct { - Query *ListMessagesQuery +// GetDeletionRequestOptions is the options needed to make a request to GetDeletion. +type GetDeletionRequestOptions struct { + PathParams *GetDeletionPath } // Validate validates all the fields in the options. // Use it if fields validation was not run. -func (o *ListMessagesRequestOptions) Validate() error { +func (o *GetDeletionRequestOptions) Validate() error { var errors runtime.ValidationErrors - if o.Query != nil { - if v, ok := any(o.Query).(runtime.Validator); ok { + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append("Query", err) + errors = errors.Append("PathParams", err) } } } @@ -1642,39 +1739,39 @@ func (o *ListMessagesRequestOptions) Validate() error { } // GetPathParams returns the path params as a map. -func (o *ListMessagesRequestOptions) GetPathParams() (map[string]any, error) { - return nil, nil +func (o *GetDeletionRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) } // GetQuery returns the query params as a map. -func (o *ListMessagesRequestOptions) GetQuery() (map[string]any, error) { - return runtime.AsMap[any](o.Query) +func (o *GetDeletionRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil } // GetBody returns the payload in any type that can be marshalled to JSON by the client. -func (o *ListMessagesRequestOptions) GetBody() any { +func (o *GetDeletionRequestOptions) GetBody() any { return nil } // GetHeader returns the headers as a map. -func (o *ListMessagesRequestOptions) GetHeader() (map[string]string, error) { +func (o *GetDeletionRequestOptions) GetHeader() (map[string]string, error) { return nil, nil } -// FilterMessagesRequestOptions is the options needed to make a request to FilterMessages. -type FilterMessagesRequestOptions struct { - Query *FilterMessagesQuery +// SearchDomainsRequestOptions is the options needed to make a request to SearchDomains. +type SearchDomainsRequestOptions struct { + Body *SearchDomainsBody } // Validate validates all the fields in the options. // Use it if fields validation was not run. -func (o *FilterMessagesRequestOptions) Validate() error { +func (o *SearchDomainsRequestOptions) Validate() error { var errors runtime.ValidationErrors - if o.Query != nil { - if v, ok := any(o.Query).(runtime.Validator); ok { + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append("Query", err) + errors = errors.Append("Body", err) } } } @@ -1686,39 +1783,39 @@ func (o *FilterMessagesRequestOptions) Validate() error { } // GetPathParams returns the path params as a map. -func (o *FilterMessagesRequestOptions) GetPathParams() (map[string]any, error) { +func (o *SearchDomainsRequestOptions) GetPathParams() (map[string]any, error) { return nil, nil } // GetQuery returns the query params as a map. -func (o *FilterMessagesRequestOptions) GetQuery() (map[string]any, error) { - return runtime.AsMap[any](o.Query) +func (o *SearchDomainsRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil } // GetBody returns the payload in any type that can be marshalled to JSON by the client. -func (o *FilterMessagesRequestOptions) GetBody() any { - return nil +func (o *SearchDomainsRequestOptions) GetBody() any { + return o.Body } // GetHeader returns the headers as a map. -func (o *FilterMessagesRequestOptions) GetHeader() (map[string]string, error) { +func (o *SearchDomainsRequestOptions) GetHeader() (map[string]string, error) { return nil, nil } -// GetGmailIDsByFilterRequestOptions is the options needed to make a request to GetGmailIDsByFilter. -type GetGmailIDsByFilterRequestOptions struct { - Query *GetGmailIDsByFilterQuery +// GetDomainRequestOptions is the options needed to make a request to GetDomain. +type GetDomainRequestOptions struct { + PathParams *GetDomainPath } // Validate validates all the fields in the options. // Use it if fields validation was not run. -func (o *GetGmailIDsByFilterRequestOptions) Validate() error { +func (o *GetDomainRequestOptions) Validate() error { var errors runtime.ValidationErrors - if o.Query != nil { - if v, ok := any(o.Query).(runtime.Validator); ok { + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append("Query", err) + errors = errors.Append("PathParams", err) } } } @@ -1730,33 +1827,34 @@ func (o *GetGmailIDsByFilterRequestOptions) Validate() error { } // GetPathParams returns the path params as a map. -func (o *GetGmailIDsByFilterRequestOptions) GetPathParams() (map[string]any, error) { - return nil, nil +func (o *GetDomainRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) } // GetQuery returns the query params as a map. -func (o *GetGmailIDsByFilterRequestOptions) GetQuery() (map[string]any, error) { - return runtime.AsMap[any](o.Query) +func (o *GetDomainRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil } // GetBody returns the payload in any type that can be marshalled to JSON by the client. -func (o *GetGmailIDsByFilterRequestOptions) GetBody() any { +func (o *GetDomainRequestOptions) GetBody() any { return nil } // GetHeader returns the headers as a map. -func (o *GetGmailIDsByFilterRequestOptions) GetHeader() (map[string]string, error) { +func (o *GetDomainRequestOptions) GetHeader() (map[string]string, error) { return nil, nil } -// GetMessageRequestOptions is the options needed to make a request to GetMessage. -type GetMessageRequestOptions struct { - PathParams *GetMessagePath +// SearchDomainFilesRequestOptions is the options needed to make a request to SearchDomainFiles. +type SearchDomainFilesRequestOptions struct { + PathParams *SearchDomainFilesPath + Body *SearchDomainFilesBody } // Validate validates all the fields in the options. // Use it if fields validation was not run. -func (o *GetMessageRequestOptions) Validate() error { +func (o *SearchDomainFilesRequestOptions) Validate() error { var errors runtime.ValidationErrors if o.PathParams != nil { @@ -1766,6 +1864,14 @@ func (o *GetMessageRequestOptions) Validate() error { } } } + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } if len(errors) == 0 { return nil } @@ -1774,34 +1880,34 @@ func (o *GetMessageRequestOptions) Validate() error { } // GetPathParams returns the path params as a map. -func (o *GetMessageRequestOptions) GetPathParams() (map[string]any, error) { +func (o *SearchDomainFilesRequestOptions) GetPathParams() (map[string]any, error) { return runtime.AsMap[any](o.PathParams) } // GetQuery returns the query params as a map. -func (o *GetMessageRequestOptions) GetQuery() (map[string]any, error) { +func (o *SearchDomainFilesRequestOptions) GetQuery() (map[string]any, error) { return nil, nil } // GetBody returns the payload in any type that can be marshalled to JSON by the client. -func (o *GetMessageRequestOptions) GetBody() any { - return nil +func (o *SearchDomainFilesRequestOptions) GetBody() any { + return o.Body } // GetHeader returns the headers as a map. -func (o *GetMessageRequestOptions) GetHeader() (map[string]string, error) { +func (o *SearchDomainFilesRequestOptions) GetHeader() (map[string]string, error) { return nil, nil } -// GetMessageInlinePartRequestOptions is the options needed to make a request to GetMessageInlinePart. -type GetMessageInlinePartRequestOptions struct { - PathParams *GetMessageInlinePartPath - Query *GetMessageInlinePartQuery +// GetDomainContextSummaryRequestOptions is the options needed to make a request to GetDomainContextSummary. +type GetDomainContextSummaryRequestOptions struct { + PathParams *GetDomainContextSummaryPath + Body *GetDomainContextSummaryBody } // Validate validates all the fields in the options. // Use it if fields validation was not run. -func (o *GetMessageInlinePartRequestOptions) Validate() error { +func (o *GetDomainContextSummaryRequestOptions) Validate() error { var errors runtime.ValidationErrors if o.PathParams != nil { @@ -1812,10 +1918,10 @@ func (o *GetMessageInlinePartRequestOptions) Validate() error { } } - if o.Query != nil { - if v, ok := any(o.Query).(runtime.Validator); ok { + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append("Query", err) + errors = errors.Append("Body", err) } } } @@ -1827,35 +1933,44 @@ func (o *GetMessageInlinePartRequestOptions) Validate() error { } // GetPathParams returns the path params as a map. -func (o *GetMessageInlinePartRequestOptions) GetPathParams() (map[string]any, error) { +func (o *GetDomainContextSummaryRequestOptions) GetPathParams() (map[string]any, error) { return runtime.AsMap[any](o.PathParams) } // GetQuery returns the query params as a map. -func (o *GetMessageInlinePartRequestOptions) GetQuery() (map[string]any, error) { - return runtime.AsMap[any](o.Query) +func (o *GetDomainContextSummaryRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil } // GetBody returns the payload in any type that can be marshalled to JSON by the client. -func (o *GetMessageInlinePartRequestOptions) GetBody() any { - return nil +func (o *GetDomainContextSummaryRequestOptions) GetBody() any { + return o.Body } // GetHeader returns the headers as a map. -func (o *GetMessageInlinePartRequestOptions) GetHeader() (map[string]string, error) { +func (o *GetDomainContextSummaryRequestOptions) GetHeader() (map[string]string, error) { return nil, nil } -// RunQueryRequestOptions is the options needed to make a request to RunQuery. -type RunQueryRequestOptions struct { - Body *RunQueryBody +// GetDomainTimelineRequestOptions is the options needed to make a request to GetDomainTimeline. +type GetDomainTimelineRequestOptions struct { + PathParams *GetDomainTimelinePath + Body *GetDomainTimelineBody } // Validate validates all the fields in the options. // Use it if fields validation was not run. -func (o *RunQueryRequestOptions) Validate() error { +func (o *GetDomainTimelineRequestOptions) Validate() error { var errors runtime.ValidationErrors + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if o.Body != nil { if v, ok := any(o.Body).(runtime.Validator); ok { if err := v.Validate(); err != nil { @@ -1871,39 +1986,39 @@ func (o *RunQueryRequestOptions) Validate() error { } // GetPathParams returns the path params as a map. -func (o *RunQueryRequestOptions) GetPathParams() (map[string]any, error) { - return nil, nil +func (o *GetDomainTimelineRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) } // GetQuery returns the query params as a map. -func (o *RunQueryRequestOptions) GetQuery() (map[string]any, error) { +func (o *GetDomainTimelineRequestOptions) GetQuery() (map[string]any, error) { return nil, nil } // GetBody returns the payload in any type that can be marshalled to JSON by the client. -func (o *RunQueryRequestOptions) GetBody() any { +func (o *GetDomainTimelineRequestOptions) GetBody() any { return o.Body } // GetHeader returns the headers as a map. -func (o *RunQueryRequestOptions) GetHeader() (map[string]string, error) { +func (o *GetDomainTimelineRequestOptions) GetHeader() (map[string]string, error) { return nil, nil } -// SearchMessagesRequestOptions is the options needed to make a request to SearchMessages. -type SearchMessagesRequestOptions struct { - Query *SearchMessagesQuery +// ExploreRequestOptions is the options needed to make a request to Explore. +type ExploreRequestOptions struct { + Body *ExploreBody } // Validate validates all the fields in the options. // Use it if fields validation was not run. -func (o *SearchMessagesRequestOptions) Validate() error { +func (o *ExploreRequestOptions) Validate() error { var errors runtime.ValidationErrors - if o.Query != nil { - if v, ok := any(o.Query).(runtime.Validator); ok { + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append("Query", err) + errors = errors.Append("Body", err) } } } @@ -1915,39 +2030,39 @@ func (o *SearchMessagesRequestOptions) Validate() error { } // GetPathParams returns the path params as a map. -func (o *SearchMessagesRequestOptions) GetPathParams() (map[string]any, error) { +func (o *ExploreRequestOptions) GetPathParams() (map[string]any, error) { return nil, nil } // GetQuery returns the query params as a map. -func (o *SearchMessagesRequestOptions) GetQuery() (map[string]any, error) { - return runtime.AsMap[any](o.Query) +func (o *ExploreRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil } // GetBody returns the payload in any type that can be marshalled to JSON by the client. -func (o *SearchMessagesRequestOptions) GetBody() any { - return nil +func (o *ExploreRequestOptions) GetBody() any { + return o.Body } // GetHeader returns the headers as a map. -func (o *SearchMessagesRequestOptions) GetHeader() (map[string]string, error) { +func (o *ExploreRequestOptions) GetHeader() (map[string]string, error) { return nil, nil } -// DeepSearchRequestOptions is the options needed to make a request to DeepSearch. -type DeepSearchRequestOptions struct { - Query *DeepSearchQuery +// ListExploreFilesRequestOptions is the options needed to make a request to ListExploreFiles. +type ListExploreFilesRequestOptions struct { + Body *ListExploreFilesBody } // Validate validates all the fields in the options. // Use it if fields validation was not run. -func (o *DeepSearchRequestOptions) Validate() error { +func (o *ListExploreFilesRequestOptions) Validate() error { var errors runtime.ValidationErrors - if o.Query != nil { - if v, ok := any(o.Query).(runtime.Validator); ok { + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append("Query", err) + errors = errors.Append("Body", err) } } } @@ -1959,39 +2074,39 @@ func (o *DeepSearchRequestOptions) Validate() error { } // GetPathParams returns the path params as a map. -func (o *DeepSearchRequestOptions) GetPathParams() (map[string]any, error) { +func (o *ListExploreFilesRequestOptions) GetPathParams() (map[string]any, error) { return nil, nil } // GetQuery returns the query params as a map. -func (o *DeepSearchRequestOptions) GetQuery() (map[string]any, error) { - return runtime.AsMap[any](o.Query) +func (o *ListExploreFilesRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil } // GetBody returns the payload in any type that can be marshalled to JSON by the client. -func (o *DeepSearchRequestOptions) GetBody() any { - return nil +func (o *ListExploreFilesRequestOptions) GetBody() any { + return o.Body } // GetHeader returns the headers as a map. -func (o *DeepSearchRequestOptions) GetHeader() (map[string]string, error) { +func (o *ListExploreFilesRequestOptions) GetHeader() (map[string]string, error) { return nil, nil } -// SearchMessagesByDomainsRequestOptions is the options needed to make a request to SearchMessagesByDomains. -type SearchMessagesByDomainsRequestOptions struct { - Query *SearchMessagesByDomainsQuery +// ExploreGroupsRequestOptions is the options needed to make a request to ExploreGroups. +type ExploreGroupsRequestOptions struct { + Body *ExploreGroupsBody } // Validate validates all the fields in the options. // Use it if fields validation was not run. -func (o *SearchMessagesByDomainsRequestOptions) Validate() error { +func (o *ExploreGroupsRequestOptions) Validate() error { var errors runtime.ValidationErrors - if o.Query != nil { - if v, ok := any(o.Query).(runtime.Validator); ok { + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append("Query", err) + errors = errors.Append("Body", err) } } } @@ -2003,39 +2118,39 @@ func (o *SearchMessagesByDomainsRequestOptions) Validate() error { } // GetPathParams returns the path params as a map. -func (o *SearchMessagesByDomainsRequestOptions) GetPathParams() (map[string]any, error) { +func (o *ExploreGroupsRequestOptions) GetPathParams() (map[string]any, error) { return nil, nil } // GetQuery returns the query params as a map. -func (o *SearchMessagesByDomainsRequestOptions) GetQuery() (map[string]any, error) { - return runtime.AsMap[any](o.Query) +func (o *ExploreGroupsRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil } // GetBody returns the payload in any type that can be marshalled to JSON by the client. -func (o *SearchMessagesByDomainsRequestOptions) GetBody() any { - return nil +func (o *ExploreGroupsRequestOptions) GetBody() any { + return o.Body } // GetHeader returns the headers as a map. -func (o *SearchMessagesByDomainsRequestOptions) GetHeader() (map[string]string, error) { +func (o *ExploreGroupsRequestOptions) GetHeader() (map[string]string, error) { return nil, nil } -// FastSearchRequestOptions is the options needed to make a request to FastSearch. -type FastSearchRequestOptions struct { - Query *FastSearchQuery +// CountExploreMatchesRequestOptions is the options needed to make a request to CountExploreMatches. +type CountExploreMatchesRequestOptions struct { + Body *CountExploreMatchesBody } // Validate validates all the fields in the options. // Use it if fields validation was not run. -func (o *FastSearchRequestOptions) Validate() error { +func (o *CountExploreMatchesRequestOptions) Validate() error { var errors runtime.ValidationErrors - if o.Query != nil { - if v, ok := any(o.Query).(runtime.Validator); ok { + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append("Query", err) + errors = errors.Append("Body", err) } } } @@ -2047,13 +2162,1555 @@ func (o *FastSearchRequestOptions) Validate() error { } // GetPathParams returns the path params as a map. -func (o *FastSearchRequestOptions) GetPathParams() (map[string]any, error) { +func (o *CountExploreMatchesRequestOptions) GetPathParams() (map[string]any, error) { return nil, nil } // GetQuery returns the query params as a map. -func (o *FastSearchRequestOptions) GetQuery() (map[string]any, error) { - return runtime.AsMap[any](o.Query) +func (o *CountExploreMatchesRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *CountExploreMatchesRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *CountExploreMatchesRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// PreflightExploreSelectionRequestOptions is the options needed to make a request to PreflightExploreSelection. +type PreflightExploreSelectionRequestOptions struct { + Body *PreflightExploreSelectionBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *PreflightExploreSelectionRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *PreflightExploreSelectionRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *PreflightExploreSelectionRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *PreflightExploreSelectionRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *PreflightExploreSelectionRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// GroupFilesRequestOptions is the options needed to make a request to GroupFiles. +type GroupFilesRequestOptions struct { + Body *GroupFilesBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GroupFilesRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GroupFilesRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *GroupFilesRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GroupFilesRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *GroupFilesRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// SearchFilesRequestOptions is the options needed to make a request to SearchFiles. +type SearchFilesRequestOptions struct { + Body *SearchFilesBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *SearchFilesRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *SearchFilesRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *SearchFilesRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *SearchFilesRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *SearchFilesRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// GetFileRequestOptions is the options needed to make a request to GetFile. +type GetFileRequestOptions struct { + PathParams *GetFilePath +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetFileRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetFileRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *GetFileRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetFileRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *GetFileRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// GetFileContentRequestOptions is the options needed to make a request to GetFileContent. +type GetFileContentRequestOptions struct { + PathParams *GetFileContentPath +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetFileContentRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetFileContentRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *GetFileContentRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetFileContentRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *GetFileContentRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// LinkIdentityParticipantsRequestOptions is the options needed to make a request to LinkIdentityParticipants. +type LinkIdentityParticipantsRequestOptions struct { + Body *LinkIdentityParticipantsBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *LinkIdentityParticipantsRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *LinkIdentityParticipantsRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *LinkIdentityParticipantsRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *LinkIdentityParticipantsRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *LinkIdentityParticipantsRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// UnlinkIdentityParticipantsRequestOptions is the options needed to make a request to UnlinkIdentityParticipants. +type UnlinkIdentityParticipantsRequestOptions struct { + Body *UnlinkIdentityParticipantsBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *UnlinkIdentityParticipantsRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *UnlinkIdentityParticipantsRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *UnlinkIdentityParticipantsRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *UnlinkIdentityParticipantsRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *UnlinkIdentityParticipantsRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// SearchIntegrationTasksRequestOptions is the options needed to make a request to SearchIntegrationTasks. +type SearchIntegrationTasksRequestOptions struct { + Query *SearchIntegrationTasksQuery +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *SearchIntegrationTasksRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *SearchIntegrationTasksRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *SearchIntegrationTasksRequestOptions) GetQuery() (map[string]any, error) { + return runtime.AsMap[any](o.Query) +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *SearchIntegrationTasksRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *SearchIntegrationTasksRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// ListMessagesRequestOptions is the options needed to make a request to ListMessages. +type ListMessagesRequestOptions struct { + Query *ListMessagesQuery +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *ListMessagesRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *ListMessagesRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *ListMessagesRequestOptions) GetQuery() (map[string]any, error) { + return runtime.AsMap[any](o.Query) +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *ListMessagesRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *ListMessagesRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// FilterMessagesRequestOptions is the options needed to make a request to FilterMessages. +type FilterMessagesRequestOptions struct { + Query *FilterMessagesQuery +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *FilterMessagesRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *FilterMessagesRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *FilterMessagesRequestOptions) GetQuery() (map[string]any, error) { + return runtime.AsMap[any](o.Query) +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *FilterMessagesRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *FilterMessagesRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// GetGmailIDsByFilterRequestOptions is the options needed to make a request to GetGmailIDsByFilter. +type GetGmailIDsByFilterRequestOptions struct { + Query *GetGmailIDsByFilterQuery +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetGmailIDsByFilterRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetGmailIDsByFilterRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *GetGmailIDsByFilterRequestOptions) GetQuery() (map[string]any, error) { + return runtime.AsMap[any](o.Query) +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetGmailIDsByFilterRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *GetGmailIDsByFilterRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// GetMessageRequestOptions is the options needed to make a request to GetMessage. +type GetMessageRequestOptions struct { + PathParams *GetMessagePath +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetMessageRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetMessageRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *GetMessageRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetMessageRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *GetMessageRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// GetMessageInlinePartRequestOptions is the options needed to make a request to GetMessageInlinePart. +type GetMessageInlinePartRequestOptions struct { + PathParams *GetMessageInlinePartPath + Query *GetMessageInlinePartQuery +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetMessageInlinePartRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetMessageInlinePartRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *GetMessageInlinePartRequestOptions) GetQuery() (map[string]any, error) { + return runtime.AsMap[any](o.Query) +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetMessageInlinePartRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *GetMessageInlinePartRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// ListMessageTasksRequestOptions is the options needed to make a request to ListMessageTasks. +type ListMessageTasksRequestOptions struct { + PathParams *ListMessageTasksPath +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *ListMessageTasksRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *ListMessageTasksRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *ListMessageTasksRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *ListMessageTasksRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *ListMessageTasksRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// CreateOrLinkMessageTaskRequestOptions is the options needed to make a request to CreateOrLinkMessageTask. +type CreateOrLinkMessageTaskRequestOptions struct { + PathParams *CreateOrLinkMessageTaskPath + Body *CreateOrLinkMessageTaskBody + Header *CreateOrLinkMessageTaskHeaders +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *CreateOrLinkMessageTaskRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + + if o.Header != nil { + if v, ok := any(o.Header).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Header", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *CreateOrLinkMessageTaskRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *CreateOrLinkMessageTaskRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *CreateOrLinkMessageTaskRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *CreateOrLinkMessageTaskRequestOptions) GetHeader() (map[string]string, error) { + return runtime.AsMap[string](o.Header) +} + +// UnlinkMessageTaskRequestOptions is the options needed to make a request to UnlinkMessageTask. +type UnlinkMessageTaskRequestOptions struct { + PathParams *UnlinkMessageTaskPath +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *UnlinkMessageTaskRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *UnlinkMessageTaskRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *UnlinkMessageTaskRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *UnlinkMessageTaskRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *UnlinkMessageTaskRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// SearchPeopleRequestOptions is the options needed to make a request to SearchPeople. +type SearchPeopleRequestOptions struct { + Body *SearchPeopleBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *SearchPeopleRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *SearchPeopleRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *SearchPeopleRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *SearchPeopleRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *SearchPeopleRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// GetPersonRequestOptions is the options needed to make a request to GetPerson. +type GetPersonRequestOptions struct { + PathParams *GetPersonPath +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetPersonRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetPersonRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *GetPersonRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetPersonRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *GetPersonRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// SearchPersonFilesRequestOptions is the options needed to make a request to SearchPersonFiles. +type SearchPersonFilesRequestOptions struct { + PathParams *SearchPersonFilesPath + Body *SearchPersonFilesBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *SearchPersonFilesRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *SearchPersonFilesRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *SearchPersonFilesRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *SearchPersonFilesRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *SearchPersonFilesRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// GetPersonContextSummaryRequestOptions is the options needed to make a request to GetPersonContextSummary. +type GetPersonContextSummaryRequestOptions struct { + PathParams *GetPersonContextSummaryPath + Body *GetPersonContextSummaryBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetPersonContextSummaryRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetPersonContextSummaryRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *GetPersonContextSummaryRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetPersonContextSummaryRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *GetPersonContextSummaryRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// GetPersonTimelineRequestOptions is the options needed to make a request to GetPersonTimeline. +type GetPersonTimelineRequestOptions struct { + PathParams *GetPersonTimelinePath + Body *GetPersonTimelineBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetPersonTimelineRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetPersonTimelineRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *GetPersonTimelineRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetPersonTimelineRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *GetPersonTimelineRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// RunQueryRequestOptions is the options needed to make a request to RunQuery. +type RunQueryRequestOptions struct { + Body *RunQueryBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *RunQueryRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *RunQueryRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *RunQueryRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *RunQueryRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *RunQueryRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// ListRelationshipsRequestOptions is the options needed to make a request to ListRelationships. +type ListRelationshipsRequestOptions struct { + Body *ListRelationshipsBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *ListRelationshipsRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *ListRelationshipsRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *ListRelationshipsRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *ListRelationshipsRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *ListRelationshipsRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// GetRelationshipTimelineRequestOptions is the options needed to make a request to GetRelationshipTimeline. +type GetRelationshipTimelineRequestOptions struct { + PathParams *GetRelationshipTimelinePath + Body *GetRelationshipTimelineBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetRelationshipTimelineRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetRelationshipTimelineRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *GetRelationshipTimelineRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetRelationshipTimelineRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *GetRelationshipTimelineRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// CreateSavedViewRequestOptions is the options needed to make a request to CreateSavedView. +type CreateSavedViewRequestOptions struct { + Body *CreateSavedViewBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *CreateSavedViewRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *CreateSavedViewRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *CreateSavedViewRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *CreateSavedViewRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *CreateSavedViewRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// DeleteSavedViewRequestOptions is the options needed to make a request to DeleteSavedView. +type DeleteSavedViewRequestOptions struct { + PathParams *DeleteSavedViewPath + Header *DeleteSavedViewHeaders +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *DeleteSavedViewRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + + if o.Header != nil { + if v, ok := any(o.Header).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Header", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *DeleteSavedViewRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *DeleteSavedViewRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *DeleteSavedViewRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *DeleteSavedViewRequestOptions) GetHeader() (map[string]string, error) { + return runtime.AsMap[string](o.Header) +} + +// GetSavedViewRequestOptions is the options needed to make a request to GetSavedView. +type GetSavedViewRequestOptions struct { + PathParams *GetSavedViewPath +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetSavedViewRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetSavedViewRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *GetSavedViewRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetSavedViewRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *GetSavedViewRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// PatchSavedViewRequestOptions is the options needed to make a request to PatchSavedView. +type PatchSavedViewRequestOptions struct { + PathParams *PatchSavedViewPath + Body *PatchSavedViewBody + Header *PatchSavedViewHeaders +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *PatchSavedViewRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.PathParams != nil { + if v, ok := any(o.PathParams).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("PathParams", err) + } + } + } + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + + if o.Header != nil { + if v, ok := any(o.Header).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Header", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *PatchSavedViewRequestOptions) GetPathParams() (map[string]any, error) { + return runtime.AsMap[any](o.PathParams) +} + +// GetQuery returns the query params as a map. +func (o *PatchSavedViewRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *PatchSavedViewRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *PatchSavedViewRequestOptions) GetHeader() (map[string]string, error) { + return runtime.AsMap[string](o.Header) +} + +// SearchMessagesRequestOptions is the options needed to make a request to SearchMessages. +type SearchMessagesRequestOptions struct { + Query *SearchMessagesQuery +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *SearchMessagesRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *SearchMessagesRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *SearchMessagesRequestOptions) GetQuery() (map[string]any, error) { + return runtime.AsMap[any](o.Query) +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *SearchMessagesRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *SearchMessagesRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// GetSearchCoverageRequestOptions is the options needed to make a request to GetSearchCoverage. +type GetSearchCoverageRequestOptions struct { + Body *GetSearchCoverageBody +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *GetSearchCoverageRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *GetSearchCoverageRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *GetSearchCoverageRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *GetSearchCoverageRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *GetSearchCoverageRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// DeepSearchRequestOptions is the options needed to make a request to DeepSearch. +type DeepSearchRequestOptions struct { + Query *DeepSearchQuery +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *DeepSearchRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *DeepSearchRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *DeepSearchRequestOptions) GetQuery() (map[string]any, error) { + return runtime.AsMap[any](o.Query) +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *DeepSearchRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *DeepSearchRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// SearchMessagesByDomainsRequestOptions is the options needed to make a request to SearchMessagesByDomains. +type SearchMessagesByDomainsRequestOptions struct { + Query *SearchMessagesByDomainsQuery +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *SearchMessagesByDomainsRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *SearchMessagesByDomainsRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *SearchMessagesByDomainsRequestOptions) GetQuery() (map[string]any, error) { + return runtime.AsMap[any](o.Query) +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *SearchMessagesByDomainsRequestOptions) GetBody() any { + return nil +} + +// GetHeader returns the headers as a map. +func (o *SearchMessagesByDomainsRequestOptions) GetHeader() (map[string]string, error) { + return nil, nil +} + +// FastSearchRequestOptions is the options needed to make a request to FastSearch. +type FastSearchRequestOptions struct { + Query *FastSearchQuery +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *FastSearchRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Query != nil { + if v, ok := any(o.Query).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Query", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *FastSearchRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *FastSearchRequestOptions) GetQuery() (map[string]any, error) { + return runtime.AsMap[any](o.Query) } // GetBody returns the payload in any type that can be marshalled to JSON by the client. @@ -2110,6 +3767,59 @@ func (o *FindSimilarMessagesRequestOptions) GetHeader() (map[string]string, erro return nil, nil } +// PatchSettingsRequestOptions is the options needed to make a request to PatchSettings. +type PatchSettingsRequestOptions struct { + Body *PatchSettingsBody + Header *PatchSettingsHeaders +} + +// Validate validates all the fields in the options. +// Use it if fields validation was not run. +func (o *PatchSettingsRequestOptions) Validate() error { + var errors runtime.ValidationErrors + + if o.Body != nil { + if v, ok := any(o.Body).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Body", err) + } + } + } + + if o.Header != nil { + if v, ok := any(o.Header).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Header", err) + } + } + } + if len(errors) == 0 { + return nil + } + + return errors +} + +// GetPathParams returns the path params as a map. +func (o *PatchSettingsRequestOptions) GetPathParams() (map[string]any, error) { + return nil, nil +} + +// GetQuery returns the query params as a map. +func (o *PatchSettingsRequestOptions) GetQuery() (map[string]any, error) { + return nil, nil +} + +// GetBody returns the payload in any type that can be marshalled to JSON by the client. +func (o *PatchSettingsRequestOptions) GetBody() any { + return o.Body +} + +// GetHeader returns the headers as a map. +func (o *PatchSettingsRequestOptions) GetHeader() (map[string]string, error) { + return runtime.AsMap[string](o.Header) +} + // ListSourceStatusRequestOptions is the options needed to make a request to ListSourceStatus. type ListSourceStatusRequestOptions struct { Query *ListSourceStatusQuery diff --git a/pkg/client/generated/client_with_response.go b/pkg/client/generated/client_with_response.go index bd7c041af..419471a3d 100644 --- a/pkg/client/generated/client_with_response.go +++ b/pkg/client/generated/client_with_response.go @@ -58,6 +58,152 @@ func (c *Client) DaemonPingWithResponse(ctx context.Context, reqEditors ...runti } } +// LogoutSession Delete the current browser session +func (c *Client) LogoutSessionWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*LogoutSessionResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/session", + Method: "DELETE", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/session") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &LogoutSessionResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 204: + return out, nil + case 429: + out.JSON429 = new(LogoutSessionErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON429); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "LogoutSessionErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetSession Get browser authentication status +func (c *Client) GetSessionWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSessionResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/session", + Method: "GET", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/session") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetSessionResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetSessionResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSessionResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// LoginSession Create an in-memory browser session +func (c *Client) LoginSessionWithResponse(ctx context.Context, options *LoginSessionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*LoginSessionResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/session/login", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/session/login") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &LoginSessionResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(LoginSessionResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "LoginSessionResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + // ListAccounts List scheduler-configured accounts (with sync schedules); use /cli/accounts for all archived sources func (c *Client) ListAccountsWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*ListAccountsResp, error) { var err error @@ -2964,6 +3110,55 @@ func (c *Client) VerifyCLIWithResponse(ctx context.Context, options *VerifyCLIRe } } +// GetConversation Get a bounded containing conversation +func (c *Client) GetConversationWithResponse(ctx context.Context, options *GetConversationRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetConversationResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/conversations/{id}", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/conversations/{id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetConversationResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetConversationResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetConversationResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + // ListDeletions List staged deletion manifests func (c *Client) ListDeletionsWithResponse(ctx context.Context, options *ListDeletionsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListDeletionsResp, error) { var err error @@ -3128,12 +3323,13 @@ func (c *Client) CancelDeletionWithResponse(ctx context.Context, options *Cancel } } -// GetHealth Get authenticated health details -func (c *Client) GetHealthWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetHealthResp, error) { +// GetDeletion Inspect a staged deletion manifest +func (c *Client) GetDeletionWithResponse(ctx context.Context, options *GetDeletionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDeletionResp, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/health", + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/deletions/{id}", Method: "GET", + Options: options, } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3141,12 +3337,12 @@ func (c *Client) GetHealthWithResponse(ctx context.Context, reqEditors ...runtim return nil, fmt.Errorf("error creating request: %w", err) } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/health") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/deletions/{id}") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } - out := &GetHealthResp{ + out := &GetDeletionResp{ HTTPResponse: resp.Raw, Body: resp.Content, StatusCode: resp.StatusCode, @@ -3154,7 +3350,7 @@ func (c *Client) GetHealthWithResponse(ctx context.Context, reqEditors ...runtim switch resp.StatusCode { case 200: - out.JSON200 = new(GetHealthResponse) + out.JSON200 = new(GetDeletionResponse) bodyBytes := resp.Content if len(bodyBytes) > 0 { if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { @@ -3162,7 +3358,7 @@ func (c *Client) GetHealthWithResponse(ctx context.Context, reqEditors ...runtim StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetHealthResponse", + TargetType: "GetDeletionResponse", Body: bodyBytes, Err: err, } @@ -3176,13 +3372,14 @@ func (c *Client) GetHealthWithResponse(ctx context.Context, reqEditors ...runtim } } -// ListMessages List messages -func (c *Client) ListMessagesWithResponse(ctx context.Context, options *ListMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListMessagesResp, error) { +// SearchDomains Search analytical domains +func (c *Client) SearchDomainsWithResponse(ctx context.Context, options *SearchDomainsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchDomainsResp, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages", - Method: "GET", - Options: options, + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/domains/search", + Method: "POST", + Options: options, + ContentType: "application/json", } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3190,12 +3387,12 @@ func (c *Client) ListMessagesWithResponse(ctx context.Context, options *ListMess return nil, fmt.Errorf("error creating request: %w", err) } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/domains/search") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } - out := &ListMessagesResp{ + out := &SearchDomainsResp{ HTTPResponse: resp.Raw, Body: resp.Content, StatusCode: resp.StatusCode, @@ -3203,7 +3400,7 @@ func (c *Client) ListMessagesWithResponse(ctx context.Context, options *ListMess switch resp.StatusCode { case 200: - out.JSON200 = new(ListMessagesResponse) + out.JSON200 = new(SearchDomainsResponse) bodyBytes := resp.Content if len(bodyBytes) > 0 { if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { @@ -3211,25 +3408,71 @@ func (c *Client) ListMessagesWithResponse(ctx context.Context, options *ListMess StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "ListMessagesResponse", + TargetType: "SearchDomainsResponse", Body: bodyBytes, Err: err, } } } return out, nil - case 500: + case 400: + out.JSON400 = new(SearchDomainsErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchDomainsErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(SearchDomainsErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchDomainsErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(SearchDomainsErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchDomainsErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) default: return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) } } -// FilterMessages List filtered messages -func (c *Client) FilterMessagesWithResponse(ctx context.Context, options *FilterMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*FilterMessagesResp, error) { +// GetDomain Get one analytical domain +func (c *Client) GetDomainWithResponse(ctx context.Context, options *GetDomainRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainResp, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/filter", + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/domains/{domain}", Method: "GET", Options: options, } @@ -3239,12 +3482,12 @@ func (c *Client) FilterMessagesWithResponse(ctx context.Context, options *Filter return nil, fmt.Errorf("error creating request: %w", err) } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/filter") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/domains/{domain}") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } - out := &FilterMessagesResp{ + out := &GetDomainResp{ HTTPResponse: resp.Raw, Body: resp.Content, StatusCode: resp.StatusCode, @@ -3252,7 +3495,7 @@ func (c *Client) FilterMessagesWithResponse(ctx context.Context, options *Filter switch resp.StatusCode { case 200: - out.JSON200 = new(FilterMessagesResponse) + out.JSON200 = new(GetDomainResponse) bodyBytes := resp.Content if len(bodyBytes) > 0 { if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { @@ -3260,7 +3503,7 @@ func (c *Client) FilterMessagesWithResponse(ctx context.Context, options *Filter StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "FilterMessagesResponse", + TargetType: "GetDomainResponse", Body: bodyBytes, Err: err, } @@ -3274,13 +3517,14 @@ func (c *Client) FilterMessagesWithResponse(ctx context.Context, options *Filter } } -// GetGmailIDsByFilter List Gmail message IDs matching a filter -func (c *Client) GetGmailIDsByFilterWithResponse(ctx context.Context, options *GetGmailIDsByFilterRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetGmailIDsByFilterResp, error) { +// SearchDomainFiles Search one domain's analytical files +func (c *Client) SearchDomainFilesWithResponse(ctx context.Context, options *SearchDomainFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchDomainFilesResp, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/gmail-ids", - Method: "GET", - Options: options, + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/domains/{domain}/files/search", + Method: "POST", + Options: options, + ContentType: "application/json", } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3288,12 +3532,12 @@ func (c *Client) GetGmailIDsByFilterWithResponse(ctx context.Context, options *G return nil, fmt.Errorf("error creating request: %w", err) } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/gmail-ids") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/domains/{domain}/files/search") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } - out := &GetGmailIDsByFilterResp{ + out := &SearchDomainFilesResp{ HTTPResponse: resp.Raw, Body: resp.Content, StatusCode: resp.StatusCode, @@ -3301,7 +3545,7 @@ func (c *Client) GetGmailIDsByFilterWithResponse(ctx context.Context, options *G switch resp.StatusCode { case 200: - out.JSON200 = new(GetGmailIDsByFilterResponse) + out.JSON200 = new(SearchDomainFilesResponse) bodyBytes := resp.Content if len(bodyBytes) > 0 { if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { @@ -3309,27 +3553,74 @@ func (c *Client) GetGmailIDsByFilterWithResponse(ctx context.Context, options *G StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetGmailIDsByFilterResponse", + TargetType: "SearchDomainFilesResponse", Body: bodyBytes, Err: err, } } } return out, nil - case 500: - return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) - default: - return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) - } -} - -// GetMessage Get one message -func (c *Client) GetMessageWithResponse(ctx context.Context, options *GetMessageRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetMessageResp, error) { + case 400: + out.JSON400 = new(SearchDomainFilesErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchDomainFilesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(SearchDomainFilesErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchDomainFilesErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(SearchDomainFilesErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchDomainFilesErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetDomainContextSummary Get one domain's contextual analytical summary +func (c *Client) GetDomainContextSummaryWithResponse(ctx context.Context, options *GetDomainContextSummaryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainContextSummaryResp, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}", - Method: "GET", - Options: options, + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/domains/{domain}/summary", + Method: "POST", + Options: options, + ContentType: "application/json", } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3337,12 +3628,12 @@ func (c *Client) GetMessageWithResponse(ctx context.Context, options *GetMessage return nil, fmt.Errorf("error creating request: %w", err) } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/domains/{domain}/summary") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } - out := &GetMessageResp{ + out := &GetDomainContextSummaryResp{ HTTPResponse: resp.Raw, Body: resp.Content, StatusCode: resp.StatusCode, @@ -3350,7 +3641,7 @@ func (c *Client) GetMessageWithResponse(ctx context.Context, options *GetMessage switch resp.StatusCode { case 200: - out.JSON200 = new(GetMessageResponse) + out.JSON200 = new(GetDomainContextSummaryResponse) bodyBytes := resp.Content if len(bodyBytes) > 0 { if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { @@ -3358,27 +3649,74 @@ func (c *Client) GetMessageWithResponse(ctx context.Context, options *GetMessage StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetMessageResponse", + TargetType: "GetDomainContextSummaryResponse", Body: bodyBytes, Err: err, } } } return out, nil - case 500: + case 400: + out.JSON400 = new(GetDomainContextSummaryErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainContextSummaryErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(GetDomainContextSummaryErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainContextSummaryErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(GetDomainContextSummaryErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainContextSummaryErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) default: return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) } } -// GetMessageInlinePart Get an inline MIME part -func (c *Client) GetMessageInlinePartWithResponse(ctx context.Context, options *GetMessageInlinePartRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetMessageInlinePartResp, error) { +// GetDomainTimeline Get one domain's canonical activity timeline +func (c *Client) GetDomainTimelineWithResponse(ctx context.Context, options *GetDomainTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetDomainTimelineResp, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}/inline", - Method: "GET", - Options: options, + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/domains/{domain}/timeline", + Method: "POST", + Options: options, + ContentType: "application/json", } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3386,12 +3724,12 @@ func (c *Client) GetMessageInlinePartWithResponse(ctx context.Context, options * return nil, fmt.Errorf("error creating request: %w", err) } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}/inline") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/domains/{domain}/timeline") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } - out := &GetMessageInlinePartResp{ + out := &GetDomainTimelineResp{ HTTPResponse: resp.Raw, Body: resp.Content, StatusCode: resp.StatusCode, @@ -3399,9 +3737,23 @@ func (c *Client) GetMessageInlinePartWithResponse(ctx context.Context, options * switch resp.StatusCode { case 200: + out.JSON200 = new(GetDomainTimelineResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainTimelineResponse", + Body: bodyBytes, + Err: err, + } + } + } return out, nil case 400: - out.JSON400 = new(GetMessageInlinePartErrorResponse) + out.JSON400 = new(GetDomainTimelineErrorResponse) bodyBytes := resp.Content if len(bodyBytes) > 0 { if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { @@ -3409,87 +3761,2819 @@ func (c *Client) GetMessageInlinePartWithResponse(ctx context.Context, options * StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetMessageInlinePartErrorResponse", + TargetType: "GetDomainTimelineErrorResponse", Body: bodyBytes, Err: err, } } } return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) - case 401: - out.JSON401 = new(GetMessageInlinePartErrorResponseJSON) + case 409: + out.JSON409 = new(GetDomainTimelineErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainTimelineErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(GetDomainTimelineErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetDomainTimelineErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// Explore Explore canonical archive entries +func (c *Client) ExploreWithResponse(ctx context.Context, options *ExploreRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ExploreResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/explore", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/explore") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &ExploreResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(ExploreResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(ExploreErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(ExploreErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(ExploreErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// ListExploreFiles List bounded chronological attachment facts +func (c *Client) ListExploreFilesWithResponse(ctx context.Context, options *ListExploreFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListExploreFilesResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/explore/files", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/explore/files") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &ListExploreFilesResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(ListExploreFilesResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListExploreFilesResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(ListExploreFilesErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListExploreFilesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(ListExploreFilesErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListExploreFilesErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(ListExploreFilesErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListExploreFilesErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// ExploreGroups Group canonical archive entries +func (c *Client) ExploreGroupsWithResponse(ctx context.Context, options *ExploreGroupsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ExploreGroupsResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/explore/groups", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/explore/groups") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &ExploreGroupsResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(ExploreGroupsResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreGroupsResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(ExploreGroupsErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreGroupsErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(ExploreGroupsErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreGroupsErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(ExploreGroupsErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ExploreGroupsErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// CountExploreMatches Count exact lexical matches in visible rows +func (c *Client) CountExploreMatchesWithResponse(ctx context.Context, options *CountExploreMatchesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CountExploreMatchesResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/explore/match-counts", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/explore/match-counts") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &CountExploreMatchesResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(CountExploreMatchesResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CountExploreMatchesResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(CountExploreMatchesErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CountExploreMatchesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(CountExploreMatchesErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CountExploreMatchesErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(CountExploreMatchesErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CountExploreMatchesErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// PreflightExploreSelection Preflight a revision-pinned analytical selection +func (c *Client) PreflightExploreSelectionWithResponse(ctx context.Context, options *PreflightExploreSelectionRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PreflightExploreSelectionResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/explore/preflight", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/explore/preflight") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &PreflightExploreSelectionResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(PreflightExploreSelectionResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PreflightExploreSelectionResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(PreflightExploreSelectionErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PreflightExploreSelectionErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(PreflightExploreSelectionErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PreflightExploreSelectionErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(PreflightExploreSelectionErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PreflightExploreSelectionErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GroupFiles Group analytical files +func (c *Client) GroupFilesWithResponse(ctx context.Context, options *GroupFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GroupFilesResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/files/groups", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/files/groups") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GroupFilesResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GroupFilesResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GroupFilesResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(GroupFilesErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GroupFilesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(GroupFilesErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GroupFilesErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(GroupFilesErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GroupFilesErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// SearchFiles Search analytical files +func (c *Client) SearchFilesWithResponse(ctx context.Context, options *SearchFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchFilesResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/files/search", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/files/search") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &SearchFilesResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(SearchFilesResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchFilesResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(SearchFilesErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchFilesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(SearchFilesErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchFilesErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(SearchFilesErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchFilesErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetFile Get authoritative file metadata +func (c *Client) GetFileWithResponse(ctx context.Context, options *GetFileRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetFileResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/files/{id}", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/files/{id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetFileResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetFileResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFileResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetFileContent Download content for one authoritative file row +func (c *Client) GetFileContentWithResponse(ctx context.Context, options *GetFileContentRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetFileContentResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/files/{id}/content", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/files/{id}/content") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetFileContentResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + return out, nil + case 400: + out.JSON400 = new(GetFileContentErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFileContentErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 401: + out.JSON401 = new(GetFileContentErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON401); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFileContentErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 404: + out.JSON404 = new(GetFileContentErrorResponseJSON404) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON404); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFileContentErrorResponseJSON404", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 500: + out.JSON500 = new(GetFileContentErrorResponseJSON500) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON500); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFileContentErrorResponseJSON500", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(GetFileContentErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetFileContentErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetHealth Get authenticated health details +func (c *Client) GetHealthWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetHealthResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/health", + Method: "GET", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/health") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetHealthResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetHealthResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetHealthResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// LinkIdentityParticipants Assert two participants are the same person +func (c *Client) LinkIdentityParticipantsWithResponse(ctx context.Context, options *LinkIdentityParticipantsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*LinkIdentityParticipantsResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/identity/links", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/identity/links") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &LinkIdentityParticipantsResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(LinkIdentityParticipantsResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "LinkIdentityParticipantsResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// UnlinkIdentityParticipants Remove a link edge between two participants +func (c *Client) UnlinkIdentityParticipantsWithResponse(ctx context.Context, options *UnlinkIdentityParticipantsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*UnlinkIdentityParticipantsResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/identity/unlinks", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/identity/unlinks") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &UnlinkIdentityParticipantsResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(UnlinkIdentityParticipantsResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "UnlinkIdentityParticipantsResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// SearchIntegrationTasks Search tasks in the configured project +func (c *Client) SearchIntegrationTasksWithResponse(ctx context.Context, options *SearchIntegrationTasksRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchIntegrationTasksResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/integrations/tasks/search", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/integrations/tasks/search") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &SearchIntegrationTasksResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(SearchIntegrationTasksResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchIntegrationTasksResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetTaskIntegrationStatus Get task integration capability status +func (c *Client) GetTaskIntegrationStatusWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetTaskIntegrationStatusResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/integrations/tasks/status", + Method: "GET", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/integrations/tasks/status") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetTaskIntegrationStatusResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetTaskIntegrationStatusResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetTaskIntegrationStatusResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// TestTaskIntegration Test task integration discovery, authentication, capabilities, and project +func (c *Client) TestTaskIntegrationWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*TestTaskIntegrationResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/integrations/tasks/test", + Method: "POST", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/integrations/tasks/test") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &TestTaskIntegrationResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(TestTaskIntegrationResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "TestTaskIntegrationResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// ListMessages List messages +func (c *Client) ListMessagesWithResponse(ctx context.Context, options *ListMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListMessagesResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &ListMessagesResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(ListMessagesResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListMessagesResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// FilterMessages List filtered messages +func (c *Client) FilterMessagesWithResponse(ctx context.Context, options *FilterMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*FilterMessagesResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/filter", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/filter") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &FilterMessagesResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(FilterMessagesResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "FilterMessagesResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetGmailIDsByFilter List Gmail message IDs matching a filter +func (c *Client) GetGmailIDsByFilterWithResponse(ctx context.Context, options *GetGmailIDsByFilterRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetGmailIDsByFilterResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/gmail-ids", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/gmail-ids") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetGmailIDsByFilterResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetGmailIDsByFilterResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetGmailIDsByFilterResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetMessage Get one message +func (c *Client) GetMessageWithResponse(ctx context.Context, options *GetMessageRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetMessageResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetMessageResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetMessageResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetMessageInlinePart Get an inline MIME part +func (c *Client) GetMessageInlinePartWithResponse(ctx context.Context, options *GetMessageInlinePartRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetMessageInlinePartResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}/inline", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}/inline") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetMessageInlinePartResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + return out, nil + case 400: + out.JSON400 = new(GetMessageInlinePartErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageInlinePartErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 401: + out.JSON401 = new(GetMessageInlinePartErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON401); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageInlinePartErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 404: + out.JSON404 = new(GetMessageInlinePartErrorResponseJSON404) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON404); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageInlinePartErrorResponseJSON404", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 415: + out.JSON415 = new(GetMessageInlinePartErrorResponseJSON415) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON415); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageInlinePartErrorResponseJSON415", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 500: + out.JSON500 = new(GetMessageInlinePartErrorResponseJSON500) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON500); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageInlinePartErrorResponseJSON500", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 501: + out.JSON501 = new(GetMessageInlinePartErrorResponseJSON501) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON501); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageInlinePartErrorResponseJSON501", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(GetMessageInlinePartErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetMessageInlinePartErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// ListMessageTasks List tasks linked to an archived email +func (c *Client) ListMessageTasksWithResponse(ctx context.Context, options *ListMessageTasksRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListMessageTasksResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}/tasks", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}/tasks") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &ListMessageTasksResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(ListMessageTasksResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListMessageTasksResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// CreateOrLinkMessageTask Create or link a task for an archived email +func (c *Client) CreateOrLinkMessageTaskWithResponse(ctx context.Context, options *CreateOrLinkMessageTaskRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CreateOrLinkMessageTaskResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}/tasks", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}/tasks") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &CreateOrLinkMessageTaskResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 201: + out.JSON201 = new(CreateOrLinkMessageTaskResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON201); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateOrLinkMessageTaskResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// UnlinkMessageTask Unlink a task from an archived email +func (c *Client) UnlinkMessageTaskWithResponse(ctx context.Context, options *UnlinkMessageTaskRequestOptions, reqEditors ...runtime.RequestEditorFn) (*UnlinkMessageTaskResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/messages/{id}/tasks/{task_id}", + Method: "DELETE", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/messages/{id}/tasks/{task_id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &UnlinkMessageTaskResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(UnlinkMessageTaskResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "UnlinkMessageTaskResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// SearchPeople Search analytical people +func (c *Client) SearchPeopleWithResponse(ctx context.Context, options *SearchPeopleRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchPeopleResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/people/search", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/people/search") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &SearchPeopleResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(SearchPeopleResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPeopleResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(SearchPeopleErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPeopleErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(SearchPeopleErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPeopleErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(SearchPeopleErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPeopleErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetPerson Get one analytical person +func (c *Client) GetPersonWithResponse(ctx context.Context, options *GetPersonRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/people/{id}", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/people/{id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetPersonResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetPersonResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// SearchPersonFiles Search one person's analytical files +func (c *Client) SearchPersonFilesWithResponse(ctx context.Context, options *SearchPersonFilesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchPersonFilesResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/people/{id}/files/search", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/people/{id}/files/search") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &SearchPersonFilesResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(SearchPersonFilesResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPersonFilesResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(SearchPersonFilesErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPersonFilesErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(SearchPersonFilesErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPersonFilesErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(SearchPersonFilesErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "SearchPersonFilesErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetPersonContextSummary Get one person's contextual analytical summary +func (c *Client) GetPersonContextSummaryWithResponse(ctx context.Context, options *GetPersonContextSummaryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonContextSummaryResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/people/{id}/summary", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/people/{id}/summary") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetPersonContextSummaryResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetPersonContextSummaryResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonContextSummaryResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(GetPersonContextSummaryErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonContextSummaryErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(GetPersonContextSummaryErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonContextSummaryErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(GetPersonContextSummaryErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonContextSummaryErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetPersonTimeline Get one person's canonical activity timeline +func (c *Client) GetPersonTimelineWithResponse(ctx context.Context, options *GetPersonTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetPersonTimelineResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/people/{id}/timeline", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/people/{id}/timeline") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetPersonTimelineResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetPersonTimelineResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonTimelineResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(GetPersonTimelineErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonTimelineErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(GetPersonTimelineErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonTimelineErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(GetPersonTimelineErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetPersonTimelineErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// RunQuery Run an aggregate query +func (c *Client) RunQueryWithResponse(ctx context.Context, options *RunQueryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*RunQueryResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/query", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/query") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &RunQueryResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(RunQueryResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "RunQueryResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// ListRelationships Rank counterparts by reciprocity-weighted interaction +func (c *Client) ListRelationshipsWithResponse(ctx context.Context, options *ListRelationshipsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListRelationshipsResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/relationships", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/relationships") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &ListRelationshipsResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(ListRelationshipsResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListRelationshipsResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(ListRelationshipsErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListRelationshipsErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(ListRelationshipsErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListRelationshipsErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(ListRelationshipsErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListRelationshipsErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetRelationshipTimeline Get one counterpart's interaction timeline, with chat grouped into local-day bursts +func (c *Client) GetRelationshipTimelineWithResponse(ctx context.Context, options *GetRelationshipTimelineRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetRelationshipTimelineResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/relationships/{id}/timeline", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/relationships/{id}/timeline") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetRelationshipTimelineResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetRelationshipTimelineResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetRelationshipTimelineResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 400: + out.JSON400 = new(GetRelationshipTimelineErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetRelationshipTimelineErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(GetRelationshipTimelineErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetRelationshipTimelineErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(GetRelationshipTimelineErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetRelationshipTimelineErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// ListSavedViews List shared analytical Saved Views +func (c *Client) ListSavedViewsWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*ListSavedViewsResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/saved-views", + Method: "GET", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/saved-views") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &ListSavedViewsResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(ListSavedViewsResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "ListSavedViewsResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// CreateSavedView Create a shared analytical Saved View +func (c *Client) CreateSavedViewWithResponse(ctx context.Context, options *CreateSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*CreateSavedViewResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/saved-views", + Method: "POST", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/saved-views") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &CreateSavedViewResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 201: + out.JSON201 = new(CreateSavedViewResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON201); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateSavedViewResponse", + Body: bodyBytes, + Err: err, + } + } + } + out.Headers201 = &CreateSavedViewResp201Headers{ + ETag: resp.Headers.Get("ETag"), + } + return out, nil + case 400: + out.JSON400 = new(CreateSavedViewErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateSavedViewErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(CreateSavedViewErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateSavedViewErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(CreateSavedViewErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "CreateSavedViewErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// DeleteSavedView Delete a shared analytical Saved View +func (c *Client) DeleteSavedViewWithResponse(ctx context.Context, options *DeleteSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*DeleteSavedViewResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/saved-views/{id}", + Method: "DELETE", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/saved-views/{id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &DeleteSavedViewResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 204: + return out, nil + case 400: + out.JSON400 = new(DeleteSavedViewErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "DeleteSavedViewErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 401: + out.JSON401 = new(DeleteSavedViewErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON401); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "DeleteSavedViewErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 404: + out.JSON404 = new(DeleteSavedViewErrorResponseJSON404) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON404); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "DeleteSavedViewErrorResponseJSON404", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(DeleteSavedViewErrorResponseJSON409) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "DeleteSavedViewErrorResponseJSON409", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 428: + out.JSON428 = new(DeleteSavedViewErrorResponseJSON428) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON428); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "DeleteSavedViewErrorResponseJSON428", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 500: + out.JSON500 = new(DeleteSavedViewErrorResponseJSON500) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON500); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "DeleteSavedViewErrorResponseJSON500", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(DeleteSavedViewErrorResponseJSON503) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "DeleteSavedViewErrorResponseJSON503", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// GetSavedView Get a shared analytical Saved View +func (c *Client) GetSavedViewWithResponse(ctx context.Context, options *GetSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetSavedViewResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/saved-views/{id}", + Method: "GET", + Options: options, + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/saved-views/{id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetSavedViewResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetSavedViewResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSavedViewResponse", + Body: bodyBytes, + Err: err, + } + } + } + out.Headers200 = &GetSavedViewResp200Headers{ + ETag: resp.Headers.Get("ETag"), + } + return out, nil + case 404: + out.JSON404 = new(GetSavedViewErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON404); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSavedViewErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(GetSavedViewErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSavedViewErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// PatchSavedView Update a shared analytical Saved View +func (c *Client) PatchSavedViewWithResponse(ctx context.Context, options *PatchSavedViewRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PatchSavedViewResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/saved-views/{id}", + Method: "PATCH", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/saved-views/{id}") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &PatchSavedViewResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(PatchSavedViewResponse) bodyBytes := resp.Content if len(bodyBytes) > 0 { - if err := json.Unmarshal(bodyBytes, out.JSON401); err != nil { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { return out, &runtime.ResponseDecodeError{ StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetMessageInlinePartErrorResponseJSON", + TargetType: "PatchSavedViewResponse", Body: bodyBytes, Err: err, } } } - return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) - case 404: - out.JSON404 = new(GetMessageInlinePartErrorResponseJSON404) + out.Headers200 = &PatchSavedViewResp200Headers{ + ETag: resp.Headers.Get("ETag"), + } + return out, nil + case 400: + out.JSON400 = new(PatchSavedViewErrorResponse) bodyBytes := resp.Content if len(bodyBytes) > 0 { - if err := json.Unmarshal(bodyBytes, out.JSON404); err != nil { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { return out, &runtime.ResponseDecodeError{ StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetMessageInlinePartErrorResponseJSON404", + TargetType: "PatchSavedViewErrorResponse", Body: bodyBytes, Err: err, } } } return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) - case 415: - out.JSON415 = new(GetMessageInlinePartErrorResponseJSON415) + case 404: + out.JSON404 = new(PatchSavedViewErrorResponseJSON) bodyBytes := resp.Content if len(bodyBytes) > 0 { - if err := json.Unmarshal(bodyBytes, out.JSON415); err != nil { + if err := json.Unmarshal(bodyBytes, out.JSON404); err != nil { return out, &runtime.ResponseDecodeError{ StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetMessageInlinePartErrorResponseJSON415", + TargetType: "PatchSavedViewErrorResponseJSON", Body: bodyBytes, Err: err, } } } return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) - case 500: - out.JSON500 = new(GetMessageInlinePartErrorResponseJSON500) + case 409: + out.JSON409 = new(PatchSavedViewErrorResponseJSON409) bodyBytes := resp.Content if len(bodyBytes) > 0 { - if err := json.Unmarshal(bodyBytes, out.JSON500); err != nil { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { return out, &runtime.ResponseDecodeError{ StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetMessageInlinePartErrorResponseJSON500", + TargetType: "PatchSavedViewErrorResponseJSON409", Body: bodyBytes, Err: err, } } } return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) - case 501: - out.JSON501 = new(GetMessageInlinePartErrorResponseJSON501) + case 428: + out.JSON428 = new(PatchSavedViewErrorResponseJSON428) bodyBytes := resp.Content if len(bodyBytes) > 0 { - if err := json.Unmarshal(bodyBytes, out.JSON501); err != nil { + if err := json.Unmarshal(bodyBytes, out.JSON428); err != nil { return out, &runtime.ResponseDecodeError{ StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetMessageInlinePartErrorResponseJSON501", + TargetType: "PatchSavedViewErrorResponseJSON428", Body: bodyBytes, Err: err, } @@ -3497,7 +6581,7 @@ func (c *Client) GetMessageInlinePartWithResponse(ctx context.Context, options * } return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) case 503: - out.JSON503 = new(GetMessageInlinePartErrorResponseJSON503) + out.JSON503 = new(PatchSavedViewErrorResponseJSON503) bodyBytes := resp.Content if len(bodyBytes) > 0 { if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { @@ -3505,7 +6589,7 @@ func (c *Client) GetMessageInlinePartWithResponse(ctx context.Context, options * StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetMessageInlinePartErrorResponseJSON503", + TargetType: "PatchSavedViewErrorResponseJSON503", Body: bodyBytes, Err: err, } @@ -3517,14 +6601,12 @@ func (c *Client) GetMessageInlinePartWithResponse(ctx context.Context, options * } } -// RunQuery Run an aggregate query -func (c *Client) RunQueryWithResponse(ctx context.Context, options *RunQueryRequestOptions, reqEditors ...runtime.RequestEditorFn) (*RunQueryResp, error) { +// GetSchedulerStatus Get scheduler status +func (c *Client) GetSchedulerStatusWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSchedulerStatusResp, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/query", - Method: "POST", - Options: options, - ContentType: "application/json", + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/scheduler/status", + Method: "GET", } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3532,12 +6614,12 @@ func (c *Client) RunQueryWithResponse(ctx context.Context, options *RunQueryRequ return nil, fmt.Errorf("error creating request: %w", err) } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/query") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/scheduler/status") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } - out := &RunQueryResp{ + out := &GetSchedulerStatusResp{ HTTPResponse: resp.Raw, Body: resp.Content, StatusCode: resp.StatusCode, @@ -3545,7 +6627,7 @@ func (c *Client) RunQueryWithResponse(ctx context.Context, options *RunQueryRequ switch resp.StatusCode { case 200: - out.JSON200 = new(RunQueryResponse) + out.JSON200 = new(GetSchedulerStatusResponse) bodyBytes := resp.Content if len(bodyBytes) > 0 { if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { @@ -3553,7 +6635,7 @@ func (c *Client) RunQueryWithResponse(ctx context.Context, options *RunQueryRequ StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "RunQueryResponse", + TargetType: "GetSchedulerStatusResponse", Body: bodyBytes, Err: err, } @@ -3567,12 +6649,13 @@ func (c *Client) RunQueryWithResponse(ctx context.Context, options *RunQueryRequ } } -// GetSchedulerStatus Get scheduler status -func (c *Client) GetSchedulerStatusWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSchedulerStatusResp, error) { +// SearchMessages Search messages +func (c *Client) SearchMessagesWithResponse(ctx context.Context, options *SearchMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchMessagesResp, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/scheduler/status", + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/search", Method: "GET", + Options: options, } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3580,12 +6663,12 @@ func (c *Client) GetSchedulerStatusWithResponse(ctx context.Context, reqEditors return nil, fmt.Errorf("error creating request: %w", err) } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/scheduler/status") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/search") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } - out := &GetSchedulerStatusResp{ + out := &SearchMessagesResp{ HTTPResponse: resp.Raw, Body: resp.Content, StatusCode: resp.StatusCode, @@ -3593,7 +6676,7 @@ func (c *Client) GetSchedulerStatusWithResponse(ctx context.Context, reqEditors switch resp.StatusCode { case 200: - out.JSON200 = new(GetSchedulerStatusResponse) + out.JSON200 = new(SearchMessagesResponse) bodyBytes := resp.Content if len(bodyBytes) > 0 { if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { @@ -3601,7 +6684,7 @@ func (c *Client) GetSchedulerStatusWithResponse(ctx context.Context, reqEditors StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "GetSchedulerStatusResponse", + TargetType: "SearchMessagesResponse", Body: bodyBytes, Err: err, } @@ -3615,13 +6698,14 @@ func (c *Client) GetSchedulerStatusWithResponse(ctx context.Context, reqEditors } } -// SearchMessages Search messages -func (c *Client) SearchMessagesWithResponse(ctx context.Context, options *SearchMessagesRequestOptions, reqEditors ...runtime.RequestEditorFn) (*SearchMessagesResp, error) { +// GetSearchCoverage Get semantic index coverage for an analytical context +func (c *Client) GetSearchCoverageWithResponse(ctx context.Context, options *GetSearchCoverageRequestOptions, reqEditors ...runtime.RequestEditorFn) (*GetSearchCoverageResp, error) { var err error reqParams := runtime.RequestOptionsParameters{ - RequestURL: c.apiClient.GetBaseURL() + "/api/v1/search", - Method: "GET", - Options: options, + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/search/coverage", + Method: "POST", + Options: options, + ContentType: "application/json", } req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) @@ -3629,12 +6713,12 @@ func (c *Client) SearchMessagesWithResponse(ctx context.Context, options *Search return nil, fmt.Errorf("error creating request: %w", err) } - resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/search") + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/search/coverage") if err != nil { return nil, fmt.Errorf("error executing request: %w", err) } - out := &SearchMessagesResp{ + out := &GetSearchCoverageResp{ HTTPResponse: resp.Raw, Body: resp.Content, StatusCode: resp.StatusCode, @@ -3642,7 +6726,7 @@ func (c *Client) SearchMessagesWithResponse(ctx context.Context, options *Search switch resp.StatusCode { case 200: - out.JSON200 = new(SearchMessagesResponse) + out.JSON200 = new(GetSearchCoverageResponse) bodyBytes := resp.Content if len(bodyBytes) > 0 { if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { @@ -3650,14 +6734,44 @@ func (c *Client) SearchMessagesWithResponse(ctx context.Context, options *Search StatusCode: resp.StatusCode, ContentType: resp.Headers.Get("Content-Type"), ContentLength: len(bodyBytes), - TargetType: "SearchMessagesResponse", + TargetType: "GetSearchCoverageResponse", Body: bodyBytes, Err: err, } } } return out, nil - case 500: + case 400: + out.JSON400 = new(GetSearchCoverageErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSearchCoverageErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 503: + out.JSON503 = new(GetSearchCoverageErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON503); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSearchCoverageErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) default: return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) @@ -3860,6 +6974,188 @@ func (c *Client) FindSimilarMessagesWithResponse(ctx context.Context, options *F } } +// GetSettings Get browser-managed settings +func (c *Client) GetSettingsWithResponse(ctx context.Context, reqEditors ...runtime.RequestEditorFn) (*GetSettingsResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/settings", + Method: "GET", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/settings") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &GetSettingsResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(GetSettingsResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "GetSettingsResponse", + Body: bodyBytes, + Err: err, + } + } + } + out.Headers200 = &GetSettingsResp200Headers{ + ETag: resp.Headers.Get("ETag"), + } + return out, nil + case 500: + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + +// PatchSettings Update browser-managed settings +func (c *Client) PatchSettingsWithResponse(ctx context.Context, options *PatchSettingsRequestOptions, reqEditors ...runtime.RequestEditorFn) (*PatchSettingsResp, error) { + var err error + reqParams := runtime.RequestOptionsParameters{ + RequestURL: c.apiClient.GetBaseURL() + "/api/v1/settings", + Method: "PATCH", + Options: options, + ContentType: "application/json", + } + + req, err := c.apiClient.CreateRequest(ctx, reqParams, reqEditors...) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + + resp, err := c.apiClient.ExecuteRequest(ctx, req, "/api/v1/settings") + if err != nil { + return nil, fmt.Errorf("error executing request: %w", err) + } + + out := &PatchSettingsResp{ + HTTPResponse: resp.Raw, + Body: resp.Content, + StatusCode: resp.StatusCode, + } + + switch resp.StatusCode { + case 200: + out.JSON200 = new(PatchSettingsResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON200); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PatchSettingsResponse", + Body: bodyBytes, + Err: err, + } + } + } + out.Headers200 = &PatchSettingsResp200Headers{ + ETag: resp.Headers.Get("ETag"), + } + return out, nil + case 400: + out.JSON400 = new(PatchSettingsErrorResponse) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON400); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PatchSettingsErrorResponse", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 409: + out.JSON409 = new(PatchSettingsErrorResponseJSON) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON409); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PatchSettingsErrorResponseJSON", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 412: + out.JSON412 = new(PatchSettingsErrorResponseJSON412) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON412); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PatchSettingsErrorResponseJSON412", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 422: + out.JSON422 = new(PatchSettingsErrorResponseJSON422) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON422); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PatchSettingsErrorResponseJSON422", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + case 428: + out.JSON428 = new(PatchSettingsErrorResponseJSON428) + bodyBytes := resp.Content + if len(bodyBytes) > 0 { + if err := json.Unmarshal(bodyBytes, out.JSON428); err != nil { + return out, &runtime.ResponseDecodeError{ + StatusCode: resp.StatusCode, + ContentType: resp.Headers.Get("Content-Type"), + ContentLength: len(bodyBytes), + TargetType: "PatchSettingsErrorResponseJSON428", + Body: bodyBytes, + Err: err, + } + } + } + return out, runtime.NewClientAPIError(fmt.Errorf("API error (status %d)", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + default: + return out, runtime.NewClientAPIError(fmt.Errorf("unexpected status code: %d", resp.StatusCode), runtime.WithStatusCode(resp.StatusCode)) + } +} + // ListSourceStatus List source sync status func (c *Client) ListSourceStatusWithResponse(ctx context.Context, options *ListSourceStatusRequestOptions, reqEditors ...runtime.RequestEditorFn) (*ListSourceStatusResp, error) { var err error diff --git a/pkg/client/generated/enums.go b/pkg/client/generated/enums.go new file mode 100644 index 000000000..314ef88bb --- /dev/null +++ b/pkg/client/generated/enums.go @@ -0,0 +1,525 @@ +// Code generated by oapi-codegen. DO NOT EDIT. + +package generated + +import ( + "fmt" + + "github.com/doordash-oss/oapi-codegen-dd/v3/pkg/runtime" +) + +type AddResultCacheState string + +const ( + Ready AddResultCacheState = "ready" + Stale AddResultCacheState = "stale" +) + +// Validate checks if the AddResultCacheState value is valid +func (a AddResultCacheState) Validate() error { + switch a { + case Ready, Stale: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid AddResultCacheState value, got: %v", a)) + } +} + +type ExploreCacheUnavailableResponseReadiness string + +const ( + ExploreCacheUnavailableResponseReadinessAbsent ExploreCacheUnavailableResponseReadiness = "absent" + ExploreCacheUnavailableResponseReadinessDrifted ExploreCacheUnavailableResponseReadiness = "drifted" + ExploreCacheUnavailableResponseReadinessInterrupted ExploreCacheUnavailableResponseReadiness = "interrupted" + ExploreCacheUnavailableResponseReadinessStaleSchema ExploreCacheUnavailableResponseReadiness = "stale_schema" +) + +// Validate checks if the ExploreCacheUnavailableResponseReadiness value is valid +func (e ExploreCacheUnavailableResponseReadiness) Validate() error { + switch e { + case ExploreCacheUnavailableResponseReadinessAbsent, ExploreCacheUnavailableResponseReadinessDrifted, ExploreCacheUnavailableResponseReadinessInterrupted, ExploreCacheUnavailableResponseReadinessStaleSchema: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreCacheUnavailableResponseReadiness value, got: %v", e)) + } +} + +type ExploreFilterDimension string + +const ( + ExploreFilterDimensionAfter ExploreFilterDimension = "after" + ExploreFilterDimensionBefore ExploreFilterDimension = "before" + ExploreFilterDimensionDeletion ExploreFilterDimension = "deletion" + ExploreFilterDimensionDomain ExploreFilterDimension = "domain" + ExploreFilterDimensionMessageType ExploreFilterDimension = "message_type" + ExploreFilterDimensionParticipant ExploreFilterDimension = "participant" + ExploreFilterDimensionSource ExploreFilterDimension = "source" +) + +// Validate checks if the ExploreFilterDimension value is valid +func (e ExploreFilterDimension) Validate() error { + switch e { + case ExploreFilterDimensionAfter, ExploreFilterDimensionBefore, ExploreFilterDimensionDeletion, ExploreFilterDimensionDomain, ExploreFilterDimensionMessageType, ExploreFilterDimensionParticipant, ExploreFilterDimensionSource: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreFilterDimension value, got: %v", e)) + } +} + +type ExploreGroupDimension string + +const ( + ExploreGroupDimensionDomain ExploreGroupDimension = "domain" + ExploreGroupDimensionKind ExploreGroupDimension = "kind" + ExploreGroupDimensionMessageType ExploreGroupDimension = "message_type" + ExploreGroupDimensionMonth ExploreGroupDimension = "month" + ExploreGroupDimensionParticipant ExploreGroupDimension = "participant" + ExploreGroupDimensionSource ExploreGroupDimension = "source" + ExploreGroupDimensionYear ExploreGroupDimension = "year" +) + +// Validate checks if the ExploreGroupDimension value is valid +func (e ExploreGroupDimension) Validate() error { + switch e { + case ExploreGroupDimensionDomain, ExploreGroupDimensionKind, ExploreGroupDimensionMessageType, ExploreGroupDimensionMonth, ExploreGroupDimensionParticipant, ExploreGroupDimensionSource, ExploreGroupDimensionYear: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreGroupDimension value, got: %v", e)) + } +} + +type ExploreGroupSortDirection string + +const ( + ExploreGroupSortDirectionAsc ExploreGroupSortDirection = "asc" + ExploreGroupSortDirectionDesc ExploreGroupSortDirection = "desc" +) + +// Validate checks if the ExploreGroupSortDirection value is valid +func (e ExploreGroupSortDirection) Validate() error { + switch e { + case ExploreGroupSortDirectionAsc, ExploreGroupSortDirectionDesc: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreGroupSortDirection value, got: %v", e)) + } +} + +type ExploreGroupSortField string + +const ( + ExploreGroupSortFieldCount ExploreGroupSortField = "count" + ExploreGroupSortFieldEstimatedBytes ExploreGroupSortField = "estimated_bytes" + ExploreGroupSortFieldKey ExploreGroupSortField = "key" + ExploreGroupSortFieldLatestAt ExploreGroupSortField = "latest_at" +) + +// Validate checks if the ExploreGroupSortField value is valid +func (e ExploreGroupSortField) Validate() error { + switch e { + case ExploreGroupSortFieldCount, ExploreGroupSortFieldEstimatedBytes, ExploreGroupSortFieldKey, ExploreGroupSortFieldLatestAt: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreGroupSortField value, got: %v", e)) + } +} + +type ExploreGroupsHTTPRequestPresentation string + +const ( + ExploreGroupsHTTPRequestPresentationTable ExploreGroupsHTTPRequestPresentation = "table" +) + +// Validate checks if the ExploreGroupsHTTPRequestPresentation value is valid +func (e ExploreGroupsHTTPRequestPresentation) Validate() error { + switch e { + case ExploreGroupsHTTPRequestPresentationTable: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreGroupsHTTPRequestPresentation value, got: %v", e)) + } +} + +type ExploreGroupsHTTPRequestSearchMode string + +const ( + ExploreGroupsHTTPRequestSearchModeFullText ExploreGroupsHTTPRequestSearchMode = "full_text" + ExploreGroupsHTTPRequestSearchModeHybrid ExploreGroupsHTTPRequestSearchMode = "hybrid" + ExploreGroupsHTTPRequestSearchModeSemantic ExploreGroupsHTTPRequestSearchMode = "semantic" +) + +// Validate checks if the ExploreGroupsHTTPRequestSearchMode value is valid +func (e ExploreGroupsHTTPRequestSearchMode) Validate() error { + switch e { + case ExploreGroupsHTTPRequestSearchModeFullText, ExploreGroupsHTTPRequestSearchModeHybrid, ExploreGroupsHTTPRequestSearchModeSemantic: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreGroupsHTTPRequestSearchMode value, got: %v", e)) + } +} + +type ExploreHTTPRequestPresentation string + +const ( + ExploreHTTPRequestPresentationFiles ExploreHTTPRequestPresentation = "files" + ExploreHTTPRequestPresentationTable ExploreHTTPRequestPresentation = "table" + ExploreHTTPRequestPresentationTimeline ExploreHTTPRequestPresentation = "timeline" +) + +// Validate checks if the ExploreHTTPRequestPresentation value is valid +func (e ExploreHTTPRequestPresentation) Validate() error { + switch e { + case ExploreHTTPRequestPresentationFiles, ExploreHTTPRequestPresentationTable, ExploreHTTPRequestPresentationTimeline: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreHTTPRequestPresentation value, got: %v", e)) + } +} + +type ExploreHTTPRequestSearchMode string + +const ( + ExploreHTTPRequestSearchModeFullText ExploreHTTPRequestSearchMode = "full_text" + ExploreHTTPRequestSearchModeHybrid ExploreHTTPRequestSearchMode = "hybrid" + ExploreHTTPRequestSearchModeSemantic ExploreHTTPRequestSearchMode = "semantic" +) + +// Validate checks if the ExploreHTTPRequestSearchMode value is valid +func (e ExploreHTTPRequestSearchMode) Validate() error { + switch e { + case ExploreHTTPRequestSearchModeFullText, ExploreHTTPRequestSearchModeHybrid, ExploreHTTPRequestSearchModeSemantic: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreHTTPRequestSearchMode value, got: %v", e)) + } +} + +type ExploreSelectionMode string + +const ( + ExploreSelectionModeAllMatching ExploreSelectionMode = "all_matching" + ExploreSelectionModeExplicit ExploreSelectionMode = "explicit" +) + +// Validate checks if the ExploreSelectionMode value is valid +func (e ExploreSelectionMode) Validate() error { + switch e { + case ExploreSelectionModeAllMatching, ExploreSelectionModeExplicit: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreSelectionMode value, got: %v", e)) + } +} + +type ExploreSortDirection string + +const ( + ExploreSortDirectionDesc ExploreSortDirection = "desc" +) + +// Validate checks if the ExploreSortDirection value is valid +func (e ExploreSortDirection) Validate() error { + switch e { + case ExploreSortDirectionDesc: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreSortDirection value, got: %v", e)) + } +} + +type ExploreSortField string + +const ( + ExploreSortFieldOccurredAt ExploreSortField = "occurred_at" +) + +// Validate checks if the ExploreSortField value is valid +func (e ExploreSortField) Validate() error { + switch e { + case ExploreSortFieldOccurredAt: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid ExploreSortField value, got: %v", e)) + } +} + +type FileMetadataResponseContentState string + +const ( + LocalContent FileMetadataResponseContentState = "local_content" + MetadataOnly FileMetadataResponseContentState = "metadata_only" + MissingBlob FileMetadataResponseContentState = "missing_blob" + URLOnly FileMetadataResponseContentState = "url_only" +) + +// Validate checks if the FileMetadataResponseContentState value is valid +func (f FileMetadataResponseContentState) Validate() error { + switch f { + case LocalContent, MetadataOnly, MissingBlob, URLOnly: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid FileMetadataResponseContentState value, got: %v", f)) + } +} + +type FileSearchRowContentState string + +const ( + FileSearchRowContentStateLocalContent FileSearchRowContentState = "local_content" + FileSearchRowContentStateMetadataOnly FileSearchRowContentState = "metadata_only" + FileSearchRowContentStateMissingBlob FileSearchRowContentState = "missing_blob" + FileSearchRowContentStateURLOnly FileSearchRowContentState = "url_only" +) + +// Validate checks if the FileSearchRowContentState value is valid +func (f FileSearchRowContentState) Validate() error { + switch f { + case FileSearchRowContentStateLocalContent, FileSearchRowContentStateMetadataOnly, FileSearchRowContentStateMissingBlob, FileSearchRowContentStateURLOnly: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid FileSearchRowContentState value, got: %v", f)) + } +} + +type IdentityLinkResponseCacheState string + +const ( + IdentityLinkResponseCacheStateReady IdentityLinkResponseCacheState = "ready" + IdentityLinkResponseCacheStateStale IdentityLinkResponseCacheState = "stale" +) + +// Validate checks if the IdentityLinkResponseCacheState value is valid +func (i IdentityLinkResponseCacheState) Validate() error { + switch i { + case IdentityLinkResponseCacheStateReady, IdentityLinkResponseCacheStateStale: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid IdentityLinkResponseCacheState value, got: %v", i)) + } +} + +type IdentitySearchSortDirection string + +const ( + IdentitySearchSortDirectionAsc IdentitySearchSortDirection = "asc" + IdentitySearchSortDirectionDesc IdentitySearchSortDirection = "desc" +) + +// Validate checks if the IdentitySearchSortDirection value is valid +func (i IdentitySearchSortDirection) Validate() error { + switch i { + case IdentitySearchSortDirectionAsc, IdentitySearchSortDirectionDesc: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid IdentitySearchSortDirection value, got: %v", i)) + } +} + +type IdentitySearchSortField string + +const ( + IdentitySearchSortFieldActivityCount IdentitySearchSortField = "activity_count" + IdentitySearchSortFieldDisplayLabel IdentitySearchSortField = "display_label" + IdentitySearchSortFieldLatestAt IdentitySearchSortField = "latest_at" +) + +// Validate checks if the IdentitySearchSortField value is valid +func (i IdentitySearchSortField) Validate() error { + switch i { + case IdentitySearchSortFieldActivityCount, IdentitySearchSortFieldDisplayLabel, IdentitySearchSortFieldLatestAt: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid IdentitySearchSortField value, got: %v", i)) + } +} + +type RemoveResultCacheState string + +const ( + RemoveResultCacheStateReady RemoveResultCacheState = "ready" + RemoveResultCacheStateStale RemoveResultCacheState = "stale" +) + +// Validate checks if the RemoveResultCacheState value is valid +func (r RemoveResultCacheState) Validate() error { + switch r { + case RemoveResultCacheStateReady, RemoveResultCacheStateStale: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid RemoveResultCacheState value, got: %v", r)) + } +} + +type SavedViewSortDirection string + +const ( + Asc SavedViewSortDirection = "asc" + Desc SavedViewSortDirection = "desc" +) + +// Validate checks if the SavedViewSortDirection value is valid +func (s SavedViewSortDirection) Validate() error { + switch s { + case Asc, Desc: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid SavedViewSortDirection value, got: %v", s)) + } +} + +type SavedViewStateEnvelopePresentation string + +const ( + Files SavedViewStateEnvelopePresentation = "files" + Table SavedViewStateEnvelopePresentation = "table" + Timeline SavedViewStateEnvelopePresentation = "timeline" +) + +// Validate checks if the SavedViewStateEnvelopePresentation value is valid +func (s SavedViewStateEnvelopePresentation) Validate() error { + switch s { + case Files, Table, Timeline: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid SavedViewStateEnvelopePresentation value, got: %v", s)) + } +} + +type SearchCoverageResponseStatus string + +const ( + Disabled SearchCoverageResponseStatus = "disabled" + Incomplete SearchCoverageResponseStatus = "incomplete" + Initializing SearchCoverageResponseStatus = "initializing" + SearchCoverageResponseStatusReady SearchCoverageResponseStatus = "ready" + SearchCoverageResponseStatusStale SearchCoverageResponseStatus = "stale" + Unavailable SearchCoverageResponseStatus = "unavailable" +) + +// Validate checks if the SearchCoverageResponseStatus value is valid +func (s SearchCoverageResponseStatus) Validate() error { + switch s { + case Disabled, Incomplete, Initializing, SearchCoverageResponseStatusReady, SearchCoverageResponseStatusStale, Unavailable: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid SearchCoverageResponseStatus value, got: %v", s)) + } +} + +type SearchCoverageResponseActions string + +const ( + BuildIndex SearchCoverageResponseActions = "build_index" + Retry SearchCoverageResponseActions = "retry" +) + +// Validate checks if the SearchCoverageResponseActions value is valid +func (s SearchCoverageResponseActions) Validate() error { + switch s { + case BuildIndex, Retry: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid SearchCoverageResponseActions value, got: %v", s)) + } +} + +type SecretSettingUpdateAction string + +const ( + Clear SecretSettingUpdateAction = "clear" + Set SecretSettingUpdateAction = "set" +) + +// Validate checks if the SecretSettingUpdateAction value is valid +func (s SecretSettingUpdateAction) Validate() error { + switch s { + case Clear, Set: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid SecretSettingUpdateAction value, got: %v", s)) + } +} + +type SessionStatusAuthMode string + +const ( + APIKey SessionStatusAuthMode = "api_key" + Loopback SessionStatusAuthMode = "loopback" + Required SessionStatusAuthMode = "required" + Session SessionStatusAuthMode = "session" +) + +// Validate checks if the SessionStatusAuthMode value is valid +func (s SessionStatusAuthMode) Validate() error { + switch s { + case APIKey, Loopback, Required, Session: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid SessionStatusAuthMode value, got: %v", s)) + } +} + +type SettingGroup string + +const ( + Archive SettingGroup = "archive" + Browser SettingGroup = "browser" + Integrations SettingGroup = "integrations" + Search SettingGroup = "search" + Server SettingGroup = "server" + Sources SettingGroup = "sources" +) + +// Validate checks if the SettingGroup value is valid +func (s SettingGroup) Validate() error { + switch s { + case Archive, Browser, Integrations, Search, Server, Sources: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid SettingGroup value, got: %v", s)) + } +} + +type SettingKind string + +const ( + Boolean SettingKind = "boolean" + Integer SettingKind = "integer" + Number SettingKind = "number" + Secret SettingKind = "secret" + String SettingKind = "string" + StringArray SettingKind = "string_array" +) + +// Validate checks if the SettingKind value is valid +func (s SettingKind) Validate() error { + switch s { + case Boolean, Integer, Number, Secret, String, StringArray: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid SettingKind value, got: %v", s)) + } +} + +// TaskIntegrationStatusResponseState Current task integration state +type TaskIntegrationStatusResponseState string + +const ( + AuthenticationRequired TaskIntegrationStatusResponseState = "authentication_required" + Incompatible TaskIntegrationStatusResponseState = "incompatible" + NotFound TaskIntegrationStatusResponseState = "not_found" + TaskIntegrationStatusResponseStateDisabled TaskIntegrationStatusResponseState = "disabled" + TaskIntegrationStatusResponseStateReady TaskIntegrationStatusResponseState = "ready" + Unreachable TaskIntegrationStatusResponseState = "unreachable" + WrongProject TaskIntegrationStatusResponseState = "wrong_project" +) + +// Validate checks if the TaskIntegrationStatusResponseState value is valid +func (t TaskIntegrationStatusResponseState) Validate() error { + switch t { + case AuthenticationRequired, Incompatible, NotFound, TaskIntegrationStatusResponseStateDisabled, TaskIntegrationStatusResponseStateReady, Unreachable, WrongProject: + return nil + default: + return runtime.NewValidationErrorsFromString("Enum", fmt.Sprintf("must be a valid TaskIntegrationStatusResponseState value, got: %v", t)) + } +} diff --git a/pkg/client/generated/headers.go b/pkg/client/generated/headers.go new file mode 100644 index 000000000..7c95826b0 --- /dev/null +++ b/pkg/client/generated/headers.go @@ -0,0 +1,43 @@ +// Code generated by oapi-codegen. DO NOT EDIT. + +package generated + +import ( + "github.com/doordash-oss/oapi-codegen-dd/v3/pkg/runtime" +) + +type CreateOrLinkMessageTaskHeaders struct { + // XRequestID Browser-generated retry-stable request ID + XRequestID string `json:"X-Request-Id" validate:"required"` +} + +func (c CreateOrLinkMessageTaskHeaders) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(c)) +} + +type DeleteSavedViewHeaders struct { + // IfMatch Strong ETag returned by the latest Saved View read + IfMatch string `json:"If-Match" validate:"required"` +} + +func (d DeleteSavedViewHeaders) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(d)) +} + +type PatchSavedViewHeaders struct { + // IfMatch Strong ETag returned by the latest Saved View read + IfMatch string `json:"If-Match" validate:"required"` +} + +func (p PatchSavedViewHeaders) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(p)) +} + +type PatchSettingsHeaders struct { + // IfMatch Strong ETag returned by the latest settings read + IfMatch string `json:"If-Match" validate:"required"` +} + +func (p PatchSettingsHeaders) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(p)) +} diff --git a/pkg/client/generated/paths.go b/pkg/client/generated/paths.go index 25f49bccf..d98535cab 100644 --- a/pkg/client/generated/paths.go +++ b/pkg/client/generated/paths.go @@ -56,6 +56,11 @@ func (a AddCLICollectionSourcesPath) Validate() error { return runtime.ConvertValidatorError(typesValidator.Struct(a)) } +type GetConversationPath struct { + // ID Conversation ID + ID int64 `json:"id"` +} + type CancelDeletionPath struct { // ID Deletion manifest ID ID string `json:"id" validate:"required"` @@ -65,6 +70,61 @@ func (c CancelDeletionPath) Validate() error { return runtime.ConvertValidatorError(typesValidator.Struct(c)) } +type GetDeletionPath struct { + // ID Deletion manifest ID + ID string `json:"id" validate:"required"` +} + +func (g GetDeletionPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type GetDomainPath struct { + // Domain Exact normalized domain fact + Domain string `json:"domain" validate:"required"` +} + +func (g GetDomainPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type SearchDomainFilesPath struct { + // Domain Exact normalized domain fact + Domain string `json:"domain" validate:"required"` +} + +func (s SearchDomainFilesPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(s)) +} + +type GetDomainContextSummaryPath struct { + // Domain Exact normalized domain fact + Domain string `json:"domain" validate:"required"` +} + +func (g GetDomainContextSummaryPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type GetDomainTimelinePath struct { + // Domain Exact normalized domain fact + Domain string `json:"domain" validate:"required"` +} + +func (g GetDomainTimelinePath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type GetFilePath struct { + // ID File attachment ID + ID int64 `json:"id"` +} + +type GetFileContentPath struct { + // ID File attachment ID + ID int64 `json:"id"` +} + type GetMessagePath struct { // ID Message ID ID int64 `json:"id"` @@ -75,6 +135,68 @@ type GetMessageInlinePartPath struct { ID int64 `json:"id"` } +type ListMessageTasksPath struct { + // ID Archived email message ID + ID int64 `json:"id"` +} + +type CreateOrLinkMessageTaskPath struct { + // ID Archived email message ID + ID int64 `json:"id"` +} + +type UnlinkMessageTaskPath struct { + // ID Archived email message ID + ID int64 `json:"id"` + + // TaskID External task ID + TaskID string `json:"task_id" validate:"required"` +} + +func (u UnlinkMessageTaskPath) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(u)) +} + +type GetPersonPath struct { + // ID Durable participant ID + ID int64 `json:"id"` +} + +type SearchPersonFilesPath struct { + // ID Durable participant ID + ID int64 `json:"id"` +} + +type GetPersonContextSummaryPath struct { + // ID Durable participant ID + ID int64 `json:"id"` +} + +type GetPersonTimelinePath struct { + // ID Durable participant ID + ID int64 `json:"id"` +} + +type GetRelationshipTimelinePath struct { + // ID Any member participant ID of the counterpart's identity cluster + ID int64 `json:"id"` +} + +type DeleteSavedViewPath struct { + // ID Saved View ID + ID int64 `json:"id"` +} + +type GetSavedViewPath struct { + // ID Saved View ID + ID int64 `json:"id"` +} + +type PatchSavedViewPath struct { + // ID Saved View ID + ID int64 `json:"id"` +} + type TriggerSyncPath struct { // Account Account email or configured source identifier Account string `json:"account" validate:"required"` diff --git a/pkg/client/generated/payloads.go b/pkg/client/generated/payloads.go index 1d7bc46df..460ab5f73 100644 --- a/pkg/client/generated/payloads.go +++ b/pkg/client/generated/payloads.go @@ -2,6 +2,8 @@ package generated +type LoginSessionBody = SessionLoginRequest + type AddAccountBody = AddAccountRequest type UploadTokenBody = TokenUploadRequest @@ -38,4 +40,52 @@ type RunCLIBody = CLIRunRequest type StageDeletionBody = StageDeletionRequest +type SearchDomainsBody = IdentitySearchHTTPRequest + +type SearchDomainFilesBody = FileSearchHTTPRequest + +type GetDomainContextSummaryBody = ExploreHTTPRequest + +type GetDomainTimelineBody = ExploreHTTPRequest + +type ExploreBody = ExploreHTTPRequest + +type ListExploreFilesBody = ExploreFilesHTTPRequest + +type ExploreGroupsBody = ExploreGroupsHTTPRequest + +type CountExploreMatchesBody = ExploreMatchCountsRequest + +type PreflightExploreSelectionBody = ExplorePreflightRequest + +type GroupFilesBody = FileGroupsHTTPRequest + +type SearchFilesBody = FileSearchHTTPRequest + +type LinkIdentityParticipantsBody = IdentityLinkRequest + +type UnlinkIdentityParticipantsBody = IdentityLinkRequest + +type CreateOrLinkMessageTaskBody = TaskLinkMutationRequest + +type SearchPeopleBody = IdentitySearchHTTPRequest + +type SearchPersonFilesBody = FileSearchHTTPRequest + +type GetPersonContextSummaryBody = ExploreHTTPRequest + +type GetPersonTimelineBody = ExploreHTTPRequest + type RunQueryBody = QueryRequest + +type ListRelationshipsBody = RelationshipsHTTPRequest + +type GetRelationshipTimelineBody = RelationshipTimelineHTTPRequest + +type CreateSavedViewBody = CreateSavedViewRequest + +type PatchSavedViewBody = PatchSavedViewRequest + +type GetSearchCoverageBody = SearchCoverageRequest + +type PatchSettingsBody = SettingsPatchRequest diff --git a/pkg/client/generated/queries.go b/pkg/client/generated/queries.go index 0a9a12897..c1c5ae330 100644 --- a/pkg/client/generated/queries.go +++ b/pkg/client/generated/queries.go @@ -240,11 +240,37 @@ func (v VerifyCLIQuery) Validate() error { return runtime.ConvertValidatorError(typesValidator.Struct(v)) } +type GetConversationQuery struct { + // Anchor Selected message ID anchoring the chronological window + Anchor int64 `json:"anchor"` + + // Before Messages before the anchor (default 25, max 50) + Before *int64 `json:"before,omitempty"` + + // After Messages after the anchor (default 25, max 50) + After *int64 `json:"after,omitempty"` + + // Start Lower UTC bound, inclusive (RFC3339). Restricts the window, before/after counts, and has_before/has_after to messages in [start, end) + Start *string `json:"start,omitempty"` + + // End Upper UTC bound, exclusive (RFC3339). Restricts the window, before/after counts, and has_before/has_after to messages in [start, end) + End *string `json:"end,omitempty"` +} + type ListDeletionsQuery struct { // Status Filter manifests by status (pending, in_progress, completed, failed, cancelled) Status *string `json:"status,omitempty"` } +type SearchIntegrationTasksQuery struct { + // Q Task title search within the configured project + Q string `json:"q" validate:"required"` +} + +func (s SearchIntegrationTasksQuery) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(s)) +} + type ListMessagesQuery struct { // Page One-based page number (default 1; values below 1 are clamped to 1). Non-numeric values are rejected with 400. Page *int64 `json:"page,omitempty"` diff --git a/pkg/client/generated/responses.go b/pkg/client/generated/responses.go index 8c04e3ecc..5e365af05 100644 --- a/pkg/client/generated/responses.go +++ b/pkg/client/generated/responses.go @@ -15,6 +15,16 @@ type DaemonPingResponse = PingInfo type DaemonPingErrorResponse = ErrorResponse +type LogoutSessionErrorResponse = ErrorResponse + +type GetSessionResponse = SessionStatus + +type GetSessionErrorResponse = ErrorResponse + +type LoginSessionResponse = SessionStatus + +type LoginSessionErrorResponse = ErrorResponse + type ListAccountsResponse = AccountListResponse type ListAccountsErrorResponse = ErrorResponse @@ -283,6 +293,10 @@ type VerifyCLIResponse = []byte type VerifyCLIErrorResponse = ErrorResponse +type GetConversationResponse = ConversationResponse + +type GetConversationErrorResponse = ErrorResponse + type ListDeletionsResponseJSON = ListDeletionsResponse type ListDeletionsErrorResponse = ErrorResponse @@ -291,51 +305,903 @@ type StageDeletionResponseJSON = StageDeletionResponse type StageDeletionResponseJSON201 = StageDeletionResponse -type StageDeletionErrorResponse = ErrorResponse +type StageDeletionErrorResponse = ErrorResponse + +type CancelDeletionResponseJSON = CancelDeletionResponse + +type CancelDeletionErrorResponse = ErrorResponse + +type GetDeletionResponse = DeletionManifestDetail + +type GetDeletionErrorResponse = ErrorResponse + +type SearchDomainsResponse = DomainSearchHTTPResponse + +type SearchDomainsErrorResponse = ErrorResponse + +type SearchDomainsErrorResponseJSON = ErrorResponse + +type SearchDomainsErrorResponseJSON503 struct { + SearchDomains_ErrorResponse_503_AnyOf *SearchDomains_ErrorResponse_503_AnyOf `json:"-"` +} + +func (s SearchDomainsErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(s.SearchDomains_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("SearchDomains_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (s *SearchDomainsErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if s.SearchDomains_ErrorResponse_503_AnyOf == nil { + s.SearchDomains_ErrorResponse_503_AnyOf = &SearchDomains_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, s.SearchDomains_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("SearchDomains_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type GetDomainResponse = DomainSummary + +type GetDomainErrorResponse = ErrorResponse + +type SearchDomainFilesResponse = FileSearchHTTPResponse + +type SearchDomainFilesErrorResponse = ErrorResponse + +type SearchDomainFilesErrorResponseJSON = ErrorResponse + +type SearchDomainFilesErrorResponseJSON503 struct { + SearchDomainFiles_ErrorResponse_503_AnyOf *SearchDomainFiles_ErrorResponse_503_AnyOf `json:"-"` +} + +func (s SearchDomainFilesErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(s.SearchDomainFiles_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("SearchDomainFiles_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (s *SearchDomainFilesErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if s.SearchDomainFiles_ErrorResponse_503_AnyOf == nil { + s.SearchDomainFiles_ErrorResponse_503_AnyOf = &SearchDomainFiles_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, s.SearchDomainFiles_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("SearchDomainFiles_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type GetDomainContextSummaryResponse = DomainContextSummaryHTTPResponse + +type GetDomainContextSummaryErrorResponse = ErrorResponse + +type GetDomainContextSummaryErrorResponseJSON = ErrorResponse + +type GetDomainContextSummaryErrorResponseJSON503 struct { + GetDomainContextSummary_ErrorResponse_503_AnyOf *GetDomainContextSummary_ErrorResponse_503_AnyOf `json:"-"` +} + +func (g GetDomainContextSummaryErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(g.GetDomainContextSummary_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("GetDomainContextSummary_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (g *GetDomainContextSummaryErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if g.GetDomainContextSummary_ErrorResponse_503_AnyOf == nil { + g.GetDomainContextSummary_ErrorResponse_503_AnyOf = &GetDomainContextSummary_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, g.GetDomainContextSummary_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("GetDomainContextSummary_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type GetDomainTimelineResponse = ExploreHTTPResponse + +type GetDomainTimelineErrorResponse = ErrorResponse + +type GetDomainTimelineErrorResponseJSON = ErrorResponse + +type GetDomainTimelineErrorResponseJSON503 struct { + GetDomainTimeline_ErrorResponse_503_AnyOf *GetDomainTimeline_ErrorResponse_503_AnyOf `json:"-"` +} + +func (g GetDomainTimelineErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(g.GetDomainTimeline_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("GetDomainTimeline_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (g *GetDomainTimelineErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if g.GetDomainTimeline_ErrorResponse_503_AnyOf == nil { + g.GetDomainTimeline_ErrorResponse_503_AnyOf = &GetDomainTimeline_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, g.GetDomainTimeline_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("GetDomainTimeline_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type ExploreResponse = ExploreHTTPResponse + +type ExploreErrorResponse = ErrorResponse + +type ExploreErrorResponseJSON = ErrorResponse + +type ExploreErrorResponseJSON503 struct { + Explore_ErrorResponse_503_AnyOf *Explore_ErrorResponse_503_AnyOf `json:"-"` +} + +func (e ExploreErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(e.Explore_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("Explore_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (e *ExploreErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if e.Explore_ErrorResponse_503_AnyOf == nil { + e.Explore_ErrorResponse_503_AnyOf = &Explore_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, e.Explore_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("Explore_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type ListExploreFilesResponse = ExploreFilesHTTPResponse + +type ListExploreFilesErrorResponse = ErrorResponse + +type ListExploreFilesErrorResponseJSON = ErrorResponse + +type ListExploreFilesErrorResponseJSON503 struct { + ListExploreFiles_ErrorResponse_503_AnyOf *ListExploreFiles_ErrorResponse_503_AnyOf `json:"-"` +} + +func (l ListExploreFilesErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(l.ListExploreFiles_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("ListExploreFiles_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (l *ListExploreFilesErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if l.ListExploreFiles_ErrorResponse_503_AnyOf == nil { + l.ListExploreFiles_ErrorResponse_503_AnyOf = &ListExploreFiles_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, l.ListExploreFiles_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("ListExploreFiles_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type ExploreGroupsResponse = ExploreGroupsHTTPResponse + +type ExploreGroupsErrorResponse = ErrorResponse + +type ExploreGroupsErrorResponseJSON = ErrorResponse + +type ExploreGroupsErrorResponseJSON503 struct { + ExploreGroups_ErrorResponse_503_AnyOf *ExploreGroups_ErrorResponse_503_AnyOf `json:"-"` +} + +func (e ExploreGroupsErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(e.ExploreGroups_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("ExploreGroups_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (e *ExploreGroupsErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if e.ExploreGroups_ErrorResponse_503_AnyOf == nil { + e.ExploreGroups_ErrorResponse_503_AnyOf = &ExploreGroups_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, e.ExploreGroups_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("ExploreGroups_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type CountExploreMatchesResponse = ExploreMatchCountsResponse + +type CountExploreMatchesErrorResponse = ErrorResponse + +type CountExploreMatchesErrorResponseJSON = ErrorResponse + +type CountExploreMatchesErrorResponseJSON503 struct { + CountExploreMatches_ErrorResponse_503_AnyOf *CountExploreMatches_ErrorResponse_503_AnyOf `json:"-"` +} + +func (c CountExploreMatchesErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(c.CountExploreMatches_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("CountExploreMatches_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (c *CountExploreMatchesErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if c.CountExploreMatches_ErrorResponse_503_AnyOf == nil { + c.CountExploreMatches_ErrorResponse_503_AnyOf = &CountExploreMatches_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, c.CountExploreMatches_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("CountExploreMatches_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type PreflightExploreSelectionResponse = ExplorePreflightResponse + +type PreflightExploreSelectionErrorResponse = ErrorResponse + +type PreflightExploreSelectionErrorResponseJSON = ErrorResponse + +type PreflightExploreSelectionErrorResponseJSON503 struct { + PreflightExploreSelection_ErrorResponse_503_AnyOf *PreflightExploreSelection_ErrorResponse_503_AnyOf `json:"-"` +} + +func (p PreflightExploreSelectionErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(p.PreflightExploreSelection_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("PreflightExploreSelection_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (p *PreflightExploreSelectionErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if p.PreflightExploreSelection_ErrorResponse_503_AnyOf == nil { + p.PreflightExploreSelection_ErrorResponse_503_AnyOf = &PreflightExploreSelection_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, p.PreflightExploreSelection_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("PreflightExploreSelection_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type GroupFilesResponse = FileGroupsHTTPResponse + +type GroupFilesErrorResponse = ErrorResponse + +type GroupFilesErrorResponseJSON = ErrorResponse + +type GroupFilesErrorResponseJSON503 struct { + GroupFiles_ErrorResponse_503_AnyOf *GroupFiles_ErrorResponse_503_AnyOf `json:"-"` +} + +func (g GroupFilesErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(g.GroupFiles_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("GroupFiles_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (g *GroupFilesErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if g.GroupFiles_ErrorResponse_503_AnyOf == nil { + g.GroupFiles_ErrorResponse_503_AnyOf = &GroupFiles_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, g.GroupFiles_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("GroupFiles_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type SearchFilesResponse = FileSearchHTTPResponse + +type SearchFilesErrorResponse = ErrorResponse + +type SearchFilesErrorResponseJSON = ErrorResponse + +type SearchFilesErrorResponseJSON503 struct { + SearchFiles_ErrorResponse_503_AnyOf *SearchFiles_ErrorResponse_503_AnyOf `json:"-"` +} + +func (s SearchFilesErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(s.SearchFiles_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("SearchFiles_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (s *SearchFilesErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if s.SearchFiles_ErrorResponse_503_AnyOf == nil { + s.SearchFiles_ErrorResponse_503_AnyOf = &SearchFiles_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, s.SearchFiles_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("SearchFiles_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type GetFileResponse = FileMetadataResponse + +type GetFileErrorResponse = ErrorResponse + +type GetFileContentResponse = []byte + +type GetFileContentErrorResponse = ErrorResponse + +type GetFileContentErrorResponseJSON = ErrorResponse + +type GetFileContentErrorResponseJSON404 = ErrorResponse + +type GetFileContentErrorResponseJSON500 = ErrorResponse + +type GetFileContentErrorResponseJSON503 = ErrorResponse + +type GetHealthResponse = HealthResponse + +type GetHealthErrorResponse = ErrorResponse + +type LinkIdentityParticipantsResponse = IdentityLinkResponse + +type LinkIdentityParticipantsErrorResponse = ErrorResponse + +type UnlinkIdentityParticipantsResponse = IdentityLinkResponse + +type UnlinkIdentityParticipantsErrorResponse = ErrorResponse + +type SearchIntegrationTasksResponse = TaskSearchResponse + +type SearchIntegrationTasksErrorResponse = ErrorResponse + +type GetTaskIntegrationStatusResponse = TaskIntegrationStatusResponse + +type GetTaskIntegrationStatusErrorResponse = ErrorResponse + +type TestTaskIntegrationResponse = TaskIntegrationStatusResponse + +type TestTaskIntegrationErrorResponse = ErrorResponse + +type ListMessagesResponse = MessageListResponse + +type ListMessagesErrorResponse = ErrorResponse + +type FilterMessagesResponse = FilteredMessagesResponse + +type FilterMessagesErrorResponse = ErrorResponse + +type GetGmailIDsByFilterResponse = GmailIDsResponse + +type GetGmailIDsByFilterErrorResponse = ErrorResponse + +type GetMessageResponse = MessageDetail + +type GetMessageErrorResponse = ErrorResponse + +type GetMessageInlinePartResponse = runtime.File + +type GetMessageInlinePartErrorResponse = ErrorResponse + +type GetMessageInlinePartErrorResponseJSON = ErrorResponse + +type GetMessageInlinePartErrorResponseJSON404 = ErrorResponse + +type GetMessageInlinePartErrorResponseJSON415 = ErrorResponse + +type GetMessageInlinePartErrorResponseJSON500 = ErrorResponse + +type GetMessageInlinePartErrorResponseJSON501 = ErrorResponse + +type GetMessageInlinePartErrorResponseJSON503 = ErrorResponse + +type ListMessageTasksResponse = TaskLinkLookupResponse + +type ListMessageTasksErrorResponse = ErrorResponse + +type CreateOrLinkMessageTaskResponse = TaskLinkMutationResponse + +type CreateOrLinkMessageTaskErrorResponse = ErrorResponse + +type UnlinkMessageTaskResponse = TaskLinkMutationResponse + +type UnlinkMessageTaskErrorResponse = ErrorResponse + +type SearchPeopleResponse = PersonSearchHTTPResponse + +type SearchPeopleErrorResponse = ErrorResponse + +type SearchPeopleErrorResponseJSON = ErrorResponse + +type SearchPeopleErrorResponseJSON503 struct { + SearchPeople_ErrorResponse_503_AnyOf *SearchPeople_ErrorResponse_503_AnyOf `json:"-"` +} + +func (s SearchPeopleErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(s.SearchPeople_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("SearchPeople_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (s *SearchPeopleErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if s.SearchPeople_ErrorResponse_503_AnyOf == nil { + s.SearchPeople_ErrorResponse_503_AnyOf = &SearchPeople_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, s.SearchPeople_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("SearchPeople_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type GetPersonResponse = PersonSummary + +type GetPersonErrorResponse = ErrorResponse + +type SearchPersonFilesResponse = FileSearchHTTPResponse + +type SearchPersonFilesErrorResponse = ErrorResponse + +type SearchPersonFilesErrorResponseJSON = ErrorResponse + +type SearchPersonFilesErrorResponseJSON503 struct { + SearchPersonFiles_ErrorResponse_503_AnyOf *SearchPersonFiles_ErrorResponse_503_AnyOf `json:"-"` +} + +func (s SearchPersonFilesErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(s.SearchPersonFiles_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("SearchPersonFiles_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (s *SearchPersonFilesErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if s.SearchPersonFiles_ErrorResponse_503_AnyOf == nil { + s.SearchPersonFiles_ErrorResponse_503_AnyOf = &SearchPersonFiles_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, s.SearchPersonFiles_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("SearchPersonFiles_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type GetPersonContextSummaryResponse = PersonContextSummaryHTTPResponse + +type GetPersonContextSummaryErrorResponse = ErrorResponse + +type GetPersonContextSummaryErrorResponseJSON = ErrorResponse + +type GetPersonContextSummaryErrorResponseJSON503 struct { + GetPersonContextSummary_ErrorResponse_503_AnyOf *GetPersonContextSummary_ErrorResponse_503_AnyOf `json:"-"` +} + +func (g GetPersonContextSummaryErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(g.GetPersonContextSummary_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("GetPersonContextSummary_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (g *GetPersonContextSummaryErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if g.GetPersonContextSummary_ErrorResponse_503_AnyOf == nil { + g.GetPersonContextSummary_ErrorResponse_503_AnyOf = &GetPersonContextSummary_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, g.GetPersonContextSummary_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("GetPersonContextSummary_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type GetPersonTimelineResponse = ExploreHTTPResponse + +type GetPersonTimelineErrorResponse = ErrorResponse + +type GetPersonTimelineErrorResponseJSON = ErrorResponse + +type GetPersonTimelineErrorResponseJSON503 struct { + GetPersonTimeline_ErrorResponse_503_AnyOf *GetPersonTimeline_ErrorResponse_503_AnyOf `json:"-"` +} + +func (g GetPersonTimelineErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(g.GetPersonTimeline_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("GetPersonTimeline_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (g *GetPersonTimelineErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if g.GetPersonTimeline_ErrorResponse_503_AnyOf == nil { + g.GetPersonTimeline_ErrorResponse_503_AnyOf = &GetPersonTimeline_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, g.GetPersonTimeline_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("GetPersonTimeline_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type RunQueryResponse = QueryResult + +type RunQueryErrorResponse = ErrorResponse + +type ListRelationshipsResponse = RelationshipsHTTPResponse + +type ListRelationshipsErrorResponse = ErrorResponse + +type ListRelationshipsErrorResponseJSON = ErrorResponse + +type ListRelationshipsErrorResponseJSON503 struct { + ListRelationships_ErrorResponse_503_AnyOf *ListRelationships_ErrorResponse_503_AnyOf `json:"-"` +} + +func (l ListRelationshipsErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(l.ListRelationships_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("ListRelationships_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (l *ListRelationshipsErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if l.ListRelationships_ErrorResponse_503_AnyOf == nil { + l.ListRelationships_ErrorResponse_503_AnyOf = &ListRelationships_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, l.ListRelationships_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("ListRelationships_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} + +type GetRelationshipTimelineResponse = RelationshipTimelineHTTPResponse + +type GetRelationshipTimelineErrorResponse = ErrorResponse + +type GetRelationshipTimelineErrorResponseJSON = ErrorResponse + +type GetRelationshipTimelineErrorResponseJSON503 struct { + GetRelationshipTimeline_ErrorResponse_503_AnyOf *GetRelationshipTimeline_ErrorResponse_503_AnyOf `json:"-"` +} + +func (g GetRelationshipTimelineErrorResponseJSON503) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(g.GetRelationshipTimeline_ErrorResponse_503_AnyOf) + if err != nil { + return nil, fmt.Errorf("GetRelationshipTimeline_ErrorResponse_503_AnyOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (g *GetRelationshipTimelineErrorResponseJSON503) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if g.GetRelationshipTimeline_ErrorResponse_503_AnyOf == nil { + g.GetRelationshipTimeline_ErrorResponse_503_AnyOf = &GetRelationshipTimeline_ErrorResponse_503_AnyOf{} + } + + if err := runtime.UnmarshalJSON(data, g.GetRelationshipTimeline_ErrorResponse_503_AnyOf); err != nil { + return fmt.Errorf("GetRelationshipTimeline_ErrorResponse_503_AnyOf unmarshal: %w", err) + } + + return nil +} -type CancelDeletionResponseJSON = CancelDeletionResponse +type ListSavedViewsResponse = SavedViewsResponse -type CancelDeletionErrorResponse = ErrorResponse +type ListSavedViewsErrorResponse = ErrorResponse -type GetHealthResponse = HealthResponse +type CreateSavedViewResponse = SavedView -type GetHealthErrorResponse = ErrorResponse +type CreateSavedViewErrorResponse = ErrorResponse -type ListMessagesResponse = MessageListResponse +type CreateSavedViewErrorResponseJSON = ErrorResponse -type ListMessagesErrorResponse = ErrorResponse +type CreateSavedViewErrorResponseJSON503 = ErrorResponse -type FilterMessagesResponse = FilteredMessagesResponse +type DeleteSavedViewErrorResponse = ErrorResponse -type FilterMessagesErrorResponse = ErrorResponse +type DeleteSavedViewErrorResponseJSON = ErrorResponse -type GetGmailIDsByFilterResponse = GmailIDsResponse +type DeleteSavedViewErrorResponseJSON404 = ErrorResponse -type GetGmailIDsByFilterErrorResponse = ErrorResponse +type DeleteSavedViewErrorResponseJSON409 = ErrorResponse -type GetMessageResponse = MessageDetail +type DeleteSavedViewErrorResponseJSON428 = ErrorResponse -type GetMessageErrorResponse = ErrorResponse +type DeleteSavedViewErrorResponseJSON500 = ErrorResponse -type GetMessageInlinePartResponse = runtime.File +type DeleteSavedViewErrorResponseJSON503 = ErrorResponse -type GetMessageInlinePartErrorResponse = ErrorResponse +type GetSavedViewResponse = SavedView -type GetMessageInlinePartErrorResponseJSON = ErrorResponse +type GetSavedViewErrorResponse = ErrorResponse -type GetMessageInlinePartErrorResponseJSON404 = ErrorResponse +type GetSavedViewErrorResponseJSON = ErrorResponse -type GetMessageInlinePartErrorResponseJSON415 = ErrorResponse +type PatchSavedViewResponse = SavedView -type GetMessageInlinePartErrorResponseJSON500 = ErrorResponse +type PatchSavedViewErrorResponse = ErrorResponse -type GetMessageInlinePartErrorResponseJSON501 = ErrorResponse +type PatchSavedViewErrorResponseJSON = ErrorResponse -type GetMessageInlinePartErrorResponseJSON503 = ErrorResponse +type PatchSavedViewErrorResponseJSON409 = ErrorResponse -type RunQueryResponse = QueryResult +type PatchSavedViewErrorResponseJSON428 = ErrorResponse -type RunQueryErrorResponse = ErrorResponse +type PatchSavedViewErrorResponseJSON503 = ErrorResponse type GetSchedulerStatusResponse = SchedulerStatusResponse @@ -381,6 +1247,12 @@ func (s *SearchMessagesResponse) UnmarshalJSON(data []byte) error { type SearchMessagesErrorResponse = ErrorResponse +type GetSearchCoverageResponse = SearchCoverageResponse + +type GetSearchCoverageErrorResponse = ErrorResponse + +type GetSearchCoverageErrorResponseJSON = ErrorResponse + type DeepSearchResponseJSON = DeepSearchResponse type DeepSearchErrorResponse = ErrorResponse @@ -397,6 +1269,22 @@ type FindSimilarMessagesResponse = SimilarSearchResponse type FindSimilarMessagesErrorResponse = ErrorResponse +type GetSettingsResponse = SettingsResponse + +type GetSettingsErrorResponse = ErrorResponse + +type PatchSettingsResponse = SettingsResponse + +type PatchSettingsErrorResponse = ErrorResponse + +type PatchSettingsErrorResponseJSON = ErrorResponse + +type PatchSettingsErrorResponseJSON412 = ErrorResponse + +type PatchSettingsErrorResponseJSON422 = ErrorResponse + +type PatchSettingsErrorResponseJSON428 = ErrorResponse + type ListSourceStatusResponse = SourceStatusResponse type ListSourceStatusErrorResponse = ErrorResponse @@ -444,6 +1332,27 @@ type DaemonPingResp struct { JSON200 *DaemonPingResponse } +type LogoutSessionResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON429 *LogoutSessionErrorResponse +} + +type GetSessionResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetSessionResponse +} + +type LoginSessionResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *LoginSessionResponse +} + type ListAccountsResp struct { HTTPResponse *http.Response Body []byte @@ -787,6 +1696,13 @@ type VerifyCLIResp struct { StatusCode int } +type GetConversationResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetConversationResponse +} + type ListDeletionsResp struct { HTTPResponse *http.Response Body []byte @@ -809,6 +1725,148 @@ type CancelDeletionResp struct { JSON200 *CancelDeletionResponseJSON } +type GetDeletionResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetDeletionResponse +} + +type SearchDomainsResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *SearchDomainsResponse + JSON400 *SearchDomainsErrorResponse + JSON409 *SearchDomainsErrorResponseJSON + JSON503 *SearchDomainsErrorResponseJSON503 +} + +type GetDomainResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetDomainResponse +} + +type SearchDomainFilesResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *SearchDomainFilesResponse + JSON400 *SearchDomainFilesErrorResponse + JSON409 *SearchDomainFilesErrorResponseJSON + JSON503 *SearchDomainFilesErrorResponseJSON503 +} + +type GetDomainContextSummaryResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetDomainContextSummaryResponse + JSON400 *GetDomainContextSummaryErrorResponse + JSON409 *GetDomainContextSummaryErrorResponseJSON + JSON503 *GetDomainContextSummaryErrorResponseJSON503 +} + +type GetDomainTimelineResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetDomainTimelineResponse + JSON400 *GetDomainTimelineErrorResponse + JSON409 *GetDomainTimelineErrorResponseJSON + JSON503 *GetDomainTimelineErrorResponseJSON503 +} + +type ExploreResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *ExploreResponse + JSON400 *ExploreErrorResponse + JSON409 *ExploreErrorResponseJSON + JSON503 *ExploreErrorResponseJSON503 +} + +type ListExploreFilesResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *ListExploreFilesResponse + JSON400 *ListExploreFilesErrorResponse + JSON409 *ListExploreFilesErrorResponseJSON + JSON503 *ListExploreFilesErrorResponseJSON503 +} + +type ExploreGroupsResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *ExploreGroupsResponse + JSON400 *ExploreGroupsErrorResponse + JSON409 *ExploreGroupsErrorResponseJSON + JSON503 *ExploreGroupsErrorResponseJSON503 +} + +type CountExploreMatchesResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *CountExploreMatchesResponse + JSON400 *CountExploreMatchesErrorResponse + JSON409 *CountExploreMatchesErrorResponseJSON + JSON503 *CountExploreMatchesErrorResponseJSON503 +} + +type PreflightExploreSelectionResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *PreflightExploreSelectionResponse + JSON400 *PreflightExploreSelectionErrorResponse + JSON409 *PreflightExploreSelectionErrorResponseJSON + JSON503 *PreflightExploreSelectionErrorResponseJSON503 +} + +type GroupFilesResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GroupFilesResponse + JSON400 *GroupFilesErrorResponse + JSON409 *GroupFilesErrorResponseJSON + JSON503 *GroupFilesErrorResponseJSON503 +} + +type SearchFilesResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *SearchFilesResponse + JSON400 *SearchFilesErrorResponse + JSON409 *SearchFilesErrorResponseJSON + JSON503 *SearchFilesErrorResponseJSON503 +} + +type GetFileResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetFileResponse +} + +type GetFileContentResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON400 *GetFileContentErrorResponse + JSON401 *GetFileContentErrorResponseJSON + JSON404 *GetFileContentErrorResponseJSON404 + JSON500 *GetFileContentErrorResponseJSON500 + JSON503 *GetFileContentErrorResponseJSON503 +} + type GetHealthResp struct { HTTPResponse *http.Response Body []byte @@ -816,6 +1874,41 @@ type GetHealthResp struct { JSON200 *GetHealthResponse } +type LinkIdentityParticipantsResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *LinkIdentityParticipantsResponse +} + +type UnlinkIdentityParticipantsResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *UnlinkIdentityParticipantsResponse +} + +type SearchIntegrationTasksResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *SearchIntegrationTasksResponse +} + +type GetTaskIntegrationStatusResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetTaskIntegrationStatusResponse +} + +type TestTaskIntegrationResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *TestTaskIntegrationResponse +} + type ListMessagesResp struct { HTTPResponse *http.Response Body []byte @@ -857,6 +1950,74 @@ type GetMessageInlinePartResp struct { JSON503 *GetMessageInlinePartErrorResponseJSON503 } +type ListMessageTasksResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *ListMessageTasksResponse +} + +type CreateOrLinkMessageTaskResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON201 *CreateOrLinkMessageTaskResponse +} + +type UnlinkMessageTaskResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *UnlinkMessageTaskResponse +} + +type SearchPeopleResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *SearchPeopleResponse + JSON400 *SearchPeopleErrorResponse + JSON409 *SearchPeopleErrorResponseJSON + JSON503 *SearchPeopleErrorResponseJSON503 +} + +type GetPersonResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetPersonResponse +} + +type SearchPersonFilesResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *SearchPersonFilesResponse + JSON400 *SearchPersonFilesErrorResponse + JSON409 *SearchPersonFilesErrorResponseJSON + JSON503 *SearchPersonFilesErrorResponseJSON503 +} + +type GetPersonContextSummaryResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetPersonContextSummaryResponse + JSON400 *GetPersonContextSummaryErrorResponse + JSON409 *GetPersonContextSummaryErrorResponseJSON + JSON503 *GetPersonContextSummaryErrorResponseJSON503 +} + +type GetPersonTimelineResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetPersonTimelineResponse + JSON400 *GetPersonTimelineErrorResponse + JSON409 *GetPersonTimelineErrorResponseJSON + JSON503 *GetPersonTimelineErrorResponseJSON503 +} + type RunQueryResp struct { HTTPResponse *http.Response Body []byte @@ -864,6 +2025,92 @@ type RunQueryResp struct { JSON200 *RunQueryResponse } +type ListRelationshipsResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *ListRelationshipsResponse + JSON400 *ListRelationshipsErrorResponse + JSON409 *ListRelationshipsErrorResponseJSON + JSON503 *ListRelationshipsErrorResponseJSON503 +} + +type GetRelationshipTimelineResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetRelationshipTimelineResponse + JSON400 *GetRelationshipTimelineErrorResponse + JSON409 *GetRelationshipTimelineErrorResponseJSON + JSON503 *GetRelationshipTimelineErrorResponseJSON503 +} + +type ListSavedViewsResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *ListSavedViewsResponse +} + +type CreateSavedViewResp201Headers struct { + ETag string `header:"ETag"` +} + +type CreateSavedViewResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON201 *CreateSavedViewResponse + Headers201 *CreateSavedViewResp201Headers + JSON400 *CreateSavedViewErrorResponse + JSON409 *CreateSavedViewErrorResponseJSON + JSON503 *CreateSavedViewErrorResponseJSON503 +} + +type DeleteSavedViewResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON400 *DeleteSavedViewErrorResponse + JSON401 *DeleteSavedViewErrorResponseJSON + JSON404 *DeleteSavedViewErrorResponseJSON404 + JSON409 *DeleteSavedViewErrorResponseJSON409 + JSON428 *DeleteSavedViewErrorResponseJSON428 + JSON500 *DeleteSavedViewErrorResponseJSON500 + JSON503 *DeleteSavedViewErrorResponseJSON503 +} + +type GetSavedViewResp200Headers struct { + ETag string `header:"ETag"` +} + +type GetSavedViewResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetSavedViewResponse + Headers200 *GetSavedViewResp200Headers + JSON404 *GetSavedViewErrorResponse + JSON503 *GetSavedViewErrorResponseJSON +} + +type PatchSavedViewResp200Headers struct { + ETag string `header:"ETag"` +} + +type PatchSavedViewResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *PatchSavedViewResponse + Headers200 *PatchSavedViewResp200Headers + JSON400 *PatchSavedViewErrorResponse + JSON404 *PatchSavedViewErrorResponseJSON + JSON409 *PatchSavedViewErrorResponseJSON409 + JSON428 *PatchSavedViewErrorResponseJSON428 + JSON503 *PatchSavedViewErrorResponseJSON503 +} + type GetSchedulerStatusResp struct { HTTPResponse *http.Response Body []byte @@ -878,6 +2125,15 @@ type SearchMessagesResp struct { JSON200 *SearchMessagesResponse } +type GetSearchCoverageResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetSearchCoverageResponse + JSON400 *GetSearchCoverageErrorResponse + JSON503 *GetSearchCoverageErrorResponseJSON +} + type DeepSearchResp struct { HTTPResponse *http.Response Body []byte @@ -906,6 +2162,35 @@ type FindSimilarMessagesResp struct { JSON200 *FindSimilarMessagesResponse } +type GetSettingsResp200Headers struct { + ETag string `header:"ETag"` +} + +type GetSettingsResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *GetSettingsResponse + Headers200 *GetSettingsResp200Headers +} + +type PatchSettingsResp200Headers struct { + ETag string `header:"ETag"` +} + +type PatchSettingsResp struct { + HTTPResponse *http.Response + Body []byte + StatusCode int + JSON200 *PatchSettingsResponse + Headers200 *PatchSettingsResp200Headers + JSON400 *PatchSettingsErrorResponse + JSON409 *PatchSettingsErrorResponseJSON + JSON412 *PatchSettingsErrorResponseJSON412 + JSON422 *PatchSettingsErrorResponseJSON422 + JSON428 *PatchSettingsErrorResponseJSON428 +} + type ListSourceStatusResp struct { HTTPResponse *http.Response Body []byte diff --git a/pkg/client/generated/types.go b/pkg/client/generated/types.go index 7027f9bd9..652ed9961 100644 --- a/pkg/client/generated/types.go +++ b/pkg/client/generated/types.go @@ -3,6 +3,8 @@ package generated import ( + "bytes" + "encoding/json" "fmt" "time" @@ -76,14 +78,38 @@ func (a AddRequest) Validate() error { } type AddResult struct { - Account string `json:"account" validate:"required"` - Identifier string `json:"identifier" validate:"required"` - Outcome string `json:"outcome" validate:"required"` - Signal string `json:"signal" validate:"required"` + Account string `json:"account" validate:"required"` + CacheState *AddResultCacheState `json:"cache_state,omitempty"` + Identifier string `json:"identifier" validate:"required"` + Outcome string `json:"outcome" validate:"required"` + Signal string `json:"signal" validate:"required"` } func (a AddResult) Validate() error { - return runtime.ConvertValidatorError(typesValidator.Struct(a)) + var errors runtime.ValidationErrors + if err := typesValidator.Var(a.Account, "required"); err != nil { + errors = errors.Append("Account", err) + } + if a.CacheState != nil { + if v, ok := any(a.CacheState).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("CacheState", err) + } + } + } + if err := typesValidator.Var(a.Identifier, "required"); err != nil { + errors = errors.Append("Identifier", err) + } + if err := typesValidator.Var(a.Outcome, "required"); err != nil { + errors = errors.Append("Outcome", err) + } + if err := typesValidator.Var(a.Signal, "required"); err != nil { + errors = errors.Append("Signal", err) + } + if len(errors) == 0 { + return nil + } + return errors } type Address struct { @@ -891,6 +917,30 @@ func (c CliStatsResponse) Validate() error { return errors } +type ConversationResponse struct { + AnchorID int64 `json:"anchor_id"` + HasAfter bool `json:"has_after"` + HasBefore bool `json:"has_before"` + ID int64 `json:"id"` + Messages []MessageDetail `json:"messages,omitempty" validate:"required"` + Total int64 `json:"total"` +} + +func (c ConversationResponse) Validate() error { + var errors runtime.ValidationErrors + for i, item := range c.Messages { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Messages[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + type CreateRequest struct { Accounts []string `json:"accounts,omitempty" validate:"required"` Name string `json:"name" validate:"required"` @@ -900,6 +950,29 @@ func (c CreateRequest) Validate() error { return runtime.ConvertValidatorError(typesValidator.Struct(c)) } +type CreateSavedViewRequest struct { + CanonicalState SavedViewStateEnvelope `json:"canonical_state"` + Description *string `json:"description,omitempty"` + Name string `json:"name" validate:"required"` + SchemaVersion int64 `json:"schema_version"` +} + +func (c CreateSavedViewRequest) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(c.CanonicalState).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("CanonicalState", err) + } + } + if err := typesValidator.Var(c.Name, "required"); err != nil { + errors = errors.Append("Name", err) + } + if len(errors) == 0 { + return nil + } + return errors +} + type DeepSearchResponse struct { BodyContexts []BodySearchContext `json:"body_contexts,omitempty"` Count int64 `json:"count"` @@ -936,6 +1009,55 @@ func (d DeepSearchResponse) Validate() error { return errors } +type DeletionManifestDetail struct { + Account *string `json:"account,omitempty"` + CreatedAt time.Time `json:"created_at" validate:"required"` + CreatedBy string `json:"created_by" validate:"required"` + Description string `json:"description" validate:"required"` + Execution *Execution `json:"execution,omitempty"` + ID string `json:"id" validate:"required"` + MessageCount int64 `json:"message_count"` + Status string `json:"status" validate:"required"` + Summary *Summary `json:"summary,omitempty"` +} + +func (d DeletionManifestDetail) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(d.CreatedAt, "required"); err != nil { + errors = errors.Append("CreatedAt", err) + } + if err := typesValidator.Var(d.CreatedBy, "required"); err != nil { + errors = errors.Append("CreatedBy", err) + } + if err := typesValidator.Var(d.Description, "required"); err != nil { + errors = errors.Append("Description", err) + } + if d.Execution != nil { + if v, ok := any(d.Execution).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Execution", err) + } + } + } + if err := typesValidator.Var(d.ID, "required"); err != nil { + errors = errors.Append("ID", err) + } + if err := typesValidator.Var(d.Status, "required"); err != nil { + errors = errors.Append("Status", err) + } + if d.Summary != nil { + if v, ok := any(d.Summary).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Summary", err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + type DeletionManifestSummary struct { CreatedAt time.Time `json:"created_at" validate:"required"` CreatedBy string `json:"created_by" validate:"required"` @@ -949,6 +1071,168 @@ func (d DeletionManifestSummary) Validate() error { return runtime.ConvertValidatorError(typesValidator.Struct(d)) } +type DomainContextSummaryHTTPResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CandidateSnapshotID *string `json:"candidate_snapshot_id,omitempty"` + SearchProvenance SearchProvenance `json:"search_provenance"` + Summary DomainSummary `json:"summary"` +} + +func (d DomainContextSummaryHTTPResponse) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(d.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + if v, ok := any(d.SearchProvenance).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchProvenance", err) + } + } + if v, ok := any(d.Summary).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Summary", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type DomainSearchHTTPResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CandidateSnapshotID *string `json:"candidate_snapshot_id,omitempty"` + NextCursor *string `json:"next_cursor,omitempty"` + Rows []DomainSummary `json:"rows,omitempty" validate:"required"` + SearchProvenance SearchProvenance `json:"search_provenance"` + TotalCount int64 `json:"total_count"` +} + +func (d DomainSearchHTTPResponse) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(d.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + for i, item := range d.Rows { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Rows[%d]", i), err) + } + } + } + if v, ok := any(d.SearchProvenance).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchProvenance", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type DomainSummary struct { + ActivityCount int64 `json:"activity_count"` + CacheRevision string `json:"cache_revision" validate:"required"` + Domain string `json:"domain" validate:"required"` + FileCount int64 `json:"file_count"` + FirstAt time.Time `json:"first_at" validate:"required"` + LastAt time.Time `json:"last_at" validate:"required"` + PersonCount int64 `json:"person_count"` + SourceCounts []SourceCount `json:"source_counts,omitempty" validate:"required"` +} + +func (d DomainSummary) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(d.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + if err := typesValidator.Var(d.Domain, "required"); err != nil { + errors = errors.Append("Domain", err) + } + if err := typesValidator.Var(d.FirstAt, "required"); err != nil { + errors = errors.Append("FirstAt", err) + } + if err := typesValidator.Var(d.LastAt, "required"); err != nil { + errors = errors.Append("LastAt", err) + } + for i, item := range d.SourceCounts { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("SourceCounts[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type EntryRow struct { + AnchorMessageID *int64 `json:"anchor_message_id,omitempty"` + AttachmentCount int64 `json:"attachment_count"` + AttachmentSize int64 `json:"attachment_size"` + ConversationID *int64 `json:"conversation_id,omitempty"` + ConversationType string `json:"conversation_type" validate:"required"` + CounterpartParticipantID *int64 `json:"counterpart_participant_id,omitempty"` + DeletedFromSource bool `json:"deleted_from_source"` + HasAttachments bool `json:"has_attachments"` + Key string `json:"key" validate:"required"` + Kind string `json:"kind" validate:"required"` + Match MatchSummary `json:"match"` + MessageCount int64 `json:"message_count"` + MessageType string `json:"message_type" validate:"required"` + OccurredAt time.Time `json:"occurred_at" validate:"required"` + ParticipantIds []int64 `json:"participant_ids,omitempty"` + ParticipantLabels []string `json:"participant_labels,omitempty"` + Preview string `json:"preview" validate:"required"` + SourceID int64 `json:"source_id"` + SourceIdentifier string `json:"source_identifier" validate:"required"` + SourceType string `json:"source_type" validate:"required"` + Title string `json:"title" validate:"required"` +} + +func (e EntryRow) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(e.ConversationType, "required"); err != nil { + errors = errors.Append("ConversationType", err) + } + if err := typesValidator.Var(e.Key, "required"); err != nil { + errors = errors.Append("Key", err) + } + if err := typesValidator.Var(e.Kind, "required"); err != nil { + errors = errors.Append("Kind", err) + } + if v, ok := any(e.Match).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Match", err) + } + } + if err := typesValidator.Var(e.MessageType, "required"); err != nil { + errors = errors.Append("MessageType", err) + } + if err := typesValidator.Var(e.OccurredAt, "required"); err != nil { + errors = errors.Append("OccurredAt", err) + } + if err := typesValidator.Var(e.Preview, "required"); err != nil { + errors = errors.Append("Preview", err) + } + if err := typesValidator.Var(e.SourceIdentifier, "required"); err != nil { + errors = errors.Append("SourceIdentifier", err) + } + if err := typesValidator.Var(e.SourceType, "required"); err != nil { + errors = errors.Append("SourceType", err) + } + if err := typesValidator.Var(e.Title, "required"); err != nil { + errors = errors.Append("Title", err) + } + if len(errors) == 0 { + return nil + } + return errors +} + type ErrorResponse struct { ErrorData string `json:"error" validate:"required"` Message *string `json:"message,omitempty"` @@ -976,211 +1260,229 @@ func (e Execution) Validate() error { return runtime.ConvertValidatorError(typesValidator.Struct(e)) } -type FilteredMessagesResponse struct { - Count int64 `json:"count"` - HasMore bool `json:"has_more"` - Limit int64 `json:"limit"` - Messages []MessageSummary `json:"messages,omitempty" validate:"required"` - Offset int64 `json:"offset"` +type ExploreActionTarget struct { + Action string `json:"action" validate:"required"` + Filename string `json:"filename" validate:"required"` + MessageID int64 `json:"message_id"` } -func (f FilteredMessagesResponse) Validate() error { +func (e ExploreActionTarget) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(e)) +} + +type ExploreCacheUnavailableResponse struct { + ErrorData string `json:"error" validate:"required"` + Message string `json:"message" validate:"required"` + Readiness ExploreCacheUnavailableResponseReadiness `json:"readiness" validate:"required"` + RecoveryAction string `json:"recovery_action" validate:"required"` +} + +func (e ExploreCacheUnavailableResponse) Validate() error { var errors runtime.ValidationErrors - for i, item := range f.Messages { - if v, ok := any(item).(runtime.Validator); ok { - if err := v.Validate(); err != nil { - errors = errors.Append(fmt.Sprintf("Messages[%d]", i), err) - } + if err := typesValidator.Var(e.ErrorData, "required"); err != nil { + errors = errors.Append("ErrorData", err) + } + if err := typesValidator.Var(e.Message, "required"); err != nil { + errors = errors.Append("Message", err) + } + if v, ok := any(e.Readiness).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Readiness", err) } } + if err := typesValidator.Var(e.RecoveryAction, "required"); err != nil { + errors = errors.Append("RecoveryAction", err) + } if len(errors) == 0 { return nil } return errors } -type Filters struct { - Account *string `json:"account,omitempty"` - After *string `json:"after,omitempty"` - Before *string `json:"before,omitempty"` - Labels []string `json:"labels,omitempty"` - Recipients []string `json:"recipients,omitempty"` - SenderDomains []string `json:"sender_domains,omitempty"` - Senders []string `json:"senders,omitempty"` -} - -type GenerationSummary struct { - ActivatedAt *string `json:"activated_at,omitempty"` - Dimension int64 `json:"dimension"` - Fingerprint string `json:"fingerprint" validate:"required"` - ID int64 `json:"id"` - MessageCount int64 `json:"message_count"` - Model string `json:"model" validate:"required"` - State string `json:"state" validate:"required"` +type ExploreFileFact struct { + ConversationID int64 `json:"conversation_id"` + EntryKey string `json:"entry_key" validate:"required"` + Filename string `json:"filename" validate:"required"` + ID int64 `json:"id"` + Key string `json:"key" validate:"required"` + MessageID int64 `json:"message_id"` + MimeType string `json:"mime_type" validate:"required"` + OccurredAt time.Time `json:"occurred_at" validate:"required"` + Size int64 `json:"size"` + SourceID int64 `json:"source_id"` + SourceIdentifier string `json:"source_identifier" validate:"required"` + Title string `json:"title" validate:"required"` } -func (g GenerationSummary) Validate() error { - return runtime.ConvertValidatorError(typesValidator.Struct(g)) +func (e ExploreFileFact) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(e)) } -type GmailIDsResponse struct { - GmailIds []string `json:"gmail_ids,omitempty" validate:"required"` +type ExploreFilesHTTPRequest struct { + Cursor *string `json:"cursor,omitempty"` + Limit *int64 `json:"limit,omitempty" validate:"omitempty,gte=0,lte=100"` + Predicate ExploreHTTPRequest `json:"predicate"` } -func (g GmailIDsResponse) Validate() error { - return runtime.ConvertValidatorError(typesValidator.Struct(g)) +func (e ExploreFilesHTTPRequest) Validate() error { + var errors runtime.ValidationErrors + if e.Limit != nil { + if err := typesValidator.Var(e.Limit, "omitempty,gte=0,lte=100"); err != nil { + errors = errors.Append("Limit", err) + } + } + if v, ok := any(e.Predicate).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Predicate", err) + } + } + if len(errors) == 0 { + return nil + } + return errors } -type HealthResponse struct { - AnalyticsEngine *string `json:"analytics_engine,omitempty"` - Operation *OperationHealth `json:"operation,omitempty"` - Status string `json:"status" validate:"required"` - Vector *VectorHealth `json:"vector,omitempty"` +type ExploreFilesHTTPResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CandidateSnapshotID *string `json:"candidate_snapshot_id,omitempty"` + Files []ExploreFileFact `json:"files" validate:"required"` + NextCursor *string `json:"next_cursor,omitempty"` + SearchProvenance SearchProvenance `json:"search_provenance"` + TotalCount int64 `json:"total_count"` } -func (h HealthResponse) Validate() error { +func (e ExploreFilesHTTPResponse) Validate() error { var errors runtime.ValidationErrors - if h.Operation != nil { - if v, ok := any(h.Operation).(runtime.Validator); ok { + if err := typesValidator.Var(e.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + for i, item := range e.Files { + if v, ok := any(item).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append("Operation", err) + errors = errors.Append(fmt.Sprintf("Files[%d]", i), err) } } } - if err := typesValidator.Var(h.Status, "required"); err != nil { - errors = errors.Append("Status", err) + if v, ok := any(e.SearchProvenance).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchProvenance", err) + } } - if h.Vector != nil { - if v, ok := any(h.Vector).(runtime.Validator); ok { - if err := v.Validate(); err != nil { - errors = errors.Append("Vector", err) - } + if len(errors) == 0 { + return nil + } + return errors +} + +type ExploreFilter struct { + Dimension ExploreFilterDimension `json:"dimension" validate:"required"` + Values []string `json:"values" validate:"required"` +} + +func (e ExploreFilter) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(e.Dimension).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Dimension", err) } } + if err := typesValidator.Var(e.Values, "required"); err != nil { + errors = errors.Append("Values", err) + } if len(errors) == 0 { return nil } return errors } -type HybridGenerationSummary struct { - Dimension int64 `json:"dimension"` - Fingerprint string `json:"fingerprint" validate:"required"` - ID int64 `json:"id"` - Model string `json:"model" validate:"required"` - State string `json:"state" validate:"required"` +type ExploreGroupRow struct { + Count int64 `json:"count"` + EstimatedBytes int64 `json:"estimated_bytes"` + Key string `json:"key" validate:"required"` + Label string `json:"label" validate:"required"` + LatestAt time.Time `json:"latest_at" validate:"required"` } -func (h HybridGenerationSummary) Validate() error { - return runtime.ConvertValidatorError(typesValidator.Struct(h)) +func (e ExploreGroupRow) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(e)) } -type HybridSearchItem struct { - Bcc []string `json:"bcc,omitempty"` - Cc []string `json:"cc,omitempty"` - ConversationID *int64 `json:"conversation_id,omitempty"` - DeletedAt *string `json:"deleted_at,omitempty"` - From string `json:"from" validate:"required"` - FromEmail *string `json:"from_email,omitempty"` - FromName *string `json:"from_name,omitempty"` - FromPhone *string `json:"from_phone,omitempty"` - HasAttachments bool `json:"has_attachments"` - ID int64 `json:"id"` - Labels []string `json:"labels,omitempty" validate:"required"` - Matches []HybridSearchMatch `json:"matches,omitempty"` - MatchesTruncated *bool `json:"matches_truncated,omitempty"` - MessageType *string `json:"message_type,omitempty"` - Score *ScoreBreakdown `json:"score,omitempty"` - SentAt string `json:"sent_at" validate:"required"` - SizeBytes int64 `json:"size_bytes"` - Snippet string `json:"snippet" validate:"required"` - SourceID *int64 `json:"source_id,omitempty"` - SourceMessageID *string `json:"source_message_id,omitempty"` - Subject string `json:"subject" validate:"required"` - To []string `json:"to,omitempty" validate:"required"` +type ExploreGroupSort struct { + Direction ExploreGroupSortDirection `json:"direction" validate:"required"` + Field ExploreGroupSortField `json:"field" validate:"required"` } -func (h HybridSearchItem) Validate() error { +func (e ExploreGroupSort) Validate() error { var errors runtime.ValidationErrors - if err := typesValidator.Var(h.From, "required"); err != nil { - errors = errors.Append("From", err) - } - if err := typesValidator.Var(h.Labels, "required"); err != nil { - errors = errors.Append("Labels", err) - } - for i, item := range h.Matches { - if v, ok := any(item).(runtime.Validator); ok { - if err := v.Validate(); err != nil { - errors = errors.Append(fmt.Sprintf("Matches[%d]", i), err) - } + if v, ok := any(e.Direction).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Direction", err) } } - if h.Score != nil { - if v, ok := any(h.Score).(runtime.Validator); ok { - if err := v.Validate(); err != nil { - errors = errors.Append("Score", err) - } + if v, ok := any(e.Field).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Field", err) } } - if err := typesValidator.Var(h.SentAt, "required"); err != nil { - errors = errors.Append("SentAt", err) - } - if err := typesValidator.Var(h.Snippet, "required"); err != nil { - errors = errors.Append("Snippet", err) - } - if err := typesValidator.Var(h.Subject, "required"); err != nil { - errors = errors.Append("Subject", err) - } - if err := typesValidator.Var(h.To, "required"); err != nil { - errors = errors.Append("To", err) - } if len(errors) == 0 { return nil } return errors } -type HybridSearchMatch struct { - CharOffset *int64 `json:"char_offset,omitempty"` - Line *int64 `json:"line,omitempty"` - Score float64 `json:"score"` - Snippet string `json:"snippet" validate:"required"` -} - -func (h HybridSearchMatch) Validate() error { - return runtime.ConvertValidatorError(typesValidator.Struct(h)) -} - -type HybridSearchResponse struct { - Generation HybridGenerationSummary `json:"generation"` - HasMore bool `json:"has_more"` - Mode string `json:"mode" validate:"required"` - PoolSaturated bool `json:"pool_saturated"` - Query string `json:"query" validate:"required"` - Results []HybridSearchItem `json:"results,omitempty" validate:"required"` - Returned int64 `json:"returned"` - ScopeLabel *string `json:"scope_label,omitempty"` - ScopeSourceCount *int64 `json:"scope_source_count,omitempty"` - TookMs int64 `json:"took_ms"` +type ExploreGroupsHTTPRequest struct { + Cursor *string `json:"cursor,omitempty"` + Filters []ExploreFilter `json:"filters,omitempty"` + Grouping []ExploreGroupDimension `json:"grouping" validate:"required,min=1,max=1"` + Limit *int64 `json:"limit,omitempty" validate:"omitempty,gte=0,lte=500"` + Presentation *ExploreGroupsHTTPRequestPresentation `json:"presentation,omitempty"` + Query *string `json:"query,omitempty"` + SearchMode *ExploreGroupsHTTPRequestSearchMode `json:"search_mode,omitempty"` + Sort []ExploreGroupSort `json:"sort,omitempty"` } -func (h HybridSearchResponse) Validate() error { +func (e ExploreGroupsHTTPRequest) Validate() error { var errors runtime.ValidationErrors - if v, ok := any(h.Generation).(runtime.Validator); ok { - if err := v.Validate(); err != nil { - errors = errors.Append("Generation", err) + if err := typesValidator.Var(e.Grouping, "required,min=1,max=1"); err != nil { + errors = errors.Append("Grouping", err) + } + for i, item := range e.Filters { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Filters[%d]", i), err) + } } } - if err := typesValidator.Var(h.Mode, "required"); err != nil { - errors = errors.Append("Mode", err) + for i, item := range e.Grouping { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Grouping[%d]", i), err) + } + } } - if err := typesValidator.Var(h.Query, "required"); err != nil { - errors = errors.Append("Query", err) + if e.Limit != nil { + if err := typesValidator.Var(e.Limit, "omitempty,gte=0,lte=500"); err != nil { + errors = errors.Append("Limit", err) + } } - for i, item := range h.Results { + if e.Presentation != nil { + if v, ok := any(e.Presentation).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Presentation", err) + } + } + } + if e.SearchMode != nil { + if v, ok := any(e.SearchMode).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchMode", err) + } + } + } + for i, item := range e.Sort { if v, ok := any(item).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append(fmt.Sprintf("Results[%d]", i), err) + errors = errors.Append(fmt.Sprintf("Sort[%d]", i), err) } } } @@ -1190,75 +1492,89 @@ func (h HybridSearchResponse) Validate() error { return errors } -type ListDeletionsResponse struct { - Manifests []DeletionManifestSummary `json:"manifests,omitempty" validate:"required"` +type ExploreGroupsHTTPResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CandidateSnapshotID *string `json:"candidate_snapshot_id,omitempty"` + NextCursor *string `json:"next_cursor,omitempty"` + Rows []ExploreGroupRow `json:"rows" validate:"required"` + SearchProvenance SearchProvenance `json:"search_provenance"` + TotalCount int64 `json:"total_count"` } -func (l ListDeletionsResponse) Validate() error { +func (e ExploreGroupsHTTPResponse) Validate() error { var errors runtime.ValidationErrors - for i, item := range l.Manifests { + if err := typesValidator.Var(e.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + for i, item := range e.Rows { if v, ok := any(item).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append(fmt.Sprintf("Manifests[%d]", i), err) + errors = errors.Append(fmt.Sprintf("Rows[%d]", i), err) } } } + if v, ok := any(e.SearchProvenance).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchProvenance", err) + } + } if len(errors) == 0 { return nil } return errors } -type Manifest struct { - CreatedAt time.Time `json:"created_at" validate:"required"` - CreatedBy string `json:"created_by" validate:"required"` - Description string `json:"description" validate:"required"` - Execution *Execution `json:"execution,omitempty"` - Filters Filters `json:"filters"` - GmailIds []string `json:"gmail_ids,omitempty" validate:"required"` - ID string `json:"id" validate:"required"` - RawFilter *struct{} `json:"raw_filter,omitempty"` - Status string `json:"status" validate:"required"` - Summary *Summary `json:"summary,omitempty"` - Version int64 `json:"version"` +type ExploreHTTPRequest struct { + CandidateSnapshotID *string `json:"candidate_snapshot_id,omitempty"` + Cursor *string `json:"cursor,omitempty"` + Filters []ExploreFilter `json:"filters,omitempty"` + Grouping []ExploreGroupDimension `json:"grouping,omitempty"` + Limit *int64 `json:"limit,omitempty" validate:"omitempty,gte=0,lte=500"` + Presentation *ExploreHTTPRequestPresentation `json:"presentation,omitempty"` + Query *string `json:"query,omitempty"` + SearchMode *ExploreHTTPRequestSearchMode `json:"search_mode,omitempty"` + Sort []ExploreSort `json:"sort,omitempty"` } -func (m Manifest) Validate() error { +func (e ExploreHTTPRequest) Validate() error { var errors runtime.ValidationErrors - if err := typesValidator.Var(m.CreatedAt, "required"); err != nil { - errors = errors.Append("CreatedAt", err) - } - if err := typesValidator.Var(m.CreatedBy, "required"); err != nil { - errors = errors.Append("CreatedBy", err) - } - if err := typesValidator.Var(m.Description, "required"); err != nil { - errors = errors.Append("Description", err) - } - if m.Execution != nil { - if v, ok := any(m.Execution).(runtime.Validator); ok { + for i, item := range e.Filters { + if v, ok := any(item).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append("Execution", err) + errors = errors.Append(fmt.Sprintf("Filters[%d]", i), err) } } } - if v, ok := any(m.Filters).(runtime.Validator); ok { - if err := v.Validate(); err != nil { - errors = errors.Append("Filters", err) + for i, item := range e.Grouping { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Grouping[%d]", i), err) + } } } - if err := typesValidator.Var(m.GmailIds, "required"); err != nil { - errors = errors.Append("GmailIds", err) + if e.Limit != nil { + if err := typesValidator.Var(e.Limit, "omitempty,gte=0,lte=500"); err != nil { + errors = errors.Append("Limit", err) + } } - if err := typesValidator.Var(m.ID, "required"); err != nil { - errors = errors.Append("ID", err) + if e.Presentation != nil { + if v, ok := any(e.Presentation).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Presentation", err) + } + } } - if err := typesValidator.Var(m.Status, "required"); err != nil { - errors = errors.Append("Status", err) + if e.SearchMode != nil { + if v, ok := any(e.SearchMode).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchMode", err) + } + } } - if m.Summary != nil { - if v, ok := any(m.Summary).(runtime.Validator); ok { + for i, item := range e.Sort { + if v, ok := any(item).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append("Summary", err) + errors = errors.Append(fmt.Sprintf("Sort[%d]", i), err) } } } @@ -1268,60 +1584,53 @@ func (m Manifest) Validate() error { return errors } -type MessageDetail struct { - Attachments []AttachmentInfo `json:"attachments,omitempty" validate:"required"` - Bcc []string `json:"bcc,omitempty"` - Body string `json:"body" validate:"required"` - BodyHTML *string `json:"body_html,omitempty"` - Cc []string `json:"cc,omitempty"` - ConversationID *int64 `json:"conversation_id,omitempty"` - DeletedAt *string `json:"deleted_at,omitempty"` - From string `json:"from" validate:"required"` - FromEmail *string `json:"from_email,omitempty"` - FromName *string `json:"from_name,omitempty"` - FromPhone *string `json:"from_phone,omitempty"` - HasAttachments bool `json:"has_attachments"` - ID int64 `json:"id"` - Labels []string `json:"labels,omitempty" validate:"required"` - MessageType *string `json:"message_type,omitempty"` - SentAt string `json:"sent_at" validate:"required"` - SizeBytes int64 `json:"size_bytes"` - Snippet string `json:"snippet" validate:"required"` - SourceID *int64 `json:"source_id,omitempty"` - SourceMessageID *string `json:"source_message_id,omitempty"` - Subject string `json:"subject" validate:"required"` - To []string `json:"to,omitempty" validate:"required"` +type ExploreHTTPResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CandidatePoolSaturated *bool `json:"candidate_pool_saturated,omitempty"` + CandidateSnapshotID *string `json:"candidate_snapshot_id,omitempty"` + NextCursor *string `json:"next_cursor,omitempty"` + Rows []EntryRow `json:"rows" validate:"required"` + SearchProvenance SearchProvenance `json:"search_provenance"` + TotalCount *int64 `json:"total_count,omitempty"` } -func (m MessageDetail) Validate() error { +func (e ExploreHTTPResponse) Validate() error { var errors runtime.ValidationErrors - for i, item := range m.Attachments { + if err := typesValidator.Var(e.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + for i, item := range e.Rows { if v, ok := any(item).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append(fmt.Sprintf("Attachments[%d]", i), err) + errors = errors.Append(fmt.Sprintf("Rows[%d]", i), err) } } } - if err := typesValidator.Var(m.Body, "required"); err != nil { - errors = errors.Append("Body", err) - } - if err := typesValidator.Var(m.From, "required"); err != nil { - errors = errors.Append("From", err) - } - if err := typesValidator.Var(m.Labels, "required"); err != nil { - errors = errors.Append("Labels", err) - } - if err := typesValidator.Var(m.SentAt, "required"); err != nil { - errors = errors.Append("SentAt", err) + if v, ok := any(e.SearchProvenance).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchProvenance", err) + } } - if err := typesValidator.Var(m.Snippet, "required"); err != nil { - errors = errors.Append("Snippet", err) + if len(errors) == 0 { + return nil } - if err := typesValidator.Var(m.Subject, "required"); err != nil { - errors = errors.Append("Subject", err) + return errors +} + +type ExploreMatchCountsRequest struct { + Predicate ExploreHTTPRequest `json:"predicate"` + RowKeys []string `json:"row_keys" validate:"required"` +} + +func (e ExploreMatchCountsRequest) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(e.Predicate).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Predicate", err) + } } - if err := typesValidator.Var(m.To, "required"); err != nil { - errors = errors.Append("To", err) + if err := typesValidator.Var(e.RowKeys, "required"); err != nil { + errors = errors.Append("RowKeys", err) } if len(errors) == 0 { return nil @@ -1329,67 +1638,1099 @@ func (m MessageDetail) Validate() error { return errors } -type MessageListResponse struct { - Messages []MessageSummary `json:"messages,omitempty" validate:"required"` - Page int64 `json:"page"` - PageSize int64 `json:"page_size"` - Total int64 `json:"total"` +type ExploreMatchCountsResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CanonicalQueryHash string `json:"canonical_query_hash" validate:"required"` + Counts []ExploreRowMatchCount `json:"counts" validate:"required"` + LexicalIndexRevision string `json:"lexical_index_revision" validate:"required"` } -func (m MessageListResponse) Validate() error { +func (e ExploreMatchCountsResponse) Validate() error { var errors runtime.ValidationErrors - for i, item := range m.Messages { + if err := typesValidator.Var(e.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + if err := typesValidator.Var(e.CanonicalQueryHash, "required"); err != nil { + errors = errors.Append("CanonicalQueryHash", err) + } + for i, item := range e.Counts { if v, ok := any(item).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append(fmt.Sprintf("Messages[%d]", i), err) + errors = errors.Append(fmt.Sprintf("Counts[%d]", i), err) } } } + if err := typesValidator.Var(e.LexicalIndexRevision, "required"); err != nil { + errors = errors.Append("LexicalIndexRevision", err) + } if len(errors) == 0 { return nil } return errors } -type MessageSummary struct { - Bcc []string `json:"bcc,omitempty"` - Cc []string `json:"cc,omitempty"` - ConversationID *int64 `json:"conversation_id,omitempty"` - DeletedAt *string `json:"deleted_at,omitempty"` - From string `json:"from" validate:"required"` - FromEmail *string `json:"from_email,omitempty"` - FromName *string `json:"from_name,omitempty"` - FromPhone *string `json:"from_phone,omitempty"` - HasAttachments bool `json:"has_attachments"` - ID int64 `json:"id"` - Labels []string `json:"labels,omitempty" validate:"required"` - MessageType *string `json:"message_type,omitempty"` - SentAt string `json:"sent_at" validate:"required"` - SizeBytes int64 `json:"size_bytes"` - Snippet string `json:"snippet" validate:"required"` - SourceID *int64 `json:"source_id,omitempty"` - SourceMessageID *string `json:"source_message_id,omitempty"` - Subject string `json:"subject" validate:"required"` - To []string `json:"to,omitempty" validate:"required"` +type ExplorePreflightRequest struct { + Selection ExploreSelection `json:"selection"` } -func (m MessageSummary) Validate() error { - return runtime.ConvertValidatorError(typesValidator.Struct(m)) +func (e ExplorePreflightRequest) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(e.Selection).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Selection", err) + } + } + if len(errors) == 0 { + return nil + } + return errors } -type MutationResult struct { - Name string `json:"name" validate:"required"` - SourceCount *int64 `json:"source_count,omitempty"` +type ExplorePreflightResponse struct { + ActionTargets []ExploreActionTarget `json:"action_targets" validate:"required"` + CacheRevision string `json:"cache_revision" validate:"required"` + Count int64 `json:"count"` + EstimatedBytes int64 `json:"estimated_bytes"` + ExpiresAt time.Time `json:"expires_at" validate:"required"` + OperationToken string `json:"operation_token" validate:"required"` + SearchProvenance SearchProvenance `json:"search_provenance"` + UnavailableActions []ExploreUnavailableAction `json:"unavailable_actions" validate:"required"` } -func (m MutationResult) Validate() error { - return runtime.ConvertValidatorError(typesValidator.Struct(m)) -} +func (e ExplorePreflightResponse) Validate() error { + var errors runtime.ValidationErrors + for i, item := range e.ActionTargets { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("ActionTargets[%d]", i), err) + } + } + } + if err := typesValidator.Var(e.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + if err := typesValidator.Var(e.ExpiresAt, "required"); err != nil { + errors = errors.Append("ExpiresAt", err) + } + if err := typesValidator.Var(e.OperationToken, "required"); err != nil { + errors = errors.Append("OperationToken", err) + } + if v, ok := any(e.SearchProvenance).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchProvenance", err) + } + } + for i, item := range e.UnavailableActions { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("UnavailableActions[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type ExploreRowMatchCount struct { + Count int64 `json:"count"` + RowKey string `json:"row_key" validate:"required"` +} + +func (e ExploreRowMatchCount) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(e)) +} + +type ExploreSelection struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CandidateSnapshotID *string `json:"candidate_snapshot_id,omitempty"` + Exclusions []string `json:"exclusions,omitempty"` + Mode ExploreSelectionMode `json:"mode" validate:"required"` + Predicate ExploreHTTPRequest `json:"predicate"` + RowKeys []string `json:"row_keys,omitempty"` + SearchProvenance SearchProvenance `json:"search_provenance"` +} + +func (e ExploreSelection) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(e.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + if v, ok := any(e.Mode).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Mode", err) + } + } + if v, ok := any(e.Predicate).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Predicate", err) + } + } + if v, ok := any(e.SearchProvenance).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchProvenance", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type ExploreSort struct { + Direction ExploreSortDirection `json:"direction" validate:"required"` + Field ExploreSortField `json:"field" validate:"required"` +} + +func (e ExploreSort) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(e.Direction).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Direction", err) + } + } + if v, ok := any(e.Field).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Field", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type ExploreUnavailableAction struct { + Action string `json:"action" validate:"required"` + Reason string `json:"reason" validate:"required"` +} + +func (e ExploreUnavailableAction) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(e)) +} + +type FileGroupsHTTPRequest struct { + Cursor *string `json:"cursor,omitempty"` + FilenameQuery *string `json:"filename_query,omitempty"` + Grouping []ExploreGroupDimension `json:"grouping" validate:"required,min=1,max=1"` + Limit *int64 `json:"limit,omitempty" validate:"omitempty,gte=0,lte=500"` + MimeFamilies []string `json:"mime_families,omitempty"` + Predicate ExploreHTTPRequest `json:"predicate"` + Sort []ExploreGroupSort `json:"sort,omitempty"` +} + +func (f FileGroupsHTTPRequest) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(f.Grouping, "required,min=1,max=1"); err != nil { + errors = errors.Append("Grouping", err) + } + for i, item := range f.Grouping { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Grouping[%d]", i), err) + } + } + } + if f.Limit != nil { + if err := typesValidator.Var(f.Limit, "omitempty,gte=0,lte=500"); err != nil { + errors = errors.Append("Limit", err) + } + } + if v, ok := any(f.Predicate).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Predicate", err) + } + } + for i, item := range f.Sort { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Sort[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type FileGroupsHTTPResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CandidateSnapshotID *string `json:"candidate_snapshot_id,omitempty"` + NextCursor *string `json:"next_cursor,omitempty"` + Rows []ExploreGroupRow `json:"rows" validate:"required"` + SearchProvenance SearchProvenance `json:"search_provenance"` + TotalCount int64 `json:"total_count"` +} + +func (f FileGroupsHTTPResponse) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(f.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + for i, item := range f.Rows { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Rows[%d]", i), err) + } + } + } + if v, ok := any(f.SearchProvenance).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchProvenance", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type FileMetadataResponse struct { + ContentAvailable bool `json:"content_available"` + ContentHash *string `json:"content_hash,omitempty"` + ContentState FileMetadataResponseContentState `json:"content_state" validate:"required"` + ConversationID int64 `json:"conversation_id"` + Filename *string `json:"filename,omitempty" validate:"required"` + ID int64 `json:"id"` + MessageID int64 `json:"message_id"` + MimeType *string `json:"mime_type,omitempty" validate:"required"` + SizeBytes int64 `json:"size_bytes"` + URL *string `json:"url,omitempty"` +} + +func (f FileMetadataResponse) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(f.ContentState).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("ContentState", err) + } + } + if err := typesValidator.Var(f.Filename, "required"); err != nil { + errors = errors.Append("Filename", err) + } + if err := typesValidator.Var(f.MimeType, "required"); err != nil { + errors = errors.Append("MimeType", err) + } + if len(errors) == 0 { + return nil + } + return errors +} + +type FileSearchHTTPRequest struct { + Cursor *string `json:"cursor,omitempty"` + FilenameQuery *string `json:"filename_query,omitempty"` + Limit *int64 `json:"limit,omitempty" validate:"omitempty,gte=0,lte=500"` + MimeFamilies []string `json:"mime_families,omitempty"` + Predicate ExploreHTTPRequest `json:"predicate"` + Sort FileSearchSort `json:"sort"` +} + +func (f FileSearchHTTPRequest) Validate() error { + var errors runtime.ValidationErrors + if f.Limit != nil { + if err := typesValidator.Var(f.Limit, "omitempty,gte=0,lte=500"); err != nil { + errors = errors.Append("Limit", err) + } + } + if v, ok := any(f.Predicate).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Predicate", err) + } + } + if v, ok := any(f.Sort).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Sort", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type FileSearchHTTPResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CandidateSnapshotID *string `json:"candidate_snapshot_id,omitempty"` + Files []FileSearchRow `json:"files,omitempty" validate:"required"` + NextCursor *string `json:"next_cursor,omitempty"` + SearchProvenance SearchProvenance `json:"search_provenance"` + TotalCount int64 `json:"total_count"` +} + +func (f FileSearchHTTPResponse) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(f.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + for i, item := range f.Files { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Files[%d]", i), err) + } + } + } + if v, ok := any(f.SearchProvenance).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchProvenance", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type FileSearchRow struct { + ContainingTitle string `json:"containing_title" validate:"required"` + ContentAvailable bool `json:"content_available"` + ContentState FileSearchRowContentState `json:"content_state" validate:"required"` + ConversationID int64 `json:"conversation_id"` + EntryKey string `json:"entry_key" validate:"required"` + Filename *string `json:"filename,omitempty" validate:"required"` + ID int64 `json:"id"` + Key string `json:"key" validate:"required"` + MessageID int64 `json:"message_id"` + MimeFamily string `json:"mime_family" validate:"required"` + MimeType *string `json:"mime_type,omitempty" validate:"required"` + OccurredAt time.Time `json:"occurred_at" validate:"required"` + ParticipantDomains []string `json:"participant_domains,omitempty"` + ParticipantIds []int64 `json:"participant_ids,omitempty"` + ParticipantLabels []string `json:"participant_labels,omitempty"` + SizeBytes int64 `json:"size_bytes"` + SourceID int64 `json:"source_id"` + SourceIdentifier string `json:"source_identifier" validate:"required"` + SourceType string `json:"source_type" validate:"required"` +} + +func (f FileSearchRow) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(f.ContainingTitle, "required"); err != nil { + errors = errors.Append("ContainingTitle", err) + } + if v, ok := any(f.ContentState).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("ContentState", err) + } + } + if err := typesValidator.Var(f.EntryKey, "required"); err != nil { + errors = errors.Append("EntryKey", err) + } + if err := typesValidator.Var(f.Filename, "required"); err != nil { + errors = errors.Append("Filename", err) + } + if err := typesValidator.Var(f.Key, "required"); err != nil { + errors = errors.Append("Key", err) + } + if err := typesValidator.Var(f.MimeFamily, "required"); err != nil { + errors = errors.Append("MimeFamily", err) + } + if err := typesValidator.Var(f.MimeType, "required"); err != nil { + errors = errors.Append("MimeType", err) + } + if err := typesValidator.Var(f.OccurredAt, "required"); err != nil { + errors = errors.Append("OccurredAt", err) + } + if err := typesValidator.Var(f.SourceIdentifier, "required"); err != nil { + errors = errors.Append("SourceIdentifier", err) + } + if err := typesValidator.Var(f.SourceType, "required"); err != nil { + errors = errors.Append("SourceType", err) + } + if len(errors) == 0 { + return nil + } + return errors +} + +type FileSearchSort struct { + Direction string `json:"direction" validate:"required"` + Field string `json:"field" validate:"required"` +} + +func (f FileSearchSort) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(f)) +} + +type FilteredMessagesResponse struct { + Count int64 `json:"count"` + HasMore bool `json:"has_more"` + Limit int64 `json:"limit"` + Messages []MessageSummary `json:"messages,omitempty" validate:"required"` + Offset int64 `json:"offset"` +} + +func (f FilteredMessagesResponse) Validate() error { + var errors runtime.ValidationErrors + for i, item := range f.Messages { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Messages[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type Filters struct { + Account *string `json:"account,omitempty"` + After *string `json:"after,omitempty"` + Before *string `json:"before,omitempty"` + Labels []string `json:"labels,omitempty"` + Recipients []string `json:"recipients,omitempty"` + SenderDomains []string `json:"sender_domains,omitempty"` + Senders []string `json:"senders,omitempty"` +} + +type GenerationSummary struct { + ActivatedAt *string `json:"activated_at,omitempty"` + Dimension int64 `json:"dimension"` + Fingerprint string `json:"fingerprint" validate:"required"` + ID int64 `json:"id"` + MessageCount int64 `json:"message_count"` + Model string `json:"model" validate:"required"` + State string `json:"state" validate:"required"` +} + +func (g GenerationSummary) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type GmailIDsResponse struct { + GmailIds []string `json:"gmail_ids,omitempty" validate:"required"` +} + +func (g GmailIDsResponse) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(g)) +} + +type HealthResponse struct { + AnalyticsEngine *string `json:"analytics_engine,omitempty"` + Operation *OperationHealth `json:"operation,omitempty"` + Status string `json:"status" validate:"required"` + Vector *VectorHealth `json:"vector,omitempty"` +} + +func (h HealthResponse) Validate() error { + var errors runtime.ValidationErrors + if h.Operation != nil { + if v, ok := any(h.Operation).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Operation", err) + } + } + } + if err := typesValidator.Var(h.Status, "required"); err != nil { + errors = errors.Append("Status", err) + } + if h.Vector != nil { + if v, ok := any(h.Vector).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Vector", err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type HybridGenerationSummary struct { + Dimension int64 `json:"dimension"` + Fingerprint string `json:"fingerprint" validate:"required"` + ID int64 `json:"id"` + Model string `json:"model" validate:"required"` + State string `json:"state" validate:"required"` +} + +func (h HybridGenerationSummary) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(h)) +} + +type HybridSearchItem struct { + Bcc []string `json:"bcc,omitempty"` + Cc []string `json:"cc,omitempty"` + ConversationID *int64 `json:"conversation_id,omitempty"` + DeletedAt *string `json:"deleted_at,omitempty"` + From string `json:"from" validate:"required"` + FromEmail *string `json:"from_email,omitempty"` + FromName *string `json:"from_name,omitempty"` + FromPhone *string `json:"from_phone,omitempty"` + HasAttachments bool `json:"has_attachments"` + ID int64 `json:"id"` + Labels []string `json:"labels,omitempty" validate:"required"` + Matches []HybridSearchMatch `json:"matches,omitempty"` + MatchesTruncated *bool `json:"matches_truncated,omitempty"` + MessageType *string `json:"message_type,omitempty"` + Score *ScoreBreakdown `json:"score,omitempty"` + SentAt string `json:"sent_at" validate:"required"` + SizeBytes int64 `json:"size_bytes"` + Snippet string `json:"snippet" validate:"required"` + SourceID *int64 `json:"source_id,omitempty"` + SourceMessageID *string `json:"source_message_id,omitempty"` + Subject string `json:"subject" validate:"required"` + To []string `json:"to,omitempty" validate:"required"` +} + +func (h HybridSearchItem) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(h.From, "required"); err != nil { + errors = errors.Append("From", err) + } + if err := typesValidator.Var(h.Labels, "required"); err != nil { + errors = errors.Append("Labels", err) + } + for i, item := range h.Matches { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Matches[%d]", i), err) + } + } + } + if h.Score != nil { + if v, ok := any(h.Score).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Score", err) + } + } + } + if err := typesValidator.Var(h.SentAt, "required"); err != nil { + errors = errors.Append("SentAt", err) + } + if err := typesValidator.Var(h.Snippet, "required"); err != nil { + errors = errors.Append("Snippet", err) + } + if err := typesValidator.Var(h.Subject, "required"); err != nil { + errors = errors.Append("Subject", err) + } + if err := typesValidator.Var(h.To, "required"); err != nil { + errors = errors.Append("To", err) + } + if len(errors) == 0 { + return nil + } + return errors +} + +type HybridSearchMatch struct { + CharOffset *int64 `json:"char_offset,omitempty"` + Line *int64 `json:"line,omitempty"` + Score float64 `json:"score"` + Snippet string `json:"snippet" validate:"required"` +} + +func (h HybridSearchMatch) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(h)) +} + +type HybridSearchResponse struct { + Generation HybridGenerationSummary `json:"generation"` + HasMore bool `json:"has_more"` + Mode string `json:"mode" validate:"required"` + PoolSaturated bool `json:"pool_saturated"` + Query string `json:"query" validate:"required"` + Results []HybridSearchItem `json:"results,omitempty" validate:"required"` + Returned int64 `json:"returned"` + ScopeLabel *string `json:"scope_label,omitempty"` + ScopeSourceCount *int64 `json:"scope_source_count,omitempty"` + TookMs int64 `json:"took_ms"` +} + +func (h HybridSearchResponse) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(h.Generation).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Generation", err) + } + } + if err := typesValidator.Var(h.Mode, "required"); err != nil { + errors = errors.Append("Mode", err) + } + if err := typesValidator.Var(h.Query, "required"); err != nil { + errors = errors.Append("Query", err) + } + for i, item := range h.Results { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Results[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type IdentityLinkRequest struct { + ParticipantA int64 `json:"participant_a"` + ParticipantB int64 `json:"participant_b"` +} + +type IdentityLinkResponse struct { + CacheState IdentityLinkResponseCacheState `json:"cache_state" validate:"required"` + IdentityRevision int64 `json:"identity_revision"` +} + +func (i IdentityLinkResponse) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(i.CacheState).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("CacheState", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type IdentitySearchHTTPRequest struct { + Cursor *string `json:"cursor,omitempty"` + IdentityQuery *string `json:"identity_query,omitempty"` + Limit *int64 `json:"limit,omitempty" validate:"omitempty,gte=0,lte=500"` + Predicate ExploreHTTPRequest `json:"predicate"` + Sort IdentitySearchSort `json:"sort"` +} + +func (i IdentitySearchHTTPRequest) Validate() error { + var errors runtime.ValidationErrors + if i.Limit != nil { + if err := typesValidator.Var(i.Limit, "omitempty,gte=0,lte=500"); err != nil { + errors = errors.Append("Limit", err) + } + } + if v, ok := any(i.Predicate).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Predicate", err) + } + } + if v, ok := any(i.Sort).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Sort", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type IdentitySearchSort struct { + Direction IdentitySearchSortDirection `json:"direction" validate:"required"` + Field IdentitySearchSortField `json:"field" validate:"required"` +} + +func (i IdentitySearchSort) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(i.Direction).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Direction", err) + } + } + if v, ok := any(i.Field).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Field", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type ListDeletionsResponse struct { + Manifests []DeletionManifestSummary `json:"manifests,omitempty" validate:"required"` +} + +func (l ListDeletionsResponse) Validate() error { + var errors runtime.ValidationErrors + for i, item := range l.Manifests { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Manifests[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type Manifest struct { + CreatedAt time.Time `json:"created_at" validate:"required"` + CreatedBy string `json:"created_by" validate:"required"` + Description string `json:"description" validate:"required"` + Execution *Execution `json:"execution,omitempty"` + Filters Filters `json:"filters"` + GmailIds []string `json:"gmail_ids,omitempty" validate:"required"` + ID string `json:"id" validate:"required"` + RawFilter *struct{} `json:"raw_filter,omitempty"` + Status string `json:"status" validate:"required"` + Summary *Summary `json:"summary,omitempty"` + Version int64 `json:"version"` +} + +func (m Manifest) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(m.CreatedAt, "required"); err != nil { + errors = errors.Append("CreatedAt", err) + } + if err := typesValidator.Var(m.CreatedBy, "required"); err != nil { + errors = errors.Append("CreatedBy", err) + } + if err := typesValidator.Var(m.Description, "required"); err != nil { + errors = errors.Append("Description", err) + } + if m.Execution != nil { + if v, ok := any(m.Execution).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Execution", err) + } + } + } + if v, ok := any(m.Filters).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Filters", err) + } + } + if err := typesValidator.Var(m.GmailIds, "required"); err != nil { + errors = errors.Append("GmailIds", err) + } + if err := typesValidator.Var(m.ID, "required"); err != nil { + errors = errors.Append("ID", err) + } + if err := typesValidator.Var(m.Status, "required"); err != nil { + errors = errors.Append("Status", err) + } + if m.Summary != nil { + if v, ok := any(m.Summary).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Summary", err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type MatchSummary struct { + LexicalMatchCount *int64 `json:"lexical_match_count,omitempty"` + SemanticScore *float64 `json:"semantic_score,omitempty"` + StrongestExcerpt *string `json:"strongest_excerpt,omitempty"` +} + +type MessageDetail struct { + Attachments []AttachmentInfo `json:"attachments,omitempty" validate:"required"` + Bcc []string `json:"bcc,omitempty"` + Body string `json:"body" validate:"required"` + BodyHTML *string `json:"body_html,omitempty"` + Cc []string `json:"cc,omitempty"` + ConversationID *int64 `json:"conversation_id,omitempty"` + DeletedAt *string `json:"deleted_at,omitempty"` + From string `json:"from" validate:"required"` + FromEmail *string `json:"from_email,omitempty"` + FromName *string `json:"from_name,omitempty"` + FromPhone *string `json:"from_phone,omitempty"` + HasAttachments bool `json:"has_attachments"` + ID int64 `json:"id"` + Labels []string `json:"labels,omitempty" validate:"required"` + MessageType *string `json:"message_type,omitempty"` + SentAt string `json:"sent_at" validate:"required"` + SizeBytes int64 `json:"size_bytes"` + Snippet string `json:"snippet" validate:"required"` + SourceID *int64 `json:"source_id,omitempty"` + SourceMessageID *string `json:"source_message_id,omitempty"` + Subject string `json:"subject" validate:"required"` + To []string `json:"to,omitempty" validate:"required"` +} + +func (m MessageDetail) Validate() error { + var errors runtime.ValidationErrors + for i, item := range m.Attachments { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Attachments[%d]", i), err) + } + } + } + if err := typesValidator.Var(m.Body, "required"); err != nil { + errors = errors.Append("Body", err) + } + if err := typesValidator.Var(m.From, "required"); err != nil { + errors = errors.Append("From", err) + } + if err := typesValidator.Var(m.Labels, "required"); err != nil { + errors = errors.Append("Labels", err) + } + if err := typesValidator.Var(m.SentAt, "required"); err != nil { + errors = errors.Append("SentAt", err) + } + if err := typesValidator.Var(m.Snippet, "required"); err != nil { + errors = errors.Append("Snippet", err) + } + if err := typesValidator.Var(m.Subject, "required"); err != nil { + errors = errors.Append("Subject", err) + } + if err := typesValidator.Var(m.To, "required"); err != nil { + errors = errors.Append("To", err) + } + if len(errors) == 0 { + return nil + } + return errors +} + +type MessageListResponse struct { + Messages []MessageSummary `json:"messages,omitempty" validate:"required"` + Page int64 `json:"page"` + PageSize int64 `json:"page_size"` + Total int64 `json:"total"` +} + +func (m MessageListResponse) Validate() error { + var errors runtime.ValidationErrors + for i, item := range m.Messages { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Messages[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type MessageSummary struct { + Bcc []string `json:"bcc,omitempty"` + Cc []string `json:"cc,omitempty"` + ConversationID *int64 `json:"conversation_id,omitempty"` + DeletedAt *string `json:"deleted_at,omitempty"` + From string `json:"from" validate:"required"` + FromEmail *string `json:"from_email,omitempty"` + FromName *string `json:"from_name,omitempty"` + FromPhone *string `json:"from_phone,omitempty"` + HasAttachments bool `json:"has_attachments"` + ID int64 `json:"id"` + Labels []string `json:"labels,omitempty" validate:"required"` + MessageType *string `json:"message_type,omitempty"` + SentAt string `json:"sent_at" validate:"required"` + SizeBytes int64 `json:"size_bytes"` + Snippet string `json:"snippet" validate:"required"` + SourceID *int64 `json:"source_id,omitempty"` + SourceMessageID *string `json:"source_message_id,omitempty"` + Subject string `json:"subject" validate:"required"` + To []string `json:"to,omitempty" validate:"required"` +} + +func (m MessageSummary) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(m)) +} + +type MutationResult struct { + Name string `json:"name" validate:"required"` + SourceCount *int64 `json:"source_count,omitempty"` +} + +func (m MutationResult) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(m)) +} + +type OperationHealth struct { + Busy bool `json:"busy"` + Label *string `json:"label,omitempty"` + StartedAt *time.Time `json:"started_at,omitempty"` +} + +type PatchSavedViewRequest struct { + CanonicalState *SavedViewStateEnvelope `json:"canonical_state,omitempty"` + Description *string `json:"description,omitempty"` + Name *string `json:"name,omitempty"` + SchemaVersion *int64 `json:"schema_version,omitempty"` +} + +func (p PatchSavedViewRequest) Validate() error { + var errors runtime.ValidationErrors + if p.CanonicalState != nil { + if v, ok := any(p.CanonicalState).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("CanonicalState", err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type PersonCluster struct { + CanonicalID int64 `json:"canonical_id"` + Edges []PersonClusterEdge `json:"edges,omitempty" validate:"required"` + MemberIds []int64 `json:"member_ids,omitempty" validate:"required"` +} + +func (p PersonCluster) Validate() error { + var errors runtime.ValidationErrors + for i, item := range p.Edges { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Edges[%d]", i), err) + } + } + } + if err := typesValidator.Var(p.MemberIds, "required"); err != nil { + errors = errors.Append("MemberIds", err) + } + if len(errors) == 0 { + return nil + } + return errors +} + +type PersonClusterEdge struct { + ParticipantA int64 `json:"participant_a"` + ParticipantB int64 `json:"participant_b"` +} + +type PersonContextSummaryHTTPResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CandidateSnapshotID *string `json:"candidate_snapshot_id,omitempty"` + SearchProvenance SearchProvenance `json:"search_provenance"` + Summary PersonSummary `json:"summary"` +} + +func (p PersonContextSummaryHTTPResponse) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(p.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + if v, ok := any(p.SearchProvenance).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchProvenance", err) + } + } + if v, ok := any(p.Summary).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Summary", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type PersonIdentifier struct { + DisplayValue *string `json:"display_value,omitempty"` + IsPrimary bool `json:"is_primary"` + ParticipantID int64 `json:"participant_id"` + Provenance string `json:"provenance" validate:"required"` + Type string `json:"type" validate:"required"` + Value string `json:"value" validate:"required"` +} + +func (p PersonIdentifier) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(p)) +} + +type PersonSearchHTTPResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CandidateSnapshotID *string `json:"candidate_snapshot_id,omitempty"` + NextCursor *string `json:"next_cursor,omitempty"` + Rows []PersonSummary `json:"rows,omitempty" validate:"required"` + SearchProvenance SearchProvenance `json:"search_provenance"` + TotalCount int64 `json:"total_count"` +} -type OperationHealth struct { - Busy bool `json:"busy"` - Label *string `json:"label,omitempty"` - StartedAt *time.Time `json:"started_at,omitempty"` +func (p PersonSearchHTTPResponse) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(p.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + for i, item := range p.Rows { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Rows[%d]", i), err) + } + } + } + if v, ok := any(p.SearchProvenance).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SearchProvenance", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type PersonSummary struct { + ActivityCount int64 `json:"activity_count"` + CacheRevision string `json:"cache_revision" validate:"required"` + Cluster *PersonCluster `json:"cluster,omitempty"` + DisplayLabel string `json:"display_label" validate:"required"` + DisplayName *string `json:"display_name,omitempty"` + FileCount int64 `json:"file_count"` + FirstAt time.Time `json:"first_at" validate:"required"` + ID int64 `json:"id"` + Identifiers []PersonIdentifier `json:"identifiers,omitempty" validate:"required"` + LastAt time.Time `json:"last_at" validate:"required"` + PartialLabel bool `json:"partial_label"` + SourceCounts []SourceCount `json:"source_counts,omitempty" validate:"required"` +} + +func (p PersonSummary) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(p.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + if p.Cluster != nil { + if v, ok := any(p.Cluster).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Cluster", err) + } + } + } + if err := typesValidator.Var(p.DisplayLabel, "required"); err != nil { + errors = errors.Append("DisplayLabel", err) + } + if err := typesValidator.Var(p.FirstAt, "required"); err != nil { + errors = errors.Append("FirstAt", err) + } + for i, item := range p.Identifiers { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Identifiers[%d]", i), err) + } + } + } + if err := typesValidator.Var(p.LastAt, "required"); err != nil { + errors = errors.Append("LastAt", err) + } + for i, item := range p.SourceCounts { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("SourceCounts[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors } type PingInfo struct { @@ -1422,6 +2763,158 @@ func (q QueryResult) Validate() error { return runtime.ConvertValidatorError(typesValidator.Struct(q)) } +type RelationshipRow struct { + CanonicalID int64 `json:"canonical_id"` + DisplayLabel string `json:"display_label" validate:"required"` + LastAt time.Time `json:"last_at" validate:"required"` + MemberIds []int64 `json:"member_ids,omitempty" validate:"required"` + Score float64 `json:"score"` + Signals RelationshipSignals `json:"signals"` +} + +func (r RelationshipRow) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(r.DisplayLabel, "required"); err != nil { + errors = errors.Append("DisplayLabel", err) + } + if err := typesValidator.Var(r.LastAt, "required"); err != nil { + errors = errors.Append("LastAt", err) + } + if err := typesValidator.Var(r.MemberIds, "required"); err != nil { + errors = errors.Append("MemberIds", err) + } + if v, ok := any(r.Signals).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Signals", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type RelationshipSignals struct { + LastInteractionAt time.Time `json:"last_interaction_at" validate:"required"` + MeetingCount int64 `json:"meeting_count"` + MeetingsTogether float64 `json:"meetings_together"` + Modalities int64 `json:"modalities"` + ReceivedFromThem float64 `json:"received_from_them"` + SentCount int64 `json:"sent_count"` + SentToThem float64 `json:"sent_to_them"` +} + +func (r RelationshipSignals) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(r)) +} + +type RelationshipTimelineHTTPRequest struct { + Cursor *string `json:"cursor,omitempty"` + Filters []ExploreFilter `json:"filters,omitempty"` + Limit *int64 `json:"limit,omitempty" validate:"omitempty,gte=0,lte=500"` + Timezone *string `json:"timezone,omitempty"` +} + +func (r RelationshipTimelineHTTPRequest) Validate() error { + var errors runtime.ValidationErrors + for i, item := range r.Filters { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Filters[%d]", i), err) + } + } + } + if r.Limit != nil { + if err := typesValidator.Var(r.Limit, "omitempty,gte=0,lte=500"); err != nil { + errors = errors.Append("Limit", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type RelationshipTimelineHTTPResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + CanonicalID int64 `json:"canonical_id"` + IdentityRevision int64 `json:"identity_revision"` + NextCursor *string `json:"next_cursor,omitempty"` + Rows []TimelineRow `json:"rows,omitempty" validate:"required"` + TotalCount int64 `json:"total_count"` +} + +func (r RelationshipTimelineHTTPResponse) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(r.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + for i, item := range r.Rows { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Rows[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type RelationshipsHTTPRequest struct { + Cursor *string `json:"cursor,omitempty"` + Filters []ExploreFilter `json:"filters,omitempty"` + Limit *int64 `json:"limit,omitempty" validate:"omitempty,gte=0,lte=500"` + ShowAll *bool `json:"show_all,omitempty"` +} + +func (r RelationshipsHTTPRequest) Validate() error { + var errors runtime.ValidationErrors + for i, item := range r.Filters { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Filters[%d]", i), err) + } + } + } + if r.Limit != nil { + if err := typesValidator.Var(r.Limit, "omitempty,gte=0,lte=500"); err != nil { + errors = errors.Append("Limit", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type RelationshipsHTTPResponse struct { + CacheRevision string `json:"cache_revision" validate:"required"` + IdentityRevision int64 `json:"identity_revision"` + NextCursor *string `json:"next_cursor,omitempty"` + Rows []RelationshipRow `json:"rows,omitempty" validate:"required"` + TotalCount int64 `json:"total_count"` +} + +func (r RelationshipsHTTPResponse) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(r.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + for i, item := range r.Rows { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Rows[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + type RemoveRequest struct { Account string `json:"account" validate:"required"` Identifier string `json:"identifier" validate:"required"` @@ -1432,14 +2925,157 @@ func (r RemoveRequest) Validate() error { } type RemoveResult struct { - Account string `json:"account" validate:"required"` - Identifier string `json:"identifier" validate:"required"` - NoIdentity *bool `json:"no_identity,omitempty"` - Removed int64 `json:"removed"` + Account string `json:"account" validate:"required"` + CacheState *RemoveResultCacheState `json:"cache_state,omitempty"` + Identifier string `json:"identifier" validate:"required"` + NoIdentity *bool `json:"no_identity,omitempty"` + Removed int64 `json:"removed"` } func (r RemoveResult) Validate() error { - return runtime.ConvertValidatorError(typesValidator.Struct(r)) + var errors runtime.ValidationErrors + if err := typesValidator.Var(r.Account, "required"); err != nil { + errors = errors.Append("Account", err) + } + if r.CacheState != nil { + if v, ok := any(r.CacheState).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("CacheState", err) + } + } + } + if err := typesValidator.Var(r.Identifier, "required"); err != nil { + errors = errors.Append("Identifier", err) + } + if len(errors) == 0 { + return nil + } + return errors +} + +type SavedView struct { + CanonicalState SavedViewStateEnvelope `json:"canonical_state"` + CreatedAt time.Time `json:"created_at" validate:"required"` + Description *string `json:"description,omitempty"` + ID int64 `json:"id"` + Name string `json:"name" validate:"required"` + Revision int64 `json:"revision"` + SchemaVersion int64 `json:"schema_version"` + UpdatedAt time.Time `json:"updated_at" validate:"required"` +} + +func (s SavedView) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(s.CanonicalState).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("CanonicalState", err) + } + } + if err := typesValidator.Var(s.CreatedAt, "required"); err != nil { + errors = errors.Append("CreatedAt", err) + } + if err := typesValidator.Var(s.Name, "required"); err != nil { + errors = errors.Append("Name", err) + } + if err := typesValidator.Var(s.UpdatedAt, "required"); err != nil { + errors = errors.Append("UpdatedAt", err) + } + if len(errors) == 0 { + return nil + } + return errors +} + +type SavedViewFilter struct { + Field string `json:"field" validate:"required"` + Operator string `json:"operator" validate:"required"` + + // Values Exact filter values; numeric identifiers use decimal strings + Values []string `json:"values" validate:"required"` +} + +func (s SavedViewFilter) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(s)) +} + +type SavedViewSort struct { + Direction SavedViewSortDirection `json:"direction" validate:"required"` + Field string `json:"field" validate:"required"` +} + +func (s SavedViewSort) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(s.Direction).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Direction", err) + } + } + if err := typesValidator.Var(s.Field, "required"); err != nil { + errors = errors.Append("Field", err) + } + if len(errors) == 0 { + return nil + } + return errors +} + +type SavedViewStateEnvelope struct { + Columns []string `json:"columns,omitempty"` + Filters []SavedViewFilter `json:"filters,omitempty"` + Grouping []string `json:"grouping,omitempty"` + InspectorPinned *bool `json:"inspector_pinned,omitempty"` + Presentation *SavedViewStateEnvelopePresentation `json:"presentation,omitempty"` + Query *string `json:"query,omitempty"` + SearchMode *string `json:"search_mode,omitempty"` + Sort []SavedViewSort `json:"sort,omitempty"` +} + +func (s SavedViewStateEnvelope) Validate() error { + var errors runtime.ValidationErrors + for i, item := range s.Filters { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Filters[%d]", i), err) + } + } + } + if s.Presentation != nil { + if v, ok := any(s.Presentation).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Presentation", err) + } + } + } + for i, item := range s.Sort { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Sort[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type SavedViewsResponse struct { + SavedViews []SavedView `json:"saved_views,omitempty" validate:"required"` +} + +func (s SavedViewsResponse) Validate() error { + var errors runtime.ValidationErrors + for i, item := range s.SavedViews { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("SavedViews[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors } type SchedulerStatusResponse struct { @@ -1447,28 +3083,82 @@ type SchedulerStatusResponse struct { Running bool `json:"running"` } -func (s SchedulerStatusResponse) Validate() error { +func (s SchedulerStatusResponse) Validate() error { + var errors runtime.ValidationErrors + for i, item := range s.Accounts { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Accounts[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type ScoreBreakdown struct { + Bm25 *float64 `json:"bm25,omitempty"` + Rrf *float64 `json:"rrf,omitempty"` + SubjectBoosted *bool `json:"subject_boosted,omitempty"` + Vector *float64 `json:"vector,omitempty"` +} + +type SearchCoverageRequest struct { + Filters []ExploreFilter `json:"filters,omitempty"` +} + +func (s SearchCoverageRequest) Validate() error { + var errors runtime.ValidationErrors + for i, item := range s.Filters { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Filters[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type SearchCoverageResponse struct { + Actions []SearchCoverageResponseActions `json:"actions" validate:"required"` + CacheRevision string `json:"cache_revision" validate:"required"` + Detail *string `json:"detail,omitempty"` + EligibleCount int64 `json:"eligible_count"` + EmbeddedCount int64 `json:"embedded_count"` + Percentage float64 `json:"percentage"` + Status SearchCoverageResponseStatus `json:"status" validate:"required"` + VectorFingerprint *string `json:"vector_fingerprint,omitempty"` + VectorGeneration *int64 `json:"vector_generation,omitempty"` +} + +func (s SearchCoverageResponse) Validate() error { var errors runtime.ValidationErrors - for i, item := range s.Accounts { + for i, item := range s.Actions { if v, ok := any(item).(runtime.Validator); ok { if err := v.Validate(); err != nil { - errors = errors.Append(fmt.Sprintf("Accounts[%d]", i), err) + errors = errors.Append(fmt.Sprintf("Actions[%d]", i), err) } } } + if err := typesValidator.Var(s.CacheRevision, "required"); err != nil { + errors = errors.Append("CacheRevision", err) + } + if v, ok := any(s.Status).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Status", err) + } + } if len(errors) == 0 { return nil } return errors } -type ScoreBreakdown struct { - Bm25 *float64 `json:"bm25,omitempty"` - Rrf *float64 `json:"rrf,omitempty"` - SubjectBoosted *bool `json:"subject_boosted,omitempty"` - Vector *float64 `json:"vector,omitempty"` -} - type SearchFastResponse struct { AppliedSourceIds []int64 `json:"applied_source_ids,omitempty"` Messages []MessageSummary `json:"messages,omitempty" validate:"required"` @@ -1502,6 +3192,11 @@ func (s SearchFastResponse) Validate() error { return errors } +type SearchProvenance struct { + LexicalIndexRevision *string `json:"lexical_index_revision,omitempty"` + VectorGeneration *int64 `json:"vector_generation,omitempty"` +} + type SearchResult struct { Messages []MessageSummary `json:"messages,omitempty" validate:"required"` Page int64 `json:"page"` @@ -1528,6 +3223,28 @@ func (s SearchResult) Validate() error { return errors } +type SecretSettingState struct { + Configured bool `json:"configured"` +} + +type SecretSettingUpdate struct { + Action SecretSettingUpdateAction `json:"action" validate:"required"` + Value *string `json:"value,omitempty"` +} + +func (s SecretSettingUpdate) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(s.Action).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Action", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + type SenderCount struct { Count int64 `json:"count"` Sender string `json:"sender" validate:"required"` @@ -1537,6 +3254,204 @@ func (s SenderCount) Validate() error { return runtime.ConvertValidatorError(typesValidator.Struct(s)) } +type SessionLoginRequest struct { + APIKey string `json:"api_key" validate:"required"` +} + +func (s SessionLoginRequest) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(s)) +} + +type SessionStatus struct { + AuthMode SessionStatusAuthMode `json:"auth_mode" validate:"required"` + CsrfToken *string `json:"csrf_token,omitempty"` + HTTPS bool `json:"https"` + PlainHTTPWarning bool `json:"plain_http_warning"` +} + +func (s SessionStatus) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(s.AuthMode).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("AuthMode", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type Setting struct { + Group SettingGroup `json:"group" validate:"required"` + Key string `json:"key" validate:"required"` + Kind SettingKind `json:"kind" validate:"required"` + Options []string `json:"options,omitempty"` + RestartRequired bool `json:"restart_required"` + Secret *SecretSettingState `json:"secret,omitempty"` + Testable *bool `json:"testable,omitempty"` + Value *SettingValue `json:"value,omitempty"` +} + +func (s Setting) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(s.Group).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Group", err) + } + } + if err := typesValidator.Var(s.Key, "required"); err != nil { + errors = errors.Append("Key", err) + } + if v, ok := any(s.Kind).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Kind", err) + } + } + if s.Secret != nil { + if v, ok := any(s.Secret).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Secret", err) + } + } + } + if s.Value != nil { + if v, ok := any(s.Value).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Value", err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type SettingUpdate struct { + Key string `json:"key" validate:"required"` + Secret *SecretSettingUpdate `json:"secret,omitempty"` + Value *SettingValue `json:"value,omitempty"` +} + +func (s SettingUpdate) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(s.Key, "required"); err != nil { + errors = errors.Append("Key", err) + } + if s.Secret != nil { + if v, ok := any(s.Secret).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Secret", err) + } + } + } + if s.Value != nil { + if v, ok := any(s.Value).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Value", err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type SettingValue struct { + SettingValue_OneOf *SettingValue_OneOf `json:"-"` +} + +func (s SettingValue) Validate() error { + var errors runtime.ValidationErrors + if s.SettingValue_OneOf != nil { + if v, ok := any(s.SettingValue_OneOf).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("SettingValue_OneOf", err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +func (s SettingValue) MarshalJSON() ([]byte, error) { + var parts []json.RawMessage + + { + b, err := runtime.MarshalJSON(s.SettingValue_OneOf) + if err != nil { + return nil, fmt.Errorf("SettingValue_OneOf marshal: %w", err) + } + parts = append(parts, b) + } + + return runtime.CoalesceOrMerge(parts...) +} + +func (s *SettingValue) UnmarshalJSON(data []byte) error { + trim := bytes.TrimSpace(data) + if bytes.Equal(trim, []byte("null")) { + return nil + } + if len(trim) == 0 { + return fmt.Errorf("empty JSON input") + } + + if s.SettingValue_OneOf == nil { + s.SettingValue_OneOf = &SettingValue_OneOf{} + } + + if err := runtime.UnmarshalJSON(data, s.SettingValue_OneOf); err != nil { + return fmt.Errorf("SettingValue_OneOf unmarshal: %w", err) + } + + return nil +} + +type SettingsPatchRequest struct { + ConfirmAPIKeyRestart *bool `json:"confirm_api_key_restart,omitempty"` + Updates []SettingUpdate `json:"updates" validate:"required"` +} + +func (s SettingsPatchRequest) Validate() error { + var errors runtime.ValidationErrors + for i, item := range s.Updates { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Updates[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type SettingsResponse struct { + PendingRestart bool `json:"pending_restart"` + Settings []Setting `json:"settings" validate:"required"` +} + +func (s SettingsResponse) Validate() error { + var errors runtime.ValidationErrors + for i, item := range s.Settings { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Settings[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + type SimilarSearchResponse struct { Generation HybridGenerationSummary `json:"generation"` Messages []MessageSummary `json:"messages,omitempty" validate:"required"` @@ -1564,23 +3479,40 @@ func (s SimilarSearchResponse) Validate() error { return errors } +type SourceCount struct { + Count int64 `json:"count"` + SourceType string `json:"source_type" validate:"required"` +} + +func (s SourceCount) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(s)) +} + type SourceStatus struct { - ActiveSync SyncRunStatus `json:"active_sync"` - DisplayName *string `json:"display_name,omitempty" validate:"required"` - ID int64 `json:"id"` - Identifier string `json:"identifier" validate:"required"` - LastSuccessfulSync SyncRunStatus `json:"last_successful_sync"` - LastSyncAt *string `json:"last_sync_at,omitempty" validate:"required"` - LatestSync SyncRunStatus `json:"latest_sync"` - SourceType string `json:"source_type" validate:"required"` - UpdatedAt string `json:"updated_at" validate:"required"` + ActiveSync *SyncRunStatus `json:"active_sync,omitempty"` + CanSync bool `json:"can_sync"` + DisplayName *string `json:"display_name,omitempty" validate:"required"` + ID int64 `json:"id"` + Identifier string `json:"identifier" validate:"required"` + LastSuccessfulSync *SyncRunStatus `json:"last_successful_sync,omitempty"` + LastSyncAt *string `json:"last_sync_at,omitempty" validate:"required"` + LatestSync *SyncRunStatus `json:"latest_sync,omitempty"` + NextSyncAt *string `json:"next_sync_at,omitempty" validate:"required"` + Schedule *string `json:"schedule,omitempty"` + Scheduled bool `json:"scheduled"` + SchedulerLastError *string `json:"scheduler_last_error,omitempty"` + SourceType string `json:"source_type" validate:"required"` + SyncUnavailableReason *string `json:"sync_unavailable_reason,omitempty"` + UpdatedAt string `json:"updated_at" validate:"required"` } func (s SourceStatus) Validate() error { var errors runtime.ValidationErrors - if v, ok := any(s.ActiveSync).(runtime.Validator); ok { - if err := v.Validate(); err != nil { - errors = errors.Append("ActiveSync", err) + if s.ActiveSync != nil { + if v, ok := any(s.ActiveSync).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("ActiveSync", err) + } } } if s.DisplayName != nil { @@ -1591,9 +3523,11 @@ func (s SourceStatus) Validate() error { if err := typesValidator.Var(s.Identifier, "required"); err != nil { errors = errors.Append("Identifier", err) } - if v, ok := any(s.LastSuccessfulSync).(runtime.Validator); ok { - if err := v.Validate(); err != nil { - errors = errors.Append("LastSuccessfulSync", err) + if s.LastSuccessfulSync != nil { + if v, ok := any(s.LastSuccessfulSync).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("LastSuccessfulSync", err) + } } } if s.LastSyncAt != nil { @@ -1601,9 +3535,16 @@ func (s SourceStatus) Validate() error { errors = errors.Append("LastSyncAt", err) } } - if v, ok := any(s.LatestSync).(runtime.Validator); ok { - if err := v.Validate(); err != nil { - errors = errors.Append("LatestSync", err) + if s.LatestSync != nil { + if v, ok := any(s.LatestSync).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("LatestSync", err) + } + } + } + if s.NextSyncAt != nil { + if err := typesValidator.Var(s.NextSyncAt, "required"); err != nil { + errors = errors.Append("NextSyncAt", err) } } if err := typesValidator.Var(s.SourceType, "required"); err != nil { @@ -1658,10 +3599,12 @@ type StageDeletionFilter struct { } type StageDeletionRequest struct { - Description *string `json:"description,omitempty"` - DryRun *bool `json:"dry_run,omitempty"` - Filter *StageDeletionFilter `json:"filter,omitempty"` - MessageIds []int64 `json:"message_ids,omitempty"` + Description *string `json:"description,omitempty"` + DryRun *bool `json:"dry_run,omitempty"` + Filter *StageDeletionFilter `json:"filter,omitempty"` + MessageIds []int64 `json:"message_ids,omitempty"` + OperationToken *string `json:"operation_token,omitempty"` + Selection *ExploreSelection `json:"selection,omitempty"` } func (s StageDeletionRequest) Validate() error { @@ -1673,6 +3616,13 @@ func (s StageDeletionRequest) Validate() error { } } } + if s.Selection != nil { + if v, ok := any(s.Selection).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Selection", err) + } + } + } if len(errors) == 0 { return nil } @@ -1851,6 +3801,161 @@ func (s SyncRunStatus) Validate() error { return errors } +type TaskIntegrationStatusResponse struct { + // Message Credential-free status explanation + Message string `json:"message" validate:"required"` + + // Project Configured project when available + Project *string `json:"project,omitempty"` + + // SecurityNote Platform-specific local socket security limitation + SecurityNote *string `json:"security_note,omitempty"` + + // State Current task integration state + State TaskIntegrationStatusResponseState `json:"state" validate:"required"` +} + +func (t TaskIntegrationStatusResponse) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(t.Message, "required"); err != nil { + errors = errors.Append("Message", err) + } + if v, ok := any(t.State).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("State", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type TaskLinkLookupResponse struct { + Complete bool `json:"complete"` + LastScan time.Time `json:"last_scan" validate:"required"` + OutboundMetadata TaskLinkOutboundMetadata `json:"outbound_metadata"` + Reason *string `json:"reason,omitempty"` + RemoteRevision *string `json:"remote_revision,omitempty"` + State string `json:"state" validate:"required"` + Tasks []TaskSummary `json:"tasks" validate:"required"` +} + +func (t TaskLinkLookupResponse) Validate() error { + var errors runtime.ValidationErrors + if err := typesValidator.Var(t.LastScan, "required"); err != nil { + errors = errors.Append("LastScan", err) + } + if v, ok := any(t.OutboundMetadata).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("OutboundMetadata", err) + } + } + if err := typesValidator.Var(t.State, "required"); err != nil { + errors = errors.Append("State", err) + } + for i, item := range t.Tasks { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Tasks[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type TaskLinkMutationRequest struct { + // AddedAt Browser-generated retry-stable RFC3339 timestamp for the outbound mail link + AddedAt string `json:"added_at" validate:"required"` + Description *string `json:"description,omitempty"` + Labels []string `json:"labels,omitempty"` + Priority *string `json:"priority,omitempty"` + + // TaskID Existing task to link; omit to create a task + TaskID *string `json:"task_id,omitempty"` + Title *string `json:"title,omitempty"` +} + +func (t TaskLinkMutationRequest) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(t)) +} + +type TaskLinkMutationResponse struct { + Task TaskLinkTask `json:"task"` +} + +func (t TaskLinkMutationResponse) Validate() error { + var errors runtime.ValidationErrors + if v, ok := any(t.Task).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append("Task", err) + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type TaskLinkOutboundMetadata struct { + ArchiveUID string `json:"archive_uid" validate:"required"` + ConversationID int64 `json:"conversation_id"` + From string `json:"from" validate:"required"` + MessageID int64 `json:"message_id"` + SentAt string `json:"sent_at" validate:"required"` + SourceIdentifier string `json:"source_identifier" validate:"required"` + SourceMessageID string `json:"source_message_id" validate:"required"` + SourceType string `json:"source_type" validate:"required"` + Subject string `json:"subject" validate:"required"` +} + +func (t TaskLinkOutboundMetadata) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(t)) +} + +type TaskLinkTask struct { + ID string `json:"id" validate:"required"` + Project string `json:"project" validate:"required"` + Revision *string `json:"revision,omitempty"` + Title string `json:"title" validate:"required"` +} + +func (t TaskLinkTask) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(t)) +} + +type TaskSearchResponse struct { + Tasks []TaskSummary `json:"tasks,omitempty" validate:"required"` +} + +func (t TaskSearchResponse) Validate() error { + var errors runtime.ValidationErrors + for i, item := range t.Tasks { + if v, ok := any(item).(runtime.Validator); ok { + if err := v.Validate(); err != nil { + errors = errors.Append(fmt.Sprintf("Tasks[%d]", i), err) + } + } + } + if len(errors) == 0 { + return nil + } + return errors +} + +type TaskSummary struct { + ID string `json:"id" validate:"required"` + Revision *string `json:"revision,omitempty"` + Title string `json:"title" validate:"required"` +} + +func (t TaskSummary) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(t)) +} + type TextConversationRow struct { ConversationID int64 `json:"conversation_id"` LastMessageAt *string `json:"last_message_at,omitempty"` @@ -1911,6 +4016,24 @@ func (t TextMessagesResponse) Validate() error { return errors } +type TimelineRow struct { + AnchorMessageID *int64 `json:"anchor_message_id,omitempty"` + ConversationID *int64 `json:"conversation_id,omitempty"` + FirstAt *time.Time `json:"first_at,omitempty"` + HasAttachments bool `json:"has_attachments"` + Key string `json:"key" validate:"required"` + Kind string `json:"kind" validate:"required"` + MessageCount int64 `json:"message_count"` + OccurredAt time.Time `json:"occurred_at" validate:"required"` + Preview string `json:"preview" validate:"required"` + SourceID int64 `json:"source_id"` + Title string `json:"title" validate:"required"` +} + +func (t TimelineRow) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(t)) +} + type TokenUploadRequest struct { AccessToken *string `json:"access_token,omitempty"` ClientID *string `json:"client_id,omitempty"` diff --git a/pkg/client/generated/unions.go b/pkg/client/generated/unions.go index 612f6a159..ef768ab85 100644 --- a/pkg/client/generated/unions.go +++ b/pkg/client/generated/unions.go @@ -3,9 +3,563 @@ package generated import ( + "encoding/json" + "github.com/doordash-oss/oapi-codegen-dd/v3/pkg/runtime" ) +type SettingValue_OneOf_0 struct { + String string `json:"string" validate:"required"` +} + +func (s SettingValue_OneOf_0) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(s)) +} + +type SettingValue_OneOf_1 struct { + Integer int64 `json:"integer"` +} + +type SettingValue_OneOf_2 struct { + Number float64 `json:"number"` +} + +type SettingValue_OneOf_3 struct { + Boolean bool `json:"boolean"` +} + +type SettingValue_OneOf_4 struct { + Strings []string `json:"strings" validate:"required"` +} + +func (s SettingValue_OneOf_4) Validate() error { + return runtime.ConvertValidatorError(typesValidator.Struct(s)) +} + +type SettingValue_OneOf struct { + union json.RawMessage +} + +func (s *SettingValue_OneOf) Validate() error { + // NOTE: Validation is not supported for unions with more than 2 elements. + // Validating would require unmarshaling against each possible type, which is inefficient. + // Use AsValidated() methods to validate after retrieving the specific type. + return nil +} + +// Raw returns the union data inside the SettingValue_OneOf as bytes +func (s *SettingValue_OneOf) Raw() json.RawMessage { + return s.union +} + +// AsSettingValue_OneOf_0 returns the union data inside the SettingValue_OneOf as a SettingValue_OneOf_0 +func (s *SettingValue_OneOf) AsSettingValue_OneOf_0() (SettingValue_OneOf_0, error) { + return runtime.UnmarshalAs[SettingValue_OneOf_0](s.union) +} + +// AsValidatedSettingValue_OneOf_0 returns the union data inside the SettingValue_OneOf as a validated SettingValue_OneOf_0 +func (s *SettingValue_OneOf) AsValidatedSettingValue_OneOf_0() (SettingValue_OneOf_0, error) { + val, err := s.AsSettingValue_OneOf_0() + if err != nil { + var zero SettingValue_OneOf_0 + return zero, err + } + if err := s.validateSettingValue_OneOf_0(val); err != nil { + var zero SettingValue_OneOf_0 + return zero, err + } + return val, nil +} + +// FromSettingValue_OneOf_0 overwrites any union data inside the SettingValue_OneOf as the provided SettingValue_OneOf_0 +func (s *SettingValue_OneOf) FromSettingValue_OneOf_0(val SettingValue_OneOf_0) error { + // Validate before storing + if err := s.validateSettingValue_OneOf_0(val); err != nil { + return err + } + bts, err := json.Marshal(val) + s.union = bts + return err +} + +// AsSettingValue_OneOf_1 returns the union data inside the SettingValue_OneOf as a SettingValue_OneOf_1 +func (s *SettingValue_OneOf) AsSettingValue_OneOf_1() (SettingValue_OneOf_1, error) { + return runtime.UnmarshalAs[SettingValue_OneOf_1](s.union) +} + +// AsValidatedSettingValue_OneOf_1 returns the union data inside the SettingValue_OneOf as a validated SettingValue_OneOf_1 +func (s *SettingValue_OneOf) AsValidatedSettingValue_OneOf_1() (SettingValue_OneOf_1, error) { + val, err := s.AsSettingValue_OneOf_1() + if err != nil { + var zero SettingValue_OneOf_1 + return zero, err + } + if err := s.validateSettingValue_OneOf_1(val); err != nil { + var zero SettingValue_OneOf_1 + return zero, err + } + return val, nil +} + +// FromSettingValue_OneOf_1 overwrites any union data inside the SettingValue_OneOf as the provided SettingValue_OneOf_1 +func (s *SettingValue_OneOf) FromSettingValue_OneOf_1(val SettingValue_OneOf_1) error { + // Validate before storing + if err := s.validateSettingValue_OneOf_1(val); err != nil { + return err + } + bts, err := json.Marshal(val) + s.union = bts + return err +} + +// AsSettingValue_OneOf_2 returns the union data inside the SettingValue_OneOf as a SettingValue_OneOf_2 +func (s *SettingValue_OneOf) AsSettingValue_OneOf_2() (SettingValue_OneOf_2, error) { + return runtime.UnmarshalAs[SettingValue_OneOf_2](s.union) +} + +// AsValidatedSettingValue_OneOf_2 returns the union data inside the SettingValue_OneOf as a validated SettingValue_OneOf_2 +func (s *SettingValue_OneOf) AsValidatedSettingValue_OneOf_2() (SettingValue_OneOf_2, error) { + val, err := s.AsSettingValue_OneOf_2() + if err != nil { + var zero SettingValue_OneOf_2 + return zero, err + } + if err := s.validateSettingValue_OneOf_2(val); err != nil { + var zero SettingValue_OneOf_2 + return zero, err + } + return val, nil +} + +// FromSettingValue_OneOf_2 overwrites any union data inside the SettingValue_OneOf as the provided SettingValue_OneOf_2 +func (s *SettingValue_OneOf) FromSettingValue_OneOf_2(val SettingValue_OneOf_2) error { + // Validate before storing + if err := s.validateSettingValue_OneOf_2(val); err != nil { + return err + } + bts, err := json.Marshal(val) + s.union = bts + return err +} + +// AsSettingValue_OneOf_3 returns the union data inside the SettingValue_OneOf as a SettingValue_OneOf_3 +func (s *SettingValue_OneOf) AsSettingValue_OneOf_3() (SettingValue_OneOf_3, error) { + return runtime.UnmarshalAs[SettingValue_OneOf_3](s.union) +} + +// AsValidatedSettingValue_OneOf_3 returns the union data inside the SettingValue_OneOf as a validated SettingValue_OneOf_3 +func (s *SettingValue_OneOf) AsValidatedSettingValue_OneOf_3() (SettingValue_OneOf_3, error) { + val, err := s.AsSettingValue_OneOf_3() + if err != nil { + var zero SettingValue_OneOf_3 + return zero, err + } + if err := s.validateSettingValue_OneOf_3(val); err != nil { + var zero SettingValue_OneOf_3 + return zero, err + } + return val, nil +} + +// FromSettingValue_OneOf_3 overwrites any union data inside the SettingValue_OneOf as the provided SettingValue_OneOf_3 +func (s *SettingValue_OneOf) FromSettingValue_OneOf_3(val SettingValue_OneOf_3) error { + // Validate before storing + if err := s.validateSettingValue_OneOf_3(val); err != nil { + return err + } + bts, err := json.Marshal(val) + s.union = bts + return err +} + +// AsSettingValue_OneOf_4 returns the union data inside the SettingValue_OneOf as a SettingValue_OneOf_4 +func (s *SettingValue_OneOf) AsSettingValue_OneOf_4() (SettingValue_OneOf_4, error) { + return runtime.UnmarshalAs[SettingValue_OneOf_4](s.union) +} + +// AsValidatedSettingValue_OneOf_4 returns the union data inside the SettingValue_OneOf as a validated SettingValue_OneOf_4 +func (s *SettingValue_OneOf) AsValidatedSettingValue_OneOf_4() (SettingValue_OneOf_4, error) { + val, err := s.AsSettingValue_OneOf_4() + if err != nil { + var zero SettingValue_OneOf_4 + return zero, err + } + if err := s.validateSettingValue_OneOf_4(val); err != nil { + var zero SettingValue_OneOf_4 + return zero, err + } + return val, nil +} + +// FromSettingValue_OneOf_4 overwrites any union data inside the SettingValue_OneOf as the provided SettingValue_OneOf_4 +func (s *SettingValue_OneOf) FromSettingValue_OneOf_4(val SettingValue_OneOf_4) error { + // Validate before storing + if err := s.validateSettingValue_OneOf_4(val); err != nil { + return err + } + bts, err := json.Marshal(val) + s.union = bts + return err +} + +// validateSettingValue_OneOf_0 validates a SettingValue_OneOf_0 value +func (s *SettingValue_OneOf) validateSettingValue_OneOf_0(val SettingValue_OneOf_0) error { + if v, ok := any(val).(runtime.Validator); ok { + return v.Validate() + } + return nil +} + +// validateSettingValue_OneOf_1 validates a SettingValue_OneOf_1 value +func (s *SettingValue_OneOf) validateSettingValue_OneOf_1(val SettingValue_OneOf_1) error { + if v, ok := any(val).(runtime.Validator); ok { + return v.Validate() + } + return nil +} + +// validateSettingValue_OneOf_2 validates a SettingValue_OneOf_2 value +func (s *SettingValue_OneOf) validateSettingValue_OneOf_2(val SettingValue_OneOf_2) error { + if v, ok := any(val).(runtime.Validator); ok { + return v.Validate() + } + return nil +} + +// validateSettingValue_OneOf_3 validates a SettingValue_OneOf_3 value +func (s *SettingValue_OneOf) validateSettingValue_OneOf_3(val SettingValue_OneOf_3) error { + if v, ok := any(val).(runtime.Validator); ok { + return v.Validate() + } + return nil +} + +// validateSettingValue_OneOf_4 validates a SettingValue_OneOf_4 value +func (s *SettingValue_OneOf) validateSettingValue_OneOf_4(val SettingValue_OneOf_4) error { + if v, ok := any(val).(runtime.Validator); ok { + return v.Validate() + } + return nil +} + +func (s SettingValue_OneOf) MarshalJSON() ([]byte, error) { + bts, err := s.union.MarshalJSON() + + return bts, err +} + +func (s *SettingValue_OneOf) UnmarshalJSON(bts []byte) error { + err := s.union.UnmarshalJSON(bts) + + return err +} + +type SearchDomains_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (s *SearchDomains_ErrorResponse_503_AnyOf) Validate() error { + if s.IsA() { + if v, ok := any(s.A).(runtime.Validator); ok { + return v.Validate() + } + } + if s.IsB() { + if v, ok := any(s.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type SearchDomainFiles_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (s *SearchDomainFiles_ErrorResponse_503_AnyOf) Validate() error { + if s.IsA() { + if v, ok := any(s.A).(runtime.Validator); ok { + return v.Validate() + } + } + if s.IsB() { + if v, ok := any(s.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type GetDomainContextSummary_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (g *GetDomainContextSummary_ErrorResponse_503_AnyOf) Validate() error { + if g.IsA() { + if v, ok := any(g.A).(runtime.Validator); ok { + return v.Validate() + } + } + if g.IsB() { + if v, ok := any(g.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type GetDomainTimeline_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (g *GetDomainTimeline_ErrorResponse_503_AnyOf) Validate() error { + if g.IsA() { + if v, ok := any(g.A).(runtime.Validator); ok { + return v.Validate() + } + } + if g.IsB() { + if v, ok := any(g.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type Explore_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (e *Explore_ErrorResponse_503_AnyOf) Validate() error { + if e.IsA() { + if v, ok := any(e.A).(runtime.Validator); ok { + return v.Validate() + } + } + if e.IsB() { + if v, ok := any(e.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type ListExploreFiles_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (l *ListExploreFiles_ErrorResponse_503_AnyOf) Validate() error { + if l.IsA() { + if v, ok := any(l.A).(runtime.Validator); ok { + return v.Validate() + } + } + if l.IsB() { + if v, ok := any(l.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type ExploreGroups_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (e *ExploreGroups_ErrorResponse_503_AnyOf) Validate() error { + if e.IsA() { + if v, ok := any(e.A).(runtime.Validator); ok { + return v.Validate() + } + } + if e.IsB() { + if v, ok := any(e.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type CountExploreMatches_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (c *CountExploreMatches_ErrorResponse_503_AnyOf) Validate() error { + if c.IsA() { + if v, ok := any(c.A).(runtime.Validator); ok { + return v.Validate() + } + } + if c.IsB() { + if v, ok := any(c.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type PreflightExploreSelection_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (p *PreflightExploreSelection_ErrorResponse_503_AnyOf) Validate() error { + if p.IsA() { + if v, ok := any(p.A).(runtime.Validator); ok { + return v.Validate() + } + } + if p.IsB() { + if v, ok := any(p.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type GroupFiles_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (g *GroupFiles_ErrorResponse_503_AnyOf) Validate() error { + if g.IsA() { + if v, ok := any(g.A).(runtime.Validator); ok { + return v.Validate() + } + } + if g.IsB() { + if v, ok := any(g.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type SearchFiles_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (s *SearchFiles_ErrorResponse_503_AnyOf) Validate() error { + if s.IsA() { + if v, ok := any(s.A).(runtime.Validator); ok { + return v.Validate() + } + } + if s.IsB() { + if v, ok := any(s.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type SearchPeople_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (s *SearchPeople_ErrorResponse_503_AnyOf) Validate() error { + if s.IsA() { + if v, ok := any(s.A).(runtime.Validator); ok { + return v.Validate() + } + } + if s.IsB() { + if v, ok := any(s.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type SearchPersonFiles_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (s *SearchPersonFiles_ErrorResponse_503_AnyOf) Validate() error { + if s.IsA() { + if v, ok := any(s.A).(runtime.Validator); ok { + return v.Validate() + } + } + if s.IsB() { + if v, ok := any(s.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type GetPersonContextSummary_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (g *GetPersonContextSummary_ErrorResponse_503_AnyOf) Validate() error { + if g.IsA() { + if v, ok := any(g.A).(runtime.Validator); ok { + return v.Validate() + } + } + if g.IsB() { + if v, ok := any(g.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type GetPersonTimeline_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (g *GetPersonTimeline_ErrorResponse_503_AnyOf) Validate() error { + if g.IsA() { + if v, ok := any(g.A).(runtime.Validator); ok { + return v.Validate() + } + } + if g.IsB() { + if v, ok := any(g.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type ListRelationships_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (l *ListRelationships_ErrorResponse_503_AnyOf) Validate() error { + if l.IsA() { + if v, ok := any(l.A).(runtime.Validator); ok { + return v.Validate() + } + } + if l.IsB() { + if v, ok := any(l.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + +type GetRelationshipTimeline_ErrorResponse_503_AnyOf struct { + runtime.Either[ExploreCacheUnavailableResponse, ErrorResponse] +} + +func (g *GetRelationshipTimeline_ErrorResponse_503_AnyOf) Validate() error { + if g.IsA() { + if v, ok := any(g.A).(runtime.Validator); ok { + return v.Validate() + } + } + if g.IsB() { + if v, ok := any(g.B).(runtime.Validator); ok { + return v.Validate() + } + } + return nil +} + type SearchMessages_Response_OneOf struct { runtime.Either[SearchResult, HybridSearchResponse] } diff --git a/pkg/client/openapi.yaml b/pkg/client/openapi.yaml index 893cfb099..a77b28d55 100644 --- a/pkg/client/openapi.yaml +++ b/pkg/client/openapi.yaml @@ -86,6 +86,11 @@ components: properties: account: type: string + cache_state: + enum: + - ready + - stale + type: string identifier: type: string outcome: @@ -1025,6 +1030,34 @@ components: required: - stats type: object + ConversationResponse: + properties: + anchor_id: + format: int64 + type: integer + has_after: + type: boolean + has_before: + type: boolean + id: + format: int64 + type: integer + messages: + items: + $ref: "#/components/schemas/MessageDetail" + nullable: true + type: array + total: + format: int64 + type: integer + required: + - id + - anchor_id + - messages + - has_before + - has_after + - total + type: object CreateRequest: additionalProperties: false properties: @@ -1039,6 +1072,23 @@ components: - name - accounts type: object + CreateSavedViewRequest: + additionalProperties: false + properties: + canonical_state: + $ref: "#/components/schemas/SavedViewStateEnvelope" + description: + type: string + name: + type: string + schema_version: + format: int64 + type: integer + required: + - name + - canonical_state + - schema_version + type: object DeepSearchResponse: properties: body_contexts: @@ -1074,6 +1124,36 @@ components: - offset - limit type: object + DeletionManifestDetail: + properties: + account: + type: string + created_at: + format: date-time + type: string + created_by: + type: string + description: + type: string + execution: + $ref: "#/components/schemas/Execution" + id: + type: string + message_count: + format: int64 + type: integer + status: + type: string + summary: + $ref: "#/components/schemas/Summary" + required: + - id + - status + - created_at + - created_by + - description + - message_count + type: object DeletionManifestSummary: properties: created_at: @@ -1098,6 +1178,158 @@ components: - description - message_count type: object + DomainContextSummaryHTTPResponse: + properties: + cache_revision: + type: string + candidate_snapshot_id: + type: string + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + summary: + $ref: "#/components/schemas/DomainSummary" + required: + - summary + - cache_revision + - search_provenance + type: object + DomainSearchHTTPResponse: + properties: + cache_revision: + type: string + candidate_snapshot_id: + type: string + next_cursor: + type: string + rows: + items: + $ref: "#/components/schemas/DomainSummary" + nullable: true + type: array + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: + format: int64 + type: integer + required: + - rows + - total_count + - cache_revision + - search_provenance + type: object + DomainSummary: + properties: + activity_count: + format: int64 + type: integer + cache_revision: + type: string + domain: + type: string + file_count: + format: int64 + type: integer + first_at: + format: date-time + type: string + last_at: + format: date-time + type: string + person_count: + format: int64 + type: integer + source_counts: + items: + $ref: "#/components/schemas/SourceCount" + nullable: true + type: array + required: + - domain + - activity_count + - person_count + - file_count + - source_counts + - first_at + - last_at + - cache_revision + type: object + EntryRow: + properties: + anchor_message_id: + format: int64 + type: integer + attachment_count: + format: int64 + type: integer + attachment_size: + format: int64 + type: integer + conversation_id: + format: int64 + type: integer + conversation_type: + type: string + counterpart_participant_id: + format: int64 + type: integer + deleted_from_source: + type: boolean + has_attachments: + type: boolean + key: + type: string + kind: + type: string + match: + $ref: "#/components/schemas/MatchSummary" + message_count: + format: int64 + type: integer + message_type: + type: string + occurred_at: + format: date-time + type: string + participant_ids: + items: + format: int64 + type: integer + nullable: true + type: array + participant_labels: + items: + type: string + nullable: true + type: array + preview: + type: string + source_id: + format: int64 + type: integer + source_identifier: + type: string + source_type: + type: string + title: + type: string + required: + - key + - kind + - occurred_at + - match + - source_id + - source_type + - source_identifier + - message_type + - conversation_type + - title + - preview + - message_count + - has_attachments + - attachment_count + - attachment_size + - deleted_from_source + type: object ErrorResponse: properties: error: @@ -1139,1181 +1371,4374 @@ components: - failed - last_processed_index type: object - FilteredMessagesResponse: + ExploreActionTarget: properties: - count: - format: int64 - type: integer - has_more: - type: boolean - limit: - format: int64 - type: integer - messages: - items: - $ref: "#/components/schemas/MessageSummary" - nullable: true - type: array - offset: + action: + type: string + filename: + type: string + message_id: format: int64 type: integer required: - - count - - has_more - - offset - - limit - - messages + - action + - message_id + - filename type: object - Filters: - additionalProperties: false + ExploreCacheUnavailableResponse: properties: - account: + error: type: string - after: + message: type: string - before: + readiness: + enum: + - absent + - interrupted + - stale_schema + - drifted type: string - labels: - items: - type: string - nullable: true - type: array - recipients: - items: - type: string - nullable: true - type: array - sender_domains: - items: - type: string - nullable: true - type: array - senders: - items: - type: string - nullable: true - type: array + x-enum-names: + - ExploreCacheUnavailableResponseReadinessAbsent + - ExploreCacheUnavailableResponseReadinessInterrupted + - ExploreCacheUnavailableResponseReadinessStaleSchema + - ExploreCacheUnavailableResponseReadinessDrifted + recovery_action: + type: string + required: + - error + - message + - readiness + - recovery_action type: object - GenerationSummary: + ExploreFileFact: properties: - activated_at: - type: string - dimension: + conversation_id: format: int64 type: integer - fingerprint: + entry_key: + type: string + filename: type: string id: format: int64 type: integer - message_count: + key: + type: string + message_id: format: int64 type: integer - model: + mime_type: type: string - state: + occurred_at: + format: date-time + type: string + size: + format: int64 + type: integer + source_id: + format: int64 + type: integer + source_identifier: + type: string + title: type: string required: - id - - model - - dimension - - fingerprint - - state - - message_count + - key + - entry_key + - message_id + - conversation_id + - occurred_at + - source_id + - source_identifier + - title + - filename + - mime_type + - size type: object - GmailIDsResponse: + ExploreFilesHTTPRequest: + additionalProperties: false properties: - gmail_ids: - items: - type: string - nullable: true - type: array + cursor: + type: string + limit: + format: int64 + maximum: 100 + minimum: 0 + type: integer + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" required: - - gmail_ids + - predicate type: object - HealthResponse: + ExploreFilesHTTPResponse: properties: - analytics_engine: + cache_revision: type: string - operation: - $ref: "#/components/schemas/OperationHealth" - status: + candidate_snapshot_id: type: string - vector: - $ref: "#/components/schemas/VectorHealth" + files: + items: + $ref: "#/components/schemas/ExploreFileFact" + type: array + next_cursor: + type: string + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: + format: int64 + type: integer required: - - status + - files + - total_count + - cache_revision + - search_provenance type: object - HybridGenerationSummary: + ExploreFilter: + additionalProperties: false properties: dimension: + enum: + - source + - participant + - domain + - message_type + - after + - before + - deletion + type: string + x-enum-names: + - ExploreFilterDimensionSource + - ExploreFilterDimensionParticipant + - ExploreFilterDimensionDomain + - ExploreFilterDimensionMessageType + - ExploreFilterDimensionAfter + - ExploreFilterDimensionBefore + - ExploreFilterDimensionDeletion + values: + items: + type: string + minItems: 1 + type: array + required: + - dimension + - values + type: object + ExploreGroupDimension: + enum: + - source + - participant + - domain + - message_type + - kind + - year + - month + type: string + x-enum-names: + - ExploreGroupDimensionSource + - ExploreGroupDimensionParticipant + - ExploreGroupDimensionDomain + - ExploreGroupDimensionMessageType + - ExploreGroupDimensionKind + - ExploreGroupDimensionYear + - ExploreGroupDimensionMonth + ExploreGroupRow: + properties: + count: format: int64 type: integer - fingerprint: - type: string - id: + estimated_bytes: format: int64 type: integer - model: + key: type: string - state: + label: + type: string + latest_at: + format: date-time type: string required: - - id - - model - - dimension - - fingerprint - - state + - key + - label + - count + - estimated_bytes + - latest_at type: object - HybridSearchItem: + ExploreGroupSort: + additionalProperties: false properties: - bcc: + direction: + enum: + - asc + - desc + type: string + x-enum-names: + - ExploreGroupSortDirectionAsc + - ExploreGroupSortDirectionDesc + field: + enum: + - key + - count + - estimated_bytes + - latest_at + type: string + x-enum-names: + - ExploreGroupSortFieldKey + - ExploreGroupSortFieldCount + - ExploreGroupSortFieldEstimatedBytes + - ExploreGroupSortFieldLatestAt + required: + - field + - direction + type: object + ExploreGroupsHTTPRequest: + additionalProperties: false + properties: + cursor: + type: string + filters: items: - type: string + $ref: "#/components/schemas/ExploreFilter" nullable: true type: array - cc: + grouping: items: - type: string - nullable: true + $ref: "#/components/schemas/ExploreGroupDimension" + maxItems: 1 + minItems: 1 type: array - conversation_id: + x-oapi-codegen-extra-tags: + validate: required,min=1,max=1 + limit: format: int64 + maximum: 500 + minimum: 0 type: integer - deleted_at: + presentation: + enum: + - table type: string - from: + x-enum-names: + - ExploreGroupsHTTPRequestPresentationTable + query: type: string - from_email: + search_mode: + enum: + - full_text + - semantic + - hybrid + type: string + x-enum-names: + - ExploreGroupsHTTPRequestSearchModeFullText + - ExploreGroupsHTTPRequestSearchModeSemantic + - ExploreGroupsHTTPRequestSearchModeHybrid + sort: + items: + $ref: "#/components/schemas/ExploreGroupSort" + maxItems: 1 + nullable: true + type: array + required: + - grouping + type: object + ExploreGroupsHTTPResponse: + properties: + cache_revision: type: string - from_name: + candidate_snapshot_id: type: string - from_phone: + next_cursor: type: string - has_attachments: - type: boolean - id: + rows: + items: + $ref: "#/components/schemas/ExploreGroupRow" + type: array + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: format: int64 type: integer - labels: + required: + - rows + - total_count + - cache_revision + - search_provenance + type: object + ExploreHTTPRequest: + additionalProperties: false + properties: + candidate_snapshot_id: + type: string + cursor: + type: string + filters: items: - type: string + $ref: "#/components/schemas/ExploreFilter" nullable: true type: array - matches: + grouping: items: - $ref: "#/components/schemas/HybridSearchMatch" + $ref: "#/components/schemas/ExploreGroupDimension" nullable: true type: array - matches_truncated: - type: boolean - message_type: - type: string - score: - $ref: "#/components/schemas/ScoreBreakdown" - sent_at: - type: string - size_bytes: - format: int64 - type: integer - snippet: - type: string - source_id: + limit: format: int64 + maximum: 500 + minimum: 0 type: integer - source_message_id: + presentation: + enum: + - table + - timeline + - files type: string - subject: + x-enum-names: + - ExploreHTTPRequestPresentationTable + - ExploreHTTPRequestPresentationTimeline + - ExploreHTTPRequestPresentationFiles + query: type: string - to: + search_mode: + enum: + - full_text + - semantic + - hybrid + type: string + x-enum-names: + - ExploreHTTPRequestSearchModeFullText + - ExploreHTTPRequestSearchModeSemantic + - ExploreHTTPRequestSearchModeHybrid + sort: items: - type: string + $ref: "#/components/schemas/ExploreSort" nullable: true type: array - required: - - id - - subject - - from - - to - - sent_at - - snippet - - labels - - has_attachments - - size_bytes type: object - HybridSearchMatch: + ExploreHTTPResponse: properties: - char_offset: - format: int64 - type: integer - line: - format: int64 - type: integer - score: - format: double - type: number - snippet: + cache_revision: type: string - required: - - snippet - - score - type: object - HybridSearchResponse: - properties: - generation: - $ref: "#/components/schemas/HybridGenerationSummary" - has_more: + candidate_pool_saturated: type: boolean - mode: + candidate_snapshot_id: type: string - pool_saturated: - type: boolean - query: + next_cursor: type: string - results: + rows: items: - $ref: "#/components/schemas/HybridSearchItem" - nullable: true + $ref: "#/components/schemas/EntryRow" type: array - returned: - format: int64 - type: integer - scope_label: - type: string - scope_source_count: - format: int64 - type: integer - took_ms: + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: format: int64 type: integer required: - - query - - mode - - returned - - pool_saturated - - has_more - - generation - - took_ms - - results + - rows + - cache_revision + - search_provenance type: object - ListDeletionsResponse: + ExploreMatchCountsRequest: + additionalProperties: false properties: - manifests: + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" + row_keys: items: - $ref: "#/components/schemas/DeletionManifestSummary" - nullable: true + type: string + maxItems: 500 + minItems: 1 type: array required: - - manifests + - predicate + - row_keys type: object - Manifest: - additionalProperties: false + ExploreMatchCountsResponse: properties: - created_at: - format: date-time + cache_revision: type: string - created_by: + canonical_query_hash: type: string - description: + counts: + items: + $ref: "#/components/schemas/ExploreRowMatchCount" + type: array + lexical_index_revision: type: string - execution: - $ref: "#/components/schemas/Execution" - filters: - $ref: "#/components/schemas/Filters" - gmail_ids: + required: + - counts + - cache_revision + - lexical_index_revision + - canonical_query_hash + type: object + ExplorePreflightRequest: + additionalProperties: false + properties: + selection: + $ref: "#/components/schemas/ExploreSelection" + required: + - selection + type: object + ExplorePreflightResponse: + properties: + action_targets: items: - type: string - nullable: true + $ref: "#/components/schemas/ExploreActionTarget" type: array - id: + cache_revision: type: string - raw_filter: {} - status: + count: + format: int64 + type: integer + estimated_bytes: + format: int64 + type: integer + expires_at: + format: date-time type: string - summary: - $ref: "#/components/schemas/Summary" - version: + operation_token: + type: string + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + unavailable_actions: + items: + $ref: "#/components/schemas/ExploreUnavailableAction" + type: array + required: + - count + - estimated_bytes + - cache_revision + - search_provenance + - unavailable_actions + - action_targets + - operation_token + - expires_at + type: object + ExploreRowMatchCount: + properties: + count: format: int64 type: integer + row_key: + type: string required: - - version - - id - - created_at - - created_by - - description - - filters - - gmail_ids - - status + - row_key + - count type: object - MessageDetail: + ExploreSelection: + additionalProperties: false properties: - attachments: + cache_revision: + type: string + candidate_snapshot_id: + type: string + exclusions: items: - $ref: "#/components/schemas/AttachmentInfo" + type: string nullable: true type: array - bcc: + mode: + enum: + - explicit + - all_matching + type: string + x-enum-names: + - ExploreSelectionModeExplicit + - ExploreSelectionModeAllMatching + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" + row_keys: items: type: string nullable: true type: array - body: + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + required: + - mode + - predicate + - cache_revision + - search_provenance + type: object + ExploreSort: + additionalProperties: false + properties: + direction: + enum: + - desc + type: string + x-enum-names: + - ExploreSortDirectionDesc + field: + enum: + - occurred_at + type: string + x-enum-names: + - ExploreSortFieldOccurredAt + required: + - field + - direction + type: object + ExploreUnavailableAction: + properties: + action: type: string - body_html: + reason: type: string - cc: - items: - type: string - nullable: true - type: array - conversation_id: - format: int64 - type: integer - deleted_at: - type: string - from: - type: string - from_email: - type: string - from_name: + required: + - action + - reason + type: object + FileGroupsHTTPRequest: + additionalProperties: false + properties: + cursor: type: string - from_phone: + filename_query: type: string - has_attachments: - type: boolean - id: + grouping: + items: + $ref: "#/components/schemas/ExploreGroupDimension" + maxItems: 1 + minItems: 1 + type: array + x-oapi-codegen-extra-tags: + validate: required,min=1,max=1 + limit: format: int64 + maximum: 500 + minimum: 0 type: integer - labels: + mime_families: items: type: string nullable: true type: array - message_type: - type: string - sent_at: - type: string - size_bytes: - format: int64 - type: integer - snippet: + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" + sort: + items: + $ref: "#/components/schemas/ExploreGroupSort" + maxItems: 1 + nullable: true + type: array + required: + - predicate + - grouping + type: object + FileGroupsHTTPResponse: + properties: + cache_revision: type: string - source_id: - format: int64 - type: integer - source_message_id: + candidate_snapshot_id: type: string - subject: + next_cursor: type: string - to: + rows: items: - type: string - nullable: true + $ref: "#/components/schemas/ExploreGroupRow" type: array + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: + format: int64 + type: integer required: - - body - - attachments - - id - - subject - - from - - to - - sent_at - - snippet - - labels - - has_attachments - - size_bytes + - rows + - total_count + - cache_revision + - search_provenance type: object - MessageListResponse: + FileMetadataResponse: properties: - messages: - items: - $ref: "#/components/schemas/MessageSummary" + content_available: + type: boolean + content_hash: + type: string + content_state: + enum: + - metadata_only + - url_only + - missing_blob + - local_content + type: string + conversation_id: + format: int64 + type: integer + filename: nullable: true - type: array - page: + type: string + id: format: int64 type: integer - page_size: + message_id: format: int64 type: integer - total: + mime_type: + nullable: true + type: string + size_bytes: format: int64 type: integer + url: + type: string required: - - total - - page - - page_size - - messages + - id + - message_id + - conversation_id + - filename + - mime_type + - size_bytes + - content_state + - content_available type: object - MessageSummary: + FileSearchHTTPRequest: + additionalProperties: false properties: - bcc: + cursor: + type: string + filename_query: + type: string + limit: + format: int64 + maximum: 500 + minimum: 0 + type: integer + mime_families: items: type: string nullable: true type: array - cc: + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" + sort: + $ref: "#/components/schemas/FileSearchSort" + required: + - predicate + - sort + type: object + FileSearchHTTPResponse: + properties: + cache_revision: + type: string + candidate_snapshot_id: + type: string + files: items: - type: string + $ref: "#/components/schemas/FileSearchRow" nullable: true type: array - conversation_id: + next_cursor: + type: string + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: format: int64 type: integer - deleted_at: - type: string - from: + required: + - files + - total_count + - cache_revision + - search_provenance + type: object + FileSearchRow: + properties: + containing_title: type: string - from_email: + content_available: + type: boolean + content_state: + enum: + - metadata_only + - url_only + - missing_blob + - local_content type: string - from_name: + conversation_id: + format: int64 + type: integer + entry_key: type: string - from_phone: + filename: + nullable: true type: string - has_attachments: - type: boolean id: format: int64 type: integer - labels: + key: + type: string + message_id: + format: int64 + type: integer + mime_family: + type: string + mime_type: + nullable: true + type: string + occurred_at: + format: date-time + type: string + participant_domains: + items: + type: string + nullable: true + type: array + participant_ids: + items: + format: int64 + type: integer + nullable: true + type: array + participant_labels: items: type: string nullable: true type: array - message_type: - type: string - sent_at: - type: string size_bytes: format: int64 type: integer - snippet: - type: string source_id: format: int64 type: integer - source_message_id: + source_identifier: type: string - subject: + source_type: type: string - to: - items: - type: string - nullable: true - type: array required: - id - - subject - - from - - to - - sent_at - - snippet - - labels - - has_attachments + - key + - entry_key + - message_id + - conversation_id + - occurred_at + - source_id + - source_type + - source_identifier + - containing_title + - filename + - mime_type + - mime_family - size_bytes + - content_state + - content_available type: object - MutationResult: - properties: - name: - type: string - source_count: - format: int64 - type: integer - required: - - name - type: object - OperationHealth: + FileSearchSort: + additionalProperties: false properties: - busy: - type: boolean - label: + direction: type: string - started_at: - format: date-time + field: type: string required: - - busy + - field + - direction type: object - PingInfo: + FilteredMessagesResponse: properties: - ok: - type: boolean - pid: + count: format: int64 type: integer - service: - type: string - version: - type: string - required: - - ok - type: object - Progress: - properties: - done: + has_more: + type: boolean + limit: format: int64 type: integer - total: + messages: + items: + $ref: "#/components/schemas/MessageSummary" + nullable: true + type: array + offset: format: int64 type: integer required: - - done - - total + - count + - has_more + - offset + - limit + - messages type: object - QueryRequest: + Filters: additionalProperties: false properties: - sql: + account: type: string - required: - - sql - type: object - QueryResult: - properties: - columns: + after: + type: string + before: + type: string + labels: items: type: string nullable: true type: array - row_count: - format: int64 - type: integer - rows: + recipients: items: - items: - x-go-type: any - nullable: true - type: array + type: string + nullable: true + type: array + sender_domains: + items: + type: string + nullable: true + type: array + senders: + items: + type: string nullable: true type: array - required: - - columns - - rows - - row_count - type: object - RemoveRequest: - additionalProperties: false - properties: - account: - type: string - identifier: - type: string - required: - - account - - identifier type: object - RemoveResult: + GenerationSummary: properties: - account: + activated_at: type: string - identifier: + dimension: + format: int64 + type: integer + fingerprint: type: string - no_identity: - type: boolean - removed: + id: + format: int64 + type: integer + message_count: format: int64 type: integer + model: + type: string + state: + type: string required: - - account - - identifier - - removed + - id + - model + - dimension + - fingerprint + - state + - message_count type: object - SchedulerStatusResponse: + GmailIDsResponse: properties: - accounts: + gmail_ids: items: - $ref: "#/components/schemas/AccountStatus" + type: string nullable: true type: array - running: - type: boolean required: - - running - - accounts + - gmail_ids type: object - ScoreBreakdown: + HealthResponse: properties: - bm25: - format: double - type: number - rrf: - format: double - type: number - subject_boosted: - type: boolean + analytics_engine: + type: string + operation: + $ref: "#/components/schemas/OperationHealth" + status: + type: string vector: - format: double - type: number + $ref: "#/components/schemas/VectorHealth" + required: + - status type: object - SearchFastResponse: + HybridGenerationSummary: properties: - applied_source_ids: + dimension: + format: int64 + type: integer + fingerprint: + type: string + id: + format: int64 + type: integer + model: + type: string + state: + type: string + required: + - id + - model + - dimension + - fingerprint + - state + type: object + HybridSearchItem: + properties: + bcc: items: - format: int64 - type: integer + type: string nullable: true type: array - messages: + cc: items: - $ref: "#/components/schemas/MessageSummary" + type: string nullable: true type: array - query: + conversation_id: + format: int64 + type: integer + deleted_at: type: string - stats: - $ref: "#/components/schemas/TotalStatsResponse" - total_count: + from: + type: string + from_email: + type: string + from_name: + type: string + from_phone: + type: string + has_attachments: + type: boolean + id: format: int64 type: integer - required: - - query - - messages - - total_count - type: object - SearchResult: - properties: - messages: + labels: items: - $ref: "#/components/schemas/MessageSummary" + type: string nullable: true type: array - page: - format: int64 - type: integer - page_size: + matches: + items: + $ref: "#/components/schemas/HybridSearchMatch" + nullable: true + type: array + matches_truncated: + type: boolean + message_type: + type: string + score: + $ref: "#/components/schemas/ScoreBreakdown" + sent_at: + type: string + size_bytes: format: int64 type: integer - query: + snippet: type: string - total: + source_id: format: int64 type: integer + source_message_id: + type: string + subject: + type: string + to: + items: + type: string + nullable: true + type: array required: - - query - - total - - page - - page_size - - messages + - id + - subject + - from + - to + - sent_at + - snippet + - labels + - has_attachments + - size_bytes type: object - SenderCount: - additionalProperties: false + HybridSearchMatch: properties: - count: + char_offset: format: int64 type: integer - sender: + line: + format: int64 + type: integer + score: + format: double + type: number + snippet: type: string required: - - sender - - count + - snippet + - score type: object - SimilarSearchResponse: + HybridSearchResponse: properties: generation: $ref: "#/components/schemas/HybridGenerationSummary" - messages: + has_more: + type: boolean + mode: + type: string + pool_saturated: + type: boolean + query: + type: string + results: items: - $ref: "#/components/schemas/MessageSummary" + $ref: "#/components/schemas/HybridSearchItem" nullable: true type: array returned: format: int64 type: integer - seed_message_id: + scope_label: + type: string + scope_source_count: + format: int64 + type: integer + took_ms: format: int64 type: integer required: - - seed_message_id + - query + - mode - returned + - pool_saturated + - has_more - generation - - messages + - took_ms + - results type: object - SourceStatus: + IdentityLinkRequest: + additionalProperties: false properties: - active_sync: - $ref: "#/components/schemas/SyncRunStatus" - display_name: - nullable: true - type: string - id: + participant_a: format: int64 type: integer - identifier: + participant_b: + format: int64 + type: integer + required: + - participant_a + - participant_b + type: object + IdentityLinkResponse: + properties: + cache_state: + enum: + - ready + - stale type: string - last_successful_sync: - $ref: "#/components/schemas/SyncRunStatus" - last_sync_at: - nullable: true - type: string - latest_sync: - $ref: "#/components/schemas/SyncRunStatus" - source_type: + identity_revision: + format: int64 + type: integer + required: + - identity_revision + - cache_state + type: object + IdentitySearchHTTPRequest: + additionalProperties: false + properties: + cursor: type: string - updated_at: + identity_query: type: string + limit: + format: int64 + maximum: 500 + minimum: 0 + type: integer + predicate: + $ref: "#/components/schemas/ExploreHTTPRequest" + sort: + $ref: "#/components/schemas/IdentitySearchSort" required: - - id - - source_type - - identifier - - display_name - - last_sync_at - - updated_at - - active_sync - - latest_sync - - last_successful_sync + - predicate + - sort type: object - SourceStatusResponse: + IdentitySearchSort: + additionalProperties: false properties: - sources: - items: - $ref: "#/components/schemas/SourceStatus" - nullable: true - type: array + direction: + enum: + - asc + - desc + type: string + x-enum-names: + - IdentitySearchSortDirectionAsc + - IdentitySearchSortDirectionDesc + field: + enum: + - activity_count + - latest_at + - display_label + type: string + x-enum-names: + - IdentitySearchSortFieldActivityCount + - IdentitySearchSortFieldLatestAt + - IdentitySearchSortFieldDisplayLabel required: - - sources + - field + - direction type: object - SourcesRequest: - additionalProperties: false + ListDeletionsResponse: properties: - accounts: + manifests: items: - type: string + $ref: "#/components/schemas/DeletionManifestSummary" nullable: true type: array required: - - accounts + - manifests type: object - StageDeletionFilter: + Manifest: additionalProperties: false properties: - after: - type: string - before: - type: string - domain: - type: string - label: - type: string - recipient: - type: string - recipient_name: - type: string - sender: + created_at: + format: date-time type: string - sender_name: + created_by: type: string - source_id: - format: int64 - type: integer - type: object - StageDeletionRequest: - additionalProperties: false - properties: description: type: string - dry_run: - type: boolean - filter: - $ref: "#/components/schemas/StageDeletionFilter" - message_ids: + execution: + $ref: "#/components/schemas/Execution" + filters: + $ref: "#/components/schemas/Filters" + gmail_ids: items: - format: int64 - type: integer + type: string nullable: true type: array - type: object - StageDeletionResponse: - properties: - account: - type: string - dry_run: - type: boolean id: type: string - message_count: - format: int64 - type: integer - sample_gmail_ids: - items: - type: string - nullable: true - type: array + raw_filter: {} status: type: string - required: - - dry_run - - message_count - type: object - StatsResponse: - properties: - active_messages: - format: int64 - type: integer - database_size_bytes: - format: int64 - type: integer - source_deleted_messages: - format: int64 - type: integer - total_accounts: - format: int64 - type: integer - total_attachments: - format: int64 - type: integer - total_labels: - format: int64 - type: integer - total_messages: - format: int64 - type: integer - total_threads: + summary: + $ref: "#/components/schemas/Summary" + version: format: int64 type: integer - vector_search: - $ref: "#/components/schemas/StatsView" - vector_status: - type: string required: - - total_messages - - active_messages - - source_deleted_messages - - total_threads - - total_accounts - - total_labels - - total_attachments - - database_size_bytes + - version + - id + - created_at + - created_by + - description + - filters + - gmail_ids + - status type: object - StatsView: + MatchSummary: properties: - active_generation: - $ref: "#/components/schemas/GenerationSummary" - building_generation: - $ref: "#/components/schemas/BuildingSummary" - enabled: - type: boolean - missing_embeddings_total: + lexical_match_count: format: int64 type: integer - required: - - enabled - - active_generation - - missing_embeddings_total - type: object - StatusMessageResponse: - properties: - message: - type: string - status: + semantic_score: + format: double + type: number + strongest_excerpt: type: string - required: - - status - - message type: object - Summary: - additionalProperties: false + MessageDetail: properties: - accounts: + attachments: items: - type: string + $ref: "#/components/schemas/AttachmentInfo" nullable: true type: array - date_range: + bcc: items: type: string - maxItems: 2 - minItems: 2 nullable: true type: array - message_count: - format: int64 - type: integer - top_senders: + body: + type: string + body_html: + type: string + cc: items: - $ref: "#/components/schemas/SenderCount" + type: string nullable: true type: array - total_size_bytes: + conversation_id: format: int64 type: integer - required: - - message_count - - total_size_bytes - - date_range - - accounts - - top_senders - type: object - SyncRunItemStatus: - properties: - created_at: - type: string - error_kind: - type: string - error_message: - type: string - phase: - type: string - source_message_id: + deleted_at: type: string - required: - - source_message_id - - phase - - error_kind - - error_message - - created_at - type: object - SyncRunStatus: - properties: - completed_at: - nullable: true + from: type: string - cursor_after: - nullable: true + from_email: type: string - cursor_before: - nullable: true + from_name: type: string - error_message: - nullable: true + from_phone: type: string - errors_count: - format: int64 - type: integer + has_attachments: + type: boolean id: format: int64 type: integer - item_errors: + labels: items: - $ref: "#/components/schemas/SyncRunItemStatus" + type: string nullable: true type: array - messages_added: - format: int64 - type: integer - messages_processed: + message_type: + type: string + sent_at: + type: string + size_bytes: format: int64 type: integer - messages_updated: + snippet: + type: string + source_id: format: int64 type: integer - skipped_count: - format: int64 - type: integer - source_id: - format: int64 - type: integer - started_at: + source_message_id: type: string - status: + subject: type: string + to: + items: + type: string + nullable: true + type: array required: + - body + - attachments - id - - source_id - - started_at - - completed_at - - status - - messages_processed - - messages_added - - messages_updated - - errors_count - - error_message - - cursor_before - - cursor_after + - subject + - from + - to + - sent_at + - snippet + - labels + - has_attachments + - size_bytes type: object - TextConversationRow: + MessageListResponse: properties: - conversation_id: + messages: + items: + $ref: "#/components/schemas/MessageSummary" + nullable: true + type: array + page: format: int64 type: integer - last_message_at: - type: string - last_preview: - type: string - message_count: + page_size: format: int64 type: integer - participant_count: + total: format: int64 type: integer - source_type: - type: string - title: - type: string required: - - conversation_id - - title - - source_type - - message_count - - participant_count - - last_preview + - total + - page + - page_size + - messages type: object - TextConversationsResponse: + MessageSummary: properties: - conversations: + bcc: items: - $ref: "#/components/schemas/TextConversationRow" + type: string nullable: true type: array - count: + cc: + items: + type: string + nullable: true + type: array + conversation_id: format: int64 type: integer - has_more: + deleted_at: + type: string + from: + type: string + from_email: + type: string + from_name: + type: string + from_phone: + type: string + has_attachments: type: boolean - limit: - format: int64 - type: integer - offset: + id: format: int64 type: integer - required: - - count - - has_more - - offset - - limit - - conversations - type: object - TextMessagesResponse: - properties: - count: + labels: + items: + type: string + nullable: true + type: array + message_type: + type: string + sent_at: + type: string + size_bytes: format: int64 type: integer - has_more: - type: boolean - limit: + snippet: + type: string + source_id: format: int64 type: integer - messages: + source_message_id: + type: string + subject: + type: string + to: items: - $ref: "#/components/schemas/CLIQueryMessageSummary" + type: string nullable: true type: array - offset: + required: + - id + - subject + - from + - to + - sent_at + - snippet + - labels + - has_attachments + - size_bytes + type: object + MutationResult: + properties: + name: + type: string + source_count: format: int64 type: integer required: - - count - - has_more - - offset - - limit - - messages + - name type: object - TokenUploadRequest: - additionalProperties: false + OperationHealth: properties: - access_token: - type: string - client_id: + busy: + type: boolean + label: type: string - expiry: + started_at: format: date-time type: string - refresh_token: - type: string - scopes: - items: - type: string - nullable: true - type: array - tenant_id: - type: string - token_type: - type: string required: - - refresh_token + - busy type: object - TotalStatsResponse: + PatchSavedViewRequest: + additionalProperties: false properties: - account_count: + canonical_state: + $ref: "#/components/schemas/SavedViewStateEnvelope" + description: + type: string + name: + type: string + schema_version: format: int64 type: integer - active_messages: + type: object + PersonCluster: + properties: + canonical_id: format: int64 type: integer - applied_search_scope: - type: boolean - applied_source_ids: + edges: + items: + $ref: "#/components/schemas/PersonClusterEdge" + nullable: true + type: array + member_ids: items: format: int64 type: integer nullable: true type: array - attachment_count: - format: int64 - type: integer - attachment_size: - format: int64 - type: integer - label_count: - format: int64 - type: integer - message_count: - format: int64 - type: integer - source_deleted_messages: + required: + - canonical_id + - member_ids + - edges + type: object + PersonClusterEdge: + properties: + participant_a: format: int64 type: integer - total_size: + participant_b: format: int64 type: integer required: - - message_count - - active_messages - - source_deleted_messages - - total_size - - attachment_count - - attachment_size - - label_count - - account_count + - participant_a + - participant_b type: object - UpdateRequest: - additionalProperties: false + PersonContextSummaryHTTPResponse: properties: - display_name: + cache_revision: type: string - email: + candidate_snapshot_id: type: string + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + summary: + $ref: "#/components/schemas/PersonSummary" required: - - email - - display_name + - summary + - cache_revision + - search_provenance type: object - UpdateResult: + PersonIdentifier: properties: - display_name: + display_value: type: string - email: + is_primary: + type: boolean + participant_id: + format: int64 + type: integer + provenance: + type: string + type: + type: string + value: type: string required: - - email - - display_name + - type + - value + - is_primary + - provenance + - participant_id type: object - VectorHealth: + PersonSearchHTTPResponse: properties: - error: + cache_revision: type: string - status: + candidate_snapshot_id: + type: string + next_cursor: type: string + rows: + items: + $ref: "#/components/schemas/PersonSummary" + nullable: true + type: array + search_provenance: + $ref: "#/components/schemas/SearchProvenance" + total_count: + format: int64 + type: integer required: - - status + - rows + - total_count + - cache_revision + - search_provenance type: object - securitySchemes: - apiKey: - in: header - name: X-Api-Key - type: apiKey -info: + PersonSummary: + properties: + activity_count: + format: int64 + type: integer + cache_revision: + type: string + cluster: + $ref: "#/components/schemas/PersonCluster" + display_label: + type: string + display_name: + type: string + file_count: + format: int64 + type: integer + first_at: + format: date-time + type: string + id: + format: int64 + type: integer + identifiers: + items: + $ref: "#/components/schemas/PersonIdentifier" + nullable: true + type: array + last_at: + format: date-time + type: string + partial_label: + type: boolean + source_counts: + items: + $ref: "#/components/schemas/SourceCount" + nullable: true + type: array + required: + - id + - display_label + - partial_label + - identifiers + - activity_count + - file_count + - source_counts + - first_at + - last_at + - cache_revision + type: object + PingInfo: + properties: + ok: + type: boolean + pid: + format: int64 + type: integer + service: + type: string + version: + type: string + required: + - ok + type: object + Progress: + properties: + done: + format: int64 + type: integer + total: + format: int64 + type: integer + required: + - done + - total + type: object + QueryRequest: + additionalProperties: false + properties: + sql: + type: string + required: + - sql + type: object + QueryResult: + properties: + columns: + items: + type: string + nullable: true + type: array + row_count: + format: int64 + type: integer + rows: + items: + items: + x-go-type: any + nullable: true + type: array + nullable: true + type: array + required: + - columns + - rows + - row_count + type: object + RelationshipRow: + properties: + canonical_id: + format: int64 + type: integer + display_label: + type: string + last_at: + format: date-time + type: string + member_ids: + items: + format: int64 + type: integer + nullable: true + type: array + score: + format: double + type: number + signals: + $ref: "#/components/schemas/RelationshipSignals" + required: + - canonical_id + - display_label + - member_ids + - score + - signals + - last_at + type: object + RelationshipSignals: + properties: + last_interaction_at: + format: date-time + type: string + meeting_count: + format: int64 + type: integer + meetings_together: + format: double + type: number + modalities: + format: int64 + type: integer + received_from_them: + format: double + type: number + sent_count: + format: int64 + type: integer + sent_to_them: + format: double + type: number + required: + - sent_to_them + - received_from_them + - meetings_together + - sent_count + - meeting_count + - modalities + - last_interaction_at + type: object + RelationshipTimelineHTTPRequest: + additionalProperties: false + properties: + cursor: + type: string + filters: + items: + $ref: "#/components/schemas/ExploreFilter" + nullable: true + type: array + limit: + format: int64 + maximum: 500 + minimum: 0 + type: integer + timezone: + type: string + type: object + RelationshipTimelineHTTPResponse: + properties: + cache_revision: + type: string + canonical_id: + format: int64 + type: integer + identity_revision: + format: int64 + type: integer + next_cursor: + type: string + rows: + items: + $ref: "#/components/schemas/TimelineRow" + nullable: true + type: array + total_count: + format: int64 + type: integer + required: + - canonical_id + - rows + - total_count + - cache_revision + - identity_revision + type: object + RelationshipsHTTPRequest: + additionalProperties: false + properties: + cursor: + type: string + filters: + items: + $ref: "#/components/schemas/ExploreFilter" + nullable: true + type: array + limit: + format: int64 + maximum: 500 + minimum: 0 + type: integer + show_all: + type: boolean + type: object + RelationshipsHTTPResponse: + properties: + cache_revision: + type: string + identity_revision: + format: int64 + type: integer + next_cursor: + type: string + rows: + items: + $ref: "#/components/schemas/RelationshipRow" + nullable: true + type: array + total_count: + format: int64 + type: integer + required: + - rows + - total_count + - cache_revision + - identity_revision + type: object + RemoveRequest: + additionalProperties: false + properties: + account: + type: string + identifier: + type: string + required: + - account + - identifier + type: object + RemoveResult: + properties: + account: + type: string + cache_state: + enum: + - ready + - stale + type: string + identifier: + type: string + no_identity: + type: boolean + removed: + format: int64 + type: integer + required: + - account + - identifier + - removed + type: object + SavedView: + properties: + canonical_state: + $ref: "#/components/schemas/SavedViewStateEnvelope" + created_at: + format: date-time + type: string + description: + type: string + id: + format: int64 + type: integer + name: + type: string + revision: + format: int64 + type: integer + schema_version: + format: int64 + type: integer + updated_at: + format: date-time + type: string + required: + - id + - name + - canonical_state + - schema_version + - revision + - created_at + - updated_at + type: object + SavedViewFilter: + additionalProperties: false + properties: + field: + type: string + operator: + type: string + values: + description: Exact filter values; numeric identifiers use decimal strings + items: + type: string + type: array + required: + - field + - operator + - values + type: object + SavedViewSort: + additionalProperties: false + properties: + direction: + enum: + - asc + - desc + type: string + field: + type: string + required: + - field + - direction + type: object + SavedViewStateEnvelope: + additionalProperties: false + properties: + columns: + items: + type: string + type: array + filters: + items: + $ref: "#/components/schemas/SavedViewFilter" + type: array + grouping: + items: + type: string + type: array + inspector_pinned: + type: boolean + presentation: + enum: + - table + - timeline + - files + type: string + query: + type: string + search_mode: + type: string + sort: + items: + $ref: "#/components/schemas/SavedViewSort" + type: array + type: object + SavedViewsResponse: + properties: + saved_views: + items: + $ref: "#/components/schemas/SavedView" + nullable: true + type: array + required: + - saved_views + type: object + SchedulerStatusResponse: + properties: + accounts: + items: + $ref: "#/components/schemas/AccountStatus" + nullable: true + type: array + running: + type: boolean + required: + - running + - accounts + type: object + ScoreBreakdown: + properties: + bm25: + format: double + type: number + rrf: + format: double + type: number + subject_boosted: + type: boolean + vector: + format: double + type: number + type: object + SearchCoverageRequest: + additionalProperties: false + properties: + filters: + items: + $ref: "#/components/schemas/ExploreFilter" + nullable: true + type: array + type: object + SearchCoverageResponse: + properties: + actions: + items: + enum: + - retry + - build_index + type: string + type: array + cache_revision: + type: string + detail: + type: string + eligible_count: + format: int64 + type: integer + embedded_count: + format: int64 + type: integer + percentage: + format: double + type: number + status: + enum: + - disabled + - initializing + - stale + - incomplete + - unavailable + - ready + type: string + vector_fingerprint: + type: string + vector_generation: + format: int64 + type: integer + required: + - eligible_count + - embedded_count + - percentage + - cache_revision + - status + - actions + type: object + SearchFastResponse: + properties: + applied_source_ids: + items: + format: int64 + type: integer + nullable: true + type: array + messages: + items: + $ref: "#/components/schemas/MessageSummary" + nullable: true + type: array + query: + type: string + stats: + $ref: "#/components/schemas/TotalStatsResponse" + total_count: + format: int64 + type: integer + required: + - query + - messages + - total_count + type: object + SearchProvenance: + additionalProperties: false + properties: + lexical_index_revision: + type: string + vector_generation: + format: int64 + type: integer + type: object + SearchResult: + properties: + messages: + items: + $ref: "#/components/schemas/MessageSummary" + nullable: true + type: array + page: + format: int64 + type: integer + page_size: + format: int64 + type: integer + query: + type: string + total: + format: int64 + type: integer + required: + - query + - total + - page + - page_size + - messages + type: object + SecretSettingState: + properties: + configured: + type: boolean + required: + - configured + type: object + SecretSettingUpdate: + additionalProperties: false + properties: + action: + enum: + - set + - clear + type: string + value: + type: string + required: + - action + type: object + SenderCount: + additionalProperties: false + properties: + count: + format: int64 + type: integer + sender: + type: string + required: + - sender + - count + type: object + SessionLoginRequest: + additionalProperties: false + properties: + api_key: + type: string + required: + - api_key + type: object + SessionStatus: + properties: + auth_mode: + enum: + - loopback + - api_key + - session + - required + type: string + csrf_token: + type: string + https: + type: boolean + plain_http_warning: + type: boolean + required: + - auth_mode + - https + - plain_http_warning + type: object + Setting: + properties: + group: + enum: + - browser + - server + - archive + - search + - sources + - integrations + type: string + key: + type: string + kind: + enum: + - string + - integer + - number + - boolean + - string_array + - secret + type: string + options: + items: + type: string + nullable: true + type: array + restart_required: + type: boolean + secret: + $ref: "#/components/schemas/SecretSettingState" + testable: + type: boolean + value: + $ref: "#/components/schemas/SettingValue" + required: + - key + - group + - kind + - restart_required + type: object + SettingUpdate: + additionalProperties: false + properties: + key: + type: string + secret: + $ref: "#/components/schemas/SecretSettingUpdate" + value: + $ref: "#/components/schemas/SettingValue" + required: + - key + type: object + SettingValue: + oneOf: + - additionalProperties: false + properties: + string: + type: string + required: + - string + type: object + - additionalProperties: false + properties: + integer: + format: int64 + type: integer + required: + - integer + type: object + - additionalProperties: false + properties: + number: + format: double + type: number + required: + - number + type: object + - additionalProperties: false + properties: + boolean: + type: boolean + required: + - boolean + type: object + - additionalProperties: false + properties: + strings: + items: + type: string + type: array + required: + - strings + type: object + SettingsPatchRequest: + additionalProperties: false + properties: + confirm_api_key_restart: + type: boolean + updates: + items: + $ref: "#/components/schemas/SettingUpdate" + minItems: 1 + type: array + required: + - updates + type: object + SettingsResponse: + properties: + pending_restart: + type: boolean + settings: + items: + $ref: "#/components/schemas/Setting" + type: array + required: + - settings + - pending_restart + type: object + SimilarSearchResponse: + properties: + generation: + $ref: "#/components/schemas/HybridGenerationSummary" + messages: + items: + $ref: "#/components/schemas/MessageSummary" + nullable: true + type: array + returned: + format: int64 + type: integer + seed_message_id: + format: int64 + type: integer + required: + - seed_message_id + - returned + - generation + - messages + type: object + SourceCount: + properties: + count: + format: int64 + type: integer + source_type: + type: string + required: + - source_type + - count + type: object + SourceStatus: + properties: + active_sync: + $ref: "#/components/schemas/SyncRunStatus" + nullable: true + type: "" + can_sync: + type: boolean + display_name: + nullable: true + type: string + id: + format: int64 + type: integer + identifier: + type: string + last_successful_sync: + $ref: "#/components/schemas/SyncRunStatus" + nullable: true + type: "" + last_sync_at: + nullable: true + type: string + latest_sync: + $ref: "#/components/schemas/SyncRunStatus" + nullable: true + type: "" + next_sync_at: + nullable: true + type: string + schedule: + type: string + scheduled: + type: boolean + scheduler_last_error: + type: string + source_type: + type: string + sync_unavailable_reason: + type: string + updated_at: + type: string + required: + - id + - source_type + - identifier + - display_name + - last_sync_at + - updated_at + - can_sync + - scheduled + - next_sync_at + type: object + SourceStatusResponse: + properties: + sources: + items: + $ref: "#/components/schemas/SourceStatus" + nullable: true + type: array + required: + - sources + type: object + SourcesRequest: + additionalProperties: false + properties: + accounts: + items: + type: string + nullable: true + type: array + required: + - accounts + type: object + StageDeletionFilter: + additionalProperties: false + properties: + after: + type: string + before: + type: string + domain: + type: string + label: + type: string + recipient: + type: string + recipient_name: + type: string + sender: + type: string + sender_name: + type: string + source_id: + format: int64 + type: integer + type: object + StageDeletionRequest: + additionalProperties: false + properties: + description: + type: string + dry_run: + type: boolean + filter: + $ref: "#/components/schemas/StageDeletionFilter" + message_ids: + items: + format: int64 + type: integer + nullable: true + type: array + operation_token: + type: string + selection: + $ref: "#/components/schemas/ExploreSelection" + type: object + StageDeletionResponse: + properties: + account: + type: string + dry_run: + type: boolean + id: + type: string + message_count: + format: int64 + type: integer + sample_gmail_ids: + items: + type: string + nullable: true + type: array + status: + type: string + required: + - dry_run + - message_count + type: object + StatsResponse: + properties: + active_messages: + format: int64 + type: integer + database_size_bytes: + format: int64 + type: integer + source_deleted_messages: + format: int64 + type: integer + total_accounts: + format: int64 + type: integer + total_attachments: + format: int64 + type: integer + total_labels: + format: int64 + type: integer + total_messages: + format: int64 + type: integer + total_threads: + format: int64 + type: integer + vector_search: + $ref: "#/components/schemas/StatsView" + vector_status: + type: string + required: + - total_messages + - active_messages + - source_deleted_messages + - total_threads + - total_accounts + - total_labels + - total_attachments + - database_size_bytes + type: object + StatsView: + properties: + active_generation: + $ref: "#/components/schemas/GenerationSummary" + building_generation: + $ref: "#/components/schemas/BuildingSummary" + enabled: + type: boolean + missing_embeddings_total: + format: int64 + type: integer + required: + - enabled + - active_generation + - missing_embeddings_total + type: object + StatusMessageResponse: + properties: + message: + type: string + status: + type: string + required: + - status + - message + type: object + Summary: + additionalProperties: false + properties: + accounts: + items: + type: string + nullable: true + type: array + date_range: + items: + type: string + maxItems: 2 + minItems: 2 + nullable: true + type: array + message_count: + format: int64 + type: integer + top_senders: + items: + $ref: "#/components/schemas/SenderCount" + nullable: true + type: array + total_size_bytes: + format: int64 + type: integer + required: + - message_count + - total_size_bytes + - date_range + - accounts + - top_senders + type: object + SyncRunItemStatus: + properties: + created_at: + type: string + error_kind: + type: string + error_message: + type: string + phase: + type: string + source_message_id: + type: string + required: + - source_message_id + - phase + - error_kind + - error_message + - created_at + type: object + SyncRunStatus: + properties: + completed_at: + nullable: true + type: string + cursor_after: + nullable: true + type: string + cursor_before: + nullable: true + type: string + error_message: + nullable: true + type: string + errors_count: + format: int64 + type: integer + id: + format: int64 + type: integer + item_errors: + items: + $ref: "#/components/schemas/SyncRunItemStatus" + nullable: true + type: array + messages_added: + format: int64 + type: integer + messages_processed: + format: int64 + type: integer + messages_updated: + format: int64 + type: integer + skipped_count: + format: int64 + type: integer + source_id: + format: int64 + type: integer + started_at: + type: string + status: + type: string + required: + - id + - source_id + - started_at + - completed_at + - status + - messages_processed + - messages_added + - messages_updated + - errors_count + - error_message + - cursor_before + - cursor_after + type: object + TaskIntegrationStatusResponse: + properties: + message: + description: Credential-free status explanation + type: string + project: + description: Configured project when available + type: string + security_note: + description: Platform-specific local socket security limitation + type: string + state: + description: Current task integration state + enum: + - disabled + - not_found + - authentication_required + - unreachable + - incompatible + - wrong_project + - ready + type: string + required: + - state + - message + type: object + TaskLinkLookupResponse: + properties: + complete: + type: boolean + last_scan: + format: date-time + type: string + outbound_metadata: + $ref: "#/components/schemas/TaskLinkOutboundMetadata" + reason: + type: string + remote_revision: + type: string + state: + type: string + tasks: + items: + $ref: "#/components/schemas/TaskSummary" + type: array + required: + - tasks + - outbound_metadata + - state + - complete + - last_scan + type: object + TaskLinkMutationRequest: + additionalProperties: false + properties: + added_at: + description: Browser-generated retry-stable RFC3339 timestamp for the outbound mail link + type: string + description: + type: string + labels: + items: + type: string + nullable: true + type: array + priority: + type: string + task_id: + description: Existing task to link; omit to create a task + type: string + title: + type: string + required: + - added_at + type: object + TaskLinkMutationResponse: + properties: + task: + $ref: "#/components/schemas/TaskLinkTask" + required: + - task + type: object + TaskLinkOutboundMetadata: + properties: + archive_uid: + type: string + conversation_id: + format: int64 + type: integer + from: + type: string + message_id: + format: int64 + type: integer + sent_at: + type: string + source_identifier: + type: string + source_message_id: + type: string + source_type: + type: string + subject: + type: string + required: + - archive_uid + - message_id + - conversation_id + - subject + - from + - sent_at + - source_type + - source_identifier + - source_message_id + type: object + TaskLinkTask: + properties: + id: + type: string + project: + type: string + revision: + type: string + title: + type: string + required: + - id + - project + - title + type: object + TaskSearchResponse: + properties: + tasks: + items: + $ref: "#/components/schemas/TaskSummary" + nullable: true + type: array + required: + - tasks + type: object + TaskSummary: + properties: + id: + type: string + revision: + type: string + title: + type: string + required: + - id + - title + type: object + TextConversationRow: + properties: + conversation_id: + format: int64 + type: integer + last_message_at: + type: string + last_preview: + type: string + message_count: + format: int64 + type: integer + participant_count: + format: int64 + type: integer + source_type: + type: string + title: + type: string + required: + - conversation_id + - title + - source_type + - message_count + - participant_count + - last_preview + type: object + TextConversationsResponse: + properties: + conversations: + items: + $ref: "#/components/schemas/TextConversationRow" + nullable: true + type: array + count: + format: int64 + type: integer + has_more: + type: boolean + limit: + format: int64 + type: integer + offset: + format: int64 + type: integer + required: + - count + - has_more + - offset + - limit + - conversations + type: object + TextMessagesResponse: + properties: + count: + format: int64 + type: integer + has_more: + type: boolean + limit: + format: int64 + type: integer + messages: + items: + $ref: "#/components/schemas/CLIQueryMessageSummary" + nullable: true + type: array + offset: + format: int64 + type: integer + required: + - count + - has_more + - offset + - limit + - messages + type: object + TimelineRow: + properties: + anchor_message_id: + format: int64 + type: integer + conversation_id: + format: int64 + type: integer + first_at: + format: date-time + type: string + has_attachments: + type: boolean + key: + type: string + kind: + type: string + message_count: + format: int64 + type: integer + occurred_at: + format: date-time + type: string + preview: + type: string + source_id: + format: int64 + type: integer + title: + type: string + required: + - key + - kind + - title + - preview + - occurred_at + - message_count + - source_id + - has_attachments + type: object + TokenUploadRequest: + additionalProperties: false + properties: + access_token: + type: string + client_id: + type: string + expiry: + format: date-time + type: string + refresh_token: + type: string + scopes: + items: + type: string + nullable: true + type: array + tenant_id: + type: string + token_type: + type: string + required: + - refresh_token + type: object + TotalStatsResponse: + properties: + account_count: + format: int64 + type: integer + active_messages: + format: int64 + type: integer + applied_search_scope: + type: boolean + applied_source_ids: + items: + format: int64 + type: integer + nullable: true + type: array + attachment_count: + format: int64 + type: integer + attachment_size: + format: int64 + type: integer + label_count: + format: int64 + type: integer + message_count: + format: int64 + type: integer + source_deleted_messages: + format: int64 + type: integer + total_size: + format: int64 + type: integer + required: + - message_count + - active_messages + - source_deleted_messages + - total_size + - attachment_count + - attachment_size + - label_count + - account_count + type: object + UpdateRequest: + additionalProperties: false + properties: + display_name: + type: string + email: + type: string + required: + - email + - display_name + type: object + UpdateResult: + properties: + display_name: + type: string + email: + type: string + required: + - email + - display_name + type: object + VectorHealth: + properties: + error: + type: string + status: + type: string + required: + - status + type: object + securitySchemes: + apiKey: + in: header + name: X-Api-Key + type: apiKey +info: title: msgvault API - version: 1.5.0 + version: 1.24.0 openapi: 3.0.3 paths: /api/ping: get: - operationId: daemonPing + operationId: daemonPing + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/PingInfo" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + summary: Daemon discovery ping + tags: + - Daemon + /api/session: + delete: + operationId: logoutSession + responses: + "204": + description: No Content + "429": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + summary: Delete the current browser session + tags: + - Session + get: + operationId: getSession + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SessionStatus" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + summary: Get browser authentication status + tags: + - Session + /api/session/login: + post: + operationId: loginSession + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SessionLoginRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SessionStatus" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + summary: Create an in-memory browser session + tags: + - Session + /api/v1/accounts: + get: + operationId: listAccounts + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/AccountListResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: List scheduler-configured accounts (with sync schedules); use /cli/accounts for all archived sources + tags: + - API + post: + operationId: addAccount + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/AddAccountRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/StatusMessageResponse" + description: OK + "201": + content: + application/json: + schema: + $ref: "#/components/schemas/StatusMessageResponse" + description: Created + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Add an account + tags: + - API + /api/v1/aggregates: + get: + operationId: getAggregates + parameters: + - description: Aggregate view type + in: query + name: view_type + schema: + type: string + - description: "Sort field: count, size, attachment_size, or name" + in: query + name: sort + schema: + type: string + - description: "Sort direction: asc or desc" + in: query + name: direction + schema: + type: string + - description: Maximum number of rows to return (default 100; values below 1 fall back to the default) + in: query + name: limit + schema: + format: int64 + type: integer + - description: Time bucket granularity + in: query + name: time_granularity + schema: + type: string + - description: Source ID + in: query + name: source_id + schema: + format: int64 + type: integer + - description: Only include messages with attachments + in: query + name: attachments_only + schema: + type: boolean + - description: Exclude deleted messages + in: query + name: hide_deleted + schema: + type: boolean + - description: Search query + in: query + name: search_query + schema: + type: string + - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: after + schema: + type: string + - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: before + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/AggregateResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get aggregate rows + tags: + - API + /api/v1/aggregates/sub: + get: + operationId: getSubAggregates + parameters: + - description: Aggregate view type + in: query + name: view_type + required: true + schema: + type: string + - description: "Sort field: count, size, attachment_size, or name" + in: query + name: sort + schema: + type: string + - description: "Sort direction: asc or desc" + in: query + name: direction + schema: + type: string + - description: Maximum number of rows to return (default 100; values below 1 fall back to the default) + in: query + name: limit + schema: + format: int64 + type: integer + - description: Time bucket granularity + in: query + name: time_granularity + schema: + type: string + - description: Source ID + in: query + name: source_id + schema: + format: int64 + type: integer + - description: Only include messages with attachments + in: query + name: attachments_only + schema: + type: boolean + - description: Exclude deleted messages + in: query + name: hide_deleted + schema: + type: boolean + - description: Search query + in: query + name: search_query + schema: + type: string + - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: after + schema: + type: string + - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: before + schema: + type: string + - description: Sender email/address filter + in: query + name: sender + schema: + type: string + - description: Sender display-name filter + in: query + name: sender_name + schema: + type: string + - description: Recipient email/address filter + in: query + name: recipient + schema: + type: string + - description: Recipient display-name filter + in: query + name: recipient_name + schema: + type: string + - description: Domain filter + in: query + name: domain + schema: + type: string + - description: Label filter + in: query + name: label + schema: + type: string + - description: Message type filter + in: query + name: message_type + schema: + type: string + - description: Named time period + in: query + name: time_period + schema: + type: string + - description: Conversation ID + in: query + name: conversation_id + schema: + format: int64 + type: integer + - description: Comma-separated aggregate view names to match empty values + in: query + name: empty_targets + schema: + type: string + - description: Zero-based row offset + in: query + name: offset + schema: + format: int64 + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/AggregateResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get nested aggregate rows + tags: + - API + /api/v1/attachments/{hash}/content: + get: + operationId: getAttachmentContent + parameters: + - description: Attachment SHA-256 content hash + in: path + name: hash + required: true + schema: + type: string + responses: + "200": + content: + "*/*": + schema: + format: binary + type: string + x-contentMediaType: application/octet-stream + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Download attachment content by SHA-256 hash + tags: + - API + /api/v1/attachments/{id}: + get: + operationId: getAttachment + parameters: + - description: Attachment ID + in: path + name: id + required: true + schema: + format: int64 + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/AttachmentInfo" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get attachment metadata + tags: + - API + /api/v1/auth/token/{email}: + post: + operationId: uploadToken + parameters: + - description: Account email address + in: path + name: email + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/TokenUploadRequest" + required: true + responses: + "201": + content: + application/json: + schema: + $ref: "#/components/schemas/StatusMessageResponse" + description: Created + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Upload an OAuth token + tags: + - API + /api/v1/backup/freeze/begin: + post: + operationId: beginBackupFreeze + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/BackupFreezeBeginResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Begin a backup freeze window + tags: + - API + /api/v1/backup/freeze/end: + post: + operationId: endBackupFreeze + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/BackupFreezeEndRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/BackupFreezeEndResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: End a backup freeze window + tags: + - API + /api/v1/cli/account: + post: + operationId: updateCLIAccount + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Not Found + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Update an account for CLI use + tags: + - CLI + /api/v1/cli/accounts: + get: + operationId: listCLIAccounts + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliAccountsResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: List accounts for CLI output + tags: + - API + /api/v1/cli/add-calendar/plan: + post: + operationId: planCLIAddCalendar + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CLIAddCalendarPlanRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CLIAddCalendarPlanResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Plan CLI Calendar account setup + tags: + - API + /api/v1/cli/attachment: + get: + operationId: getCLIAttachment + parameters: + - description: Attachment SHA-256 content hash + in: query + name: content_hash + required: true + schema: + type: string + responses: + "200": + content: + application/octet-stream: + schema: + format: binary + type: string + x-contentMediaType: application/octet-stream + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get one attachment for CLI export + tags: + - API + /api/v1/cli/build-cache: + post: + operationId: buildCLICache + parameters: + - description: Rebuild all cache files from scratch + in: query + name: full_rebuild + schema: + type: boolean + responses: + "200": + content: + application/x-ndjson: + schema: + $ref: "#/components/schemas/CLICacheBuildEvent" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Build the CLI analytics cache + tags: + - API + /api/v1/cli/cache-stats: + get: + operationId: getCLICacheStats + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CacheStats" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get CLI-compatible analytics cache statistics + tags: + - API + /api/v1/cli/collection: + get: + operationId: getCLICollection + parameters: + - description: Collection name + in: query + name: name + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliCollectionEnvelope" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get one collection for CLI output + tags: + - API + /api/v1/cli/collections: + get: + operationId: listCLICollections + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliCollectionsResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: List collections for CLI output + tags: + - API + post: + operationId: createCLICollection + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/MutationResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Not Found + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Create a collection for CLI use + tags: + - CLI + /api/v1/cli/collections/{name}: + delete: + operationId: deleteCLICollection + parameters: + - description: Collection name + in: path + name: name + required: true + schema: + description: Collection name + type: string + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/MutationResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Not Found + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Delete a CLI collection + tags: + - CLI + /api/v1/cli/collections/{name}/sources: + delete: + operationId: removeCLICollectionSources + parameters: + - description: Collection name + in: path + name: name + required: true + schema: + description: Collection name + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SourcesRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/MutationResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Not Found + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Remove accounts from a CLI collection + tags: + - CLI + patch: + operationId: addCLICollectionSources + parameters: + - description: Collection name + in: path + name: name + required: true + schema: + description: Collection name + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SourcesRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/MutationResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Not Found + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Add accounts to a CLI collection + tags: + - CLI + /api/v1/cli/deduplicate/plan: + post: + operationId: planCLIDeduplicate + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CLIDeduplicatePlanRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CLIDeduplicatePlanResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Plan interactive deduplication for CLI use + tags: + - CLI + /api/v1/cli/delete-deduped: + post: + operationId: executeCLIDeleteDeduped + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CliDeleteDedupedExecuteRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliDeleteDedupedExecuteResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Conflict + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Delete dedup-hidden messages for CLI use + tags: + - CLI + /api/v1/cli/delete-deduped/plan: + post: + operationId: planCLIDeleteDeduped + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CliDeleteDedupedPlanRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliDeleteDedupedPlanResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Plan deletion of dedup-hidden messages for CLI use + tags: + - CLI + /api/v1/cli/delete-staged/plan: + post: + operationId: planCLIDeleteStaged + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CLIDeleteStagedPlanRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CLIDeleteStagedPlanResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Plan CLI staged deletion execution + tags: + - API + /api/v1/cli/deletion-manifests: + post: + operationId: createCLIDeletionManifest + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Manifest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CLIDeletionManifestResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Create a staged deletion manifest + tags: + - API + /api/v1/cli/embeddings/plan: + post: + operationId: planCLIEmbeddings + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CLIEmbeddingsPlanRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CLIEmbeddingsPlanResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Plan CLI embeddings management + tags: + - API + /api/v1/cli/identities: + delete: + operationId: removeCLIIdentity + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/RemoveRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/RemoveResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Remove a confirmed identifier from an account identity + tags: + - CLI + get: + operationId: listCLIIdentities + parameters: + - description: Restrict to a single account + explode: false + in: query + name: account + schema: + description: Restrict to a single account + type: string + - description: Restrict to all member accounts of one collection + explode: false + in: query + name: collection + schema: + description: Restrict to all member accounts of one collection + type: string + - description: For account scope, return only the primary source instead of related sources + explode: false + in: query + name: primary_only + schema: + description: For account scope, return only the primary source instead of related sources + type: boolean + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliIdentitiesResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: List confirmed account identities + tags: + - CLI + post: + operationId: addCLIIdentity + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/AddRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/AddResult" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Bad Request + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Unprocessable Entity + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Internal Server Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ApiHTTPError" + description: Service Unavailable + security: + - apiKey: [] + summary: Add a confirmed identifier to an account identity + tags: + - CLI + /api/v1/cli/init-db: + post: + operationId: initCLIArchive + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliInitDBResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Initialize the archive for CLI use + tags: + - API + /api/v1/cli/message: + get: + operationId: getCLIMessage + parameters: + - description: Message numeric ID or source message ID + in: query + name: id + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliMessageResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get one message for CLI output + tags: + - API + /api/v1/cli/message/raw: + get: + operationId: getCLIMessageRaw + parameters: + - description: Message numeric ID or source message ID + in: query + name: id + required: true + schema: + type: string + responses: + "200": + content: + message/rfc822: + schema: + format: binary + type: string + x-contentMediaType: application/octet-stream + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "401": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "500": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get one raw message for CLI export + tags: + - API + /api/v1/cli/rebuild-fts: + post: + operationId: rebuildCLIFTS responses: "200": content: - application/json: + application/x-ndjson: schema: - $ref: "#/components/schemas/PingInfo" + $ref: "#/components/schemas/CliRebuildFTSEvent" description: OK default: content: @@ -2321,18 +5746,20 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - summary: Daemon discovery ping + security: + - apiKey: [] + summary: Rebuild the CLI full-text search index tags: - - Daemon - /api/v1/accounts: - get: - operationId: listAccounts + - API + /api/v1/cli/repair-encoding: + post: + operationId: repairEncodingCLI responses: "200": content: - application/json: + application/x-ndjson: schema: - $ref: "#/components/schemas/AccountListResponse" + $ref: "#/components/schemas/CLIRepairEncodingEvent" description: OK default: content: @@ -2342,30 +5769,25 @@ paths: description: Error security: - apiKey: [] - summary: List scheduler-configured accounts (with sync schedules); use /cli/accounts for all archived sources + summary: Repair CLI archive encoding tags: - API + /api/v1/cli/run: post: - operationId: addAccount + operationId: runCLI requestBody: content: application/json: schema: - $ref: "#/components/schemas/AddAccountRequest" + $ref: "#/components/schemas/CLIRunRequest" required: true responses: "200": content: - application/json: + application/x-ndjson: schema: - $ref: "#/components/schemas/StatusMessageResponse" + $ref: "#/components/schemas/CLIRunEvent" description: OK - "201": - content: - application/json: - schema: - $ref: "#/components/schemas/StatusMessageResponse" - description: Created default: content: application/json: @@ -2374,68 +5796,44 @@ paths: description: Error security: - apiKey: [] - summary: Add an account + summary: Run an allowlisted CLI command tags: - API - /api/v1/aggregates: + /api/v1/cli/search: get: - operationId: getAggregates + operationId: searchCLI parameters: - - description: Aggregate view type - in: query - name: view_type - schema: - type: string - - description: "Sort field: count, size, attachment_size, or name" - in: query - name: sort - schema: - type: string - - description: "Sort direction: asc or desc" + - description: Search query in: query - name: direction + name: q + required: true schema: type: string - - description: Maximum number of rows to return (default 100; values below 1 fall back to the default) + - description: Maximum number of rows to return in: query name: limit schema: format: int64 type: integer - - description: Time bucket granularity - in: query - name: time_granularity - schema: - type: string - - description: Source ID + - description: Zero-based row offset in: query - name: source_id + name: offset schema: format: int64 type: integer - - description: Only include messages with attachments - in: query - name: attachments_only - schema: - type: boolean - - description: Exclude deleted messages - in: query - name: hide_deleted - schema: - type: boolean - - description: Search query + - description: Message type filter; repeat or comma-separate for multiple values in: query - name: search_query + name: message_type schema: type: string - - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + - description: Restrict to one account/source in: query - name: after + name: account schema: type: string - - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + - description: Restrict to one collection in: query - name: before + name: collection schema: type: string responses: @@ -2443,7 +5841,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/AggregateResponse" + $ref: "#/components/schemas/CliSearchResponse" description: OK default: content: @@ -2453,134 +5851,153 @@ paths: description: Error security: - apiKey: [] - summary: Get aggregate rows + summary: Search messages for CLI output tags: - API - /api/v1/aggregates/sub: + /api/v1/cli/stats: get: - operationId: getSubAggregates + operationId: getCLIStats parameters: - - description: Aggregate view type + - description: Restrict to one account/source in: query - name: view_type - required: true + name: account schema: type: string - - description: "Sort field: count, size, attachment_size, or name" + - description: Restrict to one collection in: query - name: sort + name: collection schema: type: string - - description: "Sort direction: asc or desc" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/CliStatsResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get CLI-compatible archive statistics + tags: + - API + /api/v1/cli/sync: + post: + operationId: syncCLI + parameters: + - description: Account email or display name to sync in: query - name: direction + name: email schema: type: string - - description: Maximum number of rows to return (default 100; values below 1 fall back to the default) - in: query - name: limit - schema: - format: int64 - type: integer - - description: Time bucket granularity + responses: + "200": + content: + application/x-ndjson: + schema: + $ref: "#/components/schemas/CLISyncEvent" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Run CLI incremental sync + tags: + - API + /api/v1/cli/sync-full: + post: + operationId: syncFullCLI + parameters: + - description: Account email or display name to sync in: query - name: time_granularity + name: email schema: type: string - - description: Source ID - in: query - name: source_id - schema: - format: int64 - type: integer - - description: Only include messages with attachments - in: query - name: attachments_only - schema: - type: boolean - - description: Exclude deleted messages - in: query - name: hide_deleted - schema: - type: boolean - - description: Search query + - description: Gmail search query in: query - name: search_query + name: query schema: type: string - - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + - description: Only messages on or after this YYYY-MM-DD date in: query name: after schema: type: string - - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + - description: Only messages before this YYYY-MM-DD date in: query name: before schema: type: string - - description: Sender email/address filter - in: query - name: sender - schema: - type: string - - description: Sender display-name filter - in: query - name: sender_name - schema: - type: string - - description: Recipient email/address filter - in: query - name: recipient - schema: - type: string - - description: Recipient display-name filter - in: query - name: recipient_name - schema: - type: string - - description: Domain filter - in: query - name: domain - schema: - type: string - - description: Label filter + - description: Maximum messages to sync in: query - name: label + name: limit schema: - type: string - - description: Message type filter + format: int64 + type: integer + - description: Ignore checkpoints and start fresh in: query - name: message_type + name: noresume schema: - type: string - - description: Named time period + type: boolean + responses: + "200": + content: + application/x-ndjson: + schema: + $ref: "#/components/schemas/CLISyncEvent" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Run CLI full sync + tags: + - API + /api/v1/cli/verify: + post: + operationId: verifyCLI + parameters: + - description: Account email to verify in: query - name: time_period + name: email + required: true schema: type: string - - description: Conversation ID + - description: Number of messages to sample for MIME verification in: query - name: conversation_id + name: sample schema: format: int64 type: integer - - description: Comma-separated aggregate view names to match empty values + - description: Skip SQLite integrity check in: query - name: empty_targets + name: skip_db_check schema: - type: string - - description: Zero-based row offset + type: boolean + - description: Emit JSON summary output in: query - name: offset + name: json schema: - format: int64 - type: integer + type: boolean responses: "200": content: - application/json: + application/x-ndjson: schema: - $ref: "#/components/schemas/AggregateResponse" + $ref: "#/components/schemas/CLIVerifyEvent" description: OK default: content: @@ -2590,58 +6007,56 @@ paths: description: Error security: - apiKey: [] - summary: Get nested aggregate rows + summary: Verify the CLI archive against Gmail tags: - API - /api/v1/attachments/{hash}/content: + /api/v1/conversations/{id}: get: - operationId: getAttachmentContent + operationId: getConversation parameters: - - description: Attachment SHA-256 content hash + - description: Conversation ID in: path - name: hash + name: id + required: true + schema: + format: int64 + type: integer + - description: Selected message ID anchoring the chronological window + in: query + name: anchor required: true + schema: + format: int64 + type: integer + - description: Messages before the anchor (default 25, max 50) + in: query + name: before + schema: + format: int64 + type: integer + - description: Messages after the anchor (default 25, max 50) + in: query + name: after + schema: + format: int64 + type: integer + - description: Lower UTC bound, inclusive (RFC3339). Restricts the window, before/after counts, and has_before/has_after to messages in [start, end) + in: query + name: start + schema: + type: string + - description: Upper UTC bound, exclusive (RFC3339). Restricts the window, before/after counts, and has_before/has_after to messages in [start, end) + in: query + name: end schema: type: string responses: "200": - content: - "*/*": - schema: - format: binary - type: string - x-contentMediaType: application/octet-stream - description: OK - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "401": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "503": content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error + $ref: "#/components/schemas/ConversationResponse" + description: OK default: content: application/json: @@ -2650,26 +6065,24 @@ paths: description: Error security: - apiKey: [] - summary: Download attachment content by SHA-256 hash + summary: Get a bounded containing conversation tags: - API - /api/v1/attachments/{id}: + /api/v1/deletions: get: - operationId: getAttachment + operationId: listDeletions parameters: - - description: Attachment ID - in: path - name: id - required: true + - description: Filter manifests by status (pending, in_progress, completed, failed, cancelled) + in: query + name: status schema: - format: int64 - type: integer + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/AttachmentInfo" + $ref: "#/components/schemas/ListDeletionsResponse" description: OK default: content: @@ -2679,31 +6092,29 @@ paths: description: Error security: - apiKey: [] - summary: Get attachment metadata + summary: List staged deletion manifests tags: - API - /api/v1/auth/token/{email}: post: - operationId: uploadToken - parameters: - - description: Account email address - in: path - name: email - required: true - schema: - type: string + operationId: stageDeletion requestBody: content: application/json: schema: - $ref: "#/components/schemas/TokenUploadRequest" + $ref: "#/components/schemas/StageDeletionRequest" required: true responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/StageDeletionResponse" + description: OK "201": content: application/json: schema: - $ref: "#/components/schemas/StatusMessageResponse" + $ref: "#/components/schemas/StageDeletionResponse" description: Created default: content: @@ -2713,18 +6124,25 @@ paths: description: Error security: - apiKey: [] - summary: Upload an OAuth token + summary: Stage messages for deletion tags: - API - /api/v1/backup/freeze/begin: - post: - operationId: beginBackupFreeze + /api/v1/deletions/{id}: + delete: + operationId: cancelDeletion + parameters: + - description: Deletion manifest ID + in: path + name: id + required: true + schema: + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/BackupFreezeBeginResponse" + $ref: "#/components/schemas/CancelDeletionResponse" description: OK default: content: @@ -2734,24 +6152,24 @@ paths: description: Error security: - apiKey: [] - summary: Begin a backup freeze window + summary: Cancel a staged deletion manifest tags: - API - /api/v1/backup/freeze/end: - post: - operationId: endBackupFreeze - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/BackupFreezeEndRequest" - required: true + get: + operationId: getDeletion + parameters: + - description: Deletion manifest ID + in: path + name: id + required: true + schema: + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/BackupFreezeEndResponse" + $ref: "#/components/schemas/DeletionManifestDetail" description: OK default: content: @@ -2761,69 +6179,72 @@ paths: description: Error security: - apiKey: [] - summary: End a backup freeze window + summary: Inspect a staged deletion manifest tags: - API - /api/v1/cli/account: + /api/v1/domains/search: post: - operationId: updateCLIAccount + operationId: searchDomains requestBody: content: application/json: schema: - $ref: "#/components/schemas/UpdateRequest" + $ref: "#/components/schemas/IdentitySearchHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/UpdateResult" + $ref: "#/components/schemas/DomainSearchHTTPResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Not Found - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Update an account for CLI use + summary: Search analytical domains tags: - - CLI - /api/v1/cli/accounts: + - Exploration + /api/v1/domains/{domain}: get: - operationId: listCLIAccounts + operationId: getDomain + parameters: + - description: Exact normalized domain fact + in: path + name: domain + required: true + schema: + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliAccountsResponse" + $ref: "#/components/schemas/DomainSummary" description: OK default: content: @@ -2833,25 +6254,52 @@ paths: description: Error security: - apiKey: [] - summary: List accounts for CLI output + summary: Get one analytical domain tags: - API - /api/v1/cli/add-calendar/plan: + /api/v1/domains/{domain}/files/search: post: - operationId: planCLIAddCalendar + operationId: searchDomainFiles + parameters: + - description: Exact normalized domain fact + in: path + name: domain + required: true + schema: + type: string requestBody: content: application/json: schema: - $ref: "#/components/schemas/CLIAddCalendarPlanRequest" + $ref: "#/components/schemas/FileSearchHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CLIAddCalendarPlanResponse" + $ref: "#/components/schemas/FileSearchHTTPResponse" description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -2860,27 +6308,31 @@ paths: description: Error security: - apiKey: [] - summary: Plan CLI Calendar account setup + summary: Search one domain's analytical files tags: - - API - /api/v1/cli/attachment: - get: - operationId: getCLIAttachment + - Exploration + /api/v1/domains/{domain}/summary: + post: + operationId: getDomainContextSummary parameters: - - description: Attachment SHA-256 content hash - in: query - name: content_hash + - description: Exact normalized domain fact + in: path + name: domain required: true schema: type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreHTTPRequest" + required: true responses: "200": content: - application/octet-stream: + application/json: schema: - format: binary - type: string - x-contentMediaType: application/octet-stream + $ref: "#/components/schemas/DomainContextSummaryHTTPResponse" description: OK "400": content: @@ -2888,19 +6340,7 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - "401": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - "500": + "409": content: application/json: schema: @@ -2910,8 +6350,10 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -2920,46 +6362,52 @@ paths: description: Error security: - apiKey: [] - summary: Get one attachment for CLI export + summary: Get one domain's contextual analytical summary tags: - - API - /api/v1/cli/build-cache: + - Exploration + /api/v1/domains/{domain}/timeline: post: - operationId: buildCLICache + operationId: getDomainTimeline parameters: - - description: Rebuild all cache files from scratch - in: query - name: full_rebuild + - description: Exact normalized domain fact + in: path + name: domain + required: true schema: - type: boolean + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreHTTPRequest" + required: true responses: "200": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLICacheBuildEvent" + $ref: "#/components/schemas/ExploreHTTPResponse" description: OK - default: + "400": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - security: - - apiKey: [] - summary: Build the CLI analytics cache - tags: - - API - /api/v1/cli/cache-stats: - get: - operationId: getCLICacheStats - responses: - "200": + "409": content: application/json: schema: - $ref: "#/components/schemas/CacheStats" - description: OK + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -2968,47 +6416,45 @@ paths: description: Error security: - apiKey: [] - summary: Get CLI-compatible analytics cache statistics + summary: Get one domain's canonical activity timeline tags: - - API - /api/v1/cli/collection: - get: - operationId: getCLICollection - parameters: - - description: Collection name - in: query - name: name - required: true - schema: - type: string + - Exploration + /api/v1/explore: + post: + operationId: explore + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreHTTPRequest" + required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliCollectionEnvelope" + $ref: "#/components/schemas/ExploreHTTPResponse" description: OK - default: + "400": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - security: - - apiKey: [] - summary: Get one collection for CLI output - tags: - - API - /api/v1/cli/collections: - get: - operationId: listCLICollections - responses: - "200": + "409": content: application/json: schema: - $ref: "#/components/schemas/CliCollectionsResponse" - description: OK + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -3017,385 +6463,417 @@ paths: description: Error security: - apiKey: [] - summary: List collections for CLI output + summary: Explore canonical archive entries tags: - - API + - Exploration + /api/v1/explore/files: post: - operationId: createCLICollection + operationId: listExploreFiles requestBody: content: application/json: schema: - $ref: "#/components/schemas/CreateRequest" + $ref: "#/components/schemas/ExploreFilesHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/MutationResult" + $ref: "#/components/schemas/ExploreFilesHTTPResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Not Found - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Create a collection for CLI use + summary: List bounded chronological attachment facts tags: - - CLI - /api/v1/cli/collections/{name}: - delete: - operationId: deleteCLICollection - parameters: - - description: Collection name - in: path - name: name - required: true - schema: - description: Collection name - type: string + - Exploration + /api/v1/explore/groups: + post: + operationId: exploreGroups + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreGroupsHTTPRequest" + required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/MutationResult" + $ref: "#/components/schemas/ExploreGroupsHTTPResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Not Found - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Delete a CLI collection + summary: Group canonical archive entries tags: - - CLI - /api/v1/cli/collections/{name}/sources: - delete: - operationId: removeCLICollectionSources - parameters: - - description: Collection name - in: path - name: name - required: true - schema: - description: Collection name - type: string + - Exploration + /api/v1/explore/match-counts: + post: + operationId: countExploreMatches requestBody: content: application/json: schema: - $ref: "#/components/schemas/SourcesRequest" + $ref: "#/components/schemas/ExploreMatchCountsRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/MutationResult" + $ref: "#/components/schemas/ExploreMatchCountsResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Not Found - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Remove accounts from a CLI collection + summary: Count exact lexical matches in visible rows tags: - - CLI - patch: - operationId: addCLICollectionSources - parameters: - - description: Collection name - in: path - name: name - required: true - schema: - description: Collection name - type: string + - Exploration + /api/v1/explore/preflight: + post: + operationId: preflightExploreSelection requestBody: content: application/json: schema: - $ref: "#/components/schemas/SourcesRequest" + $ref: "#/components/schemas/ExplorePreflightRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/MutationResult" + $ref: "#/components/schemas/ExplorePreflightResponse" description: OK - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "404": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Not Found - "422": + "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error + $ref: "#/components/schemas/ErrorResponse" + description: Error "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" description: Service Unavailable + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Add accounts to a CLI collection + summary: Preflight a revision-pinned analytical selection tags: - - CLI - /api/v1/cli/deduplicate/plan: + - Exploration + /api/v1/files/groups: post: - operationId: planCLIDeduplicate + operationId: groupFiles requestBody: content: application/json: schema: - $ref: "#/components/schemas/CLIDeduplicatePlanRequest" + $ref: "#/components/schemas/FileGroupsHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CLIDeduplicatePlanResponse" + $ref: "#/components/schemas/FileGroupsHTTPResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Plan interactive deduplication for CLI use + summary: Group analytical files tags: - - CLI - /api/v1/cli/delete-deduped: + - Exploration + /api/v1/files/search: post: - operationId: executeCLIDeleteDeduped + operationId: searchFiles requestBody: content: application/json: schema: - $ref: "#/components/schemas/CliDeleteDedupedExecuteRequest" + $ref: "#/components/schemas/FileSearchHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliDeleteDedupedExecuteResponse" + $ref: "#/components/schemas/FileSearchHTTPResponse" description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request + $ref: "#/components/schemas/ErrorResponse" + description: Error "409": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Conflict - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Search analytical files + tags: + - Exploration + /api/v1/files/{id}: + get: + operationId: getFile + parameters: + - description: File attachment ID + in: path + name: id + required: true + schema: + format: int64 + type: integer + responses: + "200": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/FileMetadataResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Delete dedup-hidden messages for CLI use + summary: Get authoritative file metadata tags: - - CLI - /api/v1/cli/delete-deduped/plan: - post: - operationId: planCLIDeleteDeduped - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CliDeleteDedupedPlanRequest" - required: true + - API + /api/v1/files/{id}/content: + get: + operationId: getFileContent + parameters: + - description: File attachment ID + in: path + name: id + required: true + schema: + format: int64 + type: integer responses: "200": content: - application/json: + "*/*": schema: - $ref: "#/components/schemas/CliDeleteDedupedPlanResponse" + format: binary + type: string + x-contentMediaType: application/octet-stream description: OK "400": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "422": + $ref: "#/components/schemas/ErrorResponse" + description: Error + "401": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity + $ref: "#/components/schemas/ErrorResponse" + description: Error + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error "500": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error + $ref: "#/components/schemas/ErrorResponse" + description: Error "503": content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Plan deletion of dedup-hidden messages for CLI use + summary: Download content for one authoritative file row tags: - - CLI - /api/v1/cli/delete-staged/plan: + - API + /api/v1/health: + get: + operationId: getHealth + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/HealthResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get authenticated health details + tags: + - API + /api/v1/identity/links: post: - operationId: planCLIDeleteStaged + operationId: linkIdentityParticipants requestBody: content: application/json: schema: - $ref: "#/components/schemas/CLIDeleteStagedPlanRequest" + $ref: "#/components/schemas/IdentityLinkRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CLIDeleteStagedPlanResponse" + $ref: "#/components/schemas/IdentityLinkResponse" description: OK default: content: @@ -3405,24 +6883,24 @@ paths: description: Error security: - apiKey: [] - summary: Plan CLI staged deletion execution + summary: Assert two participants are the same person tags: - API - /api/v1/cli/deletion-manifests: + /api/v1/identity/unlinks: post: - operationId: createCLIDeletionManifest + operationId: unlinkIdentityParticipants requestBody: content: application/json: schema: - $ref: "#/components/schemas/Manifest" + $ref: "#/components/schemas/IdentityLinkRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CLIDeletionManifestResponse" + $ref: "#/components/schemas/IdentityLinkResponse" description: OK default: content: @@ -3432,24 +6910,101 @@ paths: description: Error security: - apiKey: [] - summary: Create a staged deletion manifest + summary: Remove a link edge between two participants tags: - API - /api/v1/cli/embeddings/plan: + /api/v1/integrations/tasks/search: + get: + operationId: searchIntegrationTasks + parameters: + - description: Task title search within the configured project + in: query + name: q + required: true + schema: + type: string + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/TaskSearchResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Search tasks in the configured project + tags: + - API + /api/v1/integrations/tasks/status: + get: + operationId: getTaskIntegrationStatus + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/TaskIntegrationStatusResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get task integration capability status + tags: + - API + /api/v1/integrations/tasks/test: post: - operationId: planCLIEmbeddings - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/CLIEmbeddingsPlanRequest" - required: true + operationId: testTaskIntegration + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/TaskIntegrationStatusResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Test task integration discovery, authentication, capabilities, and project + tags: + - API + /api/v1/messages: + get: + operationId: listMessages + parameters: + - description: One-based page number (default 1; values below 1 are clamped to 1). Non-numeric values are rejected with 400. + in: query + name: page + schema: + format: int64 + type: integer + - description: Page size (default 20, max 100; out-of-range values are clamped). Non-numeric values are rejected with 400. + in: query + name: page_size + schema: + format: int64 + type: integer responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CLIEmbeddingsPlanResponse" + $ref: "#/components/schemas/MessageListResponse" description: OK default: content: @@ -3459,167 +7014,249 @@ paths: description: Error security: - apiKey: [] - summary: Plan CLI embeddings management + summary: List messages tags: - API - /api/v1/cli/identities: - delete: - operationId: removeCLIIdentity - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RemoveRequest" - required: true + /api/v1/messages/filter: + get: + operationId: filterMessages + parameters: + - description: Sender email/address filter + in: query + name: sender + schema: + type: string + - description: Sender display-name filter + in: query + name: sender_name + schema: + type: string + - description: Recipient email/address filter + in: query + name: recipient + schema: + type: string + - description: Recipient display-name filter + in: query + name: recipient_name + schema: + type: string + - description: Domain filter + in: query + name: domain + schema: + type: string + - description: Label filter + in: query + name: label + schema: + type: string + - description: Message type filter + in: query + name: message_type + schema: + type: string + - description: Named time period + in: query + name: time_period + schema: + type: string + - description: Time bucket granularity + in: query + name: time_granularity + schema: + type: string + - description: Conversation ID + in: query + name: conversation_id + schema: + format: int64 + type: integer + - description: Source ID + in: query + name: source_id + schema: + format: int64 + type: integer + - description: Only include messages with attachments + in: query + name: attachments_only + schema: + type: boolean + - description: Exclude deleted messages + in: query + name: hide_deleted + schema: + type: boolean + - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: after + schema: + type: string + - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: before + schema: + type: string + - description: Comma-separated aggregate view names to match empty values + in: query + name: empty_targets + schema: + type: string + - description: Zero-based row offset + in: query + name: offset + schema: + format: int64 + type: integer + - description: Maximum number of rows to return (default and max 500; larger values are clamped) + in: query + name: limit + schema: + format: int64 + type: integer + - description: "Sort field: date, size, or subject" + in: query + name: sort + schema: + type: string + - description: "Sort direction: asc or desc" + in: query + name: direction + schema: + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/RemoveResult" + $ref: "#/components/schemas/FilteredMessagesResponse" description: OK - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "422": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": + default: content: application/json: schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable + $ref: "#/components/schemas/ErrorResponse" + description: Error security: - apiKey: [] - summary: Remove a confirmed identifier from an account identity + summary: List filtered messages tags: - - CLI + - API + /api/v1/messages/gmail-ids: get: - operationId: listCLIIdentities + operationId: getGmailIDsByFilter parameters: - - description: Restrict to a single account - explode: false + - description: Sender email/address filter in: query - name: account + name: sender schema: - description: Restrict to a single account type: string - - description: Restrict to all member accounts of one collection - explode: false + - description: Sender display-name filter + in: query + name: sender_name + schema: + type: string + - description: Recipient email/address filter + in: query + name: recipient + schema: + type: string + - description: Recipient display-name filter + in: query + name: recipient_name + schema: + type: string + - description: Domain filter + in: query + name: domain + schema: + type: string + - description: Label filter + in: query + name: label + schema: + type: string + - description: Message type filter + in: query + name: message_type + schema: + type: string + - description: Named time period + in: query + name: time_period + schema: + type: string + - description: Time bucket granularity + in: query + name: time_granularity + schema: + type: string + - description: Conversation ID + in: query + name: conversation_id + schema: + format: int64 + type: integer + - description: Source ID + in: query + name: source_id + schema: + format: int64 + type: integer + - description: Only include messages with attachments + in: query + name: attachments_only + schema: + type: boolean + - description: Exclude deleted messages + in: query + name: hide_deleted + schema: + type: boolean + - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: after + schema: + type: string + - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) + in: query + name: before + schema: + type: string + - description: Comma-separated aggregate view names to match empty values + in: query + name: empty_targets + schema: + type: string + - description: Zero-based row offset + in: query + name: offset + schema: + format: int64 + type: integer + - description: Maximum number of rows to return (default and max 500; larger values are clamped) + in: query + name: limit + schema: + format: int64 + type: integer + - description: "Sort field: date, size, or subject" in: query - name: collection + name: sort schema: - description: Restrict to all member accounts of one collection type: string - - description: For account scope, return only the primary source instead of related sources - explode: false + - description: "Sort direction: asc or desc" in: query - name: primary_only + name: direction schema: - description: For account scope, return only the primary source instead of related sources - type: boolean - responses: - "200": - content: - application/json: - schema: - $ref: "#/components/schemas/CliIdentitiesResponse" - description: OK - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "422": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable - security: - - apiKey: [] - summary: List confirmed account identities - tags: - - CLI - post: - operationId: addCLIIdentity - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/AddRequest" - required: true - responses: - "200": - content: - application/json: - schema: - $ref: "#/components/schemas/AddResult" - description: OK - "400": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Bad Request - "422": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Unprocessable Entity - "500": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Internal Server Error - "503": - content: - application/json: - schema: - $ref: "#/components/schemas/ApiHTTPError" - description: Service Unavailable - security: - - apiKey: [] - summary: Add a confirmed identifier to an account identity - tags: - - CLI - /api/v1/cli/init-db: - post: - operationId: initCLIArchive + type: string responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliInitDBResponse" + $ref: "#/components/schemas/GmailIDsResponse" description: OK default: content: @@ -3629,25 +7266,26 @@ paths: description: Error security: - apiKey: [] - summary: Initialize the archive for CLI use + summary: List Gmail message IDs matching a filter tags: - API - /api/v1/cli/message: + /api/v1/messages/{id}: get: - operationId: getCLIMessage + operationId: getMessage parameters: - - description: Message numeric ID or source message ID - in: query + - description: Message ID + in: path name: id required: true schema: - type: string + format: int64 + type: integer responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliMessageResponse" + $ref: "#/components/schemas/MessageDetail" description: OK default: content: @@ -3657,23 +7295,30 @@ paths: description: Error security: - apiKey: [] - summary: Get one message for CLI output + summary: Get one message tags: - API - /api/v1/cli/message/raw: + /api/v1/messages/{id}/inline: get: - operationId: getCLIMessageRaw + operationId: getMessageInlinePart parameters: - - description: Message numeric ID or source message ID - in: query + - description: Message ID + in: path name: id required: true + schema: + format: int64 + type: integer + - description: Inline MIME Content-ID + in: query + name: cid + required: true schema: type: string responses: "200": content: - message/rfc822: + application/octet-stream: schema: format: binary type: string @@ -3697,39 +7342,30 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - "500": + "415": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - "503": + "500": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - default: + "501": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - security: - - apiKey: [] - summary: Get one raw message for CLI export - tags: - - API - /api/v1/cli/rebuild-fts: - post: - operationId: rebuildCLIFTS - responses: - "200": + "503": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CliRebuildFTSEvent" - description: OK + $ref: "#/components/schemas/ErrorResponse" + description: Error default: content: application/json: @@ -3738,18 +7374,26 @@ paths: description: Error security: - apiKey: [] - summary: Rebuild the CLI full-text search index + summary: Get an inline MIME part tags: - API - /api/v1/cli/repair-encoding: - post: - operationId: repairEncodingCLI + /api/v1/messages/{id}/tasks: + get: + operationId: listMessageTasks + parameters: + - description: Archived email message ID + in: path + name: id + required: true + schema: + format: int64 + type: integer responses: "200": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLIRepairEncodingEvent" + $ref: "#/components/schemas/TaskLinkLookupResponse" description: OK default: content: @@ -3759,25 +7403,38 @@ paths: description: Error security: - apiKey: [] - summary: Repair CLI archive encoding + summary: List tasks linked to an archived email tags: - API - /api/v1/cli/run: post: - operationId: runCLI + operationId: createOrLinkMessageTask + parameters: + - description: Archived email message ID + in: path + name: id + required: true + schema: + format: int64 + type: integer + - description: Browser-generated retry-stable request ID + in: header + name: X-Request-Id + required: true + schema: + type: string requestBody: content: application/json: schema: - $ref: "#/components/schemas/CLIRunRequest" + $ref: "#/components/schemas/TaskLinkMutationRequest" required: true responses: - "200": + "201": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLIRunEvent" - description: OK + $ref: "#/components/schemas/TaskLinkMutationResponse" + description: Created default: content: application/json: @@ -3786,44 +7443,24 @@ paths: description: Error security: - apiKey: [] - summary: Run an allowlisted CLI command + summary: Create or link a task for an archived email tags: - API - /api/v1/cli/search: - get: - operationId: searchCLI + /api/v1/messages/{id}/tasks/{task_id}: + delete: + operationId: unlinkMessageTask parameters: - - description: Search query - in: query - name: q + - description: Archived email message ID + in: path + name: id required: true - schema: - type: string - - description: Maximum number of rows to return - in: query - name: limit schema: format: int64 type: integer - - description: Zero-based row offset - in: query - name: offset - schema: - format: int64 - type: integer - - description: Message type filter; repeat or comma-separate for multiple values - in: query - name: message_type - schema: - type: string - - description: Restrict to one account/source - in: query - name: account - schema: - type: string - - description: Restrict to one collection - in: query - name: collection + - description: External task ID + in: path + name: task_id + required: true schema: type: string responses: @@ -3831,8 +7468,55 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/CliSearchResponse" - description: OK + $ref: "#/components/schemas/TaskLinkMutationResponse" + description: OK + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Unlink a task from an archived email + tags: + - API + /api/v1/people/search: + post: + operationId: searchPeople + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/IdentitySearchHTTPRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/PersonSearchHTTPResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -3841,29 +7525,26 @@ paths: description: Error security: - apiKey: [] - summary: Search messages for CLI output + summary: Search analytical people tags: - - API - /api/v1/cli/stats: + - Exploration + /api/v1/people/{id}: get: - operationId: getCLIStats + operationId: getPerson parameters: - - description: Restrict to one account/source - in: query - name: account - schema: - type: string - - description: Restrict to one collection - in: query - name: collection + - description: Durable participant ID + in: path + name: id + required: true schema: - type: string + format: int64 + type: integer responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CliStatsResponse" + $ref: "#/components/schemas/PersonSummary" description: OK default: content: @@ -3873,25 +7554,53 @@ paths: description: Error security: - apiKey: [] - summary: Get CLI-compatible archive statistics + summary: Get one analytical person tags: - API - /api/v1/cli/sync: + /api/v1/people/{id}/files/search: post: - operationId: syncCLI + operationId: searchPersonFiles parameters: - - description: Account email or display name to sync - in: query - name: email + - description: Durable participant ID + in: path + name: id + required: true schema: - type: string + format: int64 + type: integer + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/FileSearchHTTPRequest" + required: true responses: "200": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLISyncEvent" + $ref: "#/components/schemas/FileSearchHTTPResponse" description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -3900,51 +7609,53 @@ paths: description: Error security: - apiKey: [] - summary: Run CLI incremental sync + summary: Search one person's analytical files tags: - - API - /api/v1/cli/sync-full: + - Exploration + /api/v1/people/{id}/summary: post: - operationId: syncFullCLI + operationId: getPersonContextSummary parameters: - - description: Account email or display name to sync - in: query - name: email - schema: - type: string - - description: Gmail search query - in: query - name: query - schema: - type: string - - description: Only messages on or after this YYYY-MM-DD date - in: query - name: after - schema: - type: string - - description: Only messages before this YYYY-MM-DD date - in: query - name: before - schema: - type: string - - description: Maximum messages to sync - in: query - name: limit + - description: Durable participant ID + in: path + name: id + required: true schema: format: int64 type: integer - - description: Ignore checkpoints and start fresh - in: query - name: noresume - schema: - type: boolean + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreHTTPRequest" + required: true responses: "200": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLISyncEvent" + $ref: "#/components/schemas/PersonContextSummaryHTTPResponse" description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -3953,42 +7664,53 @@ paths: description: Error security: - apiKey: [] - summary: Run CLI full sync + summary: Get one person's contextual analytical summary tags: - - API - /api/v1/cli/verify: + - Exploration + /api/v1/people/{id}/timeline: post: - operationId: verifyCLI + operationId: getPersonTimeline parameters: - - description: Account email to verify - in: query - name: email + - description: Durable participant ID + in: path + name: id required: true - schema: - type: string - - description: Number of messages to sample for MIME verification - in: query - name: sample schema: format: int64 type: integer - - description: Skip SQLite integrity check - in: query - name: skip_db_check - schema: - type: boolean - - description: Emit JSON summary output - in: query - name: json - schema: - type: boolean + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/ExploreHTTPRequest" + required: true responses: "200": content: - application/x-ndjson: + application/json: schema: - $ref: "#/components/schemas/CLIVerifyEvent" + $ref: "#/components/schemas/ExploreHTTPResponse" description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -3997,24 +7719,24 @@ paths: description: Error security: - apiKey: [] - summary: Verify the CLI archive against Gmail + summary: Get one person's canonical activity timeline tags: - - API - /api/v1/deletions: - get: - operationId: listDeletions - parameters: - - description: Filter manifests by status (pending, in_progress, completed, failed, cancelled) - in: query - name: status - schema: - type: string + - Exploration + /api/v1/query: + post: + operationId: runQuery + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/QueryRequest" + required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/ListDeletionsResponse" + $ref: "#/components/schemas/QueryResult" description: OK default: content: @@ -4024,30 +7746,45 @@ paths: description: Error security: - apiKey: [] - summary: List staged deletion manifests + summary: Run an aggregate query tags: - API + /api/v1/relationships: post: - operationId: stageDeletion + operationId: listRelationships requestBody: content: application/json: schema: - $ref: "#/components/schemas/StageDeletionRequest" + $ref: "#/components/schemas/RelationshipsHTTPRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/StageDeletionResponse" + $ref: "#/components/schemas/RelationshipsHTTPResponse" description: OK - "201": + "400": content: application/json: schema: - $ref: "#/components/schemas/StageDeletionResponse" - description: Created + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -4056,81 +7793,53 @@ paths: description: Error security: - apiKey: [] - summary: Stage messages for deletion + summary: Rank counterparts by reciprocity-weighted interaction tags: - - API - /api/v1/deletions/{id}: - delete: - operationId: cancelDeletion + - Exploration + /api/v1/relationships/{id}/timeline: + post: + operationId: getRelationshipTimeline parameters: - - description: Deletion manifest ID + - description: Any member participant ID of the counterpart's identity cluster in: path name: id required: true schema: - type: string + format: int64 + type: integer + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/RelationshipTimelineHTTPRequest" + required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/CancelDeletionResponse" + $ref: "#/components/schemas/RelationshipTimelineHTTPResponse" description: OK - default: - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorResponse" - description: Error - security: - - apiKey: [] - summary: Cancel a staged deletion manifest - tags: - - API - /api/v1/health: - get: - operationId: getHealth - responses: - "200": + "400": content: application/json: schema: - $ref: "#/components/schemas/HealthResponse" - description: OK - default: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - security: - - apiKey: [] - summary: Get authenticated health details - tags: - - API - /api/v1/messages: - get: - operationId: listMessages - parameters: - - description: One-based page number (default 1; values below 1 are clamped to 1). Non-numeric values are rejected with 400. - in: query - name: page - schema: - format: int64 - type: integer - - description: Page size (default 20, max 100; out-of-range values are clamped). Non-numeric values are rejected with 400. - in: query - name: page_size - schema: - format: int64 - type: integer - responses: - "200": + "503": content: application/json: schema: - $ref: "#/components/schemas/MessageListResponse" - description: OK + anyOf: + - $ref: "#/components/schemas/ExploreCacheUnavailableResponse" + - $ref: "#/components/schemas/ErrorResponse" + description: Service Unavailable default: content: application/json: @@ -4139,123 +7848,18 @@ paths: description: Error security: - apiKey: [] - summary: List messages + summary: Get one counterpart's interaction timeline, with chat grouped into local-day bursts tags: - - API - /api/v1/messages/filter: + - Exploration + /api/v1/saved-views: get: - operationId: filterMessages - parameters: - - description: Sender email/address filter - in: query - name: sender - schema: - type: string - - description: Sender display-name filter - in: query - name: sender_name - schema: - type: string - - description: Recipient email/address filter - in: query - name: recipient - schema: - type: string - - description: Recipient display-name filter - in: query - name: recipient_name - schema: - type: string - - description: Domain filter - in: query - name: domain - schema: - type: string - - description: Label filter - in: query - name: label - schema: - type: string - - description: Message type filter - in: query - name: message_type - schema: - type: string - - description: Named time period - in: query - name: time_period - schema: - type: string - - description: Time bucket granularity - in: query - name: time_granularity - schema: - type: string - - description: Conversation ID - in: query - name: conversation_id - schema: - format: int64 - type: integer - - description: Source ID - in: query - name: source_id - schema: - format: int64 - type: integer - - description: Only include messages with attachments - in: query - name: attachments_only - schema: - type: boolean - - description: Exclude deleted messages - in: query - name: hide_deleted - schema: - type: boolean - - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) - in: query - name: after - schema: - type: string - - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) - in: query - name: before - schema: - type: string - - description: Comma-separated aggregate view names to match empty values - in: query - name: empty_targets - schema: - type: string - - description: Zero-based row offset - in: query - name: offset - schema: - format: int64 - type: integer - - description: Maximum number of rows to return (default and max 500; larger values are clamped) - in: query - name: limit - schema: - format: int64 - type: integer - - description: "Sort field: date, size, or subject" - in: query - name: sort - schema: - type: string - - description: "Sort direction: asc or desc" - in: query - name: direction - schema: - type: string + operationId: listSavedViews responses: "200": content: application/json: schema: - $ref: "#/components/schemas/FilteredMessagesResponse" + $ref: "#/components/schemas/SavedViewsResponse" description: OK default: content: @@ -4265,153 +7869,47 @@ paths: description: Error security: - apiKey: [] - summary: List filtered messages + summary: List shared analytical Saved Views tags: - API - /api/v1/messages/gmail-ids: - get: - operationId: getGmailIDsByFilter - parameters: - - description: Sender email/address filter - in: query - name: sender - schema: - type: string - - description: Sender display-name filter - in: query - name: sender_name - schema: - type: string - - description: Recipient email/address filter - in: query - name: recipient - schema: - type: string - - description: Recipient display-name filter - in: query - name: recipient_name - schema: - type: string - - description: Domain filter - in: query - name: domain - schema: - type: string - - description: Label filter - in: query - name: label - schema: - type: string - - description: Message type filter - in: query - name: message_type - schema: - type: string - - description: Named time period - in: query - name: time_period - schema: - type: string - - description: Time bucket granularity - in: query - name: time_granularity - schema: - type: string - - description: Conversation ID - in: query - name: conversation_id - schema: - format: int64 - type: integer - - description: Source ID - in: query - name: source_id - schema: - format: int64 - type: integer - - description: Only include messages with attachments - in: query - name: attachments_only - schema: - type: boolean - - description: Exclude deleted messages - in: query - name: hide_deleted - schema: - type: boolean - - description: Lower date/time bound (RFC3339 or YYYY-MM-DD) - in: query - name: after - schema: - type: string - - description: Upper date/time bound (RFC3339 or YYYY-MM-DD) - in: query - name: before - schema: - type: string - - description: Comma-separated aggregate view names to match empty values - in: query - name: empty_targets - schema: - type: string - - description: Zero-based row offset - in: query - name: offset - schema: - format: int64 - type: integer - - description: Maximum number of rows to return (default and max 500; larger values are clamped) - in: query - name: limit - schema: - format: int64 - type: integer - - description: "Sort field: date, size, or subject" - in: query - name: sort - schema: - type: string - - description: "Sort direction: asc or desc" - in: query - name: direction - schema: - type: string + post: + operationId: createSavedView + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/CreateSavedViewRequest" + required: true responses: - "200": + "201": content: application/json: schema: - $ref: "#/components/schemas/GmailIDsResponse" - description: OK - default: + $ref: "#/components/schemas/SavedView" + description: Created + headers: + ETag: + description: Strong Saved View revision tag for optimistic concurrency + schema: + type: string + "400": content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - security: - - apiKey: [] - summary: List Gmail message IDs matching a filter - tags: - - API - /api/v1/messages/{id}: - get: - operationId: getMessage - parameters: - - description: Message ID - in: path - name: id - required: true - schema: - format: int64 - type: integer - responses: - "200": + "409": content: application/json: schema: - $ref: "#/components/schemas/MessageDetail" - description: OK + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error default: content: application/json: @@ -4420,35 +7918,29 @@ paths: description: Error security: - apiKey: [] - summary: Get one message + summary: Create a shared analytical Saved View tags: - API - /api/v1/messages/{id}/inline: - get: - operationId: getMessageInlinePart + /api/v1/saved-views/{id}: + delete: + operationId: deleteSavedView parameters: - - description: Message ID + - description: Saved View ID in: path name: id required: true schema: format: int64 type: integer - - description: Inline MIME Content-ID - in: query - name: cid + - description: Strong ETag returned by the latest Saved View read + in: header + name: If-Match required: true schema: type: string responses: - "200": - content: - application/octet-stream: - schema: - format: binary - type: string - x-contentMediaType: application/octet-stream - description: OK + "204": + description: No Content "400": content: application/json: @@ -4467,7 +7959,13 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - "415": + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "428": content: application/json: schema: @@ -4479,7 +7977,46 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" description: Error - "501": + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Delete a shared analytical Saved View + tags: + - API + get: + operationId: getSavedView + parameters: + - description: Saved View ID + in: path + name: id + required: true + schema: + format: int64 + type: integer + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SavedView" + description: OK + headers: + ETag: + description: Strong Saved View revision tag for optimistic concurrency + schema: + type: string + "404": content: application/json: schema: @@ -4499,25 +8036,73 @@ paths: description: Error security: - apiKey: [] - summary: Get an inline MIME part + summary: Get a shared analytical Saved View tags: - API - /api/v1/query: - post: - operationId: runQuery + patch: + operationId: patchSavedView + parameters: + - description: Saved View ID + in: path + name: id + required: true + schema: + format: int64 + type: integer + - description: Strong ETag returned by the latest Saved View read + in: header + name: If-Match + required: true + schema: + type: string requestBody: content: application/json: schema: - $ref: "#/components/schemas/QueryRequest" + $ref: "#/components/schemas/PatchSavedViewRequest" required: true responses: "200": content: application/json: schema: - $ref: "#/components/schemas/QueryResult" + $ref: "#/components/schemas/SavedView" description: OK + headers: + ETag: + description: Strong Saved View revision tag for optimistic concurrency + schema: + type: string + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "404": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "428": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error default: content: application/json: @@ -4526,7 +8111,7 @@ paths: description: Error security: - apiKey: [] - summary: Run an aggregate query + summary: Update a shared analytical Saved View tags: - API /api/v1/scheduler/status: @@ -4633,6 +8218,45 @@ paths: summary: Search messages tags: - API + /api/v1/search/coverage: + post: + operationId: getSearchCoverage + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SearchCoverageRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SearchCoverageResponse" + description: OK + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "503": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get semantic index coverage for an analytical context + tags: + - Exploration /api/v1/search/deep: get: operationId: deepSearch @@ -5025,6 +8649,100 @@ paths: summary: Find messages similar to a seed message tags: - API + /api/v1/settings: + get: + operationId: getSettings + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SettingsResponse" + description: OK + headers: + ETag: + description: Strong content hash for optimistic concurrency + schema: + type: string + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Get browser-managed settings + tags: + - API + patch: + operationId: patchSettings + parameters: + - description: Strong ETag returned by the latest settings read + in: header + name: If-Match + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/SettingsPatchRequest" + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/SettingsResponse" + description: OK + headers: + ETag: + description: Strong content hash for optimistic concurrency + schema: + type: string + "400": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "409": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "412": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "422": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + "428": + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + default: + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + description: Error + security: + - apiKey: [] + summary: Update browser-managed settings + tags: + - API /api/v1/sources/status: get: operationId: listSourceStatus diff --git a/scripts/check-web-assets.mjs b/scripts/check-web-assets.mjs new file mode 100755 index 000000000..8be429f6b --- /dev/null +++ b/scripts/check-web-assets.mjs @@ -0,0 +1,280 @@ +#!/usr/bin/env node + +import { createHash } from 'node:crypto'; +import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, writeFileSync } from 'node:fs'; +import { dirname, isAbsolute, posix, relative, resolve, sep } from 'node:path'; +import { createRequire } from 'node:module'; +import { fileURLToPath } from 'node:url'; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const webRequire = createRequire(resolve(repositoryRoot, 'web/package.json')); + +function loadHTMLParser() { + try { + return webRequire('jsdom').JSDOM; + } catch (error) { + throw new Error(`jsdom is unavailable; run 'make web-install' first (${error.message})`); + } +} + +function loadAssetParsers() { + try { + const lexer = webRequire('es-module-lexer'); + lexer.initSync(); + return { parseJavaScript: lexer.parse, postcss: webRequire('postcss') }; + } catch (error) { + throw new Error(`asset parsers are unavailable; run 'make web-install' first (${error.message})`); + } +} + +function sha256(bytes) { + return createHash('sha256').update(bytes).digest('hex'); +} + +function assertContained(root, candidate, label) { + const rel = relative(realpathSync(root), realpathSync(candidate)); + if (rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) { + throw new Error(`${label} escapes ${root}: ${candidate}`); + } +} + +function releaseReference(value, containingPath, label, externalDescription = 'absolute external URL') { + const reference = value.trim(); + if (!reference) throw new Error(`${label} is empty`); + if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(reference)) { + throw new Error(`${label} uses an ${externalDescription}: ${reference}`); + } + let clean; + try { + clean = decodeURIComponent(reference.split(/[?#]/, 1)[0]); + } catch { + throw new Error(`${label} has invalid URL encoding: ${reference}`); + } + if (!clean || clean.includes('\\')) { + throw new Error(`${label} escapes the release root: ${reference}`); + } + const logical = clean.startsWith('/') + ? posix.normalize(clean.slice(1)) + : posix.normalize(posix.join(posix.dirname(containingPath), clean)); + if (!logical || logical === '..' || logical.startsWith('../') || posix.isAbsolute(logical)) { + throw new Error(`${label} escapes the release root: ${reference}`); + } + return logical; +} + +function rootReference(value, label) { + return releaseReference(value, 'index.html', label); +} + +function assertImmutableName(logicalPath) { + if (!logicalPath.startsWith('assets/')) return; + if (!/-[A-Za-z0-9_-]{8}(?:\.[A-Za-z0-9]+)+$/.test(posix.basename(logicalPath))) { + throw new Error(`immutable release asset lacks a content hash: ${logicalPath}`); + } +} + +function manifestGraph(manifest, entryKey = 'index.html') { + const reachable = new Set(['index.html', '.vite/manifest.json']); + const visiting = new Set(); + + function visit(key) { + if (visiting.has(key)) return; + const entry = manifest[key]; + if (!entry) throw new Error(`Vite manifest references missing entry ${key}`); + visiting.add(key); + for (const field of [entry.file, ...(entry.css ?? []), ...(entry.assets ?? [])]) { + if (typeof field !== 'string') throw new Error(`Vite manifest entry ${key} has a non-string asset`); + reachable.add(rootReference(field, `manifest ${key}`)); + } + for (const dependency of [...(entry.imports ?? []), ...(entry.dynamicImports ?? [])]) visit(dependency); + } + + visit(entryKey); + return reachable; +} + +function sourceMapReferences(text) { + return [...text.matchAll(/[#@]\s*sourceMappingURL\s*=\s*([^\s*]+)/gi)].map((match) => match[1]); +} + +function cssURLReferences(value) { + return [...value.matchAll(/url\(\s*(?:(['"])(.*?)\1|([^)'"\s]+))\s*\)/gi)] + .map((match) => match[2] ?? match[3]); +} + +function cssImportReference(params) { + const match = params.match(/^\s*(?:url\(\s*(?:(['"])(.*?)\1|([^)'"\s]+))\s*\)|(['"])(.*?)\4|([^\s;]+))/i); + return match ? (match[2] ?? match[3] ?? match[5] ?? match[6]) : undefined; +} + +function ignorableReference(value) { + return /^(?:data:|blob:|#)/i.test(value.trim()); +} + +function inspectReferencedFile(path, logicalPath, enqueue, parsers, allowSourceMaps) { + if (!/\.(?:css|js|mjs)$/i.test(logicalPath)) return; + const text = readFileSync(path, 'utf8'); + for (const value of sourceMapReferences(text)) { + if (!allowSourceMaps) throw new Error(`source map is present in release graph: ${value}`); + enqueue(value, logicalPath, `${logicalPath} source map`); + } + if (/\.css$/i.test(logicalPath)) { + const root = parsers.postcss.parse(text, { from: logicalPath }); + root.walkDecls((declaration) => { + for (const value of cssURLReferences(declaration.value)) { + if (!ignorableReference(value)) { + enqueue(value, logicalPath, `${logicalPath} url()`, 'external font/asset URL'); + } + } + }); + root.walkAtRules('import', (rule) => { + const value = cssImportReference(rule.params); + if (value && !ignorableReference(value)) { + enqueue(value, logicalPath, `${logicalPath} @import`, 'external font/asset URL'); + } + }); + return; + } + + const [imports] = parsers.parseJavaScript(text, logicalPath); + for (const imported of imports) { + const value = imported.n; + if (typeof value !== 'string' || ignorableReference(value)) continue; + if (/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i.test(value)) { + throw new Error(`${logicalPath} uses an external runtime asset: ${value}`); + } + if (!/^(?:\.{0,2}\/)/.test(value)) { + throw new Error(`${logicalPath} uses a non-local runtime asset: ${value}`); + } + enqueue(value, logicalPath, `${logicalPath} import`, 'external runtime asset'); + } + for (const match of text.matchAll(/new\s+URL\s*\(\s*(['"])([^'"]+)\1\s*,\s*import\.meta\.url\s*\)/g)) { + const value = match[2]; + if (!ignorableReference(value)) { + enqueue(value, logicalPath, `${logicalPath} new URL()`, 'external runtime asset'); + } + } +} + +function distributionFiles(root, directory = root) { + const files = []; + for (const entry of readdirSync(directory, { withFileTypes: true })) { + const path = resolve(directory, entry.name); + const logical = relative(root, path).split(sep).join('/'); + if (entry.isSymbolicLink()) throw new Error(`release output contains a symlink: ${logical}`); + if (entry.isDirectory()) files.push(...distributionFiles(root, path)); + else if (entry.isFile()) files.push(logical); + else throw new Error(`release output contains an unsupported filesystem entry: ${logical}`); + } + return files; +} + +export function validateWebAssets(options = {}) { + const dist = resolve(options.dist ?? resolve(repositoryRoot, 'web/dist')); + const embedded = resolve(options.embedded ?? resolve(repositoryRoot, 'internal/web/dist')); + const allowSourceMaps = options.allowSourceMaps ?? process.env.MSGVAULT_ALLOW_WEB_SOURCEMAPS === '1'; + const indexPath = resolve(dist, 'index.html'); + const manifestPath = resolve(dist, '.vite/manifest.json'); + if (!existsSync(indexPath) || !existsSync(manifestPath)) { + throw new Error(`release web output is incomplete under ${dist}; run 'make web-build'`); + } + + const JSDOM = loadHTMLParser(); + const parsers = loadAssetParsers(); + const document = new JSDOM(readFileSync(indexPath, 'utf8')).window.document; + const references = new Set(); + const queue = []; + function enqueue(value, containingPath = 'index.html', label = containingPath, externalDescription) { + const logicalPath = releaseReference(value, containingPath, label, externalDescription); + if (!allowSourceMaps && /\.map$/i.test(logicalPath)) { + throw new Error(`source map is present in release graph: ${logicalPath}`); + } + assertImmutableName(logicalPath); + if (!references.has(logicalPath)) { + references.add(logicalPath); + queue.push(logicalPath); + } + } + for (const element of document.querySelectorAll('script[src], link[href]')) { + const attr = element.tagName === 'SCRIPT' ? 'src' : 'href'; + enqueue(element.getAttribute(attr), 'index.html', `index.html ${element.tagName.toLowerCase()}[${attr}]`); + } + + const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')); + for (const reference of manifestGraph(manifest)) enqueue(reference); + // Vite copies this compliance artifact from web/public. It is intentionally + // distributed even though application code does not request it at runtime. + enqueue('licenses/Figtree-OFL.txt'); + + const files = []; + for (let index = 0; index < queue.length; index += 1) { + const reference = queue[index]; + if (!allowSourceMaps && /\.map$/i.test(reference)) { + throw new Error(`source map is present in release graph: ${reference}`); + } + const path = resolve(dist, reference); + if (!existsSync(path) || !lstatSync(path).isFile()) throw new Error(`release asset is missing: ${reference}`); + assertContained(dist, path, `release asset ${reference}`); + inspectReferencedFile(path, reference, enqueue, parsers, allowSourceMaps); + const bytes = readFileSync(path); + files.push({ path: reference, bytes: bytes.length, sha256: sha256(bytes) }); + } + files.sort((left, right) => left.path.localeCompare(right.path)); + const recorded = new Set(files.map((file) => file.path)); + const untracked = distributionFiles(dist).filter((file) => !recorded.has(file)); + if (untracked.length > 0) throw new Error(`untracked release asset: ${untracked.join(', ')}`); + + if (options.checkEmbedded !== false) { + for (const file of files) { + const embeddedPath = resolve(embedded, file.path); + if (!existsSync(embeddedPath) || !lstatSync(embeddedPath).isFile()) { + throw new Error(`embedded asset is missing: ${file.path}`); + } + assertContained(embedded, embeddedPath, `embedded asset ${file.path}`); + const bytes = readFileSync(embeddedPath); + if (sha256(bytes) !== file.sha256) throw new Error(`embedded asset differs from release build: ${file.path}`); + } + } + + if (options.binary) { + const binary = readFileSync(resolve(options.binary)); + for (const file of files) { + const bytes = readFileSync(resolve(dist, file.path)); + if (binary.indexOf(bytes) < 0) throw new Error(`binary does not embed release asset bytes: ${file.path}`); + } + } + + return { schema: 1, files }; +} + +function parseArgs(argv) { + const options = {}; + let list = false; + let jsonPath = ''; + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i]; + if (arg === '--dist') options.dist = argv[++i]; + else if (arg === '--embedded') options.embedded = argv[++i]; + else if (arg === '--binary') options.binary = argv[++i]; + else if (arg === '--no-embedded') options.checkEmbedded = false; + else if (arg === '--allow-source-maps') options.allowSourceMaps = true; + else if (arg === '--list') list = true; + else if (arg === '--json') jsonPath = argv[++i]; + else throw new Error(`unknown argument: ${arg}`); + } + return { options, list, jsonPath }; +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + try { + const { options, list, jsonPath } = parseArgs(process.argv.slice(2)); + const result = validateWebAssets(options); + const canonical = `${JSON.stringify(result, null, 2)}\n`; + if (jsonPath) writeFileSync(resolve(jsonPath), canonical); + if (list) for (const file of result.files) process.stdout.write(`${file.path}\n`); + else process.stdout.write(`validated ${result.files.length} release web assets\n`); + } catch (error) { + process.stderr.write(`web asset validation failed: ${error.message}\n`); + process.exitCode = 1; + } +} diff --git a/scripts/check-web-assets.test.mjs b/scripts/check-web-assets.test.mjs new file mode 100644 index 000000000..8d96e2b41 --- /dev/null +++ b/scripts/check-web-assets.test.mjs @@ -0,0 +1,157 @@ +import assert from 'node:assert/strict'; +import { cpSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { resolve } from 'node:path'; +import test from 'node:test'; + +import { validateWebAssets } from './check-web-assets.mjs'; + +const root = resolve(import.meta.dirname, '..'); + +function releaseCopy(t) { + const dir = mkdtempSync(resolve(tmpdir(), 'msgvault-assets-')); + t.after(() => rmSync(dir, { recursive: true, force: true })); + const dist = resolve(dir, 'dist'); + const embedded = resolve(dir, 'embedded'); + cpSync(resolve(root, 'web/dist'), dist, { recursive: true }); + cpSync(resolve(root, 'web/dist'), embedded, { recursive: true }); + return { dist, embedded }; +} + +function releaseEntry(paths, extension) { + const manifest = JSON.parse(readFileSync(resolve(paths.dist, '.vite/manifest.json'))); + const candidates = [manifest['index.html'].file, ...(manifest['index.html'].css ?? [])]; + return candidates.find((path) => path.endsWith(extension)); +} + +function writeReleaseFile(paths, logicalPath, contents) { + for (const rootPath of [paths.dist, paths.embedded]) { + const path = resolve(rootPath, logicalPath); + mkdirSync(resolve(path, '..'), { recursive: true }); + writeFileSync(path, contents); + } +} + +function appendReleaseFile(paths, logicalPath, contents) { + const original = readFileSync(resolve(paths.dist, logicalPath), 'utf8'); + writeReleaseFile(paths, logicalPath, `${original}\n${contents}\n`); +} + +test('validates the production Vite graph and staged embed', (t) => { + const paths = releaseCopy(t); + const result = validateWebAssets(paths); + assert.ok(result.files.some((file) => file.path === 'index.html')); + assert.ok(result.files.some((file) => file.path.endsWith('.woff2'))); + assert.ok(result.files.some((file) => file.path.endsWith('.mjs'))); + assert.ok(result.files.some((file) => file.path === 'licenses/Figtree-OFL.txt')); +}); + +test('rejects missing and escaping release references', (t) => { + const missing = releaseCopy(t); + const missingManifest = JSON.parse(readFileSync(resolve(missing.dist, '.vite/manifest.json'))); + rmSync(resolve(missing.dist, missingManifest['index.html'].file)); + assert.throws(() => validateWebAssets(missing), /release asset is missing/); + + const escaping = releaseCopy(t); + writeFileSync(resolve(escaping.dist, 'index.html'), ''); + assert.throws(() => validateWebAssets(escaping), /escapes the release root/); +}); + +test('rejects source maps, external scripts, fonts, and embed drift', (t) => { + const sourceMap = releaseCopy(t); + const manifestPath = resolve(sourceMap.dist, '.vite/manifest.json'); + const manifest = JSON.parse(readFileSync(manifestPath)); + manifest['index.html'].assets.push('assets/index.js.map'); + mkdirSync(resolve(sourceMap.dist, 'assets'), { recursive: true }); + writeFileSync(resolve(sourceMap.dist, 'assets/index.js.map'), '{}'); + writeFileSync(manifestPath, JSON.stringify(manifest)); + assert.throws(() => validateWebAssets(sourceMap), /source map/); + + const externalScript = releaseCopy(t); + writeFileSync(resolve(externalScript.dist, 'index.html'), ''); + assert.throws(() => validateWebAssets(externalScript), /absolute external URL/); + + const externalFont = releaseCopy(t); + const css = JSON.parse(readFileSync(resolve(externalFont.dist, '.vite/manifest.json')))['index.html'].css[0]; + writeFileSync(resolve(externalFont.dist, css), '@font-face{src:url(https://cdn.example.invalid/font.woff2)}'); + cpSync(resolve(externalFont.dist, css), resolve(externalFont.embedded, css)); + assert.throws(() => validateWebAssets(externalFont), /external font\/asset URL/); + + const drift = releaseCopy(t); + writeFileSync(resolve(drift.embedded, 'index.html'), 'drift'); + assert.throws(() => validateWebAssets(drift), /embedded asset differs/); + + const extra = releaseCopy(t); + writeFileSync(resolve(extra.dist, 'assets/untracked.txt'), 'not in the release graph'); + assert.throws(() => validateWebAssets(extra), /untracked release asset/); +}); + +test('walks nested CSS and JavaScript references from the real distribution', (t) => { + const paths = releaseCopy(t); + const css = releaseEntry(paths, '.css'); + const js = releaseEntry(paths, '.js'); + assert.ok(css); + assert.ok(js); + + appendReleaseFile(paths, css, '@import "./theme-A1B2C3D4.css";'); + writeReleaseFile(paths, 'assets/theme-A1B2C3D4.css', '.nested{background:url("./tile-E5F6G7H8.png")}'); + writeReleaseFile(paths, 'assets/tile-E5F6G7H8.png', 'synthetic image bytes'); + appendReleaseFile(paths, js, 'import("./runtime-I9J0K1L2.js");'); + writeReleaseFile(paths, 'assets/runtime-I9J0K1L2.js', 'new URL("./worker-M3N4O5P6.mjs", import.meta.url);'); + writeReleaseFile(paths, 'assets/worker-M3N4O5P6.mjs', 'export default "worker";'); + + const result = validateWebAssets(paths); + for (const expected of [ + 'assets/theme-A1B2C3D4.css', + 'assets/tile-E5F6G7H8.png', + 'assets/runtime-I9J0K1L2.js', + 'assets/worker-M3N4O5P6.mjs' + ]) assert.ok(result.files.some(({ path }) => path === expected), expected); +}); + +test('rejects missing, escaping, mapped, and external nested CSS references', (t) => { + const missing = releaseCopy(t); + appendReleaseFile(missing, releaseEntry(missing, '.css'), '.missing{background:url("./missing-A1B2C3D4.png")}'); + assert.throws(() => validateWebAssets(missing), /release asset is missing/); + + const escaping = releaseCopy(t); + appendReleaseFile(escaping, releaseEntry(escaping, '.css'), '@import "../../escape-A1B2C3D4.css";'); + assert.throws(() => validateWebAssets(escaping), /escapes the release root/); + + const mapped = releaseCopy(t); + appendReleaseFile(mapped, releaseEntry(mapped, '.css'), '/*# sourceMappingURL=./index-A1B2C3D4.css.map */'); + assert.throws(() => validateWebAssets(mapped), /source map/); + + const external = releaseCopy(t); + appendReleaseFile(external, releaseEntry(external, '.css'), '@import "https://cdn.example.invalid/theme.css";'); + assert.throws(() => validateWebAssets(external), /external font\/asset URL/); +}); + +test('rejects missing, escaping, mapped, and external nested JavaScript references', (t) => { + const missing = releaseCopy(t); + appendReleaseFile(missing, releaseEntry(missing, '.js'), 'import("./missing-A1B2C3D4.js");'); + assert.throws(() => validateWebAssets(missing), /release asset is missing/); + + const escaping = releaseCopy(t); + appendReleaseFile(escaping, releaseEntry(escaping, '.js'), 'import("../../escape-A1B2C3D4.js");'); + assert.throws(() => validateWebAssets(escaping), /escapes the release root/); + + const mapped = releaseCopy(t); + appendReleaseFile(mapped, releaseEntry(mapped, '.js'), '//# sourceMappingURL=./index-A1B2C3D4.js.map'); + assert.throws(() => validateWebAssets(mapped), /source map/); + + const external = releaseCopy(t); + appendReleaseFile(external, releaseEntry(external, '.js'), 'import "https://cdn.example.invalid/runtime.js";'); + assert.throws(() => validateWebAssets(external), /external runtime asset/); +}); + +test('requires immutable release assets to carry Vite hash-bearing names', (t) => { + const paths = releaseCopy(t); + const manifestPath = resolve(paths.dist, '.vite/manifest.json'); + const manifest = JSON.parse(readFileSync(manifestPath)); + manifest['index.html'].assets.push('assets/unhashed.css'); + writeReleaseFile(paths, 'assets/unhashed.css', '.unhashed{color:inherit}'); + writeFileSync(manifestPath, JSON.stringify(manifest)); + writeFileSync(resolve(paths.embedded, '.vite/manifest.json'), JSON.stringify(manifest)); + assert.throws(() => validateWebAssets(paths), /immutable release asset lacks a content hash/); +}); diff --git a/scripts/generate-web-client.mjs b/scripts/generate-web-client.mjs new file mode 100644 index 000000000..70d5115f4 --- /dev/null +++ b/scripts/generate-web-client.mjs @@ -0,0 +1,26 @@ +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import path from 'node:path'; + +const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const webDirectory = path.join(repositoryRoot, 'web'); +const generator = path.join( + webDirectory, + 'node_modules', + '.bin', + process.platform === 'win32' ? 'openapi-typescript.cmd' : 'openapi-typescript' +); +const result = spawnSync( + generator, + ['../api/openapi.yaml', '--output', 'src/lib/api/generated/schema.d.ts'], + { + cwd: webDirectory, + stdio: 'inherit' + } +); + +if (result.error) { + throw result.error; +} + +process.exitCode = result.status ?? 1; diff --git a/scripts/smoke-api-key-proxy.mjs b/scripts/smoke-api-key-proxy.mjs new file mode 100644 index 000000000..47478f6d5 --- /dev/null +++ b/scripts/smoke-api-key-proxy.mjs @@ -0,0 +1,54 @@ +#!/usr/bin/env node + +import { createServer, request } from 'node:http'; + +const [upstreamValue, expectedAPIKey] = process.argv.slice(2); +if (!upstreamValue || !expectedAPIKey) { + process.stderr.write('usage: smoke-api-key-proxy.mjs UPSTREAM API_KEY\n'); + process.exit(2); +} + +const upstream = new URL(upstreamValue); +if (upstream.protocol !== 'http:' || upstream.hostname !== '127.0.0.1') { + process.stderr.write('smoke API-key proxy requires a loopback HTTP upstream\n'); + process.exit(2); +} + +const server = createServer((incoming, outgoing) => { + if (incoming.headers['x-api-key'] !== expectedAPIKey) { + outgoing.writeHead(401, { 'content-type': 'application/json' }); + outgoing.end('{"error":"X-Api-Key required"}\n'); + return; + } + + const target = new URL(incoming.url ?? '/', upstream); + const headers = { ...incoming.headers, host: target.host }; + delete headers['x-api-key']; + headers.authorization = `Bearer ${expectedAPIKey}`; + const forwarded = request(target, { method: incoming.method, headers }, (response) => { + outgoing.writeHead(response.statusCode ?? 502, response.headers); + response.pipe(outgoing); + }); + forwarded.on('error', (error) => { + if (!outgoing.headersSent) outgoing.writeHead(502, { 'content-type': 'text/plain' }); + outgoing.end(`proxy request failed: ${error.message}\n`); + }); + incoming.pipe(forwarded); +}); + +server.on('error', (error) => { + process.stderr.write(`smoke API-key proxy failed: ${error.message}\n`); + process.exitCode = 1; +}); +server.listen(0, '127.0.0.1', () => { + const address = server.address(); + if (!address || typeof address === 'string') { + process.stderr.write('smoke API-key proxy did not bind TCP\n'); + process.exit(1); + } + process.stdout.write(`http://127.0.0.1:${address.port}\n`); +}); + +for (const signal of ['SIGINT', 'SIGTERM']) { + process.on(signal, () => server.close(() => process.exit(0))); +} diff --git a/scripts/smoke-fixture/main.go b/scripts/smoke-fixture/main.go new file mode 100644 index 000000000..a1e3bd24e --- /dev/null +++ b/scripts/smoke-fixture/main.go @@ -0,0 +1,99 @@ +// Command smoke-fixture creates the smallest production-shaped archive used by +// the release smoke gate. It is a test-data producer, never part of the binary. +package main + +import ( + "crypto/sha256" + "database/sql" + "encoding/hex" + "fmt" + "os" + "path/filepath" + "time" + + "go.kenn.io/msgvault/internal/export" + "go.kenn.io/msgvault/internal/store" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintln(os.Stderr, "usage: smoke-fixture DATA_DIR") + os.Exit(2) + } + if err := seed(os.Args[1]); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func seed(dataDir string) error { + // #nosec G703 -- this validation-only helper writes solely beneath the + // explicit scratch data directory supplied by smoke-web-release.sh. + if err := os.MkdirAll(dataDir, 0o700); err != nil { + return fmt.Errorf("create fixture data directory: %w", err) + } + st, err := store.Open(filepath.Join(dataDir, "msgvault.db")) + if err != nil { + return fmt.Errorf("open fixture archive: %w", err) + } + defer func() { _ = st.Close() }() + if err := st.InitSchema(); err != nil { + return fmt.Errorf("initialize fixture archive: %w", err) + } + source, err := st.GetOrCreateSource("gmail", "release-smoke@example.com") + if err != nil { + return fmt.Errorf("create fixture source: %w", err) + } + conversationID, err := st.EnsureConversation(source.ID, "release-smoke-thread", "Release smoke") + if err != nil { + return fmt.Errorf("create fixture conversation: %w", err) + } + senderID, err := st.EnsureParticipant("sender@example.com", "Synthetic Sender", "example.com") + if err != nil { + return fmt.Errorf("create fixture participant: %w", err) + } + now := time.Date(2026, time.January, 2, 3, 4, 5, 0, time.UTC) + messageID, err := st.UpsertMessage(&store.Message{ + ConversationID: conversationID, SourceID: source.ID, SourceMessageID: "release-smoke-message", + MessageType: "email", SentAt: sql.NullTime{Time: now, Valid: true}, + SenderID: sql.NullInt64{Int64: senderID, Valid: true}, + Subject: sql.NullString{String: "Synthetic release smoke", Valid: true}, + Snippet: sql.NullString{String: "A local release verification message", Valid: true}, + SizeEstimate: 64, HasAttachments: true, AttachmentCount: 1, + }) + if err != nil { + return fmt.Errorf("create fixture message: %w", err) + } + if err := st.ReplaceMessageRecipients(messageID, "from", []int64{senderID}, []string{"Synthetic Sender"}); err != nil { + return fmt.Errorf("create fixture sender: %w", err) + } + content := []byte("msgvault release smoke attachment\n") + digest := sha256.Sum256(content) + hash := hex.EncodeToString(digest[:]) + attachmentsDir := filepath.Join(dataDir, "attachments") + contentPath, err := export.StoragePath(attachmentsDir, hash) + if err != nil { + return fmt.Errorf("resolve fixture attachment: %w", err) + } + // #nosec G703 -- StoragePath validates the content hash and constructs a + // content-addressed path beneath attachmentsDir. + if err := os.MkdirAll(filepath.Dir(contentPath), 0o700); err != nil { + return fmt.Errorf("create fixture attachment directory: %w", err) + } + // #nosec G703 -- contentPath is the validated content-addressed path above. + if err := os.WriteFile(contentPath, content, 0o600); err != nil { + return fmt.Errorf("write fixture attachment: %w", err) + } + storagePath, err := filepath.Rel(attachmentsDir, contentPath) + if err != nil { + return fmt.Errorf("make fixture attachment path relative: %w", err) + } + if err := st.UpsertAttachment(messageID, "release-smoke.txt", "text/plain", storagePath, hash, len(content)); err != nil { + return fmt.Errorf("create fixture attachment record: %w", err) + } + if err := st.RecomputeMessageAttachmentStats(messageID); err != nil { + return fmt.Errorf("refresh fixture attachment stats: %w", err) + } + fmt.Println(hash) + return nil +} diff --git a/scripts/smoke-web-release.sh b/scripts/smoke-web-release.sh new file mode 100755 index 000000000..1d39be3f6 --- /dev/null +++ b/scripts/smoke-web-release.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)" +scratch="$(mktemp -d "${TMPDIR:-/tmp}/msgvault-web-release.XXXXXX")" +daemon_pid="" +proxy_pid="" + +cleanup() { + local exit_code="${1:-$?}" + local cleanup_status=0 + trap - EXIT INT TERM + if [[ -n "$daemon_pid" ]] && kill -0 "$daemon_pid" 2>/dev/null; then + kill -TERM "$daemon_pid" 2>/dev/null || cleanup_status=1 + wait "$daemon_pid" || cleanup_status=1 + fi + if [[ -n "$proxy_pid" ]] && kill -0 "$proxy_pid" 2>/dev/null; then + kill -TERM "$proxy_pid" 2>/dev/null || cleanup_status=1 + wait "$proxy_pid" || cleanup_status=1 + fi + rm -rf -- "$scratch" || cleanup_status=1 + if [[ "$cleanup_status" -ne 0 ]]; then + echo "release smoke cleanup failed" >&2 + if [[ "$exit_code" -eq 0 ]]; then + exit "$cleanup_status" + fi + fi + exit "$exit_code" +} +trap 'cleanup $?' EXIT +trap 'cleanup 130' INT +trap 'cleanup 143' TERM + +for command in bun go node curl; do + command -v "$command" >/dev/null || { echo "required command is unavailable: $command" >&2; exit 1; } +done + +home_dir="$scratch/archive" +os_home="$scratch/os-home" +binary="$scratch/msgvault" +mkdir -p "$home_dir" "$os_home" +# Keep immutable Go module downloads outside disposable HOME. Go marks module +# files read-only, which would make a strict scratch cleanup fail closed. +go_module_cache="$(go env GOMODCACHE)" +go_build_cache="$(go env GOCACHE)" +export GOMODCACHE="$go_module_cache" +export GOCACHE="$go_build_cache" +export HOME="$os_home" +export MSGVAULT_HOME="$home_dir" +# Kept explicit for harnesses that enforce a generic data-dir guard. Msgvault's +# supported override remains MSGVAULT_HOME. +export MSGVAULT_DATA_DIR="$home_dir" +export XDG_CONFIG_HOME="$scratch/xdg-config" +export XDG_CACHE_HOME="$scratch/xdg-cache" + +cd "$repo_root" +make web-install web-embed +node scripts/check-web-assets.mjs + +CGO_ENABLED=1 go build -tags "fts5 sqlite_vec" -trimpath -ldflags="-s -w" -o "$binary" ./cmd/msgvault +node scripts/check-web-assets.mjs --binary "$binary" + +attachment_hash="$(go run -tags "fts5 sqlite_vec" ./scripts/smoke-fixture "$home_dir")" +[[ "$attachment_hash" =~ ^[0-9a-f]{64}$ ]] || { echo "fixture returned invalid attachment hash" >&2; exit 1; } + +cat > "$home_dir/config.toml" <<'EOF' +[server] +api_port = 0 +bind_addr = "127.0.0.1" +api_key = "release-smoke-api-key" + +[analytics] +engine = "duckdb" +auto_build_cache = false + +[vector] +enabled = false +EOF + +if ! MSGVAULT_DAEMON_BUILD_CACHE_PARENT_PID="$$" \ + "$binary" --home "$home_dir" --local build-cache --full-rebuild > "$scratch/cache.log" 2>&1; then + cat "$scratch/cache.log" >&2 + exit 1 +fi +"$binary" --home "$home_dir" serve > "$scratch/serve.log" 2>&1 & +daemon_pid=$! + +base_url="" +for _ in $(seq 1 120); do + if ! kill -0 "$daemon_pid" 2>/dev/null; then + cat "$scratch/serve.log" >&2 + echo "release daemon exited before startup completed" >&2 + exit 1 + fi + base_url="$(sed -n 's/^ API server: //p' "$scratch/serve.log" | tail -1)" + if [[ -n "$base_url" ]] && curl --fail --silent --show-error "$base_url/" -o "$scratch/index.html"; then + break + fi + sleep 0.1 +done +[[ -n "$base_url" && -s "$scratch/index.html" ]] || { cat "$scratch/serve.log" >&2; echo "release daemon did not become ready" >&2; exit 1; } +cmp -s "$scratch/index.html" "$repo_root/web/dist/index.html" || { + echo "served index differs from the built distribution" >&2 + exit 1 +} + +auth_header="Authorization: Bearer release-smoke-api-key" +while IFS= read -r asset; do + [[ "$asset" == "index.html" || "$asset" == ".vite/manifest.json" ]] && continue + curl --fail --silent --show-error "$base_url/$asset" -o "$scratch/asset" + cmp -s "$scratch/asset" "$repo_root/web/dist/$asset" || { + echo "served asset differs from the built distribution: $asset" >&2 + exit 1 + } +done < <(node scripts/check-web-assets.mjs --list) + +curl --fail --silent --show-error "$base_url/api/session" -o "$scratch/session.json" +node -e 'const x=JSON.parse(require("fs").readFileSync(process.argv[1]));if(!x.auth_mode)process.exit(1)' "$scratch/session.json" +curl --fail --silent --show-error "$base_url/openapi.json" -o "$scratch/openapi.json" +node -e 'const x=JSON.parse(require("fs").readFileSync(process.argv[1]));if(x.openapi!=="3.1.0")process.exit(1)' "$scratch/openapi.json" +curl --fail --silent --show-error -H "$auth_header" -H 'Content-Type: application/json' \ + --data '{}' "$base_url/api/v1/explore" -o "$scratch/explore.json" +node -e 'const x=JSON.parse(require("fs").readFileSync(process.argv[1]));if(!Array.isArray(x.rows)||x.rows.length<1)process.exit(1)' "$scratch/explore.json" +curl --fail --silent --show-error -H "$auth_header" \ + "$base_url/api/v1/attachments/$attachment_hash/content" -o "$scratch/attachment.txt" +cmp -s "$scratch/attachment.txt" <(printf 'msgvault release smoke attachment\n') + +# Existing bearer clients remain accepted on the same daemon. This is an +# authenticated API operation, not a loopback-trust-only health probe. +curl --fail --silent --show-error -H "$auth_header" "$base_url/api/v1/stats" -o "$scratch/bearer-stats.json" +node -e 'JSON.parse(require("fs").readFileSync(process.argv[1]))' "$scratch/bearer-stats.json" + +# Drive the built CLI through the runtime record published by this daemon. +# This exercises the same daemonclient path used by existing local CLI users. +"$binary" --home "$home_dir" stats > "$scratch/local-cli-stats.txt" +node -e 'const text=require("fs").readFileSync(process.argv[1],"utf8");if(!text.includes("Messages: 1")||!text.includes("Database:"))process.exit(1)' \ + "$scratch/local-cli-stats.txt" + +# Re-run the built CLI through configured-remote resolution. The proxy rejects +# every request without the expected X-Api-Key, then forwards accepted requests +# to the same isolated daemon using its separately verified bearer path. +node scripts/smoke-api-key-proxy.mjs "$base_url" release-smoke-api-key \ + > "$scratch/proxy-url.txt" 2> "$scratch/proxy.log" & +proxy_pid=$! +proxy_url="" +for _ in $(seq 1 100); do + if ! kill -0 "$proxy_pid" 2>/dev/null; then + cat "$scratch/proxy.log" >&2 + echo "API-key verification proxy exited before startup completed" >&2 + exit 1 + fi + proxy_url="$(head -1 "$scratch/proxy-url.txt" 2>/dev/null || true)" + [[ -n "$proxy_url" ]] && break + sleep 0.05 +done +[[ -n "$proxy_url" ]] || { echo "API-key verification proxy did not become ready" >&2; exit 1; } +cat >> "$home_dir/config.toml" < "$scratch/remote-cli-stats.txt" +node -e 'const text=require("fs").readFileSync(process.argv[1],"utf8");const url=process.argv[2];if(!text.includes(`Remote: ${url}`)||!text.includes("Messages: 1"))process.exit(1)' \ + "$scratch/remote-cli-stats.txt" "$proxy_url" + +echo "release web smoke passed" diff --git a/scripts/smoke-web-release.test.mjs b/scripts/smoke-web-release.test.mjs new file mode 100644 index 000000000..4b8aba68a --- /dev/null +++ b/scripts/smoke-web-release.test.mjs @@ -0,0 +1,68 @@ +import assert from 'node:assert/strict'; +import { mkdtempSync, readdirSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { spawn } from 'node:child_process'; +import test from 'node:test'; + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); + +function delay(milliseconds) { + return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds)); +} + +async function waitForScratch(parent, child) { + for (let attempt = 0; attempt < 400; attempt += 1) { + const scratch = readdirSync(parent).find((entry) => entry.startsWith('msgvault-web-release.')); + if (scratch) return resolve(parent, scratch); + if (child.exitCode !== null || child.signalCode !== null) break; + await delay(5); + } + throw new Error('smoke process did not create its isolated scratch directory'); +} + +async function interruptSmoke(signal, expectedCode) { + const scratchParent = mkdtempSync(resolve(tmpdir(), 'msgvault-smoke-signal-')); + try { + const child = spawn('bash', ['scripts/smoke-web-release.sh'], { + cwd: repositoryRoot, + env: { ...process.env, TMPDIR: scratchParent }, + stdio: ['ignore', 'pipe', 'pipe'] + }); + let stdout = ''; + let stderr = ''; + child.stdout.setEncoding('utf8'); + child.stderr.setEncoding('utf8'); + child.stdout.on('data', (chunk) => { stdout += chunk; }); + child.stderr.on('data', (chunk) => { stderr += chunk; }); + + await waitForScratch(scratchParent, child); + await delay(25); + assert.equal(child.kill(signal), true, `send ${signal}`); + const result = await new Promise((resolveClose, reject) => { + const timeout = setTimeout(() => { + child.kill('SIGKILL'); + reject(new Error(`${signal} smoke did not exit; stdout=${stdout}; stderr=${stderr}`)); + }, 30_000); + child.once('close', (code, exitSignal) => { + clearTimeout(timeout); + resolveClose({ code, exitSignal }); + }); + }); + + assert.deepEqual(result, { code: expectedCode, exitSignal: null }, `${signal}: ${stderr}`); + assert.deepEqual( + readdirSync(scratchParent).filter((entry) => entry.startsWith('msgvault-web-release.')), + [], + `${signal} left smoke scratch state` + ); + } finally { + rmSync(scratchParent, { recursive: true, force: true }); + } +} + +test('INT and TERM preserve conventional nonzero statuses and remove scratch state', async () => { + await interruptSmoke('SIGINT', 130); + await interruptSmoke('SIGTERM', 143); +}); diff --git a/web/bun.lock b/web/bun.lock new file mode 100644 index 000000000..843f84e85 --- /dev/null +++ b/web/bun.lock @@ -0,0 +1,548 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "msgvault-web", + "dependencies": { + "@fontsource-variable/figtree": "5.3.0", + "@kenn-io/kit-ui": "github:kenn-io/kit-ui#1e9dc7d45525a471040b72b894432c4956542c38", + "@tanstack/svelte-virtual": "3.13.32", + "dompurify": "3.4.11", + "openapi-fetch": "0.17.0", + "pdfjs-dist": "5.4.296", + }, + "devDependencies": { + "@axe-core/playwright": "4.10.2", + "@playwright/test": "1.61.1", + "@sveltejs/vite-plugin-svelte": "7.1.2", + "@testing-library/svelte": "5.4.2", + "@tsconfig/svelte": "5.0.8", + "jsdom": "29.1.1", + "openapi-typescript": "7.13.0", + "svelte": "5.56.1", + "svelte-check": "4.6.0", + "typescript": "5.9.3", + "vite": "8.1.3", + "vitest": "4.1.10", + }, + }, + }, + "packages": { + "@asamuzakjp/css-color": ["@asamuzakjp/css-color@5.1.11", "", { "dependencies": { "@asamuzakjp/generational-cache": "^1.0.1", "@csstools/css-calc": "^3.2.0", "@csstools/css-color-parser": "^4.1.0", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg=="], + + "@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@7.1.1", "", { "dependencies": { "@asamuzakjp/generational-cache": "^1.0.1", "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.2.1", "is-potential-custom-element-name": "^1.0.1" } }, "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ=="], + + "@asamuzakjp/generational-cache": ["@asamuzakjp/generational-cache@1.0.1", "", {}, "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg=="], + + "@asamuzakjp/nwsapi": ["@asamuzakjp/nwsapi@2.3.9", "", {}, "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q=="], + + "@axe-core/playwright": ["@axe-core/playwright@4.10.2", "", { "dependencies": { "axe-core": "~4.10.3" }, "peerDependencies": { "playwright-core": ">= 1.0.0" } }, "sha512-6/b5BJjG6hDaRNtgzLIfKr5DfwyiLHO4+ByTLB0cJgWSM8Ll7KqtdblIS6bEkwSF642/Ex91vNqIl3GLXGlceg=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@bramus/specificity": ["@bramus/specificity@2.4.2", "", { "dependencies": { "css-tree": "^3.0.0" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="], + + "@csstools/color-helpers": ["@csstools/color-helpers@6.1.0", "", {}, "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg=="], + + "@csstools/css-calc": ["@csstools/css-calc@3.2.1", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg=="], + + "@csstools/css-color-parser": ["@csstools/css-color-parser@4.1.9", "", { "dependencies": { "@csstools/color-helpers": "^6.1.0", "@csstools/css-calc": "^3.2.1" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A=="], + + "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="], + + "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.6", "", { "peerDependencies": { "css-tree": "^3.2.1" }, "optionalPeers": ["css-tree"] }, "sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ=="], + + "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="], + + "@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + + "@exodus/bytes": ["@exodus/bytes@1.15.1", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q=="], + + "@fontsource-variable/figtree": ["@fontsource-variable/figtree@5.3.0", "", {}, "sha512-VRVodD7OiG7apQwE8/2fMjaqpY/a0qRJqL5HtYj8CKIVps+9amaFcdfKmDIf7iqYQx6iPLlArVXAfKeIZmVcNQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@kenn-io/kit-ui": ["@kenn-io/kit-ui@github:kenn-io/kit-ui#1e9dc7d", { "peerDependencies": { "@lucide/svelte": ">=1.0.0", "dompurify": "^3.2.0", "marked": "^18.0.0", "shiki": "^4.0.0", "svelte": "^5.29.0" }, "bin": { "kit-ui-check": "./bin/kit-ui-check.mjs" } }, "kenn-io-kit-ui-1e9dc7d", "sha512-u0oMlxMAo2UUz1USWbp0LsJUwAop59GyG0NxCNygJvqm7+QST31jq5eVLBFHjtnY015mr0u0HO5tFbSFQMHcTA=="], + + "@lucide/svelte": ["@lucide/svelte@1.25.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-v9m+dD68jxVnqkU3K59mG/RSRFlPGzmKCGSyMfnXcaGv9jODDQMyQkcp1CGvk3Y/cUj9v7f8rw1n//K0B53xGQ=="], + + "@napi-rs/canvas": ["@napi-rs/canvas@0.1.100", "", { "optionalDependencies": { "@napi-rs/canvas-android-arm64": "0.1.100", "@napi-rs/canvas-darwin-arm64": "0.1.100", "@napi-rs/canvas-darwin-x64": "0.1.100", "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100", "@napi-rs/canvas-linux-arm64-gnu": "0.1.100", "@napi-rs/canvas-linux-arm64-musl": "0.1.100", "@napi-rs/canvas-linux-riscv64-gnu": "0.1.100", "@napi-rs/canvas-linux-x64-gnu": "0.1.100", "@napi-rs/canvas-linux-x64-musl": "0.1.100", "@napi-rs/canvas-win32-arm64-msvc": "0.1.100", "@napi-rs/canvas-win32-x64-msvc": "0.1.100" } }, "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA=="], + + "@napi-rs/canvas-android-arm64": ["@napi-rs/canvas-android-arm64@0.1.100", "", { "os": "android", "cpu": "arm64" }, "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ=="], + + "@napi-rs/canvas-darwin-arm64": ["@napi-rs/canvas-darwin-arm64@0.1.100", "", { "os": "darwin", "cpu": "arm64" }, "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A=="], + + "@napi-rs/canvas-darwin-x64": ["@napi-rs/canvas-darwin-x64@0.1.100", "", { "os": "darwin", "cpu": "x64" }, "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw=="], + + "@napi-rs/canvas-linux-arm-gnueabihf": ["@napi-rs/canvas-linux-arm-gnueabihf@0.1.100", "", { "os": "linux", "cpu": "arm" }, "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA=="], + + "@napi-rs/canvas-linux-arm64-gnu": ["@napi-rs/canvas-linux-arm64-gnu@0.1.100", "", { "os": "linux", "cpu": "arm64" }, "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw=="], + + "@napi-rs/canvas-linux-arm64-musl": ["@napi-rs/canvas-linux-arm64-musl@0.1.100", "", { "os": "linux", "cpu": "arm64" }, "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ=="], + + "@napi-rs/canvas-linux-riscv64-gnu": ["@napi-rs/canvas-linux-riscv64-gnu@0.1.100", "", { "os": "linux", "cpu": "none" }, "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw=="], + + "@napi-rs/canvas-linux-x64-gnu": ["@napi-rs/canvas-linux-x64-gnu@0.1.100", "", { "os": "linux", "cpu": "x64" }, "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg=="], + + "@napi-rs/canvas-linux-x64-musl": ["@napi-rs/canvas-linux-x64-musl@0.1.100", "", { "os": "linux", "cpu": "x64" }, "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA=="], + + "@napi-rs/canvas-win32-arm64-msvc": ["@napi-rs/canvas-win32-arm64-msvc@0.1.100", "", { "os": "win32", "cpu": "arm64" }, "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw=="], + + "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.100", "", { "os": "win32", "cpu": "x64" }, "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], + + "@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="], + + "@playwright/test": ["@playwright/test@1.61.1", "", { "dependencies": { "playwright": "1.61.1" }, "bin": { "playwright": "cli.js" } }, "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig=="], + + "@redocly/ajv": ["@redocly/ajv@8.11.2", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js-replace": "^1.0.1" } }, "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg=="], + + "@redocly/config": ["@redocly/config@0.22.0", "", {}, "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ=="], + + "@redocly/openapi-core": ["@redocly/openapi-core@1.34.17", "", { "dependencies": { "@redocly/ajv": "8.11.2", "@redocly/config": "0.22.0", "colorette": "1.4.0", "https-proxy-agent": "7.0.6", "js-levenshtein": "1.1.6", "js-yaml": "4.2.0", "minimatch": "5.1.9", "pluralize": "8.0.0", "yaml-ast-parser": "0.0.43" } }, "sha512-wsV2keCt6B806XpSdezbWZ9aFJYf14YVh+XQf0ESt7M90yqVuxH9//PxvtC70sgj9OCkRM3nRaLfu4MsGQZRig=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.5", "", { "os": "android", "cpu": "arm64" }, "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.5", "", { "os": "linux", "cpu": "arm" }, "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA=="], + + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg=="], + + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.5", "", { "os": "none", "cpu": "arm64" }, "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.5", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="], + + "@shikijs/core": ["@shikijs/core@4.3.1", "", { "dependencies": { "@shikijs/primitive": "4.3.1", "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg=="], + + "@shikijs/langs": ["@shikijs/langs@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1" } }, "sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ=="], + + "@shikijs/primitive": ["@shikijs/primitive@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A=="], + + "@shikijs/themes": ["@shikijs/themes@4.3.1", "", { "dependencies": { "@shikijs/types": "4.3.1" } }, "sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA=="], + + "@shikijs/types": ["@shikijs/types@4.3.1", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + + "@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.11", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw=="], + + "@sveltejs/load-config": ["@sveltejs/load-config@0.1.1", "", {}, "sha512-BXXm+VOH/9X4N7Dd1iZ2MqA1h7M+9i2noI8QYuLDY8QcN2WHYn7D/VK/+IJNfcAmRw7ACNJ538UT9GXIhnBTiA=="], + + "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@7.1.2", "", { "dependencies": { "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.2" }, "peerDependencies": { "svelte": "^5.46.4", "vite": "^8.0.0-beta.7 || ^8.0.0" } }, "sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA=="], + + "@tanstack/svelte-virtual": ["@tanstack/svelte-virtual@3.13.32", "", { "dependencies": { "@tanstack/virtual-core": "3.17.4" }, "peerDependencies": { "svelte": "^3.48.0 || ^4.0.0 || ^5.0.0" } }, "sha512-jA7zRXPAP6Duvdk1mHYAtBNHu5yQo43Wlf8iE3pJ+umj9GdE+dXMzIky3apFBZBP6MOmSr0wT/cHA4f/qHbA3w=="], + + "@tanstack/virtual-core": ["@tanstack/virtual-core@3.17.4", "", {}, "sha512-nGm5KteqxasUdThLc2izl6dHUqLv0LQj7Nuyo5gYalTPf/U8a9ermvsl7reT+6ioBW1l8WfpP/mcU338nLXpqw=="], + + "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], + + "@testing-library/svelte": ["@testing-library/svelte@5.4.2", "", { "dependencies": { "@testing-library/dom": "9.x.x || 10.x.x", "@testing-library/svelte-core": "1.1.3" }, "peerDependencies": { "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", "vite": "*", "vitest": "*" }, "optionalPeers": ["vite", "vitest"] }, "sha512-4o31E4HGo5BU5KwPkulNRocEden+7Tt9JYm9uhln5ajF7DULeyFA46BBWVfKJ8Ms9B3JmOFPTIiVamH7n3KpuQ=="], + + "@testing-library/svelte-core": ["@testing-library/svelte-core@1.1.3", "", { "peerDependencies": { "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0" } }, "sha512-KkMAvXeWorxN2Yn0kdC1lfoAItxpoj4uOWzxK5leDrNxonLvS5nwBFvztrroyTszQ0Wf/EU6iLT8JhY5qcn22g=="], + + "@tsconfig/svelte": ["@tsconfig/svelte@5.0.8", "", {}, "sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + + "@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="], + + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], + + "@types/hast": ["@types/hast@3.0.5", "", { "dependencies": { "@types/unist": "*" } }, "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="], + + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.3", "", {}, "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg=="], + + "@vitest/expect": ["@vitest/expect@4.1.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.10", "", { "dependencies": { "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.10", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q=="], + + "@vitest/runner": ["@vitest/runner@4.1.10", "", { "dependencies": { "@vitest/utils": "4.1.10", "pathe": "^2.0.3" } }, "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw=="], + + "@vitest/spy": ["@vitest/spy@4.1.10", "", {}, "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw=="], + + "@vitest/utils": ["@vitest/utils@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA=="], + + "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], + + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="], + + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + + "axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], + + "brace-expansion": ["brace-expansion@2.1.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + + "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], + + "data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + + "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "devalue": ["devalue@5.8.1", "", {}, "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="], + + "dompurify": ["dompurify@3.4.11", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw=="], + + "entities": ["entities@8.0.0", "", {}, "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA=="], + + "es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="], + + "esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="], + + "esrap": ["esrap@2.3.0", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-GQ/7RN8uOtEfNpzZzBMTzW9JBcX42oaSVtPzdF+6cEL8pqIL094iUpr9jzYGn4O4P/1S60dJ6izyT8F4LYARng=="], + + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + + "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + + "index-to-position": ["index-to-position@1.2.0", "", {}, "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw=="], + + "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + + "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], + + "js-levenshtein": ["js-levenshtein@1.1.6", "", {}, "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="], + + "jsdom": ["jsdom@29.1.1", "", { "dependencies": { "@asamuzakjp/css-color": "^5.1.11", "@asamuzakjp/dom-selector": "^7.1.1", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.3", "@exodus/bytes": "^1.15.0", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.3.5", "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.1", "undici": "^7.25.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.1", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="], + + "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + + "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "marked": ["marked@18.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-MrV5puXBfuiy6wl6DLaq3BtIJQAJToAd5zt/ZKhRfGRAuFPALE7/4Y7jnxRQoEgK/pBgurGqLyAuRgZ2xOjr6w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], + + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], + + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="], + + "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], + + "oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.6", "", { "dependencies": { "oniguruma-parser": "^0.12.2", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA=="], + + "openapi-fetch": ["openapi-fetch@0.17.0", "", { "dependencies": { "openapi-typescript-helpers": "^0.1.0" } }, "sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig=="], + + "openapi-typescript": ["openapi-typescript@7.13.0", "", { "dependencies": { "@redocly/openapi-core": "^1.34.6", "ansi-colors": "^4.1.3", "change-case": "^5.4.4", "parse-json": "^8.3.0", "supports-color": "^10.2.2", "yargs-parser": "^21.1.1" }, "peerDependencies": { "typescript": "^5.x" }, "bin": { "openapi-typescript": "bin/cli.js" } }, "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ=="], + + "openapi-typescript-helpers": ["openapi-typescript-helpers@0.1.0", "", {}, "sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw=="], + + "parse-json": ["parse-json@8.3.0", "", { "dependencies": { "@babel/code-frame": "^7.26.2", "index-to-position": "^1.1.0", "type-fest": "^4.39.1" } }, "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ=="], + + "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "pdfjs-dist": ["pdfjs-dist@5.4.296", "", { "optionalDependencies": { "@napi-rs/canvas": "^0.1.80" } }, "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], + + "playwright": ["playwright@1.61.1", "", { "dependencies": { "playwright-core": "1.61.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ=="], + + "playwright-core": ["playwright-core@1.61.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg=="], + + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], + + "postcss": ["postcss@8.5.19", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ=="], + + "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + + "property-information": ["property-information@7.2.0", "", {}, "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "rolldown": ["rolldown@1.1.5", "", { "dependencies": { "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-x64-msvc": "1.1.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA=="], + + "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], + + "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], + + "shiki": ["shiki@4.3.1", "", { "dependencies": { "@shikijs/core": "4.3.1", "@shikijs/engine-javascript": "4.3.1", "@shikijs/engine-oniguruma": "4.3.1", "@shikijs/langs": "4.3.1", "@shikijs/themes": "4.3.1", "@shikijs/types": "4.3.1", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw=="], + + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + + "std-env": ["std-env@4.2.0", "", {}, "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + + "svelte": ["svelte@5.56.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.10", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.8.1", "esm-env": "^1.2.1", "esrap": "^2.2.9", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-eArsJmvl3xZVuTYD852PzIEdg2wgDdIZ1NEsIPbzAukHwi284B18No4nK2rCO9AwsWUDza4Cjvmoa4HaojTl5g=="], + + "svelte-check": ["svelte-check@4.6.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "@sveltejs/load-config": "0.1.1", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-KhVnDFDSid57mmZtHz8gfW8AAGylOZ0vPnOIzVmAL+urzwK8sBYXRss953gD8T0OdgAQ11mdWhE6uadmtOz8TQ=="], + + "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], + + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + + "tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="], + + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], + + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + + "tldts": ["tldts@7.4.9", "", { "dependencies": { "tldts-core": "^7.4.9" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-3kZ8wQQ/k5DrChD4X4FVvr2D7E5uoRgAqkPyLpSCGUvqOvqu+JEdr3mwMUaVWb+vMHZaKhF5fp2PBigKsui7hA=="], + + "tldts-core": ["tldts-core@7.4.9", "", {}, "sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg=="], + + "tough-cookie": ["tough-cookie@6.0.2", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA=="], + + "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="], + + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + + "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], + + "uri-js-replace": ["uri-js-replace@1.0.1", "", {}, "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "vite": ["vite@8.1.3", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.16", "rolldown": "~1.1.3", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA=="], + + "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], + + "vitest": ["vitest@4.1.10", "", { "dependencies": { "@vitest/expect": "4.1.10", "@vitest/mocker": "4.1.10", "@vitest/pretty-format": "4.1.10", "@vitest/runner": "4.1.10", "@vitest/snapshot": "4.1.10", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.10", "@vitest/browser-preview": "4.1.10", "@vitest/browser-webdriverio": "4.1.10", "@vitest/coverage-istanbul": "4.1.10", "@vitest/coverage-v8": "4.1.10", "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw=="], + + "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="], + + "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@5.0.0", "", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="], + + "whatwg-url": ["whatwg-url@16.0.1", "", { "dependencies": { "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw=="], + + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + + "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], + + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + + "yaml-ast-parser": ["yaml-ast-parser@0.0.43", "", {}, "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="], + + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + } +} diff --git a/web/index.html b/web/index.html new file mode 100644 index 000000000..a7d3fa852 --- /dev/null +++ b/web/index.html @@ -0,0 +1,13 @@ + + + + + + + msgvault + + +
+ + + diff --git a/web/package.json b/web/package.json new file mode 100644 index 000000000..1b368b4b7 --- /dev/null +++ b/web/package.json @@ -0,0 +1,40 @@ +{ + "name": "msgvault-web", + "private": true, + "version": "0.0.0", + "type": "module", + "packageManager": "bun@1.3.14", + "scripts": { + "dev": "vite", + "build": "vite build", + "check": "svelte-check --tsconfig ./tsconfig.json", + "generate": "bun ../scripts/generate-web-client.mjs", + "test": "vitest run", + "test:browser": "playwright test", + "test:e2e": "playwright test e2e conversation-content-frame.spec.ts files-viewer.spec.ts" + }, + "dependencies": { + "@fontsource-variable/figtree": "5.3.0", + "@kenn-io/kit-ui": "github:kenn-io/kit-ui#1e9dc7d45525a471040b72b894432c4956542c38", + "@tanstack/svelte-virtual": "3.13.32", + "dompurify": "3.4.11", + "openapi-fetch": "0.17.0", + "pdfjs-dist": "5.4.296" + }, + "devDependencies": { + "@axe-core/playwright": "4.10.2", + "@playwright/test": "1.61.1", + "@sveltejs/vite-plugin-svelte": "7.1.2", + "@testing-library/svelte": "5.4.2", + "@tsconfig/svelte": "5.0.8", + "es-module-lexer": "2.3.1", + "jsdom": "29.1.1", + "openapi-typescript": "7.13.0", + "postcss": "8.5.19", + "svelte": "5.56.1", + "svelte-check": "4.6.0", + "typescript": "5.9.3", + "vite": "8.1.3", + "vitest": "4.1.10" + } +} diff --git a/web/playwright.config.ts b/web/playwright.config.ts new file mode 100644 index 000000000..9e1c99b61 --- /dev/null +++ b/web/playwright.config.ts @@ -0,0 +1,32 @@ +import { defineConfig } from '@playwright/test'; + +const previewPort = process.env.MSGVAULT_PLAYWRIGHT_PORT ?? '4173'; +const previewURL = `http://127.0.0.1:${previewPort}`; + +export default defineConfig({ + testDir: './tests', + reporter: [ + ['list'], + ['html', { open: 'never', outputFolder: 'playwright-report' }] + ], + // Deep virtualizer fixtures intentionally exercise tens of thousands of rows. + // Serial execution keeps their timing and visual rasterization deterministic + // across developer laptops and the Linux CI runner. + workers: 1, + use: { + baseURL: previewURL, + viewport: { width: 1280, height: 720 }, + locale: 'en-US', + timezoneId: 'UTC', + colorScheme: 'light', + contextOptions: { reducedMotion: 'reduce' }, + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + video: 'retain-on-failure' + }, + webServer: { + command: `bun run build && bunx vite preview --host 127.0.0.1 --port ${previewPort}`, + url: previewURL, + reuseExistingServer: false + } +}); diff --git a/web/public/licenses/Figtree-OFL.txt b/web/public/licenses/Figtree-OFL.txt new file mode 100644 index 000000000..9766a6f81 --- /dev/null +++ b/web/public/licenses/Figtree-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Figtree Project Authors (https://github.com/erikdkennedy/figtree) Figtree-Italic[wght].ttf: Copyright 2022 The Figtree Project Authors (https://github.com/erikdkennedy/figtree) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/web/src/App.svelte b/web/src/App.svelte new file mode 100644 index 000000000..96b6734b4 --- /dev/null +++ b/web/src/App.svelte @@ -0,0 +1,67 @@ + + + + Everything · msgvault + + +{#if session.authMode === 'required'} + +{:else} + + {#snippet settings()} + + {/snippet} + +{/if} diff --git a/web/src/App.test.ts b/web/src/App.test.ts new file mode 100644 index 000000000..6ab2bef59 --- /dev/null +++ b/web/src/App.test.ts @@ -0,0 +1,171 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import App from './App.svelte'; +import { createAPIClient } from './lib/api/client'; +import { createSessionController } from './lib/api/session.svelte'; + +describe('application foundation', () => { + it('mounts the Relationships landmark as the default landing workspace', () => { + const session = createSessionController(async () => + Response.json({ auth_mode: 'loopback', https: false, plain_http_warning: true }) + ); + render(App, { session }); + + expect(screen.getByRole('main', { name: 'Relationships' })).toBeDefined(); + }); + + it('shows login only when bootstrap reports authentication required', async () => { + const session = createSessionController(async () => + Response.json({ auth_mode: 'required', https: false, plain_http_warning: true }) + ); + render(App, { session }); + + expect(screen.getByRole('main', { name: 'Relationships' })).toBeDefined(); + expect(screen.queryByRole('form', { name: 'Log in' })).toBeNull(); + + await session.bootstrap(); + + expect(await screen.findByRole('form', { name: 'Log in' })).toBeDefined(); + expect(screen.queryByRole('main', { name: 'Relationships' })).toBeNull(); + }); + + it('requests health from the same-origin API', async () => { + const fetchFn = vi.fn(async () => + Response.json({ + status: 'ok', + version: 'test' + }) + ); + const client = createAPIClient(fetchFn); + + await client.GET('/api/v1/health'); + + expect(fetchFn).toHaveBeenCalledOnce(); + const request = fetchFn.mock.calls[0]?.[0]; + expect(request).toBeInstanceOf(Request); + expect(new URL((request as Request).url).pathname).toBe('/api/v1/health'); + expect(new URL((request as Request).url).origin).toBe(window.location.origin); + }); + + it('navigates to settings and uses the session-aware mutation client', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (new URL(request.url).pathname === '/api/session') { + return Response.json({ + auth_mode: 'session', + csrf_token: 'csrf-token', + https: true, + plain_http_warning: false + }); + } + if (request.method === 'GET') { + return settingsResponse('system', '"etag-a"'); + } + return settingsResponse('dark', '"etag-b"', true); + }); + const session = createSessionController(fetchFn); + render(App, { session }); + + await session.bootstrap(); + await fireEvent.click(await screen.findByRole('button', { name: 'Settings' })); + await fireEvent.change(await screen.findByLabelText('Theme'), { target: { value: 'dark' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Save settings' })); + + await waitFor(() => expect(requests.some((request) => request.method === 'PATCH')).toBe(true)); + const patch = requests.find((request) => request.method === 'PATCH'); + expect(patch?.headers.get('X-CSRF-Token')).toBe('csrf-token'); + expect(patch?.headers.get('If-Match')).toBe('"etag-a"'); + }); + + it('returns to login when a settings mutation is unauthorized', async () => { + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path === '/api/session') { + return Response.json({ + auth_mode: 'session', + csrf_token: 'csrf-token', + https: true, + plain_http_warning: false + }); + } + if (request.method === 'GET') return settingsResponse('system', '"etag-a"'); + return Response.json({ error: 'unauthorized', message: 'Session expired' }, { status: 401 }); + }); + const session = createSessionController(fetchFn); + render(App, { session }); + + await session.bootstrap(); + await fireEvent.click(await screen.findByRole('button', { name: 'Settings' })); + await fireEvent.change(await screen.findByLabelText('Theme'), { target: { value: 'dark' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Save settings' })); + + expect(await screen.findByRole('form', { name: 'Log in' })).toBeDefined(); + }); + + it('loads appearance once after interactive login while a session override wins', async () => { + window.history.replaceState(null, '', '/'); + sessionStorage.setItem('msgvault.appearance.override', JSON.stringify({ theme: 'light' })); + let settingsRequests = 0; + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path === '/api/session') { + return Response.json({ auth_mode: 'required', https: true, plain_http_warning: false }); + } + if (path === '/api/session/login') { + return Response.json({ + auth_mode: 'session', csrf_token: 'csrf-token', https: true, plain_http_warning: false + }); + } + if (path === '/api/v1/settings') { + settingsRequests += 1; + return Response.json({ + settings: [ + { key: 'web.theme', value: { string: 'dark' } }, + { key: 'web.density', value: { string: 'comfortable' } } + ], + pending_restart: false + }); + } + if (path === '/api/v1/explore') { + return Response.json({ rows: [], total_count: 0, cache_revision: 'login', search_provenance: {} }); + } + return Response.json({}, { status: 404 }); + }); + const session = createSessionController(fetchFn); + render(App, { session }); + expect(await screen.findByRole('form', { name: 'Log in' })).toBeDefined(); + + await fireEvent.input(screen.getByLabelText('API key'), { target: { value: 'test-key' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Log in' })); + + await waitFor(() => expect(document.documentElement.dataset.density).toBe('comfortable')); + expect(document.documentElement.dataset.theme).toBe('light'); + expect(settingsRequests).toBe(1); + await new Promise((resolve) => setTimeout(resolve)); + expect(settingsRequests).toBe(1); + }); +}); + +function settingsResponse(theme: string, etag: string, pendingRestart = false): Response { + return Response.json( + { + settings: [ + { + key: 'web.theme', + group: 'browser', + kind: 'string', + value: { string: theme }, + options: ['system', 'light', 'dark'], + restart_required: true + } + ], + pending_restart: pendingRestart + }, + { headers: { ETag: etag } } + ); +} diff --git a/web/src/app.css b/web/src/app.css new file mode 100644 index 000000000..421487bc8 --- /dev/null +++ b/web/src/app.css @@ -0,0 +1,33 @@ +@import '@fontsource-variable/figtree'; +@import '@kenn-io/kit-ui/theme.css'; +@import './styles/tokens.css'; +@import './styles/theme-light.css'; +@import './styles/theme-dark.css'; +@import './styles/density.css'; + +* { + box-sizing: border-box; +} + +body { + min-width: 320px; + min-height: 100vh; + margin: 0; + background: var(--bg-primary); + color: var(--text-primary); + font-family: var(--font-sans); + font-size: var(--font-size-root); + font-synthesis: none; + text-rendering: optimizeLegibility; +} + +button, +input, +select, +textarea { + font: inherit; +} + +.kit-command-palette__option.highlighted { + box-shadow: inset 3px 0 var(--accent-blue); +} diff --git a/web/src/lib/a11y/contrast.test.ts b/web/src/lib/a11y/contrast.test.ts new file mode 100644 index 000000000..32d4471b1 --- /dev/null +++ b/web/src/lib/a11y/contrast.test.ts @@ -0,0 +1,435 @@ +import { existsSync, readFileSync, readdirSync } from 'node:fs'; +import { basename, dirname, extname, join, resolve } from 'node:path'; +import { describe, expect, it } from 'vitest'; + +type Theme = 'light' | 'dark'; +type Pair = { foreground: string; background: string; source: string; threshold: number }; + +const root = process.cwd(); +const sourceFiles = collect(join(root, 'src')).filter((path) => ['.css', '.svelte'].includes(extname(path))); +const kitComponentFiles = discoverKitComponents(sourceFiles); +const componentCSS = [...sourceFiles, ...kitComponentFiles] + .flatMap((path) => cssBlocks(path).map((css) => ({ path, css }))); +const sharedCSS = [ + readFileSync(join(root, 'node_modules/@kenn-io/kit-ui/src/lib/theme.css'), 'utf8'), + readFileSync(join(root, 'src/styles/tokens.css'), 'utf8'), + readFileSync(join(root, 'src/styles/density.css'), 'utf8') +].join('\n'); + +describe.each(['light', 'dark'])('%s rendered semantic color graph', (theme) => { + const graph = parseTokens(`${sharedCSS}\n${readFileSync(join(root, `src/styles/theme-${theme}.css`), 'utf8')}`); + + it('loads every consumed kit-ui component style block into the rendered graph', () => { + const kitDirectory = join(root, 'node_modules/@kenn-io/kit-ui/src/lib/components'); + const discovered = new Set(componentCSS.filter(({ path }) => path.startsWith(kitDirectory)).map(({ path }) => path)); + + expect(kitComponentFiles.length).toBeGreaterThan(0); + expect(kitComponentFiles.map((path) => basename(path))).toEqual(expect.arrayContaining(['Button.svelte', 'CommandPalette.svelte'])); + expect([...discovered].sort()).toEqual(kitComponentFiles.filter((path) => cssBlocks(path).length > 0).sort()); + }); + + it('includes styles from known transitive kit-ui component dependencies', () => { + expect(kitComponentFiles.map((path) => basename(path))).toEqual(expect.arrayContaining([ + 'SearchInput.svelte', + 'TextInput.svelte', + 'Modal.svelte', + 'IconButton.svelte' + ])); + }); + + it('defines every semantic variable consumed by application CSS', () => { + const missing = new Set(); + for (const { css } of componentCSS) { + for (const declaration of declarations(css)) { + if (!isSemanticProperty(declaration.property)) continue; + for (const reference of variableReferences(declaration.value)) { + if (!graph.has(reference.name) && !reference.hasFallback) missing.add(reference.name); + } + } + } + expect([...missing].sort()).toEqual([]); + }); + + it('meets contrast for every rendered component-local semantic pair', () => { + const failures = renderedPairs(graph).flatMap((pair) => { + const background = resolveColor(graph, pair.background); + const ratio = contrast(resolveColor(graph, pair.foreground, new Set(), background), background); + return ratio + 1e-9 < pair.threshold + ? [`${pair.source}: ${pair.foreground} on ${pair.background} = ${ratio.toFixed(2)}:1 < ${pair.threshold}:1`] + : []; + }); + expect(failures).toEqual([]); + }); + + it('treats inset box-shadow color layers as structural indicators only', () => { + const pairs = renderedPairs(graph); + expect(pairs.some(({ source }) => source.includes('FilesWorkspace.svelte .files-grid:focus-visible'))).toBe(true); + expect(pairs.some(({ source }) => source.includes('EverythingTable.svelte .data-row--selected'))).toBe(true); + expect(pairs).toEqual(expect.arrayContaining([ + expect.objectContaining({ + source: expect.stringContaining('EverythingTable.svelte .data-row--selected'), + foreground: 'var(--text-secondary)' + }) + ])); + expect(pairs.some(({ foreground }) => foreground === 'var(--shadow-md)')).toBe(false); + }); +}); + +describe('density token authority', () => { + const graph = parseTokens(sharedCSS); + + it('keeps the CSS-owned row heights within the approved ranges', () => { + const css = readFileSync(join(root, 'src/styles/density.css'), 'utf8'); + const compact = parseRule(css, ':root[data-density="compact"]'); + const comfortable = parseRule(css, ':root[data-density="comfortable"]'); + expect(px(compact, '--row-height')).toBeGreaterThanOrEqual(34); + expect(px(compact, '--row-height')).toBeLessThanOrEqual(38); + expect(px(comfortable, '--row-height')).toBeGreaterThanOrEqual(44); + expect(px(comfortable, '--row-height')).toBeLessThanOrEqual(48); + expect(graph.has('--row-height')).toBe(true); + }); +}); + +describe('Settings semantic styling', () => { + it('uses theme roles instead of light-only raw colors', () => { + const css = cssBlocks(join(root, 'src/lib/components/settings/SettingsWorkspace.svelte')).join('\n'); + expect(css.match(/#[0-9a-f]{3,8}\b/gi) ?? []).toEqual([]); + }); +}); + +function renderedPairs(graph: Map): Pair[] { + const pairs = new Map(); + const add = (foreground: string, background: string, source: string, threshold: number) => { + if (foreground === background) return; + if ([...variables(foreground), ...variables(background)].some((token) => !graph.has(token))) return; + pairs.set(`${foreground}\0${background}\0${source}\0${threshold}`, { foreground, background, source, threshold }); + }; + for (const { path, css } of componentCSS) { + const parsedRules = rules(css); + for (const rule of parsedRules) { + const ownForegrounds = rule.declarations + .filter(({ property }) => property === 'color') + .map(({ value }) => colorExpression(value)) + .filter(isColorExpression); + const backgrounds = rule.declarations + .filter(({ property }) => property === 'background' || property === 'background-color') + .map(({ value }) => backgroundExpression(value)) + .filter(isColorExpression); + const boundaries = rule.declarations + .filter(({ property }) => isBoundaryProperty(property)) + .map(({ value }) => colorExpression(value)) + .filter(isColorExpression); + const shadowIndicators = rule.declarations + .filter(({ property }) => property === 'box-shadow') + .flatMap(({ value }) => structuralShadowColors(value)); + const surfaces = backgrounds.length > 0 ? backgrounds : ['var(--bg-primary)', 'var(--bg-surface)']; + const foregrounds = ownForegrounds.length > 0 + ? ownForegrounds + : backgrounds.length > 0 ? inheritedForegrounds(rule.selector, parsedRules) : []; + for (const foreground of foregrounds) for (const background of surfaces) { + add(foreground, background, `${relative(path)} ${rule.selector}`, 4.5); + } + if (isStructuralSelector(rule.selector)) { + for (const boundary of boundaries) for (const background of surfaces) { + add(boundary, background, `${relative(path)} ${rule.selector}`, 3); + } + } + for (const indicator of shadowIndicators) for (const background of surfaces) { + add(indicator, background, `${relative(path)} ${rule.selector}`, 3); + } + } + } + for (const [name] of graph) { + const match = /^(--status-[\w-]+)-ink$/.exec(name); + if (match && graph.has(`${match[1]}-bg`)) { + add(`var(${name})`, `var(${match[1]}-bg)`, 'semantic status recipe', 4.5); + } + } + return [...pairs.values()]; +} + +function inheritedForegrounds( + selector: string, + parsedRules: ReturnType +): string[] { + const bases = new Set(); + for (const part of selector.split(',').map((value) => value.trim())) { + const withoutPseudo = part.replace(/:{1,2}[\w-]+(?:\([^)]*\))?/g, ''); + bases.add(withoutPseudo); + for (const match of withoutPseudo.matchAll(/\.([\w-]+)--[\w-]+/g)) { + bases.add(withoutPseudo.replace(match[0], `.${match[1]}`)); + } + } + const inherited: string[] = []; + for (const candidate of parsedRules) { + if (!candidate.selector.split(',').map((value) => value.trim()).some((value) => bases.has(value))) continue; + inherited.push(...candidate.declarations + .filter(({ property }) => property === 'color') + .map(({ value }) => colorExpression(value)) + .filter(isColorExpression)); + } + return inherited; +} + +function isStructuralSelector(selector: string): boolean { + return /(?::focus(?:-visible)?|--selected\b|\.selected\b|\.highlighted\b|\[aria-(?:current|selected)=)/.test(selector); +} + +function collect(directory: string): string[] { + return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { + const path = join(directory, entry.name); + return entry.isDirectory() ? collect(path) : [path]; + }); +} + +function discoverKitComponents(paths: string[]): string[] { + const kitDirectory = join(root, 'node_modules/@kenn-io/kit-ui/src/lib'); + const entry = join(kitDirectory, 'index.ts'); + const exports = exportedKitModules(entry, kitDirectory); + const roots = new Set(); + for (const path of paths) { + const contents = readFileSync(path, 'utf8'); + for (const match of contents.matchAll(/import\s*\{([\s\S]*?)\}\s*from\s*['"]@kenn-io\/kit-ui['"]/g)) { + for (const imported of match[1]!.split(',')) { + const name = imported.trim().replace(/^type\s+/, '').split(/\s+as\s+/)[0]; + if (!name) continue; + const module = exports.get(name); + if (module) roots.add(module); + } + } + } + + const visited = new Set(); + const components = new Set(); + const visit = (path: string): void => { + if (visited.has(path)) return; + visited.add(path); + if (extname(path) === '.svelte') components.add(path); + const contents = readFileSync(path, 'utf8'); + for (const specifier of moduleSpecifiers(contents)) { + const dependency = resolveKitModule(specifier, path, kitDirectory); + if (dependency) visit(dependency); + } + }; + for (const path of roots) visit(path); + return [...components].sort(); +} + +function exportedKitModules(entry: string, kitDirectory: string): Map { + const modules = new Map(); + const contents = readFileSync(entry, 'utf8'); + for (const match of contents.matchAll(/export\s+(?:type\s+)?\{([\s\S]*?)\}\s*from\s*['"]([^'"]+)['"]/g)) { + const module = resolveKitModule(match[2]!, entry, kitDirectory); + if (!module) continue; + for (const binding of match[1]!.split(',')) { + const parts = binding.trim().replace(/^type\s+/, '').split(/\s+as\s+/); + const exported = parts[1] ?? parts[0]; + if (exported) modules.set(exported, module); + } + } + for (const match of contents.matchAll(/export\s*\*\s*from\s*['"]([^'"]+)['"]/g)) { + const barrel = resolveKitModule(match[1]!, entry, kitDirectory); + if (!barrel) continue; + for (const [name, module] of exportedKitModules(barrel, kitDirectory)) modules.set(name, module); + } + return modules; +} + +function moduleSpecifiers(contents: string): string[] { + return [...contents.matchAll(/(?:import|export)\s+(?:type\s+)?(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g)] + .map((match) => match[1]!); +} + +function resolveKitModule(specifier: string, importer: string, kitDirectory: string): string | undefined { + if (!specifier.startsWith('.')) return undefined; + const unresolved = resolve(dirname(importer), specifier); + if (!unresolved.startsWith(`${kitDirectory}/`) && unresolved !== kitDirectory) return undefined; + const candidates = [ + unresolved, + unresolved.replace(/\.js$/, '.ts'), + `${unresolved}.svelte`, + `${unresolved}.ts`, + `${unresolved}.js`, + join(unresolved, 'index.ts'), + join(unresolved, 'index.js') + ]; + return candidates.find((candidate) => existsSync(candidate)); +} + +function cssBlocks(path: string): string[] { + const contents = readFileSync(path, 'utf8'); + if (extname(path) === '.css') return [contents]; + return [...contents.matchAll(/]*)?>([\s\S]*?)<\/style>/g)].map((match) => match[1]!); +} + +function rules(css: string): Array<{ selector: string; declarations: ReturnType }> { + const plain = css.replace(/\/\*[\s\S]*?\*\//g, ''); + return [...plain.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map((match) => ({ + selector: match[1]!.trim().replace(/\s+/g, ' '), + declarations: declarations(match[2]!) + })); +} + +function declarations(css: string): Array<{ property: string; value: string }> { + return [...css.matchAll(/([\w-]+)\s*:\s*([^;{}]+);/g)].map((match) => ({ + property: match[1]!.toLowerCase(), value: match[2]!.trim() + })); +} + +function isSemanticProperty(property: string): boolean { + return /^(color|background(?:-color)?|fill|stroke|box-shadow)$/.test(property) || isBoundaryProperty(property); +} + +function isBoundaryProperty(property: string): boolean { + return /^(border|border-color|border-(?:top|right|bottom|left)(?:-color)?|outline|outline-color)$/.test(property); +} + +function variables(value: string): string[] { + return variableReferences(value).map(({ name }) => name); +} + +function variableReferences(value: string): Array<{ name: string; hasFallback: boolean }> { + return [...value.matchAll(/var\((--[\w-]+)/g)].map((match) => { + let depth = 1; + let hasFallback = false; + const start = match.index! + match[0].length; + for (let index = start; index < value.length && depth > 0; index += 1) { + if (value[index] === '(') depth += 1; + else if (value[index] === ')') depth -= 1; + else if (value[index] === ',' && depth === 1) hasFallback = true; + } + return { name: match[1]!, hasFallback }; + }); +} + +function parseTokens(css: string): Map { + const tokens = new Map(); + for (const match of css.matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)) tokens.set(match[1]!, match[2]!.trim()); + return tokens; +} + +function parseRule(css: string, selector: string): Map { + const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const match = new RegExp(`${escaped}\\s*\\{([^}]+)\\}`).exec(css); + expect(match, `missing ${selector}`).not.toBeNull(); + return parseTokens(match![1]!); +} + +function colorExpression(value: string): string { + const mix = value.match(/color-mix\([\s\S]+\)/)?.[0]; + if (mix) return mix; + if (/^var\([\s\S]+\)$/.test(value.trim())) return value.trim(); + const token = variables(value).at(-1); + if (token) return `var(${token})`; + return value.trim(); +} + +function backgroundExpression(value: string): string { + return colorExpression(splitTopLevel(value, ',').at(-1) ?? value); +} + +// Only inset layers are component boundaries/focus/selection indicators. +// Non-inset shadow recipes provide elevation and are intentionally excluded +// from WCAG non-text contrast pairing against the element surface. +function structuralShadowColors(value: string): string[] { + return splitTopLevel(value, ',') + .filter((layer) => /\binset\b/.test(layer)) + .map(colorExpression) + .filter(isColorExpression); +} + +function isColorExpression(value: string): boolean { + return /^(?:var\(--|color-mix\(|rgb\(|#[0-9a-f]{3}(?:[0-9a-f]{3})?\b)/i.test(value); +} + +function splitTopLevel(value: string, separator: string): string[] { + const parts: string[] = []; + let depth = 0; + let start = 0; + for (let index = 0; index < value.length; index += 1) { + if (value[index] === '(') depth += 1; + else if (value[index] === ')') depth -= 1; + else if (value[index] === separator && depth === 0) { + parts.push(value.slice(start, index).trim()); + start = index + 1; + } + } + parts.push(value.slice(start).trim()); + return parts; +} + +function resolveColor( + tokens: Map, expression: string, seen = new Set(), underlay?: string +): string { + if (expression.trim() === 'transparent') return underlay ?? resolveColor(tokens, 'var(--bg-surface)', seen); + const variable = /^var\((--[\w-]+)(?:\s*,\s*([\s\S]+))?\)$/.exec(expression.trim()); + if (variable) { + const name = variable[1]!; + if (seen.has(name)) throw new Error(`cyclic token ${name}`); + seen.add(name); + const value = tokens.get(name); + if (!value) { + if (variable[2]) return resolveColor(tokens, variable[2], seen, underlay); + throw new Error(`missing token ${name}`); + } + return resolveColor(tokens, colorExpression(value), seen, underlay); + } + const mixHeader = /^color-mix\(\s*in srgb\s*,/i.exec(expression.trim()); + if (mixHeader && expression.trim().endsWith(')')) { + const parts = splitTopLevel(expression.trim().slice(mixHeader[0].length, -1), ','); + if (parts.length !== 2) throw new Error(`expected two color-mix operands, got ${expression}`); + const operands = parts.map((part) => { + const weighted = /^(.*?)(?:\s+(\d+(?:\.\d+)?)%)?$/.exec(part)!; + return { color: weighted[1]!.trim(), weight: weighted[2] ? Number(weighted[2]) / 100 : undefined }; + }); + const left = rgb(resolveColor(tokens, operands[0]!.color, new Set(seen), underlay)); + const right = rgb(resolveColor(tokens, operands[1]!.color, new Set(seen), underlay)); + const leftWeight = operands[0]!.weight ?? (operands[1]!.weight === undefined ? 0.5 : 1 - operands[1]!.weight); + const rightWeight = operands[1]!.weight ?? 1 - leftWeight; + return hex(left.map((channel, index) => channel * leftWeight + right[index]! * rightWeight)); + } + const functional = /^rgb\(\s*(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)(?:\s*\/\s*(\d+(?:\.\d+)?)%)?\s*\)$/i.exec(expression.trim()); + if (functional) { + const channels = functional.slice(1, 4).map(Number); + const alpha = functional[4] === undefined ? 1 : Number(functional[4]) / 100; + if (alpha === 1) return hex(channels); + const base = rgb(underlay ?? resolveColor(tokens, 'var(--bg-surface)', new Set(seen))); + return hex(channels.map((channel, index) => channel * alpha + base[index]! * (1 - alpha))); + } + if (/^#[0-9a-f]{3}$/i.test(expression)) { + return `#${[...expression.slice(1)].map((digit) => `${digit}${digit}`).join('')}`; + } + if (!/^#[0-9a-f]{6}$/i.test(expression)) { + throw new Error(`expected color expression, got ${expression}`); + } + return expression; +} + +function px(tokens: Map, name: string): number { + const value = tokens.get(name); + expect(value).toMatch(/^\d+(?:\.\d+)?px$/); + return Number.parseFloat(value!); +} + +function contrast(foreground: string, background: string): number { + const values = [luminance(foreground), luminance(background)].sort((a, b) => b - a); + return (values[0]! + 0.05) / (values[1]! + 0.05); +} + +function luminance(color: string): number { + const channels = [1, 3, 5].map((offset) => Number.parseInt(color.slice(offset, offset + 2), 16) / 255); + const [r, g, b] = channels.map((channel) => channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4); + return 0.2126 * r! + 0.7152 * g! + 0.0722 * b!; +} + +function rgb(color: string): number[] { + return [1, 3, 5].map((offset) => Number.parseInt(color.slice(offset, offset + 2), 16)); +} + +function hex(channels: number[]): string { + return `#${channels.map((channel) => Math.round(channel).toString(16).padStart(2, '0')).join('')}`; +} + +function relative(path: string): string { + return path.slice(root.length + 1); +} diff --git a/web/src/lib/a11y/font-license.test.ts b/web/src/lib/a11y/font-license.test.ts new file mode 100644 index 000000000..92f62fd22 --- /dev/null +++ b/web/src/lib/a11y/font-license.test.ts @@ -0,0 +1,13 @@ +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; + +describe('bundled Figtree license', () => { + it('redistributes the authoritative Fontsource notice with production assets', () => { + const upstream = readFileSync('node_modules/@fontsource-variable/figtree/LICENSE', 'utf8'); + const distributed = readFileSync('public/licenses/Figtree-OFL.txt', 'utf8'); + + expect(distributed).toBe(upstream); + expect(distributed).toContain('SIL OPEN FONT LICENSE Version 1.1'); + expect(distributed).toContain('Copyright 2022 The Figtree Project Authors'); + }); +}); diff --git a/web/src/lib/a11y/theme-parity.test.ts b/web/src/lib/a11y/theme-parity.test.ts new file mode 100644 index 000000000..3877e0078 --- /dev/null +++ b/web/src/lib/a11y/theme-parity.test.ts @@ -0,0 +1,102 @@ +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { describe, expect, it } from 'vitest'; + +const root = process.cwd(); + +// Strip comments before any block/declaration matching below: a comment +// immediately preceding a selector (e.g. `/* note */\n:root`) would +// otherwise be captured into the selector text by the block-matching regex, +// and a comment inside a declaration's value could otherwise defeat the +// value-shape check. +function stripCSSComments(css: string): string { + return css.replace(/\/\*[\s\S]*?\*\//g, ''); +} + +function definedTokenNames(cssPath: string): string[] { + const css = stripCSSComments(readFileSync(join(root, cssPath), 'utf8')); + return [...new Set([...css.matchAll(/--[\w-]+(?=\s*:)/g)].map((match) => match[0]))].sort(); +} + +// Heuristic: a custom property counts as a "colour token" if its declared +// value looks like a literal colour (hex, rgb()/rgba(), hsl()/hsla(), +// oklch(), or color-mix()) OR its name matches the naming convention the +// theme files already use for colour roles (--bg-*, --text-*, --border-*, +// --control-*, --selected-*, --status-*, --accent-*, plus the handful of +// one-off ink/overlay/focus names below). Matching on both value shape and +// name catches tokens that reference another custom property (no literal +// colour in the value) as well as tokens whose value is a literal colour. +const COLOR_VALUE_PATTERN = /(#[0-9a-f]{3,8}\b|\brgba?\(|\bhsla?\(|\boklch\(|\bcolor-mix\()/i; +const COLOR_NAME_PATTERN = + /^--(bg|text|border|control|selected|status|accent)-|^--(active-ink|link-ink|focus-color|artifact-ink|overlay-bg|status-waiting)$/; + +function unscopedRootColorTokensFromCSS(css: string): Array<{ name: string; value: string }> { + const found: Array<{ name: string; value: string }> = []; + for (const block of stripCSSComments(css).matchAll(/([^{}]+)\{([^{}]*)\}/g)) { + const selectors = block[1]!.split(',').map((selector) => selector.trim()); + if (!selectors.includes(':root')) continue; + // CSS allows the last declaration in a block to omit its trailing + // semicolon; the block's own captured content never includes the + // closing brace, so append one if missing rather than silently skipping + // that declaration. + const body = block[2]!.trim().replace(/;?$/, ';'); + for (const declaration of body.matchAll(/(--[\w-]+)\s*:\s*([^;]+);/g)) { + const [, name, value] = declaration as unknown as [string, string, string]; + if (COLOR_VALUE_PATTERN.test(value) || COLOR_NAME_PATTERN.test(name)) { + found.push({ name, value: value.trim() }); + } + } + } + return found; +} + +function unscopedRootColorTokens(cssPath: string): Array<{ name: string; value: string }> { + return unscopedRootColorTokensFromCSS(readFileSync(join(root, cssPath), 'utf8')); +} + +describe('theme token parity', () => { + it('defines the identical custom-property set in light and dark themes', () => { + expect(definedTokenNames('src/styles/theme-dark.css')) + .toEqual(definedTokenNames('src/styles/theme-light.css')); + }); + + it('overrides color-scheme for dark mode', () => { + const tokens = readFileSync(join(root, 'src/styles/tokens.css'), 'utf8'); + expect(tokens).toMatch(/:root\[data-theme='dark'\]\s*\{[^}]*color-scheme:\s*dark/); + }); + + it('declares no colour token on an unscoped :root block', () => { + // A bare `:root` selector applies regardless of `data-theme`, so any + // colour token declared there silently leaks its value into every theme + // that forgets to override it. Colour tokens must live only in + // `:root[data-theme='light']` / `:root[data-theme='dark']` (or a shared + // selector like `:root:not([data-theme='dark'])`), never on a bare + // `:root` block. + for (const file of ['src/styles/theme-light.css', 'src/styles/theme-dark.css', 'src/styles/tokens.css']) { + expect(unscopedRootColorTokens(file), file).toEqual([]); + } + }); + + it('catches an unscoped colour token whose :root selector is preceded by a comment', () => { + // The block-matching regex captures everything up to the opening brace + // into the selector group, including a leading comment — without + // stripping comments first, `selectors.includes(':root')` never matches + // a trimmed string like `/* note */\n:root`. + const css = '/* leaked token */\n:root {\n --bg-surface: #ffffff;\n}\n'; + expect(unscopedRootColorTokensFromCSS(css)).toEqual([{ name: '--bg-surface', value: '#ffffff' }]); + }); + + it('catches an unscoped colour token that is the last declaration with no trailing semicolon', () => { + // CSS allows the final declaration in a block to omit the semicolon; + // the declaration-matching regex requires one, so without normalizing + // the block's own captured text first, this declaration is silently + // skipped rather than flagged. + const css = ':root {\n --text-primary: #000000\n}\n'; + expect(unscopedRootColorTokensFromCSS(css)).toEqual([{ name: '--text-primary', value: '#000000' }]); + }); + + it('ignores a colour-shaped value inside a comment on an unscoped :root block', () => { + const css = ':root {\n /* was #ff0000 */\n --accent-primary: #00ff00;\n}\n'; + expect(unscopedRootColorTokensFromCSS(css)).toEqual([{ name: '--accent-primary', value: '#00ff00' }]); + }); +}); diff --git a/web/src/lib/api/client.ts b/web/src/lib/api/client.ts new file mode 100644 index 000000000..3d2737245 --- /dev/null +++ b/web/src/lib/api/client.ts @@ -0,0 +1,54 @@ +import createClient from 'openapi-fetch'; + +import type { paths } from './generated/schema'; + +class SameOriginRequest extends Request { + constructor(input: RequestInfo | URL, init?: RequestInit) { + const requestInput = + typeof input === 'string' && input.startsWith('/') && typeof document !== 'undefined' + ? new URL(input, document.baseURI) + : input; + + super(requestInput, init); + } +} + +export function createAPIClient(fetchFn: typeof fetch = fetch) { + return createSessionAwareAPIClient(fetchFn); +} + +type CSRFTokenProvider = () => string | undefined; +type UnauthorizedHandler = () => void; + +export function createSessionAwareAPIClient( + fetchFn: typeof fetch = fetch, + csrfToken: CSRFTokenProvider = () => undefined, + onUnauthorized: UnauthorizedHandler = () => undefined +) { + const sessionFetch: typeof fetch = async (input, init) => { + const original = input instanceof Request ? input : new SameOriginRequest(input, init); + let request = original; + const method = request.method.toUpperCase(); + const unsafe = !['GET', 'HEAD', 'OPTIONS', 'TRACE'].includes(method); + const sameOrigin = + typeof window === 'undefined' || new URL(request.url).origin === window.location.origin; + const token = csrfToken(); + if (unsafe && sameOrigin && token) { + const headers = new Headers(request.headers); + headers.set('X-CSRF-Token', token); + request = new Request(request, { headers }); + } + + const response = await fetchFn(request); + if (response.status === 401) onUnauthorized(); + return response; + }; + + return createClient({ + baseUrl: '', + fetch: sessionFetch, + Request: SameOriginRequest + }); +} + +export type APIClient = ReturnType; diff --git a/web/src/lib/api/generated/schema.d.ts b/web/src/lib/api/generated/schema.d.ts new file mode 100644 index 000000000..58ea315cd --- /dev/null +++ b/web/src/lib/api/generated/schema.d.ts @@ -0,0 +1,8883 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/api/ping": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Daemon discovery ping */ + get: operations["daemonPing"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/session": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get browser authentication status */ + get: operations["getSession"]; + put?: never; + post?: never; + /** Delete the current browser session */ + delete: operations["logoutSession"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/session/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Create an in-memory browser session */ + post: operations["loginSession"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/accounts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List scheduler-configured accounts (with sync schedules); use /cli/accounts for all archived sources */ + get: operations["listAccounts"]; + put?: never; + /** Add an account */ + post: operations["addAccount"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/aggregates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get aggregate rows */ + get: operations["getAggregates"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/aggregates/sub": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get nested aggregate rows */ + get: operations["getSubAggregates"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/attachments/{hash}/content": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Download attachment content by SHA-256 hash */ + get: operations["getAttachmentContent"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/attachments/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get attachment metadata */ + get: operations["getAttachment"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/auth/token/{email}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Upload an OAuth token */ + post: operations["uploadToken"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/backup/freeze/begin": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Begin a backup freeze window */ + post: operations["beginBackupFreeze"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/backup/freeze/end": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** End a backup freeze window */ + post: operations["endBackupFreeze"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/account": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Update an account for CLI use */ + post: operations["updateCLIAccount"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/accounts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List accounts for CLI output */ + get: operations["listCLIAccounts"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/add-calendar/plan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Plan CLI Calendar account setup */ + post: operations["planCLIAddCalendar"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get one attachment for CLI export */ + get: operations["getCLIAttachment"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/build-cache": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Build the CLI analytics cache */ + post: operations["buildCLICache"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/cache-stats": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get CLI-compatible analytics cache statistics */ + get: operations["getCLICacheStats"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/collection": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get one collection for CLI output */ + get: operations["getCLICollection"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/collections": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List collections for CLI output */ + get: operations["listCLICollections"]; + put?: never; + /** Create a collection for CLI use */ + post: operations["createCLICollection"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/collections/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Delete a CLI collection */ + delete: operations["deleteCLICollection"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/collections/{name}/sources": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Remove accounts from a CLI collection */ + delete: operations["removeCLICollectionSources"]; + options?: never; + head?: never; + /** Add accounts to a CLI collection */ + patch: operations["addCLICollectionSources"]; + trace?: never; + }; + "/api/v1/cli/deduplicate/plan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Plan interactive deduplication for CLI use */ + post: operations["planCLIDeduplicate"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/delete-deduped": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Delete dedup-hidden messages for CLI use */ + post: operations["executeCLIDeleteDeduped"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/delete-deduped/plan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Plan deletion of dedup-hidden messages for CLI use */ + post: operations["planCLIDeleteDeduped"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/delete-staged/plan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Plan CLI staged deletion execution */ + post: operations["planCLIDeleteStaged"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/deletion-manifests": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Create a staged deletion manifest */ + post: operations["createCLIDeletionManifest"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/embeddings/plan": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Plan CLI embeddings management */ + post: operations["planCLIEmbeddings"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/identities": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List confirmed account identities */ + get: operations["listCLIIdentities"]; + put?: never; + /** Add a confirmed identifier to an account identity */ + post: operations["addCLIIdentity"]; + /** Remove a confirmed identifier from an account identity */ + delete: operations["removeCLIIdentity"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/init-db": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Initialize the archive for CLI use */ + post: operations["initCLIArchive"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/message": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get one message for CLI output */ + get: operations["getCLIMessage"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/message/raw": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get one raw message for CLI export */ + get: operations["getCLIMessageRaw"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/rebuild-fts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Rebuild the CLI full-text search index */ + post: operations["rebuildCLIFTS"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/repair-encoding": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Repair CLI archive encoding */ + post: operations["repairEncodingCLI"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/run": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Run an allowlisted CLI command */ + post: operations["runCLI"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Search messages for CLI output */ + get: operations["searchCLI"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/stats": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get CLI-compatible archive statistics */ + get: operations["getCLIStats"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/sync": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Run CLI incremental sync */ + post: operations["syncCLI"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/sync-full": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Run CLI full sync */ + post: operations["syncFullCLI"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/cli/verify": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Verify the CLI archive against Gmail */ + post: operations["verifyCLI"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/conversations/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get a bounded containing conversation */ + get: operations["getConversation"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/deletions": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List staged deletion manifests */ + get: operations["listDeletions"]; + put?: never; + /** Stage messages for deletion */ + post: operations["stageDeletion"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/deletions/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Inspect a staged deletion manifest */ + get: operations["getDeletion"]; + put?: never; + post?: never; + /** Cancel a staged deletion manifest */ + delete: operations["cancelDeletion"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/domains/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Search analytical domains */ + post: operations["searchDomains"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/domains/{domain}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get one analytical domain */ + get: operations["getDomain"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/domains/{domain}/files/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Search one domain's analytical files */ + post: operations["searchDomainFiles"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/domains/{domain}/summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Get one domain's contextual analytical summary */ + post: operations["getDomainContextSummary"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/domains/{domain}/timeline": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Get one domain's canonical activity timeline */ + post: operations["getDomainTimeline"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/explore": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Explore canonical archive entries */ + post: operations["explore"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/explore/files": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** List bounded chronological attachment facts */ + post: operations["listExploreFiles"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/explore/groups": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Group canonical archive entries */ + post: operations["exploreGroups"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/explore/match-counts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Count exact lexical matches in visible rows */ + post: operations["countExploreMatches"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/explore/preflight": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Preflight a revision-pinned analytical selection */ + post: operations["preflightExploreSelection"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/files/groups": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Group analytical files */ + post: operations["groupFiles"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/files/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Search analytical files */ + post: operations["searchFiles"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/files/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get authoritative file metadata */ + get: operations["getFile"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/files/{id}/content": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Download content for one authoritative file row */ + get: operations["getFileContent"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get authenticated health details */ + get: operations["getHealth"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/identity/links": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Assert two participants are the same person */ + post: operations["linkIdentityParticipants"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/identity/unlinks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Remove a link edge between two participants */ + post: operations["unlinkIdentityParticipants"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/integrations/tasks/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Search tasks in the configured project */ + get: operations["searchIntegrationTasks"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/integrations/tasks/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get task integration capability status */ + get: operations["getTaskIntegrationStatus"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/integrations/tasks/test": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Test task integration discovery, authentication, capabilities, and project */ + post: operations["testTaskIntegration"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/messages": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List messages */ + get: operations["listMessages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/messages/filter": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List filtered messages */ + get: operations["filterMessages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/messages/gmail-ids": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Gmail message IDs matching a filter */ + get: operations["getGmailIDsByFilter"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/messages/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get one message */ + get: operations["getMessage"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/messages/{id}/inline": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get an inline MIME part */ + get: operations["getMessageInlinePart"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/messages/{id}/tasks": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List tasks linked to an archived email */ + get: operations["listMessageTasks"]; + put?: never; + /** Create or link a task for an archived email */ + post: operations["createOrLinkMessageTask"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/messages/{id}/tasks/{task_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Unlink a task from an archived email */ + delete: operations["unlinkMessageTask"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/people/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Search analytical people */ + post: operations["searchPeople"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/people/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get one analytical person */ + get: operations["getPerson"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/people/{id}/files/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Search one person's analytical files */ + post: operations["searchPersonFiles"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/people/{id}/summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Get one person's contextual analytical summary */ + post: operations["getPersonContextSummary"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/people/{id}/timeline": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Get one person's canonical activity timeline */ + post: operations["getPersonTimeline"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/query": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Run an aggregate query */ + post: operations["runQuery"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/relationships": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Rank counterparts by reciprocity-weighted interaction */ + post: operations["listRelationships"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/relationships/{id}/timeline": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Get one counterpart's interaction timeline, with chat grouped into local-day bursts */ + post: operations["getRelationshipTimeline"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/saved-views": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List shared analytical Saved Views */ + get: operations["listSavedViews"]; + put?: never; + /** Create a shared analytical Saved View */ + post: operations["createSavedView"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/saved-views/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get a shared analytical Saved View */ + get: operations["getSavedView"]; + put?: never; + post?: never; + /** Delete a shared analytical Saved View */ + delete: operations["deleteSavedView"]; + options?: never; + head?: never; + /** Update a shared analytical Saved View */ + patch: operations["patchSavedView"]; + trace?: never; + }; + "/api/v1/scheduler/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get scheduler status */ + get: operations["getSchedulerStatus"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Search messages */ + get: operations["searchMessages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/search/coverage": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Get semantic index coverage for an analytical context */ + post: operations["getSearchCoverage"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/search/deep": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Run full-text message search */ + get: operations["deepSearch"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/search/domains": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Search messages by participant domains */ + get: operations["searchMessagesByDomains"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/search/fast": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Run fast aggregate search */ + get: operations["fastSearch"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/search/similar": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Find messages similar to a seed message */ + get: operations["findSimilarMessages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/settings": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get browser-managed settings */ + get: operations["getSettings"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** Update browser-managed settings */ + patch: operations["patchSettings"]; + trace?: never; + }; + "/api/v1/sources/status": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List source sync status */ + get: operations["listSourceStatus"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/stats": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get archive statistics */ + get: operations["getStats"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/stats/total": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get aggregate totals */ + get: operations["getTotalStats"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/sync/{account}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Trigger account sync */ + post: operations["triggerSync"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/text/aggregates": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get text aggregate rows */ + get: operations["getTextAggregates"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/text/conversations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List text conversations */ + get: operations["listTextConversations"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/text/conversations/{id}/messages": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List messages in a text conversation */ + get: operations["listTextConversationMessages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/text/search": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Search text messages */ + get: operations["searchTextMessages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/text/stats": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get text message totals */ + get: operations["getTextStats"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Health check */ + get: operations["health"]; + put?: never; + post?: never; + delete?: never; + options?: never; + /** Health check */ + head: operations["headHealth"]; + patch?: never; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + AccountInfo: { + display_name?: string; + email: string; + enabled: boolean; + /** Format: int64 */ + id: number; + last_sync_at?: string; + next_sync_at?: string; + schedule?: string; + } & { + [key: string]: unknown; + }; + AccountListResponse: { + accounts: components["schemas"]["AccountInfo"][] | null; + } & { + [key: string]: unknown; + }; + AccountStatus: { + email: string; + last_error?: string; + /** Format: date-time */ + last_run?: string; + /** Format: date-time */ + next_run: string; + running: boolean; + schedule: string; + } & { + [key: string]: unknown; + }; + AddAccountRequest: { + email: string; + enabled: boolean; + schedule: string; + }; + AddRequest: { + account: string; + identifier: string; + signal: string; + }; + AddResult: { + account: string; + /** @enum {string} */ + cache_state?: "ready" | "stale"; + identifier: string; + outcome: string; + signal: string; + } & { + [key: string]: unknown; + }; + Address: { + Email: string; + Name: string; + } & { + [key: string]: unknown; + }; + AggregateResponse: { + rows: components["schemas"]["AggregateRowJSON"][] | null; + view_type: string; + } & { + [key: string]: unknown; + }; + AggregateRowJSON: { + /** Format: int64 */ + attachment_count: number; + /** Format: int64 */ + attachment_size: number; + /** Format: int64 */ + count: number; + key: string; + /** Format: int64 */ + total_size: number; + /** Format: int64 */ + total_unique: number; + } & { + [key: string]: unknown; + }; + ApiHTTPError: { + error: string; + message?: string; + } & { + [key: string]: unknown; + }; + AttachmentInfo: { + content_hash?: string; + filename: string; + /** Format: int64 */ + id: number; + mime_type: string; + /** Format: int64 */ + size_bytes: number; + url?: string; + } & { + [key: string]: unknown; + }; + BackupFreezeBeginResponse: { + token: string; + } & { + [key: string]: unknown; + }; + BackupFreezeEndRequest: { + token: string; + }; + BackupFreezeEndResponse: { + [key: string]: unknown; + }; + BodySearchContext: { + context_snippets?: string[] | null; + context_snippets_truncated?: boolean; + /** Format: int64 */ + message_id: number; + } & { + [key: string]: unknown; + }; + BuildingSummary: { + /** Format: int64 */ + dimension: number; + /** Format: int64 */ + id: number; + model: string; + progress: components["schemas"]["Progress"]; + started_at?: string; + } & { + [key: string]: unknown; + }; + CLIAddCalendarPlanRequest: { + email: string; + headless?: boolean; + oauth_app?: string; + oauth_app_explicit?: boolean; + }; + CLIAddCalendarPlanResponse: { + body_lines?: string[] | null; + cancel_hint?: string; + headline?: string; + needs_client_check?: boolean; + needs_scope_escalation: boolean; + oauth_app?: string; + oauth_app_resolved?: boolean; + } & { + [key: string]: unknown; + }; + CLICacheBuildEvent: { + data?: string; + error?: string; + type: string; + } & { + [key: string]: unknown; + }; + CLIDeduplicatePlanItem: { + /** Format: int64 */ + backfilled_count?: number; + /** Format: int64 */ + duplicate_messages?: number; + needs_confirmation: boolean; + plan_fingerprint?: string; + scope_is_collection?: boolean; + scope_label?: string; + /** Format: int64 */ + source_id?: number; + stdout?: string; + } & { + [key: string]: unknown; + }; + CLIDeduplicatePlanRequest: { + account?: string; + collection?: string; + content_hash?: boolean; + delete_dups_from_source_server?: boolean; + prefer?: string; + }; + CLIDeduplicatePlanResponse: { + footer_stdout?: string; + items: components["schemas"]["CLIDeduplicatePlanItem"][] | null; + prefix_stdout?: string; + } & { + [key: string]: unknown; + }; + CLIDeleteStagedPlanRequest: { + account?: string; + batch_id?: string; + dry_run?: boolean; + list?: boolean; + permanent?: boolean; + remote_delete_enabled?: boolean; + yes?: boolean; + }; + CLIDeleteStagedPlanResponse: { + blocked_error?: string; + confirmation_mode?: string; + needs_confirmation: boolean; + needs_execution: boolean; + needs_scope_escalation?: boolean; + plan_fingerprint?: string; + planned_batch_ids?: string[] | null; + remote_delete_env_var?: string; + scope_escalation_account?: string; + scope_escalation_body_lines?: string[] | null; + scope_escalation_cancel_hint?: string; + scope_escalation_headline?: string; + scope_escalation_oauth_app?: string; + stdout?: string; + } & { + [key: string]: unknown; + }; + CLIDeletionManifestResponse: { + id: string; + /** Format: int64 */ + message_count: number; + } & { + [key: string]: unknown; + }; + CLIEmbeddingsPlanRequest: { + force?: boolean; + /** Format: int64 */ + generation_id: number; + operation: string; + }; + CLIEmbeddingsPlanResponse: { + needs_confirmation: boolean; + prompt?: string; + } & { + [key: string]: unknown; + }; + CLIQueryMessageSummary: { + /** Format: int64 */ + attachment_count: number; + bcc?: components["schemas"]["Address"][] | null; + body_text?: string; + cc?: components["schemas"]["Address"][] | null; + /** Format: int64 */ + conversation_id: number; + conversation_title?: string; + /** Format: date-time */ + deleted_at?: string; + from_email: string; + from_name: string; + from_phone?: string; + has_attachments: boolean; + /** Format: int64 */ + id: number; + labels: string[] | null; + message_type?: string; + /** Format: date-time */ + sent_at: string; + /** Format: int64 */ + size_estimate: number; + snippet: string; + source_conversation_id: string; + /** Format: int64 */ + source_id?: number; + source_message_id: string; + subject: string; + to?: components["schemas"]["Address"][] | null; + } & { + [key: string]: unknown; + }; + CLIRepairEncodingEvent: { + data?: string; + error?: string; + type: string; + } & { + [key: string]: unknown; + }; + CLIRunEvent: { + data?: string; + error?: string; + type: string; + } & { + [key: string]: unknown; + }; + CLIRunRequest: { + args: string[] | null; + cwd?: string; + env?: { + [key: string]: string; + }; + }; + CLISyncEvent: { + data?: string; + error?: string; + type: string; + } & { + [key: string]: unknown; + }; + CLIVerifyEvent: { + data?: string; + error?: string; + type: string; + } & { + [key: string]: unknown; + }; + CacheStats: { + /** Format: int64 */ + attachment_size_bytes?: number; + /** Format: int64 */ + last_message_id?: number; + /** Format: date-time */ + last_sync_at?: string; + /** Format: int64 */ + max_year?: number; + /** Format: int64 */ + min_year?: number; + /** Format: int64 */ + sources?: number; + status: string; + /** Format: int64 */ + total_messages?: number; + /** Format: int64 */ + total_size_bytes?: number; + /** Format: int64 */ + unique_domains?: number; + /** Format: int64 */ + unique_senders?: number; + warnings?: string[] | null; + } & { + [key: string]: unknown; + }; + CancelDeletionResponse: { + id: string; + status: string; + } & { + [key: string]: unknown; + }; + CliAccountResponse: { + display_name: string; + email: string; + /** Format: int64 */ + id: number; + /** Format: date-time */ + last_sync: string | null; + /** Format: int64 */ + message_count: number; + oauth_app?: string; + /** Format: int64 */ + source_deleted_count: number; + type: string; + } & { + [key: string]: unknown; + }; + CliAccountsResponse: { + accounts: components["schemas"]["CliAccountResponse"][] | null; + } & { + [key: string]: unknown; + }; + CliCollectionEnvelope: { + collection: components["schemas"]["CliCollectionResponse"]; + } & { + [key: string]: unknown; + }; + CliCollectionResponse: { + /** Format: date-time */ + created_at: string; + description?: string; + /** Format: int64 */ + id: number; + /** Format: int64 */ + message_count: number; + name: string; + /** Format: int64 */ + source_deleted_count: number; + source_ids: number[] | null; + sources?: components["schemas"]["CliCollectionSourceResponse"][] | null; + } & { + [key: string]: unknown; + }; + CliCollectionSourceResponse: { + display_name?: string; + /** Format: int64 */ + id: number; + identifier: string; + } & { + [key: string]: unknown; + }; + CliCollectionsResponse: { + collections: components["schemas"]["CliCollectionResponse"][] | null; + } & { + [key: string]: unknown; + }; + CliDeleteDedupedBatchResponse: { + /** Format: int64 */ + count: number; + id: string; + }; + CliDeleteDedupedExecuteRequest: { + all_hidden?: boolean; + batch_ids?: string[] | null; + /** Format: int64 */ + expected_batch_count: number; + expected_batches: components["schemas"]["CliDeleteDedupedBatchResponse"][]; + /** Format: int64 */ + expected_total: number; + no_backup?: boolean; + }; + CliDeleteDedupedExecuteResponse: { + backup_path?: string; + /** Format: int64 */ + batch_count: number; + /** Format: int64 */ + deleted: number; + } & { + [key: string]: unknown; + }; + CliDeleteDedupedPlanRequest: { + all_hidden?: boolean; + batch_ids?: string[] | null; + }; + CliDeleteDedupedPlanResponse: { + /** Format: int64 */ + batch_count: number; + batches?: components["schemas"]["CliDeleteDedupedBatchResponse"][] | null; + /** Format: int64 */ + total: number; + } & { + [key: string]: unknown; + }; + CliIdentitiesResponse: { + rows: components["schemas"]["CliIdentityRowResponse"][] | null; + } & { + [key: string]: unknown; + }; + CliIdentityRowResponse: { + account: string; + /** Format: date-time */ + confirmed_at?: string; + identifier?: string; + none?: boolean; + signals: string[] | null; + /** Format: int64 */ + source_id: number; + source_type: string; + } & { + [key: string]: unknown; + }; + CliInitDBResponse: { + notice?: string; + stats: components["schemas"]["StatsResponse"]; + } & { + [key: string]: unknown; + }; + CliMessageAddress: { + email: string; + name: string; + } & { + [key: string]: unknown; + }; + CliMessageAttachment: { + content_hash: string; + filename: string; + /** Format: int64 */ + id: number; + mime_type: string; + /** Format: int64 */ + size: number; + url?: string; + } & { + [key: string]: unknown; + }; + CliMessageResponse: { + attachments: components["schemas"]["CliMessageAttachment"][] | null; + bcc: components["schemas"]["CliMessageAddress"][] | null; + body_html: string; + body_text: string; + cc: components["schemas"]["CliMessageAddress"][] | null; + /** Format: int64 */ + conversation_id: number; + /** Format: date-time */ + deleted_at: string | null; + from: components["schemas"]["CliMessageAddress"][] | null; + has_attachments: boolean; + /** Format: int64 */ + id: number; + labels: string[] | null; + message_type?: string; + /** Format: date-time */ + received_at: string | null; + /** Format: date-time */ + sent_at: string; + /** Format: int64 */ + size_estimate: number; + snippet: string; + source_conversation_id: string; + source_message_id: string; + subject: string; + to: components["schemas"]["CliMessageAddress"][] | null; + } & { + [key: string]: unknown; + }; + CliRebuildFTSEvent: { + /** Format: int64 */ + done?: number; + error?: string; + /** Format: int64 */ + indexed?: number; + /** Format: int64 */ + total?: number; + type: string; + } & { + [key: string]: unknown; + }; + CliSearchResponse: { + index_built?: boolean; + index_state?: string; + /** Format: int64 */ + indexed_messages?: number; + results: components["schemas"]["CLIQueryMessageSummary"][] | null; + scope_label?: string; + /** Format: int64 */ + scope_source_count?: number; + } & { + [key: string]: unknown; + }; + CliStatsResponse: { + scope_label?: string; + /** Format: int64 */ + scope_source_count?: number; + stats: components["schemas"]["StatsResponse"]; + } & { + [key: string]: unknown; + }; + ConversationResponse: { + /** Format: int64 */ + anchor_id: number; + has_after: boolean; + has_before: boolean; + /** Format: int64 */ + id: number; + messages: components["schemas"]["MessageDetail"][] | null; + /** Format: int64 */ + total: number; + } & { + [key: string]: unknown; + }; + CreateRequest: { + accounts: string[] | null; + name: string; + }; + CreateSavedViewRequest: { + canonical_state: components["schemas"]["SavedViewStateEnvelope"]; + description?: string; + name: string; + /** Format: int64 */ + schema_version: number; + }; + DeepSearchResponse: { + body_contexts?: components["schemas"]["BodySearchContext"][] | null; + /** Format: int64 */ + count: number; + has_more: boolean; + /** Format: int64 */ + limit: number; + messages: components["schemas"]["MessageSummary"][] | null; + /** Format: int64 */ + offset: number; + query: string; + scope?: string; + } & { + [key: string]: unknown; + }; + DeletionManifestDetail: { + account?: string; + /** Format: date-time */ + created_at: string; + created_by: string; + description: string; + execution?: components["schemas"]["Execution"]; + id: string; + /** Format: int64 */ + message_count: number; + status: string; + summary?: components["schemas"]["Summary"]; + } & { + [key: string]: unknown; + }; + DeletionManifestSummary: { + /** Format: date-time */ + created_at: string; + created_by: string; + description: string; + id: string; + /** Format: int64 */ + message_count: number; + status: string; + } & { + [key: string]: unknown; + }; + DomainContextSummaryHTTPResponse: { + cache_revision: string; + candidate_snapshot_id?: string; + search_provenance: components["schemas"]["SearchProvenance"]; + summary: components["schemas"]["DomainSummary"]; + } & { + [key: string]: unknown; + }; + DomainSearchHTTPResponse: { + cache_revision: string; + candidate_snapshot_id?: string; + next_cursor?: string; + rows: components["schemas"]["DomainSummary"][] | null; + search_provenance: components["schemas"]["SearchProvenance"]; + /** Format: int64 */ + total_count: number; + } & { + [key: string]: unknown; + }; + DomainSummary: { + /** Format: int64 */ + activity_count: number; + cache_revision: string; + domain: string; + /** Format: int64 */ + file_count: number; + /** Format: date-time */ + first_at: string; + /** Format: date-time */ + last_at: string; + /** Format: int64 */ + person_count: number; + source_counts: components["schemas"]["SourceCount"][] | null; + } & { + [key: string]: unknown; + }; + EntryRow: { + /** Format: int64 */ + anchor_message_id?: number; + /** Format: int64 */ + attachment_count: number; + /** Format: int64 */ + attachment_size: number; + /** Format: int64 */ + conversation_id?: number; + conversation_type: string; + /** Format: int64 */ + counterpart_participant_id?: number; + deleted_from_source: boolean; + has_attachments: boolean; + key: string; + kind: string; + match: components["schemas"]["MatchSummary"]; + /** Format: int64 */ + message_count: number; + message_type: string; + /** Format: date-time */ + occurred_at: string; + participant_ids?: number[] | null; + participant_labels?: string[] | null; + preview: string; + /** Format: int64 */ + source_id: number; + source_identifier: string; + source_type: string; + title: string; + } & { + [key: string]: unknown; + }; + ErrorResponse: { + error: string; + message?: string; + } & { + [key: string]: unknown; + }; + Execution: { + /** Format: date-time */ + completed_at?: string; + /** Format: int64 */ + failed: number; + failed_ids?: string[] | null; + /** Format: int64 */ + last_processed_index: number; + method: string; + /** Format: date-time */ + started_at: string; + /** Format: int64 */ + succeeded: number; + }; + ExploreActionTarget: { + action: string; + filename: string; + /** Format: int64 */ + message_id: number; + } & { + [key: string]: unknown; + }; + ExploreCacheUnavailableResponse: { + error: string; + message: string; + /** @enum {string} */ + readiness: "absent" | "interrupted" | "stale_schema" | "drifted"; + recovery_action: string; + } & { + [key: string]: unknown; + }; + ExploreFileFact: { + /** Format: int64 */ + conversation_id: number; + entry_key: string; + filename: string; + /** Format: int64 */ + id: number; + key: string; + /** Format: int64 */ + message_id: number; + mime_type: string; + /** Format: date-time */ + occurred_at: string; + /** Format: int64 */ + size: number; + /** Format: int64 */ + source_id: number; + source_identifier: string; + title: string; + } & { + [key: string]: unknown; + }; + ExploreFilesHTTPRequest: { + cursor?: string; + /** Format: int64 */ + limit?: number; + predicate: components["schemas"]["ExploreHTTPRequest"]; + }; + ExploreFilesHTTPResponse: { + cache_revision: string; + candidate_snapshot_id?: string; + files: components["schemas"]["ExploreFileFact"][]; + next_cursor?: string; + search_provenance: components["schemas"]["SearchProvenance"]; + /** Format: int64 */ + total_count: number; + } & { + [key: string]: unknown; + }; + ExploreFilter: { + /** @enum {string} */ + dimension: "source" | "participant" | "domain" | "message_type" | "after" | "before" | "deletion"; + values: string[]; + }; + /** @enum {string} */ + ExploreGroupDimension: "source" | "participant" | "domain" | "message_type" | "kind" | "year" | "month"; + ExploreGroupRow: { + /** Format: int64 */ + count: number; + /** Format: int64 */ + estimated_bytes: number; + key: string; + label: string; + /** Format: date-time */ + latest_at: string; + } & { + [key: string]: unknown; + }; + ExploreGroupSort: { + /** @enum {string} */ + direction: "asc" | "desc"; + /** @enum {string} */ + field: "key" | "count" | "estimated_bytes" | "latest_at"; + }; + ExploreGroupsHTTPRequest: { + cursor?: string; + filters?: components["schemas"]["ExploreFilter"][] | null; + grouping: components["schemas"]["ExploreGroupDimension"][]; + /** Format: int64 */ + limit?: number; + /** @enum {string} */ + presentation?: "table"; + query?: string; + /** @enum {string} */ + search_mode?: "full_text" | "semantic" | "hybrid"; + sort?: components["schemas"]["ExploreGroupSort"][] | null; + }; + ExploreGroupsHTTPResponse: { + cache_revision: string; + candidate_snapshot_id?: string; + next_cursor?: string; + rows: components["schemas"]["ExploreGroupRow"][]; + search_provenance: components["schemas"]["SearchProvenance"]; + /** Format: int64 */ + total_count: number; + } & { + [key: string]: unknown; + }; + ExploreHTTPRequest: { + candidate_snapshot_id?: string; + cursor?: string; + filters?: components["schemas"]["ExploreFilter"][] | null; + grouping?: components["schemas"]["ExploreGroupDimension"][] | null; + /** Format: int64 */ + limit?: number; + /** @enum {string} */ + presentation?: "table" | "timeline" | "files"; + query?: string; + /** @enum {string} */ + search_mode?: "full_text" | "semantic" | "hybrid"; + sort?: components["schemas"]["ExploreSort"][] | null; + }; + ExploreHTTPResponse: { + cache_revision: string; + candidate_pool_saturated?: boolean; + candidate_snapshot_id?: string; + next_cursor?: string; + rows: components["schemas"]["EntryRow"][]; + search_provenance: components["schemas"]["SearchProvenance"]; + /** Format: int64 */ + total_count?: number; + } & { + [key: string]: unknown; + }; + ExploreMatchCountsRequest: { + predicate: components["schemas"]["ExploreHTTPRequest"]; + row_keys: string[]; + }; + ExploreMatchCountsResponse: { + cache_revision: string; + canonical_query_hash: string; + counts: components["schemas"]["ExploreRowMatchCount"][]; + lexical_index_revision: string; + } & { + [key: string]: unknown; + }; + ExplorePreflightRequest: { + selection: components["schemas"]["ExploreSelection"]; + }; + ExplorePreflightResponse: { + action_targets: components["schemas"]["ExploreActionTarget"][]; + cache_revision: string; + /** Format: int64 */ + count: number; + /** Format: int64 */ + estimated_bytes: number; + /** Format: date-time */ + expires_at: string; + operation_token: string; + search_provenance: components["schemas"]["SearchProvenance"]; + unavailable_actions: components["schemas"]["ExploreUnavailableAction"][]; + } & { + [key: string]: unknown; + }; + ExploreRowMatchCount: { + /** Format: int64 */ + count: number; + row_key: string; + } & { + [key: string]: unknown; + }; + ExploreSelection: { + cache_revision: string; + candidate_snapshot_id?: string; + exclusions?: string[] | null; + /** @enum {string} */ + mode: "explicit" | "all_matching"; + predicate: components["schemas"]["ExploreHTTPRequest"]; + row_keys?: string[] | null; + search_provenance: components["schemas"]["SearchProvenance"]; + }; + ExploreSort: { + /** @enum {string} */ + direction: "desc"; + /** @enum {string} */ + field: "occurred_at"; + }; + ExploreUnavailableAction: { + action: string; + reason: string; + } & { + [key: string]: unknown; + }; + FileGroupsHTTPRequest: { + cursor?: string; + filename_query?: string; + grouping: components["schemas"]["ExploreGroupDimension"][]; + /** Format: int64 */ + limit?: number; + mime_families?: string[] | null; + predicate: components["schemas"]["ExploreHTTPRequest"]; + sort?: components["schemas"]["ExploreGroupSort"][] | null; + }; + FileGroupsHTTPResponse: { + cache_revision: string; + candidate_snapshot_id?: string; + next_cursor?: string; + rows: components["schemas"]["ExploreGroupRow"][]; + search_provenance: components["schemas"]["SearchProvenance"]; + /** Format: int64 */ + total_count: number; + } & { + [key: string]: unknown; + }; + FileMetadataResponse: { + content_available: boolean; + content_hash?: string; + /** @enum {string} */ + content_state: "metadata_only" | "url_only" | "missing_blob" | "local_content"; + /** Format: int64 */ + conversation_id: number; + filename: string; + /** Format: int64 */ + id: number; + /** Format: int64 */ + message_id: number; + mime_type: string; + /** Format: int64 */ + size_bytes: number; + url?: string; + } & { + [key: string]: unknown; + }; + FileSearchHTTPRequest: { + cursor?: string; + filename_query?: string; + /** Format: int64 */ + limit?: number; + mime_families?: string[] | null; + predicate: components["schemas"]["ExploreHTTPRequest"]; + sort: components["schemas"]["FileSearchSort"]; + }; + FileSearchHTTPResponse: { + cache_revision: string; + candidate_snapshot_id?: string; + files: components["schemas"]["FileSearchRow"][] | null; + next_cursor?: string; + search_provenance: components["schemas"]["SearchProvenance"]; + /** Format: int64 */ + total_count: number; + } & { + [key: string]: unknown; + }; + FileSearchRow: { + containing_title: string; + content_available: boolean; + /** @enum {string} */ + content_state: "metadata_only" | "url_only" | "missing_blob" | "local_content"; + /** Format: int64 */ + conversation_id: number; + entry_key: string; + filename: string; + /** Format: int64 */ + id: number; + key: string; + /** Format: int64 */ + message_id: number; + mime_family: string; + mime_type: string; + /** Format: date-time */ + occurred_at: string; + participant_domains?: string[] | null; + participant_ids?: number[] | null; + participant_labels?: string[] | null; + /** Format: int64 */ + size_bytes: number; + /** Format: int64 */ + source_id: number; + source_identifier: string; + source_type: string; + } & { + [key: string]: unknown; + }; + FileSearchSort: { + direction: string; + field: string; + }; + FilteredMessagesResponse: { + /** Format: int64 */ + count: number; + has_more: boolean; + /** Format: int64 */ + limit: number; + messages: components["schemas"]["MessageSummary"][] | null; + /** Format: int64 */ + offset: number; + } & { + [key: string]: unknown; + }; + Filters: { + account?: string; + after?: string; + before?: string; + labels?: string[] | null; + recipients?: string[] | null; + sender_domains?: string[] | null; + senders?: string[] | null; + }; + GenerationSummary: { + activated_at?: string; + /** Format: int64 */ + dimension: number; + fingerprint: string; + /** Format: int64 */ + id: number; + /** Format: int64 */ + message_count: number; + model: string; + state: string; + } & { + [key: string]: unknown; + }; + GmailIDsResponse: { + gmail_ids: string[] | null; + } & { + [key: string]: unknown; + }; + HealthResponse: { + analytics_engine?: string; + operation?: components["schemas"]["OperationHealth"]; + status: string; + vector?: components["schemas"]["VectorHealth"]; + } & { + [key: string]: unknown; + }; + HybridGenerationSummary: { + /** Format: int64 */ + dimension: number; + fingerprint: string; + /** Format: int64 */ + id: number; + model: string; + state: string; + } & { + [key: string]: unknown; + }; + HybridSearchItem: { + bcc?: string[] | null; + cc?: string[] | null; + /** Format: int64 */ + conversation_id?: number; + deleted_at?: string; + from: string; + from_email?: string; + from_name?: string; + from_phone?: string; + has_attachments: boolean; + /** Format: int64 */ + id: number; + labels: string[] | null; + matches?: components["schemas"]["HybridSearchMatch"][] | null; + matches_truncated?: boolean; + message_type?: string; + score?: components["schemas"]["ScoreBreakdown"]; + sent_at: string; + /** Format: int64 */ + size_bytes: number; + snippet: string; + /** Format: int64 */ + source_id?: number; + source_message_id?: string; + subject: string; + to: string[] | null; + } & { + [key: string]: unknown; + }; + HybridSearchMatch: { + /** Format: int64 */ + char_offset?: number; + /** Format: int64 */ + line?: number; + /** Format: double */ + score: number; + snippet: string; + } & { + [key: string]: unknown; + }; + HybridSearchResponse: { + generation: components["schemas"]["HybridGenerationSummary"]; + has_more: boolean; + mode: string; + pool_saturated: boolean; + query: string; + results: components["schemas"]["HybridSearchItem"][] | null; + /** Format: int64 */ + returned: number; + scope_label?: string; + /** Format: int64 */ + scope_source_count?: number; + /** Format: int64 */ + took_ms: number; + } & { + [key: string]: unknown; + }; + IdentityLinkRequest: { + /** Format: int64 */ + participant_a: number; + /** Format: int64 */ + participant_b: number; + }; + IdentityLinkResponse: { + /** @enum {string} */ + cache_state: "ready" | "stale"; + /** Format: int64 */ + identity_revision: number; + } & { + [key: string]: unknown; + }; + IdentitySearchHTTPRequest: { + cursor?: string; + identity_query?: string; + /** Format: int64 */ + limit?: number; + predicate: components["schemas"]["ExploreHTTPRequest"]; + sort: components["schemas"]["IdentitySearchSort"]; + }; + IdentitySearchSort: { + /** @enum {string} */ + direction: "asc" | "desc"; + /** @enum {string} */ + field: "activity_count" | "latest_at" | "display_label"; + }; + ListDeletionsResponse: { + manifests: components["schemas"]["DeletionManifestSummary"][] | null; + } & { + [key: string]: unknown; + }; + Manifest: { + /** Format: date-time */ + created_at: string; + created_by: string; + description: string; + execution?: components["schemas"]["Execution"]; + filters: components["schemas"]["Filters"]; + gmail_ids: string[] | null; + id: string; + raw_filter?: unknown; + status: string; + summary?: components["schemas"]["Summary"]; + /** Format: int64 */ + version: number; + }; + MatchSummary: { + /** Format: int64 */ + lexical_match_count?: number; + /** Format: double */ + semantic_score?: number; + strongest_excerpt?: string; + } & { + [key: string]: unknown; + }; + MessageDetail: { + attachments: components["schemas"]["AttachmentInfo"][] | null; + bcc?: string[] | null; + body: string; + body_html?: string; + cc?: string[] | null; + /** Format: int64 */ + conversation_id?: number; + deleted_at?: string; + from: string; + from_email?: string; + from_name?: string; + from_phone?: string; + has_attachments: boolean; + /** Format: int64 */ + id: number; + labels: string[] | null; + message_type?: string; + sent_at: string; + /** Format: int64 */ + size_bytes: number; + snippet: string; + /** Format: int64 */ + source_id?: number; + source_message_id?: string; + subject: string; + to: string[] | null; + } & { + [key: string]: unknown; + }; + MessageListResponse: { + messages: components["schemas"]["MessageSummary"][] | null; + /** Format: int64 */ + page: number; + /** Format: int64 */ + page_size: number; + /** Format: int64 */ + total: number; + } & { + [key: string]: unknown; + }; + MessageSummary: { + bcc?: string[] | null; + cc?: string[] | null; + /** Format: int64 */ + conversation_id?: number; + deleted_at?: string; + from: string; + from_email?: string; + from_name?: string; + from_phone?: string; + has_attachments: boolean; + /** Format: int64 */ + id: number; + labels: string[] | null; + message_type?: string; + sent_at: string; + /** Format: int64 */ + size_bytes: number; + snippet: string; + /** Format: int64 */ + source_id?: number; + source_message_id?: string; + subject: string; + to: string[] | null; + } & { + [key: string]: unknown; + }; + MutationResult: { + name: string; + /** Format: int64 */ + source_count?: number; + } & { + [key: string]: unknown; + }; + OperationHealth: { + busy: boolean; + label?: string; + /** Format: date-time */ + started_at?: string; + } & { + [key: string]: unknown; + }; + PatchSavedViewRequest: { + canonical_state?: components["schemas"]["SavedViewStateEnvelope"]; + description?: string; + name?: string; + /** Format: int64 */ + schema_version?: number; + }; + PersonCluster: { + /** Format: int64 */ + canonical_id: number; + edges: components["schemas"]["PersonClusterEdge"][] | null; + member_ids: number[] | null; + } & { + [key: string]: unknown; + }; + PersonClusterEdge: { + /** Format: int64 */ + participant_a: number; + /** Format: int64 */ + participant_b: number; + } & { + [key: string]: unknown; + }; + PersonContextSummaryHTTPResponse: { + cache_revision: string; + candidate_snapshot_id?: string; + search_provenance: components["schemas"]["SearchProvenance"]; + summary: components["schemas"]["PersonSummary"]; + } & { + [key: string]: unknown; + }; + PersonIdentifier: { + display_value?: string; + is_primary: boolean; + /** Format: int64 */ + participant_id: number; + provenance: string; + type: string; + value: string; + } & { + [key: string]: unknown; + }; + PersonSearchHTTPResponse: { + cache_revision: string; + candidate_snapshot_id?: string; + next_cursor?: string; + rows: components["schemas"]["PersonSummary"][] | null; + search_provenance: components["schemas"]["SearchProvenance"]; + /** Format: int64 */ + total_count: number; + } & { + [key: string]: unknown; + }; + PersonSummary: { + /** Format: int64 */ + activity_count: number; + cache_revision: string; + cluster?: components["schemas"]["PersonCluster"]; + display_label: string; + display_name?: string; + /** Format: int64 */ + file_count: number; + /** Format: date-time */ + first_at: string; + /** Format: int64 */ + id: number; + identifiers: components["schemas"]["PersonIdentifier"][] | null; + /** Format: date-time */ + last_at: string; + partial_label: boolean; + source_counts: components["schemas"]["SourceCount"][] | null; + } & { + [key: string]: unknown; + }; + PingInfo: { + ok: boolean; + /** Format: int64 */ + pid?: number; + service?: string; + version?: string; + } & { + [key: string]: unknown; + }; + Progress: { + /** Format: int64 */ + done: number; + /** Format: int64 */ + total: number; + } & { + [key: string]: unknown; + }; + QueryRequest: { + sql: string; + }; + QueryResult: { + columns: string[] | null; + /** Format: int64 */ + row_count: number; + rows: (unknown[] | null)[] | null; + } & { + [key: string]: unknown; + }; + RelationshipRow: { + /** Format: int64 */ + canonical_id: number; + display_label: string; + /** Format: date-time */ + last_at: string; + member_ids: number[] | null; + /** Format: double */ + score: number; + signals: components["schemas"]["RelationshipSignals"]; + } & { + [key: string]: unknown; + }; + RelationshipSignals: { + /** Format: date-time */ + last_interaction_at: string; + /** Format: int64 */ + meeting_count: number; + /** Format: double */ + meetings_together: number; + /** Format: int64 */ + modalities: number; + /** Format: double */ + received_from_them: number; + /** Format: int64 */ + sent_count: number; + /** Format: double */ + sent_to_them: number; + } & { + [key: string]: unknown; + }; + RelationshipTimelineHTTPRequest: { + cursor?: string; + filters?: components["schemas"]["ExploreFilter"][] | null; + /** Format: int64 */ + limit?: number; + timezone?: string; + }; + RelationshipTimelineHTTPResponse: { + cache_revision: string; + /** Format: int64 */ + canonical_id: number; + /** Format: int64 */ + identity_revision: number; + next_cursor?: string; + rows: components["schemas"]["TimelineRow"][] | null; + /** Format: int64 */ + total_count: number; + } & { + [key: string]: unknown; + }; + RelationshipsHTTPRequest: { + cursor?: string; + filters?: components["schemas"]["ExploreFilter"][] | null; + /** Format: int64 */ + limit?: number; + show_all?: boolean; + }; + RelationshipsHTTPResponse: { + cache_revision: string; + /** Format: int64 */ + identity_revision: number; + next_cursor?: string; + rows: components["schemas"]["RelationshipRow"][] | null; + /** Format: int64 */ + total_count: number; + } & { + [key: string]: unknown; + }; + RemoveRequest: { + account: string; + identifier: string; + }; + RemoveResult: { + account: string; + /** @enum {string} */ + cache_state?: "ready" | "stale"; + identifier: string; + no_identity?: boolean; + /** Format: int64 */ + removed: number; + } & { + [key: string]: unknown; + }; + SavedView: { + canonical_state: components["schemas"]["SavedViewStateEnvelope"]; + /** Format: date-time */ + created_at: string; + description?: string; + /** Format: int64 */ + id: number; + name: string; + /** Format: int64 */ + revision: number; + /** Format: int64 */ + schema_version: number; + /** Format: date-time */ + updated_at: string; + } & { + [key: string]: unknown; + }; + SavedViewFilter: { + field: string; + operator: string; + /** @description Exact filter values; numeric identifiers use decimal strings */ + values: string[]; + }; + SavedViewSort: { + /** @enum {string} */ + direction: "asc" | "desc"; + field: string; + }; + SavedViewStateEnvelope: { + columns?: string[]; + filters?: components["schemas"]["SavedViewFilter"][]; + grouping?: string[]; + inspector_pinned?: boolean; + /** @enum {string} */ + presentation?: "table" | "timeline" | "files"; + query?: string; + search_mode?: string; + sort?: components["schemas"]["SavedViewSort"][]; + }; + SavedViewsResponse: { + saved_views: components["schemas"]["SavedView"][] | null; + } & { + [key: string]: unknown; + }; + SchedulerStatusResponse: { + accounts: components["schemas"]["AccountStatus"][] | null; + running: boolean; + } & { + [key: string]: unknown; + }; + ScoreBreakdown: { + /** Format: double */ + bm25?: number; + /** Format: double */ + rrf?: number; + subject_boosted?: boolean; + /** Format: double */ + vector?: number; + } & { + [key: string]: unknown; + }; + SearchCoverageRequest: { + filters?: components["schemas"]["ExploreFilter"][] | null; + }; + SearchCoverageResponse: { + actions: ("retry" | "build_index")[]; + cache_revision: string; + detail?: string; + /** Format: int64 */ + eligible_count: number; + /** Format: int64 */ + embedded_count: number; + /** Format: double */ + percentage: number; + /** @enum {string} */ + status: "disabled" | "initializing" | "stale" | "incomplete" | "unavailable" | "ready"; + vector_fingerprint?: string; + /** Format: int64 */ + vector_generation?: number; + } & { + [key: string]: unknown; + }; + SearchFastResponse: { + applied_source_ids?: number[] | null; + messages: components["schemas"]["MessageSummary"][] | null; + query: string; + stats?: components["schemas"]["TotalStatsResponse"]; + /** Format: int64 */ + total_count: number; + } & { + [key: string]: unknown; + }; + SearchProvenance: { + lexical_index_revision?: string; + /** Format: int64 */ + vector_generation?: number; + }; + SearchResult: { + messages: components["schemas"]["MessageSummary"][] | null; + /** Format: int64 */ + page: number; + /** Format: int64 */ + page_size: number; + query: string; + /** Format: int64 */ + total: number; + } & { + [key: string]: unknown; + }; + SecretSettingState: { + configured: boolean; + } & { + [key: string]: unknown; + }; + SecretSettingUpdate: { + /** @enum {string} */ + action: "set" | "clear"; + value?: string; + }; + SenderCount: { + /** Format: int64 */ + count: number; + sender: string; + }; + SessionLoginRequest: { + api_key: string; + }; + SessionStatus: { + /** @enum {string} */ + auth_mode: "loopback" | "api_key" | "session" | "required"; + csrf_token?: string; + https: boolean; + plain_http_warning: boolean; + } & { + [key: string]: unknown; + }; + Setting: { + /** @enum {string} */ + group: "browser" | "server" | "archive" | "search" | "sources" | "integrations"; + key: string; + /** @enum {string} */ + kind: "string" | "integer" | "number" | "boolean" | "string_array" | "secret"; + options?: string[] | null; + restart_required: boolean; + secret?: components["schemas"]["SecretSettingState"]; + testable?: boolean; + value?: components["schemas"]["SettingValue"]; + } & { + [key: string]: unknown; + }; + SettingUpdate: { + key: string; + secret?: components["schemas"]["SecretSettingUpdate"]; + value?: components["schemas"]["SettingValue"]; + }; + SettingValue: { + string: string; + } | { + /** Format: int64 */ + integer: number; + } | { + /** Format: double */ + number: number; + } | { + boolean: boolean; + } | { + strings: string[]; + }; + SettingsPatchRequest: { + confirm_api_key_restart?: boolean; + updates: components["schemas"]["SettingUpdate"][]; + }; + SettingsResponse: { + pending_restart: boolean; + settings: components["schemas"]["Setting"][]; + } & { + [key: string]: unknown; + }; + SimilarSearchResponse: { + generation: components["schemas"]["HybridGenerationSummary"]; + messages: components["schemas"]["MessageSummary"][] | null; + /** Format: int64 */ + returned: number; + /** Format: int64 */ + seed_message_id: number; + } & { + [key: string]: unknown; + }; + SourceCount: { + /** Format: int64 */ + count: number; + source_type: string; + } & { + [key: string]: unknown; + }; + SourceStatus: { + active_sync: components["schemas"]["SyncRunStatus"] | null; + can_sync: boolean; + display_name: string | null; + /** Format: int64 */ + id: number; + identifier: string; + last_successful_sync: components["schemas"]["SyncRunStatus"] | null; + last_sync_at: string | null; + latest_sync: components["schemas"]["SyncRunStatus"] | null; + next_sync_at: string | null; + schedule?: string; + scheduled: boolean; + scheduler_last_error?: string; + source_type: string; + sync_unavailable_reason?: string; + updated_at: string; + } & { + [key: string]: unknown; + }; + SourceStatusResponse: { + sources: components["schemas"]["SourceStatus"][] | null; + } & { + [key: string]: unknown; + }; + SourcesRequest: { + accounts: string[] | null; + }; + StageDeletionFilter: { + after?: string; + before?: string; + domain?: string; + label?: string; + recipient?: string; + recipient_name?: string; + sender?: string; + sender_name?: string; + /** Format: int64 */ + source_id?: number; + }; + StageDeletionRequest: { + description?: string; + dry_run?: boolean; + filter?: components["schemas"]["StageDeletionFilter"]; + message_ids?: number[] | null; + operation_token?: string; + selection?: components["schemas"]["ExploreSelection"]; + }; + StageDeletionResponse: { + account?: string; + dry_run: boolean; + id?: string; + /** Format: int64 */ + message_count: number; + sample_gmail_ids?: string[] | null; + status?: string; + } & { + [key: string]: unknown; + }; + StatsResponse: { + /** Format: int64 */ + active_messages: number; + /** Format: int64 */ + database_size_bytes: number; + /** Format: int64 */ + source_deleted_messages: number; + /** Format: int64 */ + total_accounts: number; + /** Format: int64 */ + total_attachments: number; + /** Format: int64 */ + total_labels: number; + /** Format: int64 */ + total_messages: number; + /** Format: int64 */ + total_threads: number; + vector_search?: components["schemas"]["StatsView"]; + vector_status?: string; + } & { + [key: string]: unknown; + }; + StatsView: { + active_generation: components["schemas"]["GenerationSummary"]; + building_generation?: components["schemas"]["BuildingSummary"]; + enabled: boolean; + /** Format: int64 */ + missing_embeddings_total: number; + } & { + [key: string]: unknown; + }; + StatusMessageResponse: { + message: string; + status: string; + } & { + [key: string]: unknown; + }; + Summary: { + accounts: string[] | null; + date_range: string[] | null; + /** Format: int64 */ + message_count: number; + top_senders: components["schemas"]["SenderCount"][] | null; + /** Format: int64 */ + total_size_bytes: number; + }; + SyncRunItemStatus: { + created_at: string; + error_kind: string; + error_message: string; + phase: string; + source_message_id: string; + } & { + [key: string]: unknown; + }; + SyncRunStatus: { + completed_at: string | null; + cursor_after: string | null; + cursor_before: string | null; + error_message: string | null; + /** Format: int64 */ + errors_count: number; + /** Format: int64 */ + id: number; + item_errors?: components["schemas"]["SyncRunItemStatus"][] | null; + /** Format: int64 */ + messages_added: number; + /** Format: int64 */ + messages_processed: number; + /** Format: int64 */ + messages_updated: number; + /** Format: int64 */ + skipped_count?: number; + /** Format: int64 */ + source_id: number; + started_at: string; + status: string; + } & { + [key: string]: unknown; + }; + TaskIntegrationStatusResponse: { + /** @description Credential-free status explanation */ + message: string; + /** @description Configured project when available */ + project?: string; + /** @description Platform-specific local socket security limitation */ + security_note?: string; + /** + * @description Current task integration state + * @enum {string} + */ + state: "disabled" | "not_found" | "authentication_required" | "unreachable" | "incompatible" | "wrong_project" | "ready"; + } & { + [key: string]: unknown; + }; + TaskLinkLookupResponse: { + complete: boolean; + /** Format: date-time */ + last_scan: string; + outbound_metadata: components["schemas"]["TaskLinkOutboundMetadata"]; + reason?: string; + remote_revision?: string; + state: string; + tasks: components["schemas"]["TaskSummary"][]; + } & { + [key: string]: unknown; + }; + TaskLinkMutationRequest: { + /** @description Browser-generated retry-stable RFC3339 timestamp for the outbound mail link */ + added_at: string; + description?: string; + labels?: string[] | null; + priority?: string; + /** @description Existing task to link; omit to create a task */ + task_id?: string; + title?: string; + }; + TaskLinkMutationResponse: { + task: components["schemas"]["TaskLinkTask"]; + } & { + [key: string]: unknown; + }; + TaskLinkOutboundMetadata: { + archive_uid: string; + /** Format: int64 */ + conversation_id: number; + from: string; + /** Format: int64 */ + message_id: number; + sent_at: string; + source_identifier: string; + source_message_id: string; + source_type: string; + subject: string; + } & { + [key: string]: unknown; + }; + TaskLinkTask: { + id: string; + project: string; + revision?: string; + title: string; + } & { + [key: string]: unknown; + }; + TaskSearchResponse: { + tasks: components["schemas"]["TaskSummary"][] | null; + } & { + [key: string]: unknown; + }; + TaskSummary: { + id: string; + revision?: string; + title: string; + } & { + [key: string]: unknown; + }; + TextConversationRow: { + /** Format: int64 */ + conversation_id: number; + last_message_at?: string; + last_preview: string; + /** Format: int64 */ + message_count: number; + /** Format: int64 */ + participant_count: number; + source_type: string; + title: string; + } & { + [key: string]: unknown; + }; + TextConversationsResponse: { + conversations: components["schemas"]["TextConversationRow"][] | null; + /** Format: int64 */ + count: number; + has_more: boolean; + /** Format: int64 */ + limit: number; + /** Format: int64 */ + offset: number; + } & { + [key: string]: unknown; + }; + TextMessagesResponse: { + /** Format: int64 */ + count: number; + has_more: boolean; + /** Format: int64 */ + limit: number; + messages: components["schemas"]["CLIQueryMessageSummary"][] | null; + /** Format: int64 */ + offset: number; + } & { + [key: string]: unknown; + }; + TimelineRow: { + /** Format: int64 */ + anchor_message_id?: number; + /** Format: int64 */ + conversation_id?: number; + /** Format: date-time */ + first_at?: string; + has_attachments: boolean; + key: string; + kind: string; + /** Format: int64 */ + message_count: number; + /** Format: date-time */ + occurred_at: string; + preview: string; + /** Format: int64 */ + source_id: number; + title: string; + } & { + [key: string]: unknown; + }; + TokenUploadRequest: { + access_token?: string; + client_id?: string; + /** Format: date-time */ + expiry?: string; + refresh_token: string; + scopes?: string[] | null; + tenant_id?: string; + token_type?: string; + }; + TotalStatsResponse: { + /** Format: int64 */ + account_count: number; + /** Format: int64 */ + active_messages: number; + applied_search_scope?: boolean; + applied_source_ids?: number[] | null; + /** Format: int64 */ + attachment_count: number; + /** Format: int64 */ + attachment_size: number; + /** Format: int64 */ + label_count: number; + /** Format: int64 */ + message_count: number; + /** Format: int64 */ + source_deleted_messages: number; + /** Format: int64 */ + total_size: number; + } & { + [key: string]: unknown; + }; + UpdateRequest: { + display_name: string; + email: string; + }; + UpdateResult: { + display_name: string; + email: string; + } & { + [key: string]: unknown; + }; + VectorHealth: { + error?: string; + status: string; + } & { + [key: string]: unknown; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + daemonPing: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PingInfo"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getSession: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionStatus"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + logoutSession: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Error */ + 429: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + loginSession: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SessionLoginRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SessionStatus"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listAccounts: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AccountListResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + addAccount: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AddAccountRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StatusMessageResponse"]; + }; + }; + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StatusMessageResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getAggregates: { + parameters: { + query?: { + /** @description Aggregate view type */ + view_type?: string; + /** @description Sort field: count, size, attachment_size, or name */ + sort?: string; + /** @description Sort direction: asc or desc */ + direction?: string; + /** @description Maximum number of rows to return (default 100; values below 1 fall back to the default) */ + limit?: number; + /** @description Time bucket granularity */ + time_granularity?: string; + /** @description Source ID */ + source_id?: number; + /** @description Only include messages with attachments */ + attachments_only?: boolean; + /** @description Exclude deleted messages */ + hide_deleted?: boolean; + /** @description Search query */ + search_query?: string; + /** @description Lower date/time bound (RFC3339 or YYYY-MM-DD) */ + after?: string; + /** @description Upper date/time bound (RFC3339 or YYYY-MM-DD) */ + before?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AggregateResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getSubAggregates: { + parameters: { + query: { + /** @description Aggregate view type */ + view_type: string; + /** @description Sort field: count, size, attachment_size, or name */ + sort?: string; + /** @description Sort direction: asc or desc */ + direction?: string; + /** @description Maximum number of rows to return (default 100; values below 1 fall back to the default) */ + limit?: number; + /** @description Time bucket granularity */ + time_granularity?: string; + /** @description Source ID */ + source_id?: number; + /** @description Only include messages with attachments */ + attachments_only?: boolean; + /** @description Exclude deleted messages */ + hide_deleted?: boolean; + /** @description Search query */ + search_query?: string; + /** @description Lower date/time bound (RFC3339 or YYYY-MM-DD) */ + after?: string; + /** @description Upper date/time bound (RFC3339 or YYYY-MM-DD) */ + before?: string; + /** @description Sender email/address filter */ + sender?: string; + /** @description Sender display-name filter */ + sender_name?: string; + /** @description Recipient email/address filter */ + recipient?: string; + /** @description Recipient display-name filter */ + recipient_name?: string; + /** @description Domain filter */ + domain?: string; + /** @description Label filter */ + label?: string; + /** @description Message type filter */ + message_type?: string; + /** @description Named time period */ + time_period?: string; + /** @description Conversation ID */ + conversation_id?: number; + /** @description Comma-separated aggregate view names to match empty values */ + empty_targets?: string; + /** @description Zero-based row offset */ + offset?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AggregateResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getAttachmentContent: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Attachment SHA-256 content hash */ + hash: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": string; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getAttachment: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Attachment ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AttachmentInfo"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + uploadToken: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Account email address */ + email: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TokenUploadRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StatusMessageResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + beginBackupFreeze: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BackupFreezeBeginResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + endBackupFreeze: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BackupFreezeEndRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BackupFreezeEndResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + updateCLIAccount: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UpdateResult"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + }; + }; + listCLIAccounts: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CliAccountsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + planCLIAddCalendar: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CLIAddCalendarPlanRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CLIAddCalendarPlanResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getCLIAttachment: { + parameters: { + query: { + /** @description Attachment SHA-256 content hash */ + content_hash: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/octet-stream": string; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + buildCLICache: { + parameters: { + query?: { + /** @description Rebuild all cache files from scratch */ + full_rebuild?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/x-ndjson": components["schemas"]["CLICacheBuildEvent"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getCLICacheStats: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CacheStats"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getCLICollection: { + parameters: { + query: { + /** @description Collection name */ + name: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CliCollectionEnvelope"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listCLICollections: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CliCollectionsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + createCLICollection: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MutationResult"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + }; + }; + deleteCLICollection: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Collection name */ + name: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MutationResult"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + }; + }; + removeCLICollectionSources: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Collection name */ + name: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SourcesRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MutationResult"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + }; + }; + addCLICollectionSources: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Collection name */ + name: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SourcesRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MutationResult"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Not Found */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + }; + }; + planCLIDeduplicate: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CLIDeduplicatePlanRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CLIDeduplicatePlanResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + }; + }; + executeCLIDeleteDeduped: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CliDeleteDedupedExecuteRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CliDeleteDedupedExecuteResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Conflict */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + }; + }; + planCLIDeleteDeduped: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CliDeleteDedupedPlanRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CliDeleteDedupedPlanResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + }; + }; + planCLIDeleteStaged: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CLIDeleteStagedPlanRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CLIDeleteStagedPlanResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + createCLIDeletionManifest: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Manifest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CLIDeletionManifestResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + planCLIEmbeddings: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CLIEmbeddingsPlanRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CLIEmbeddingsPlanResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listCLIIdentities: { + parameters: { + query?: { + /** @description Restrict to a single account */ + account?: string; + /** @description Restrict to all member accounts of one collection */ + collection?: string; + /** @description For account scope, return only the primary source instead of related sources */ + primary_only?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CliIdentitiesResponse"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + }; + }; + addCLIIdentity: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AddRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AddResult"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + }; + }; + removeCLIIdentity: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RemoveRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RemoveResult"]; + }; + }; + /** @description Bad Request */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Unprocessable Entity */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Internal Server Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiHTTPError"]; + }; + }; + }; + }; + initCLIArchive: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CliInitDBResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getCLIMessage: { + parameters: { + query: { + /** @description Message numeric ID or source message ID */ + id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CliMessageResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getCLIMessageRaw: { + parameters: { + query: { + /** @description Message numeric ID or source message ID */ + id: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "message/rfc822": string; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + rebuildCLIFTS: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/x-ndjson": components["schemas"]["CliRebuildFTSEvent"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + repairEncodingCLI: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/x-ndjson": components["schemas"]["CLIRepairEncodingEvent"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + runCLI: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CLIRunRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/x-ndjson": components["schemas"]["CLIRunEvent"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + searchCLI: { + parameters: { + query: { + /** @description Search query */ + q: string; + /** @description Maximum number of rows to return */ + limit?: number; + /** @description Zero-based row offset */ + offset?: number; + /** @description Message type filter; repeat or comma-separate for multiple values */ + message_type?: string; + /** @description Restrict to one account/source */ + account?: string; + /** @description Restrict to one collection */ + collection?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CliSearchResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getCLIStats: { + parameters: { + query?: { + /** @description Restrict to one account/source */ + account?: string; + /** @description Restrict to one collection */ + collection?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CliStatsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + syncCLI: { + parameters: { + query?: { + /** @description Account email or display name to sync */ + email?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/x-ndjson": components["schemas"]["CLISyncEvent"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + syncFullCLI: { + parameters: { + query?: { + /** @description Account email or display name to sync */ + email?: string; + /** @description Gmail search query */ + query?: string; + /** @description Only messages on or after this YYYY-MM-DD date */ + after?: string; + /** @description Only messages before this YYYY-MM-DD date */ + before?: string; + /** @description Maximum messages to sync */ + limit?: number; + /** @description Ignore checkpoints and start fresh */ + noresume?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/x-ndjson": components["schemas"]["CLISyncEvent"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + verifyCLI: { + parameters: { + query: { + /** @description Account email to verify */ + email: string; + /** @description Number of messages to sample for MIME verification */ + sample?: number; + /** @description Skip SQLite integrity check */ + skip_db_check?: boolean; + /** @description Emit JSON summary output */ + json?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/x-ndjson": components["schemas"]["CLIVerifyEvent"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getConversation: { + parameters: { + query: { + /** @description Selected message ID anchoring the chronological window */ + anchor: number; + /** @description Messages before the anchor (default 25, max 50) */ + before?: number; + /** @description Messages after the anchor (default 25, max 50) */ + after?: number; + /** @description Lower UTC bound, inclusive (RFC3339). Restricts the window, before/after counts, and has_before/has_after to messages in [start, end) */ + start?: string; + /** @description Upper UTC bound, exclusive (RFC3339). Restricts the window, before/after counts, and has_before/has_after to messages in [start, end) */ + end?: string; + }; + header?: never; + path: { + /** @description Conversation ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ConversationResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listDeletions: { + parameters: { + query?: { + /** @description Filter manifests by status (pending, in_progress, completed, failed, cancelled) */ + status?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListDeletionsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + stageDeletion: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["StageDeletionRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StageDeletionResponse"]; + }; + }; + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StageDeletionResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDeletion: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Deletion manifest ID */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeletionManifestDetail"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + cancelDeletion: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Deletion manifest ID */ + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CancelDeletionResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + searchDomains: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IdentitySearchHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DomainSearchHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDomain: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Exact normalized domain fact */ + domain: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DomainSummary"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + searchDomainFiles: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Exact normalized domain fact */ + domain: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FileSearchHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FileSearchHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDomainContextSummary: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Exact normalized domain fact */ + domain: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExploreHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DomainContextSummaryHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getDomainTimeline: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Exact normalized domain fact */ + domain: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExploreHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + explore: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExploreHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listExploreFiles: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExploreFilesHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreFilesHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + exploreGroups: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExploreGroupsHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreGroupsHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + countExploreMatches: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExploreMatchCountsRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreMatchCountsResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + preflightExploreSelection: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExplorePreflightRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExplorePreflightResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + groupFiles: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FileGroupsHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FileGroupsHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + searchFiles: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FileSearchHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FileSearchHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getFile: { + parameters: { + query?: never; + header?: never; + path: { + /** @description File attachment ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FileMetadataResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getFileContent: { + parameters: { + query?: never; + header?: never; + path: { + /** @description File attachment ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "*/*": string; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getHealth: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HealthResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + linkIdentityParticipants: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IdentityLinkRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IdentityLinkResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + unlinkIdentityParticipants: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IdentityLinkRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["IdentityLinkResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + searchIntegrationTasks: { + parameters: { + query: { + /** @description Task title search within the configured project */ + q: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskSearchResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getTaskIntegrationStatus: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskIntegrationStatusResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + testTaskIntegration: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskIntegrationStatusResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listMessages: { + parameters: { + query?: { + /** @description One-based page number (default 1; values below 1 are clamped to 1). Non-numeric values are rejected with 400. */ + page?: number; + /** @description Page size (default 20, max 100; out-of-range values are clamped). Non-numeric values are rejected with 400. */ + page_size?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageListResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + filterMessages: { + parameters: { + query?: { + /** @description Sender email/address filter */ + sender?: string; + /** @description Sender display-name filter */ + sender_name?: string; + /** @description Recipient email/address filter */ + recipient?: string; + /** @description Recipient display-name filter */ + recipient_name?: string; + /** @description Domain filter */ + domain?: string; + /** @description Label filter */ + label?: string; + /** @description Message type filter */ + message_type?: string; + /** @description Named time period */ + time_period?: string; + /** @description Time bucket granularity */ + time_granularity?: string; + /** @description Conversation ID */ + conversation_id?: number; + /** @description Source ID */ + source_id?: number; + /** @description Only include messages with attachments */ + attachments_only?: boolean; + /** @description Exclude deleted messages */ + hide_deleted?: boolean; + /** @description Lower date/time bound (RFC3339 or YYYY-MM-DD) */ + after?: string; + /** @description Upper date/time bound (RFC3339 or YYYY-MM-DD) */ + before?: string; + /** @description Comma-separated aggregate view names to match empty values */ + empty_targets?: string; + /** @description Zero-based row offset */ + offset?: number; + /** @description Maximum number of rows to return (default and max 500; larger values are clamped) */ + limit?: number; + /** @description Sort field: date, size, or subject */ + sort?: string; + /** @description Sort direction: asc or desc */ + direction?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FilteredMessagesResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getGmailIDsByFilter: { + parameters: { + query?: { + /** @description Sender email/address filter */ + sender?: string; + /** @description Sender display-name filter */ + sender_name?: string; + /** @description Recipient email/address filter */ + recipient?: string; + /** @description Recipient display-name filter */ + recipient_name?: string; + /** @description Domain filter */ + domain?: string; + /** @description Label filter */ + label?: string; + /** @description Message type filter */ + message_type?: string; + /** @description Named time period */ + time_period?: string; + /** @description Time bucket granularity */ + time_granularity?: string; + /** @description Conversation ID */ + conversation_id?: number; + /** @description Source ID */ + source_id?: number; + /** @description Only include messages with attachments */ + attachments_only?: boolean; + /** @description Exclude deleted messages */ + hide_deleted?: boolean; + /** @description Lower date/time bound (RFC3339 or YYYY-MM-DD) */ + after?: string; + /** @description Upper date/time bound (RFC3339 or YYYY-MM-DD) */ + before?: string; + /** @description Comma-separated aggregate view names to match empty values */ + empty_targets?: string; + /** @description Zero-based row offset */ + offset?: number; + /** @description Maximum number of rows to return (default and max 500; larger values are clamped) */ + limit?: number; + /** @description Sort field: date, size, or subject */ + sort?: string; + /** @description Sort direction: asc or desc */ + direction?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GmailIDsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getMessage: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Message ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageDetail"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getMessageInlinePart: { + parameters: { + query: { + /** @description Inline MIME Content-ID */ + cid: string; + }; + header?: never; + path: { + /** @description Message ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/octet-stream": string; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 415: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 501: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listMessageTasks: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Archived email message ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskLinkLookupResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + createOrLinkMessageTask: { + parameters: { + query?: never; + header: { + /** @description Browser-generated retry-stable request ID */ + "X-Request-Id": string; + }; + path: { + /** @description Archived email message ID */ + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TaskLinkMutationRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskLinkMutationResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + unlinkMessageTask: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Archived email message ID */ + id: number; + /** @description External task ID */ + task_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TaskLinkMutationResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + searchPeople: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["IdentitySearchHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PersonSearchHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getPerson: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Durable participant ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PersonSummary"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + searchPersonFiles: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Durable participant ID */ + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["FileSearchHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FileSearchHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getPersonContextSummary: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Durable participant ID */ + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExploreHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PersonContextSummaryHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getPersonTimeline: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Durable participant ID */ + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ExploreHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + runQuery: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["QueryRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["QueryResult"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listRelationships: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RelationshipsHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RelationshipsHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getRelationshipTimeline: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Any member participant ID of the counterpart's identity cluster */ + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RelationshipTimelineHTTPRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RelationshipTimelineHTTPResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Service Unavailable */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExploreCacheUnavailableResponse"] | components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listSavedViews: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SavedViewsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + createSavedView: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CreateSavedViewRequest"]; + }; + }; + responses: { + /** @description Created */ + 201: { + headers: { + /** @description Strong Saved View revision tag for optimistic concurrency */ + ETag?: string; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SavedView"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getSavedView: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Saved View ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + /** @description Strong Saved View revision tag for optimistic concurrency */ + ETag?: string; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SavedView"]; + }; + }; + /** @description Error */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + deleteSavedView: { + parameters: { + query?: never; + header: { + /** @description Strong ETag returned by the latest Saved View read */ + "If-Match": string; + }; + path: { + /** @description Saved View ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description No Content */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 428: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + patchSavedView: { + parameters: { + query?: never; + header: { + /** @description Strong ETag returned by the latest Saved View read */ + "If-Match": string; + }; + path: { + /** @description Saved View ID */ + id: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PatchSavedViewRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + /** @description Strong Saved View revision tag for optimistic concurrency */ + ETag?: string; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SavedView"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 428: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getSchedulerStatus: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SchedulerStatusResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + searchMessages: { + parameters: { + query: { + /** @description Search query */ + q: string; + /** @description Search mode: fts, vector, or hybrid */ + mode?: string; + /** @description One-based page number (default 1; values below 1 are clamped to 1). Non-numeric values are rejected with 400. */ + page?: number; + /** @description Page size (default 20, max 100; out-of-range values are clamped). Non-numeric values are rejected with 400. */ + page_size?: number; + /** @description Zero-based ranking offset for vector or hybrid search (default 0) */ + offset?: number; + /** @description Include score explanation when mode is vector or hybrid */ + explain?: boolean; + /** @description Include scored semantic chunk excerpts for vector or hybrid results */ + include_matches?: boolean; + /** @description Minimum chunk score for included excerpts; does not filter ranked messages */ + min_score?: number; + /** @description Message type filter; repeat or comma-separate for multiple values */ + message_type?: string; + /** @description Restrict to one account/source */ + account?: string; + /** @description Restrict to one collection */ + collection?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SearchResult"] | components["schemas"]["HybridSearchResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getSearchCoverage: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SearchCoverageRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SearchCoverageResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 503: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + deepSearch: { + parameters: { + query: { + /** @description Search query */ + q: string; + /** @description Exact search scope: body; omit for composite full-text search */ + scope?: string; + /** @description Sender email/address filter */ + sender?: string; + /** @description Sender display-name filter */ + sender_name?: string; + /** @description Recipient email/address filter */ + recipient?: string; + /** @description Recipient display-name filter */ + recipient_name?: string; + /** @description Domain filter */ + domain?: string; + /** @description Label filter */ + label?: string; + /** @description Message type filter */ + message_type?: string; + /** @description Named time period */ + time_period?: string; + /** @description Time bucket granularity */ + time_granularity?: string; + /** @description Conversation ID */ + conversation_id?: number; + /** @description Source ID */ + source_id?: number; + /** @description Only include messages with attachments */ + attachments_only?: boolean; + /** @description Exclude deleted messages */ + hide_deleted?: boolean; + /** @description Lower date/time bound (RFC3339 or YYYY-MM-DD) */ + after?: string; + /** @description Upper date/time bound (RFC3339 or YYYY-MM-DD) */ + before?: string; + /** @description Comma-separated aggregate view names to match empty values */ + empty_targets?: string; + /** @description Zero-based row offset */ + offset?: number; + /** @description Maximum number of rows to return (default and max 500; larger values are clamped) */ + limit?: number; + /** @description Sort field: date, size, or subject */ + sort?: string; + /** @description Sort direction: asc or desc */ + direction?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DeepSearchResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + searchMessagesByDomains: { + parameters: { + query: { + /** @description Comma-separated participant domains */ + domains: string; + /** @description Lower date/time bound (RFC3339 or YYYY-MM-DD) */ + after?: string; + /** @description Upper date/time bound (RFC3339 or YYYY-MM-DD) */ + before?: string; + /** @description Zero-based row offset */ + offset?: number; + /** @description Maximum number of rows to return */ + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["FilteredMessagesResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + fastSearch: { + parameters: { + query: { + /** @description Search query */ + q: string; + /** @description Stats grouping view type */ + view_type?: string; + /** @description Sender email/address filter */ + sender?: string; + /** @description Sender display-name filter */ + sender_name?: string; + /** @description Recipient email/address filter */ + recipient?: string; + /** @description Recipient display-name filter */ + recipient_name?: string; + /** @description Domain filter */ + domain?: string; + /** @description Label filter */ + label?: string; + /** @description Message type filter */ + message_type?: string; + /** @description Named time period */ + time_period?: string; + /** @description Time bucket granularity */ + time_granularity?: string; + /** @description Conversation ID */ + conversation_id?: number; + /** @description Source ID */ + source_id?: number; + /** @description Only include messages with attachments */ + attachments_only?: boolean; + /** @description Exclude deleted messages */ + hide_deleted?: boolean; + /** @description Lower date/time bound (RFC3339 or YYYY-MM-DD) */ + after?: string; + /** @description Upper date/time bound (RFC3339 or YYYY-MM-DD) */ + before?: string; + /** @description Comma-separated aggregate view names to match empty values */ + empty_targets?: string; + /** @description Zero-based row offset */ + offset?: number; + /** @description Maximum number of rows to return (default and max 500; larger values are clamped) */ + limit?: number; + /** @description Sort field: date, size, or subject */ + sort?: string; + /** @description Sort direction: asc or desc */ + direction?: string; + /** @description Source IDs; repeat the parameter for multiple sources */ + source_ids?: number[]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SearchFastResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + findSimilarMessages: { + parameters: { + query: { + /** @description Seed message ID */ + message_id: number; + /** @description Maximum number of rows to return */ + limit?: number; + /** @description Account email or configured source identifier */ + account?: string; + /** @description Message type filter */ + message_type?: string; + /** @description Lower date/time bound (RFC3339 or YYYY-MM-DD) */ + after?: string; + /** @description Upper date/time bound (RFC3339 or YYYY-MM-DD) */ + before?: string; + /** @description Only include messages with attachments */ + has_attachment?: boolean; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SimilarSearchResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getSettings: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + /** @description Strong content hash for optimistic concurrency */ + ETag?: string; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SettingsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + patchSettings: { + parameters: { + query?: never; + header: { + /** @description Strong ETag returned by the latest settings read */ + "If-Match": string; + }; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SettingsPatchRequest"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + /** @description Strong content hash for optimistic concurrency */ + ETag?: string; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SettingsResponse"]; + }; + }; + /** @description Error */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 412: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 428: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listSourceStatus: { + parameters: { + query?: { + /** @description Restrict to one source type */ + source_type?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SourceStatusResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getStats: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StatsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getTotalStats: { + parameters: { + query?: { + /** @description Source ID */ + source_id?: number; + /** @description Source IDs; repeat the parameter for multiple sources */ + source_ids?: number[]; + /** @description Only include messages with attachments */ + attachments_only?: boolean; + /** @description Exclude deleted messages */ + hide_deleted?: boolean; + /** @description Search query */ + search_query?: string; + /** @description Include all message types when the search has no explicit message_type */ + search_scope?: boolean; + /** @description Aggregate view type for grouping */ + group_by?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TotalStatsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + triggerSync: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Account email or configured source identifier */ + account: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Accepted */ + 202: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StatusMessageResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getTextAggregates: { + parameters: { + query?: { + /** @description Text aggregate view type */ + view_type?: string; + /** @description Sort field: count or name */ + sort?: string; + /** @description Sort direction: asc or desc */ + direction?: string; + /** @description Maximum number of rows to return */ + limit?: number; + /** @description Time bucket granularity */ + time_granularity?: string; + /** @description Source ID */ + source_id?: number; + /** @description Search query */ + search_query?: string; + /** @description Lower date/time bound (RFC3339 or YYYY-MM-DD) */ + after?: string; + /** @description Upper date/time bound (RFC3339 or YYYY-MM-DD) */ + before?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AggregateResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listTextConversations: { + parameters: { + query?: { + /** @description Source ID */ + source_id?: number; + /** @description Sender phone/address filter */ + contact_phone?: string; + /** @description Sender display-name filter */ + contact_name?: string; + /** @description Source type filter */ + source_type?: string; + /** @description Label filter */ + label?: string; + /** @description Named time period */ + time_period?: string; + /** @description Time bucket granularity */ + time_granularity?: string; + /** @description Lower date/time bound (RFC3339 or YYYY-MM-DD) */ + after?: string; + /** @description Upper date/time bound (RFC3339 or YYYY-MM-DD) */ + before?: string; + /** @description Zero-based row offset */ + offset?: number; + /** @description Maximum number of rows to return */ + limit?: number; + /** @description Sort field: last_message, count, or name */ + sort?: string; + /** @description Sort direction: asc or desc */ + direction?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TextConversationsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + listTextConversationMessages: { + parameters: { + query?: { + /** @description Source ID */ + source_id?: number; + /** @description Sender phone/address filter */ + contact_phone?: string; + /** @description Sender display-name filter */ + contact_name?: string; + /** @description Source type filter */ + source_type?: string; + /** @description Label filter */ + label?: string; + /** @description Named time period */ + time_period?: string; + /** @description Time bucket granularity */ + time_granularity?: string; + /** @description Lower date/time bound (RFC3339 or YYYY-MM-DD) */ + after?: string; + /** @description Upper date/time bound (RFC3339 or YYYY-MM-DD) */ + before?: string; + /** @description Zero-based row offset */ + offset?: number; + /** @description Maximum number of rows to return */ + limit?: number; + /** @description Sort field: last_message, count, or name */ + sort?: string; + /** @description Sort direction: asc or desc */ + direction?: string; + }; + header?: never; + path: { + /** @description Conversation ID */ + id: number; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TextMessagesResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + searchTextMessages: { + parameters: { + query: { + /** @description Search query */ + q: string; + /** @description Zero-based row offset */ + offset?: number; + /** @description Maximum number of rows to return */ + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TextMessagesResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + getTextStats: { + parameters: { + query?: { + /** @description Source ID */ + source_id?: number; + /** @description Search query */ + search_query?: string; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TotalStatsResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + health: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HealthResponse"]; + }; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + headHealth: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Error */ + default: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; +} diff --git a/web/src/lib/api/session.svelte.ts b/web/src/lib/api/session.svelte.ts new file mode 100644 index 000000000..4e096e057 --- /dev/null +++ b/web/src/lib/api/session.svelte.ts @@ -0,0 +1,106 @@ +import type { components } from './generated/schema'; +import { createSessionAwareAPIClient, type APIClient } from './client'; + +type SessionStatus = components['schemas']['SessionStatus']; + +function errorMessage(error: unknown, fallback: string): string { + if (typeof error === 'object' && error !== null && 'message' in error) { + const message = (error as { message?: unknown }).message; + if (typeof message === 'string' && message) return message; + } + return fallback; +} + +export class SessionController { + status = $state(); + loading = $state(false); + error = $state(); + readonly client: APIClient; + + constructor(fetchFn: typeof fetch = fetch) { + this.client = createSessionAwareAPIClient( + fetchFn, + () => this.csrfToken, + () => this.requireLogin() + ); + } + + get authMode(): SessionStatus['auth_mode'] | undefined { + return this.status?.auth_mode; + } + + get csrfToken(): string | undefined { + return this.status?.csrf_token; + } + + async bootstrap(): Promise { + this.loading = true; + this.error = undefined; + try { + const { data, error } = await this.client.GET('/api/session'); + if (data) { + this.status = data; + } else { + this.error = errorMessage(error, 'Could not determine browser session status'); + } + } catch (error) { + this.error = errorMessage(error, 'Could not determine browser session status'); + } finally { + this.loading = false; + } + } + + async login(apiKey: string): Promise { + this.loading = true; + this.error = undefined; + try { + const { data, error } = await this.client.POST('/api/session/login', { + body: { api_key: apiKey } + }); + if (!data) { + this.requireLogin(); + this.error = errorMessage(error, 'Login failed'); + return false; + } + this.status = data; + return true; + } catch (error) { + this.requireLogin(); + this.error = errorMessage(error, 'Login failed'); + return false; + } finally { + this.loading = false; + } + } + + async logout(): Promise { + this.loading = true; + this.error = undefined; + try { + const { response, error } = await this.client.DELETE('/api/session'); + if (!response.ok) { + this.error = errorMessage(error, 'Logout failed'); + return false; + } + this.requireLogin(); + return true; + } catch (error) { + this.error = errorMessage(error, 'Logout failed'); + return false; + } finally { + this.loading = false; + } + } + + private requireLogin(): void { + this.status = { + auth_mode: 'required', + https: this.status?.https ?? false, + plain_http_warning: this.status?.plain_http_warning ?? true + }; + } +} + +export function createSessionController(fetchFn: typeof fetch = fetch): SessionController { + return new SessionController(fetchFn); +} diff --git a/web/src/lib/api/session.test.ts b/web/src/lib/api/session.test.ts new file mode 100644 index 000000000..1b5953e9d --- /dev/null +++ b/web/src/lib/api/session.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { createSessionController } from './session.svelte'; + +function sessionResponse( + authMode: 'loopback' | 'api_key' | 'session' | 'required', + csrfToken?: string +) { + return Response.json({ + auth_mode: authMode, + ...(csrfToken ? { csrf_token: csrfToken } : {}), + https: false, + plain_http_warning: true + }); +} + +describe('browser session controller', () => { + it('bootstraps authentication from the same-origin session endpoint', async () => { + const fetchFn = vi.fn(async () => sessionResponse('session', 'csrf-token')); + const session = createSessionController(fetchFn); + + await session.bootstrap(); + + expect(session.authMode).toBe('session'); + expect(session.csrfToken).toBe('csrf-token'); + expect(fetchFn).toHaveBeenCalledOnce(); + expect(new URL((fetchFn.mock.calls[0]?.[0] as Request).url).pathname).toBe('/api/session'); + }); + + it('attaches the session token to same-origin mutations', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn(async (input) => { + const request = input as Request; + requests.push(request); + if (new URL(request.url).pathname === '/api/session' && request.method === 'GET') { + return sessionResponse('session', 'csrf-token'); + } + return new Response(null, { status: 204 }); + }); + const session = createSessionController(fetchFn); + await session.bootstrap(); + + await session.client.DELETE('/api/session'); + + expect(requests[1]?.headers.get('X-CSRF-Token')).toBe('csrf-token'); + expect(requests[1]?.headers.get('Origin')).toBeNull(); + }); + + it('does not retry a mutation after an unauthorized response', async () => { + const fetchFn = vi.fn(async (input) => { + const request = input as Request; + if (request.method === 'GET') return sessionResponse('session', 'csrf-token'); + return Response.json({ error: 'unauthorized', message: 'Session expired' }, { status: 401 }); + }); + const session = createSessionController(fetchFn); + await session.bootstrap(); + + await session.client.DELETE('/api/session'); + + expect(fetchFn).toHaveBeenCalledTimes(2); + expect(session.authMode).toBe('required'); + }); +}); diff --git a/web/src/lib/archive/stale-request.test.ts b/web/src/lib/archive/stale-request.test.ts new file mode 100644 index 000000000..c99cb2ba1 --- /dev/null +++ b/web/src/lib/archive/stale-request.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it } from 'vitest'; + +import { createStaleRequestGuard } from './stale-request'; + +function deferred() { + let resolve!: (value: T) => void; + const promise = new Promise((done) => { + resolve = done; + }); + return { promise, resolve }; +} + +describe('stale request guard', () => { + it('rejects an older response when a newer request resolves first', async () => { + const guard = createStaleRequestGuard(); + const first = deferred(); + const second = deferred(); + const accepted: string[] = []; + + const firstGeneration = guard.begin(); + const firstCommit = first.promise.then((value) => + guard.commit(firstGeneration, () => accepted.push(value)) + ); + const secondGeneration = guard.begin(); + const secondCommit = second.promise.then((value) => + guard.commit(secondGeneration, () => accepted.push(value)) + ); + + second.resolve('new result'); + await expect(secondCommit).resolves.toBe(true); + first.resolve('old result'); + await expect(firstCommit).resolves.toBe(false); + expect(accepted).toEqual(['new result']); + }); + + it('invalidates an in-flight response without starting another request', () => { + const guard = createStaleRequestGuard(); + const generation = guard.begin(); + guard.invalidate(); + + expect(guard.commit(generation, () => {})).toBe(false); + }); +}); diff --git a/web/src/lib/archive/stale-request.ts b/web/src/lib/archive/stale-request.ts new file mode 100644 index 000000000..a4cdaddef --- /dev/null +++ b/web/src/lib/archive/stale-request.ts @@ -0,0 +1,33 @@ +export interface StaleRequestGuard { + begin(): number; + invalidate(): void; + isCurrent(generation: number): boolean; + commit(generation: number, effect: () => void): boolean; +} + +/** + * Tracks the newest asynchronous request without assuming cancellation support. + * Callers may still abort network work separately; this guard prevents an old + * completion from replacing state owned by a newer request. + */ +export function createStaleRequestGuard(): StaleRequestGuard { + let currentGeneration = 0; + + return { + begin() { + currentGeneration += 1; + return currentGeneration; + }, + invalidate() { + currentGeneration += 1; + }, + isCurrent(generation) { + return generation === currentGeneration; + }, + commit(generation, effect) { + if (generation !== currentGeneration) return false; + effect(); + return true; + } + }; +} diff --git a/web/src/lib/archive/thread-window.test.ts b/web/src/lib/archive/thread-window.test.ts new file mode 100644 index 000000000..174231de5 --- /dev/null +++ b/web/src/lib/archive/thread-window.test.ts @@ -0,0 +1,61 @@ +import { describe, expect, it } from 'vitest'; + +import type { ArchiveMessageSummary } from './types'; +import { selectInclusiveThreadWindow } from './thread-window'; + +function message(id: number, sentAt: string): ArchiveMessageSummary { + return { + id, + conversationId: 1, + subject: `Message ${id}`, + sender: 'alice@example.com', + recipients: ['bob@example.com'], + sentAt, + snippet: '' + }; +} + +function buildSorted(count: number): ArchiveMessageSummary[] { + const base = Date.UTC(2026, 0, 1); + return Array.from({ length: count }, (_, index) => + message(index + 1, new Date(base + index * 60_000).toISOString()) + ); +} + +describe('selectInclusiveThreadWindow', () => { + it('returns the full thread at or below the cap', () => { + const sorted = buildSorted(10); + + expect(selectInclusiveThreadWindow(sorted, 5, 50)).toEqual({ + messages: sorted, + truncated: false + }); + }); + + it('returns the most recent cap when the selected message is in the tail', () => { + const result = selectInclusiveThreadWindow(buildSorted(60), 60, 50); + + expect(result.truncated).toBe(true); + expect(result.messages).toHaveLength(50); + expect(result.messages.at(0)?.id).toBe(11); + expect(result.messages.at(-1)?.id).toBe(60); + }); + + it('keeps an older selected message in a bounded chronological window', () => { + const result = selectInclusiveThreadWindow(buildSorted(60), 5, 50); + + expect(result.messages).toHaveLength(50); + expect(result.messages.some((item) => item.id === 5)).toBe(true); + expect(result.messages.some((item) => item.id === 11)).toBe(false); + expect(result.messages.every((item, index, items) => + index === 0 || items[index - 1]!.sentAt <= item.sentAt + )).toBe(true); + }); + + it('falls back to the recent window when the selection is absent', () => { + const result = selectInclusiveThreadWindow(buildSorted(60), 999, 50); + + expect(result.messages.at(0)?.id).toBe(11); + expect(result.messages.at(-1)?.id).toBe(60); + }); +}); diff --git a/web/src/lib/archive/thread-window.ts b/web/src/lib/archive/thread-window.ts new file mode 100644 index 000000000..28cddb191 --- /dev/null +++ b/web/src/lib/archive/thread-window.ts @@ -0,0 +1,27 @@ +import type { ArchiveMessageSummary } from './types'; + +export interface ThreadWindow { + messages: ArchiveMessageSummary[]; + truncated: boolean; +} + +export function selectInclusiveThreadWindow( + sorted: ArchiveMessageSummary[], + selectedId: number, + cap = 50 +): ThreadWindow { + if (sorted.length <= cap) return { messages: sorted, truncated: false }; + + const recent = sorted.slice(-cap); + if (recent.some((message) => message.id === selectedId)) { + return { messages: recent, truncated: true }; + } + + const selected = sorted.find((message) => message.id === selectedId); + if (!selected) return { messages: recent, truncated: true }; + + const messages = [...recent.slice(1), selected].sort((left, right) => + left.sentAt.localeCompare(right.sentAt) + ); + return { messages, truncated: true }; +} diff --git a/web/src/lib/archive/types.ts b/web/src/lib/archive/types.ts new file mode 100644 index 000000000..83a2082f4 --- /dev/null +++ b/web/src/lib/archive/types.ts @@ -0,0 +1,61 @@ +export type ArchiveSearchMode = 'fts' | 'vector' | 'hybrid'; + +export type ArchiveFilterValue = string | number | boolean | string[]; + +export interface ArchiveFilter { + field: string; + operator: string; + value: ArchiveFilterValue; +} + +export interface ArchiveSort { + field: string; + direction: 'asc' | 'desc'; +} + +export type ArchivePresentation = 'table' | 'timeline' | 'files'; + +/** + * Browser-restorable analytical context. Bulk selection is deliberately + * excluded: selection can be large and is scoped to one live archive revision. + */ +export interface ArchiveURLState { + schemaVersion: number; + query: string; + searchMode: ArchiveSearchMode; + filters: ArchiveFilter[]; + groupingChain: string[]; + presentation: ArchivePresentation; + sort: ArchiveSort[]; + columns: string[]; + selectedRow: string | null; + inspectorPinned: boolean; + conversationAnchor: string | null; + scrollKey: string | null; + bulkSelection?: never; + [futureField: string]: unknown; +} + +export interface ArchiveMessageSummary { + id: number; + conversationId: number; + subject: string; + sender: string; + recipients: string[]; + sentAt: string; + snippet: string; +} + +export interface ArchiveAttachment { + filename: string; + mimeType: string; + sizeBytes: number; +} + +export interface ArchiveMessageDetail extends ArchiveMessageSummary { + body: string; + bodyHtml?: string; + attachments: ArchiveAttachment[]; +} + +export type MessageViewMode = 'html' | 'text'; diff --git a/web/src/lib/archive/url-state.test.ts b/web/src/lib/archive/url-state.test.ts new file mode 100644 index 000000000..e8dfc4df7 --- /dev/null +++ b/web/src/lib/archive/url-state.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from 'vitest'; + +import type { ArchiveURLState } from './types'; +import { + defaultArchiveURLState, + parseArchiveURLState, + serializeArchiveURLState +} from './url-state'; + +describe('archive URL state', () => { + it('round-trips the complete restorable browse context', () => { + const state: ArchiveURLState = { + schemaVersion: 1, + query: 'from:alice quarterly plan', + searchMode: 'hybrid', + filters: [ + { field: 'message_type', operator: 'equals', value: 'email' }, + { field: 'sent_at', operator: 'after', value: '2025-01-01' } + ], + groupingChain: ['person', 'year'], + presentation: 'table', + sort: [{ field: 'sent_at', direction: 'desc' }], + columns: ['kind', 'people', 'subject', 'time'], + selectedRow: 'message:42', + inspectorPinned: true, + conversationAnchor: 'message:37', + scrollKey: 'message:31' + }; + + expect(parseArchiveURLState(serializeArchiveURLState(state))).toEqual(state); + }); + + it('preserves unknown future fields inside the versioned state envelope', () => { + const futureState = { + ...defaultArchiveURLState, + schemaVersion: 2, + futurePresentationOption: { density: 'compact' } + } as ArchiveURLState; + + expect(parseArchiveURLState(serializeArchiveURLState(futureState))).toMatchObject({ + schemaVersion: 2, + futurePresentationOption: { density: 'compact' } + }); + }); + + it('explicitly drops bulk selection from browser-restorable state', () => { + const stateWithSelection = { + ...defaultArchiveURLState, + selectedRow: 'message:42', + bulkSelection: ['message:42', 'message:43'] + } as unknown as ArchiveURLState; + + const restored = parseArchiveURLState(serializeArchiveURLState(stateWithSelection)); + + expect(restored.selectedRow).toBe('message:42'); + expect(restored).not.toHaveProperty('bulkSelection'); + }); + + it('uses defaults for a missing or malformed state parameter', () => { + expect(parseArchiveURLState('')).toEqual(defaultArchiveURLState); + expect(parseArchiveURLState('?state=%7Bnot-json')).toEqual(defaultArchiveURLState); + }); + + it('returns independent defaults for missing and malformed state', () => { + const missing = parseArchiveURLState(''); + const malformed = parseArchiveURLState('?state=%7Bnot-json'); + + missing.filters.push({ field: 'message_type', operator: 'equals', value: 'email' }); + missing.groupingChain.push('person'); + missing.sort.push({ field: 'sent_at', direction: 'desc' }); + missing.columns.push('attachment_count'); + + expect(malformed).toEqual(defaultArchiveURLState); + expect(missing.filters).not.toBe(defaultArchiveURLState.filters); + expect(missing.groupingChain).not.toBe(defaultArchiveURLState.groupingChain); + expect(missing.sort).not.toBe(defaultArchiveURLState.sort); + expect(missing.columns).not.toBe(defaultArchiveURLState.columns); + }); +}); diff --git a/web/src/lib/archive/url-state.ts b/web/src/lib/archive/url-state.ts new file mode 100644 index 000000000..da88140fb --- /dev/null +++ b/web/src/lib/archive/url-state.ts @@ -0,0 +1,148 @@ +import type { + ArchiveFilter, + ArchivePresentation, + ArchiveSearchMode, + ArchiveSort, + ArchiveURLState +} from './types'; + +const STATE_PARAMETER = 'state'; + +export const defaultArchiveURLState: ArchiveURLState = { + schemaVersion: 1, + query: '', + searchMode: 'fts', + filters: [], + groupingChain: [], + presentation: 'table', + sort: [], + columns: ['kind', 'people', 'subject', 'excerpt', 'time'], + selectedRow: null, + inspectorPinned: false, + conversationAnchor: null, + scrollKey: null +}; + +function freshDefaultArchiveURLState(): ArchiveURLState { + return { + ...defaultArchiveURLState, + filters: defaultArchiveURLState.filters.map((filter) => ({ + ...filter, + value: Array.isArray(filter.value) ? [...filter.value] : filter.value + })), + groupingChain: [...defaultArchiveURLState.groupingChain], + sort: defaultArchiveURLState.sort.map((sort) => ({ ...sort })), + columns: [...defaultArchiveURLState.columns] + }; +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function isSearchMode(value: unknown): value is ArchiveSearchMode { + return value === 'fts' || value === 'vector' || value === 'hybrid'; +} + +function isPresentation(value: unknown): value is ArchivePresentation { + return value === 'table' || value === 'timeline' || value === 'files'; +} + +function stringOrNull(value: unknown, fallback: string | null): string | null { + return typeof value === 'string' || value === null ? value : fallback; +} + +function stringArray(value: unknown, fallback: string[]): string[] { + return Array.isArray(value) && value.every((item) => typeof item === 'string') + ? [...value] + : [...fallback]; +} + +function isFilterValue(value: unknown): boolean { + return ( + typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean' || + (Array.isArray(value) && value.every((item) => typeof item === 'string')) + ); +} + +function isFilter(value: unknown): value is ArchiveFilter { + return ( + isRecord(value) && + typeof value.field === 'string' && + typeof value.operator === 'string' && + isFilterValue(value.value) + ); +} + +function filterArray(value: unknown): ArchiveFilter[] { + return Array.isArray(value) && value.every(isFilter) ? value : []; +} + +function isSort(value: unknown): value is ArchiveSort { + return ( + isRecord(value) && + typeof value.field === 'string' && + (value.direction === 'asc' || value.direction === 'desc') + ); +} + +function sortArray(value: unknown): ArchiveSort[] { + return Array.isArray(value) && value.every(isSort) ? value : []; +} + +function normalizeArchiveURLState(value: unknown): ArchiveURLState { + if (!isRecord(value)) return freshDefaultArchiveURLState(); + + // Session-owned bulk selection is intentionally discarded even if an old or + // future producer writes it into the state envelope. + const { bulkSelection: _bulkSelection, ...futureAndKnownFields } = value; + + return { + ...futureAndKnownFields, + schemaVersion: + typeof value.schemaVersion === 'number' && Number.isSafeInteger(value.schemaVersion) + ? value.schemaVersion + : defaultArchiveURLState.schemaVersion, + query: typeof value.query === 'string' ? value.query : defaultArchiveURLState.query, + searchMode: isSearchMode(value.searchMode) + ? value.searchMode + : defaultArchiveURLState.searchMode, + filters: filterArray(value.filters), + groupingChain: stringArray(value.groupingChain, defaultArchiveURLState.groupingChain), + presentation: isPresentation(value.presentation) + ? value.presentation + : defaultArchiveURLState.presentation, + sort: sortArray(value.sort), + columns: stringArray(value.columns, defaultArchiveURLState.columns), + selectedRow: stringOrNull(value.selectedRow, defaultArchiveURLState.selectedRow), + inspectorPinned: + typeof value.inspectorPinned === 'boolean' + ? value.inspectorPinned + : defaultArchiveURLState.inspectorPinned, + conversationAnchor: stringOrNull( + value.conversationAnchor, + defaultArchiveURLState.conversationAnchor + ), + scrollKey: stringOrNull(value.scrollKey, defaultArchiveURLState.scrollKey) + }; +} + +export function serializeArchiveURLState(state: ArchiveURLState): string { + const parameters = new URLSearchParams(); + parameters.set(STATE_PARAMETER, JSON.stringify(normalizeArchiveURLState(state))); + return `?${parameters.toString()}`; +} + +export function parseArchiveURLState(search: string): ArchiveURLState { + const parameters = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search); + const encoded = parameters.get(STATE_PARAMETER); + if (encoded === null) return freshDefaultArchiveURLState(); + + try { + return normalizeArchiveURLState(JSON.parse(encoded)); + } catch { + return freshDefaultArchiveURLState(); + } +} diff --git a/web/src/lib/commands/registry.ts b/web/src/lib/commands/registry.ts new file mode 100644 index 000000000..4028838e9 --- /dev/null +++ b/web/src/lib/commands/registry.ts @@ -0,0 +1,73 @@ +export type CommandHandler = (event?: KeyboardEvent) => void; + +export const COMMAND_DEFINITIONS = [ + command('move-next', 'Move to next row', ['J', '↓'], ['j', 'arrowdown'], 'Navigate'), + command('move-previous', 'Move to previous row', ['K', '↑'], ['k', 'arrowup'], 'Navigate'), + command('reader-previous', 'Previous item in reader', ['H'], ['h'], 'Navigate'), + command('reader-next', 'Next item in reader', ['L'], ['l'], 'Navigate'), + command('page-up', 'Move up one page', ['PgUp'], ['pageup'], 'Navigate'), + command('page-down', 'Move down one page', ['PgDn'], ['pagedown'], 'Navigate'), + command('first-row', 'Move to first row', ['Home'], ['home'], 'Navigate'), + command('last-row', 'Move to last row', ['End'], ['end'], 'Navigate'), + command('open-row', 'Open or drill into focused row', ['Enter'], ['enter'], 'Navigate'), + command('close-layer', 'Close current layer or restore context', ['Esc'], ['escape'], 'Navigate'), + command('focus-search', 'Focus search', ['/'], ['/'], 'Navigate'), + command('toggle-selection', 'Toggle focused row selection', ['Space'], ['space'], 'Selection'), + command('select-visible', 'Select all visible rows', ['A'], ['shift+a'], 'Selection'), + command('clear-selection', 'Clear selection', ['x'], ['x'], 'Selection'), + command('review-delete-selected', 'Review selected messages for deletion', ['d'], ['d'], 'Safety', true), + command('review-delete-matching', 'Review all matching messages for deletion', ['D'], ['shift+d'], 'Safety', true), + command('open-filters', 'Open filters', ['F'], ['f'], 'Analyze'), + command('open-grouping', 'Open grouping controls', ['G'], ['g'], 'Analyze'), + command('change-sort', 'Change sort', ['S'], ['s'], 'Analyze'), + command('reverse-sort', 'Reverse sort direction', ['R'], ['r'], 'Analyze'), + command('open-keyboard-help', 'Open searchable keyboard help', ['?'], ['shift+/'], 'Help'), + command('open-command-palette', 'Open command palette', ['Mod', 'K'], ['mod+k'], 'Help') +] as const; + +export type CommandID = typeof COMMAND_DEFINITIONS[number]['id']; +export type CommandHandlers = Record; + +export interface CommandDefinition { + id: string; + label: string; + keys: readonly string[]; + combos: readonly string[]; + section: string; + keywords: string; + destructive: boolean; + review: boolean; + disabled?: boolean; +} + +export interface AppCommand extends CommandDefinition { + run: CommandHandler; +} + +export function createCommandRegistry(handlers: CommandHandlers): AppCommand[] { + return COMMAND_DEFINITIONS.map((definition) => { + const handler = handlers[definition.id]; + if (typeof handler !== 'function') throw new Error(`Documented command ${definition.id} lacks a handler`); + return { ...definition, run: handler }; + }); +} + +function command( + id: ID, + label: string, + keys: readonly string[], + combos: readonly string[], + section: string, + destructive = false +) { + return { + id, + label, + keys, + combos, + section, + keywords: `${section} ${label} ${keys.join(' ')}`, + destructive, + review: destructive + } as const; +} diff --git a/web/src/lib/components/auth/Login.svelte b/web/src/lib/components/auth/Login.svelte new file mode 100644 index 000000000..2d2510b8f --- /dev/null +++ b/web/src/lib/components/auth/Login.svelte @@ -0,0 +1,37 @@ + + +
+
+

msgvault

+

Log in

+

Enter the API key configured for this daemon.

+ + + + + {#if session.error} +

{session.error}

+ {/if} + + +
+
diff --git a/web/src/lib/components/auth/Login.test.ts b/web/src/lib/components/auth/Login.test.ts new file mode 100644 index 000000000..26981a63e --- /dev/null +++ b/web/src/lib/components/auth/Login.test.ts @@ -0,0 +1,45 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { createSessionController } from '../../api/session.svelte'; +import Login from './Login.svelte'; + +function response(status: number) { + if (status === 200) { + return Response.json({ + auth_mode: 'session', + csrf_token: 'csrf-token', + https: true, + plain_http_warning: false + }); + } + return Response.json({ error: 'unauthorized', message: 'Invalid API key' }, { status }); +} + +describe('Login', () => { + it('exchanges the API key and leaves required mode on success', async () => { + const fetchFn = vi.fn(async () => response(200)); + const session = createSessionController(fetchFn); + render(Login, { session }); + + await fireEvent.input(screen.getByLabelText('API key'), { target: { value: 'test-key' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Log in' })); + + await waitFor(() => expect(session.authMode).toBe('session')); + const request = fetchFn.mock.calls[0]?.[0] as Request; + expect(request.method).toBe('POST'); + expect(new URL(request.url).pathname).toBe('/api/session/login'); + await expect(request.clone().json()).resolves.toEqual({ api_key: 'test-key' }); + }); + + it('shows the server error after a rejected key', async () => { + const session = createSessionController(vi.fn(async () => response(401))); + render(Login, { session }); + + await fireEvent.input(screen.getByLabelText('API key'), { target: { value: 'wrong-key' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Log in' })); + + expect((await screen.findByRole('alert')).textContent).toContain('Invalid API key'); + expect(session.authMode).toBe('required'); + }); +}); diff --git a/web/src/lib/components/deletions/DeletionsWorkspace.svelte b/web/src/lib/components/deletions/DeletionsWorkspace.svelte new file mode 100644 index 000000000..4a20cf64f --- /dev/null +++ b/web/src/lib/components/deletions/DeletionsWorkspace.svelte @@ -0,0 +1,312 @@ + + +
+

Archive workspace

Deletions

Staged manifest lifecycle
+ {#if error}{/if} + +
+

Reviewed staging

Preflight the current session selection before creating a manifest.

+
+
+ {#if reviewed} +
+ {reviewed.count.toLocaleString()} {reviewed.count === 1 ? 'item' : 'items'} · {reviewed.estimated_bytes.toLocaleString()} bytes + Authority expires {reviewed.expires_at} + {#each reviewed.unavailable_actions as unavailable (`${unavailable.action}:${unavailable.reason}`)} + {unavailable.action}: {unavailable.reason} + {/each} +
+
+
+ {/if} + {#if preview}

{preview}

{/if} +
+ + {#if loading}

Loading deletion manifests…

+ {:else if manifests.length === 0}

No deletion manifests yet.

+ {:else} +
+ {#each manifests as manifest (manifest.id)} +
+
{manifest.id}{manifest.description}
+ {manifest.message_count.toLocaleString()} {manifest.message_count === 1 ? 'item' : 'items'} + {manifest.status} +
+
+
+ {/each} +
+ {/if} + + {#if detail} + + {/if} +
+ +{#if confirmStage && reviewed} + { confirmStage = undefined; }}> +

{selectionDescription()}. This creates a staged manifest; it does not execute deletion.

+ {#snippet footer()} +
+ +
+ {#if query} + {searchMode}: “{query}” + {/if} + {#each filters as filter (`${filter.dimension}:${filter.values.join('\u0000')}`)} + Filter {filter.dimension}: {filter.values.join(', ')} + {/each} + {#each groupingChain as dimension, index (`${dimension}:${index}`)} + + Group {groupingDimensionLabel(dimension)} + + + {/each} + {#if !query && filters.length === 0 && groupingChain.length === 0} + All archive entries + {/if} +
+ + {totalCount === undefined ? 'Count pending' : `${totalCount.toLocaleString()} results`} + + {#if filtersOpen} +
+ {#if filters.length === 0} + No active filters. Filtering controls will expand with additional canonical dimensions. + {:else} + {filters.length} active {filters.length === 1 ? 'filter' : 'filters'} +
+ {/if} + + + diff --git a/web/src/lib/components/explore/ContextBar.test.ts b/web/src/lib/components/explore/ContextBar.test.ts new file mode 100644 index 000000000..94612fcf5 --- /dev/null +++ b/web/src/lib/components/explore/ContextBar.test.ts @@ -0,0 +1,22 @@ +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import ContextBar from './ContextBar.svelte'; + +describe('ContextBar presentation control', () => { + it('exposes Table, Timeline, and Files as one keyboard-operable Show-as control', async () => { + const onPresentationChange = vi.fn(); + render(ContextBar, { + query: 'pasta', searchMode: 'hybrid', filters: [], groupingChain: [], + presentation: 'table', onPresentationChange, + onAddGroup: vi.fn(), onRemoveGroup: vi.fn(), onClearFilters: vi.fn() + }); + + const control = screen.getByRole('combobox', { name: 'Show as' }); + expect([...control.querySelectorAll('option')].map((option) => option.textContent)) + .toEqual(['Table', 'Timeline', 'Files']); + + await fireEvent.change(control, { target: { value: 'timeline' } }); + expect(onPresentationChange).toHaveBeenCalledWith('timeline'); + }); +}); diff --git a/web/src/lib/components/explore/EverythingTable.svelte b/web/src/lib/components/explore/EverythingTable.svelte new file mode 100644 index 000000000..e2b4ab6b8 --- /dev/null +++ b/web/src/lib/components/explore/EverythingTable.svelte @@ -0,0 +1,828 @@ + + +
+
+
+ Columns +
+ {#each ALL_COLUMNS as column (column.id)} + + {/each} +
+
+
+ +
+
+ {#each visibleColumns as column (column)} + + {column === 'attachments' ? '⌕' : ALL_COLUMNS.find((entry) => entry.id === column)?.label} + + {/each} +
+
+ {#if unavailable} +
+
+
+ {:else if error} +
+
+
+ {:else if loading && rows.length === 0} +
+ {#each { length: 10 } as _, index (index)} +
+ {#each visibleColumns as column (column)} + + {/each} +
+ {/each} +
+ {:else if rows.length === 0} +
No items match this view.
+ {:else if !slice || rowHeight === undefined} +

Preparing table layout…

+ {:else} +
+
+ {#each renderedRows as row, offset (row.key)} + {@const index = slice.start + offset} +
{ + activeKey = row.key; + onActiveKey?.(row.key); + gridElement?.focus(); + }} + ondblclick={() => onOpen?.(row)} + > + {#each visibleColumns as column (column)} + + {#if column === 'kind'} + {#if selection.isSelected(row.key)} + + {/if} + + {:else if column === 'people'} + {people(row)} + {:else if column === 'title'} + {row.title || '(untitled)'} + {:else if column === 'excerpt'} + {row.match.strongest_excerpt || row.preview} + {#if row.match.lexical_match_count !== undefined} + {row.match.lexical_match_count} lexical matches + {/if} + {:else if column === 'time'} + + {:else if column === 'attachments'} + {#if row.has_attachments} + + {:else} + + {/if} + {:else if column === 'size'} + {formatBytes(row.attachment_size)} + {/if} + + {/each} +
+ {/each} +
+
+ {/if} + {#if loadingMore} +
+
Loading more… {rows.length.toLocaleString()} loaded
+
+ {/if} +
+
+
+ + diff --git a/web/src/lib/components/explore/EverythingTable.test.ts b/web/src/lib/components/explore/EverythingTable.test.ts new file mode 100644 index 000000000..37c5e9302 --- /dev/null +++ b/web/src/lib/components/explore/EverythingTable.test.ts @@ -0,0 +1,367 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { ExploreSelectionState } from '../../explore/state.svelte'; +import type { EntryRow } from '../../explore/models'; +import EverythingTable from './EverythingTable.svelte'; + +function row(index: number, overrides: Partial = {}): EntryRow { + return { + key: `message:${index}`, + kind: 'message', + message_type: 'email', + conversation_type: 'email', + title: `Synthetic subject ${index}`, + preview: `Synthetic excerpt ${index}`, + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + source_type: 'gmail', + participant_labels: ['Alice Example'], + participant_ids: [1], + attachment_count: 0, + attachment_size: 0, + has_attachments: false, + deleted_from_source: false, + message_count: 1, + match: {}, + ...overrides + }; +} + +describe('EverythingTable', () => { + it('owns headers, virtual rows, and named states in one focusable grid', () => { + const { rerender } = render(EverythingTable, { + rows: [row(1)], selection: new ExploreSelectionState() + }); + const grid = screen.getByRole('grid', { name: 'Everything results' }); + expect(screen.getAllByRole('grid')).toHaveLength(1); + expect(grid.contains(screen.getByRole('columnheader', { name: 'Kind' }))).toBe(true); + expect(grid.contains(screen.getByRole('row', { name: /Synthetic subject 1/ }))).toBe(true); + + void rerender({ rows: [], selection: new ExploreSelectionState(), error: 'Synthetic failure' }); + const alert = screen.getByRole('alert'); + expect(alert.closest('[role="gridcell"]')?.closest('[role="row"]')).not.toBeNull(); + }); + + it('waits for computed row geometry before virtualizing and becomes ready reactively', async () => { + document.documentElement.style.removeProperty('--row-height'); + document.documentElement.removeAttribute('data-density'); + const rendered = render(EverythingTable, { + rows: [row(1)], + selection: new ExploreSelectionState() + }); + + expect(screen.getByText('Preparing table layout…')).toBeDefined(); + expect(rendered.container.querySelector('.virtual-spacer')).toBeNull(); + + document.documentElement.style.setProperty('--row-height', '41px'); + document.documentElement.dataset.density = 'comfortable'; + + await waitFor(() => { + const spacer = rendered.container.querySelector('.virtual-spacer'); + expect(spacer?.style.height).toBe('41px'); + }); + expect(screen.getByRole('row', { name: /Synthetic subject 1/ })).toBeDefined(); + }); + + it('marks the inspected row independently from bulk selection', () => { + render(EverythingTable, { + rows: [row(1), row(2)], + selection: new ExploreSelectionState(), + inspectedKey: 'message:2' + }); + + expect(screen.getByRole('row', { name: /Synthetic subject 1/ }).getAttribute('aria-current')).toBeNull(); + expect(screen.getByRole('row', { name: /Synthetic subject 2/ }).getAttribute('aria-current')).toBe('true'); + }); + + it('reports rendered conversation rows for lazy exact counts and displays the result', async () => { + const onVisibleRows = vi.fn(); + render(EverythingTable, { + rows: [row(1, { kind: 'conversation', message_type: 'sms', match: { lexical_match_count: 3 } })], + selection: new ExploreSelectionState(), + onVisibleRows + }); + + await waitFor(() => expect(onVisibleRows).toHaveBeenCalledWith(['message:1'])); + expect(screen.getByText('3 lexical matches')).toBeDefined(); + }); + + it('leaves row height to the computed CSS token with initial columns and textual modality', () => { + render(EverythingTable, { rows: [row(1)], selection: new ExploreSelectionState() }); + + const rendered = screen.getByRole('row', { name: /Email item/ }); + expect((rendered as HTMLElement).style.height).toBe(''); + expect(getComputedStyle(document.documentElement).getPropertyValue('--row-height').trim()).toBe('36px'); + expect(screen.getByRole('columnheader', { name: 'Kind' })).toBeDefined(); + expect(screen.getByRole('columnheader', { name: 'People / source' })).toBeDefined(); + expect(screen.getByRole('columnheader', { name: 'Attachments' }).textContent).toBe('⌕'); + expect(screen.getByLabelText('Email item')).toBeDefined(); + }); + + it('exposes size through the column picker without showing it initially', async () => { + const onColumnsChange = vi.fn(); + render(EverythingTable, { + rows: [row(1, { attachment_size: 2048 })], + selection: new ExploreSelectionState(), + onColumnsChange + }); + + expect(screen.queryByRole('columnheader', { name: 'Size' })).toBeNull(); + await fireEvent.click(screen.getByText('Columns')); + await fireEvent.click(screen.getByRole('checkbox', { name: 'Size' })); + + expect(screen.getByRole('columnheader', { name: 'Size' })).toBeDefined(); + expect(onColumnsChange).toHaveBeenCalled(); + }); + + it('keeps keyboard focus on the grid while j/k move a stable keyed cursor', async () => { + render(EverythingTable, { + rows: [row(1), row(2), row(3)], + selection: new ExploreSelectionState() + }); + const grid = screen.getByRole('grid', { name: 'Everything results' }); + grid.focus(); + + await fireEvent.keyDown(grid, { key: 'j' }); + await fireEvent.keyDown(grid, { key: 'j' }); + await fireEvent.keyDown(grid, { key: 'k' }); + + expect(document.activeElement).toBe(grid); + expect(grid.getAttribute('aria-activedescendant')).toContain('message-3a-2'); + }); + + it('requests another cursor page when navigation or scrolling reaches the loaded boundary', async () => { + const onLoadMore = vi.fn().mockResolvedValue(undefined); + render(EverythingTable, { + rows: [row(1), row(2)], + selection: new ExploreSelectionState(), + hasMore: true, + onLoadMore + }); + const grid = screen.getByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + grid.focus(); + + await fireEvent.keyDown(grid, { key: 'j' }); + await fireEvent.keyDown(grid, { key: 'j' }); + expect(onLoadMore).toHaveBeenCalled(); + + onLoadMore.mockClear(); + Object.defineProperty(grid, 'scrollTop', { configurable: true, value: 36, writable: true }); + await fireEvent.scroll(grid); + expect(onLoadMore).toHaveBeenCalled(); + }); + + it('uses lowercase a to select only non-overscan viewport rows', async () => { + const selection = new ExploreSelectionState(); + render(EverythingTable, { + rows: Array.from({ length: 20 }, (_, index) => row(index + 1)), + selection + }); + const grid = screen.getByRole('grid', { name: 'Everything results' }); + grid.focus(); + + await fireEvent.keyDown(grid, { key: 'a' }); + + expect(selection.count).toBe(10); + expect(selection.selectedKeys(['message:1', 'message:10', 'message:11'])).toEqual([ + 'message:1', + 'message:10' + ]); + }); + + it('keeps the active stable key across reorder and reconciles manual scrolling to a rendered row', async () => { + const selection = new ExploreSelectionState(); + const rows = Array.from({ length: 100 }, (_, index) => row(index + 1)); + const rendered = render(EverythingTable, { rows, selection }); + const grid = screen.getByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + grid.focus(); + await fireEvent.keyDown(grid, { key: 'j' }); + expect(grid.getAttribute('aria-activedescendant')).toContain('message-3a-2'); + + await rendered.rerender({ rows: [row(100), ...rows.slice(0, 99)], selection }); + expect(grid.getAttribute('aria-activedescendant')).toContain('message-3a-2'); + + Object.defineProperty(grid, 'scrollTop', { configurable: true, value: 50 * 36, writable: true }); + await fireEvent.scroll(grid); + await waitFor(() => { + const active = grid.getAttribute('aria-activedescendant'); + expect(active).toBeTruthy(); + expect(document.getElementById(active!)).toBeDefined(); + expect(document.getElementById(active!)).not.toBeNull(); + }); + expect(document.activeElement).toBe(grid); + }); + + it('reports a replacement stable key when search results remove the active row', async () => { + const onActiveKey = vi.fn(); + const selection = new ExploreSelectionState(); + const rendered = render(EverythingTable, { + rows: [row(1), row(2)], + selection, + focusedKey: 'message:2', + onActiveKey + }); + + await rendered.rerender({ rows: [row(10), row(11)], selection, focusedKey: 'message:2', onActiveKey }); + + await waitFor(() => expect(onActiveKey).toHaveBeenCalledWith('message:10')); + expect(screen.getByRole('grid', { name: 'Everything results' }).getAttribute('aria-activedescendant')) + .toContain('message-3a-10'); + }); + + it('preserves a deep restoration key while its cursor pages are still loading', async () => { + const onActiveKey = vi.fn(); + const selection = new ExploreSelectionState(); + const rendered = render(EverythingTable, { + rows: [row(1), row(2)], + selection, + focusedKey: 'message:5000', + restoring: true, + onActiveKey + }); + + expect(onActiveKey).not.toHaveBeenCalled(); + expect(screen.getByRole('grid', { name: 'Everything results' }).getAttribute('aria-activedescendant')).toBeNull(); + + await rendered.rerender({ + rows: [row(1), row(2), row(5000)], + selection, + focusedKey: 'message:5000', + restoring: false, + onActiveKey + }); + await waitFor(() => expect(screen.getByRole('grid', { name: 'Everything results' }) + .getAttribute('aria-activedescendant')).toContain('message-3a-5000')); + expect(onActiveKey).not.toHaveBeenCalled(); + }); + + it('restores the keyed focus and scroll anchor supplied by URL state', async () => { + render(EverythingTable, { + rows: [row(1), row(2), row(3)], + selection: new ExploreSelectionState(), + focusedKey: 'message:3', + scrollAnchor: { key: 'message:2', offset: 7 } + }); + const grid = screen.getByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + await Promise.resolve(); + + expect(grid.getAttribute('aria-activedescendant')).toContain('message-3a-3'); + expect(grid.scrollTop).toBe(43); + }); + + it('reapplies a retained anchor across same-count result generations', async () => { + const rows = Array.from({ length: 200 }, (_, index) => row(index + 1)); + const selection = new ExploreSelectionState(); + const rendered = render(EverythingTable, { + rows, + selection, + generation: 1, + restoring: true, + focusedKey: 'message:150', + scrollAnchor: { key: 'message:140', offset: 5 } + }); + const grid = screen.getByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + await waitFor(() => expect(grid.scrollTop).toBe(139 * 36 + 5)); + + grid.scrollTop = 0; + await rendered.rerender({ + rows: rows.map((entry) => ({ ...entry, title: `Rebuilt ${entry.title}` })), + selection, + generation: 2, + restoring: true, + focusedKey: 'message:150', + scrollAnchor: { key: 'message:140', offset: 5 } + }); + + await waitFor(() => expect(grid.scrollTop).toBe(139 * 36 + 5)); + }); + + it('does not discard the first real user scroll when a restored anchor was already at zero', async () => { + const onScrollAnchor = vi.fn(); + render(EverythingTable, { + rows: [row(1), row(2)], + selection: new ExploreSelectionState(), + scrollAnchor: { key: 'message:1', offset: 0 }, + onScrollAnchor + }); + const grid = screen.getByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + await Promise.resolve(); + Object.defineProperty(grid, 'scrollTop', { configurable: true, value: 36, writable: true }); + await fireEvent.scroll(grid); + + expect(onScrollAnchor).toHaveBeenCalledWith('message:2', 0); + }); + + it('supports Space, Shift range, A visible, x clear, and Enter open', async () => { + const selection = new ExploreSelectionState(); + const onOpen = vi.fn(); + render(EverythingTable, { rows: [row(1), row(2), row(3)], selection, onOpen }); + const grid = screen.getByRole('grid', { name: 'Everything results' }); + grid.focus(); + + await fireEvent.keyDown(grid, { key: ' ' }); + await fireEvent.keyDown(grid, { key: 'j' }); + await fireEvent.keyDown(grid, { key: 'j' }); + await fireEvent.keyDown(grid, { key: ' ', shiftKey: true }); + expect(selection.selectedKeys(['message:1', 'message:2', 'message:3'])).toHaveLength(3); + + await fireEvent.keyDown(grid, { key: 'x' }); + expect(selection.count).toBe(0); + await fireEvent.keyDown(grid, { key: 'A', shiftKey: true }); + expect(selection.count).toBe(3); + await fireEvent.keyDown(grid, { key: 'Enter' }); + expect(onOpen).toHaveBeenCalledWith(expect.objectContaining({ key: 'message:3' })); + }); + + it('distinguishes loading, empty, and persistent cache-unavailable states', async () => { + const selection = new ExploreSelectionState(); + const onRetry = vi.fn(); + const { rerender } = render(EverythingTable, { rows: [], selection, loading: true, onRetry }); + const skeleton = screen.getAllByTestId('everything-skeleton')[0]!; + expect(skeleton.style.height).toBe(''); + expect(getComputedStyle(document.documentElement).getPropertyValue('--row-height').trim()).toBe('36px'); + const grid = screen.getByRole('grid', { name: 'Everything results' }); + expect(grid.getAttribute('aria-busy')).toBe('true'); + expect(grid.getAttribute('aria-rowcount')).toBeNull(); + expect(skeleton.style.gridTemplateColumns).toBe( + screen.getByRole('row', { name: /Kind/ }).style.gridTemplateColumns + ); + expect(skeleton.children).toHaveLength(6); + + void rerender({ rows: [], selection, loading: false }); + expect(screen.getByText('No items match this view.')).toBeDefined(); + expect(grid.getAttribute('aria-rowcount')).toBe('2'); + + void rerender({ + rows: [], + selection, + loading: false, + unavailable: { + error: 'cache_unavailable', + message: 'The analytical cache has not been built.', + readiness: 'absent', + recovery_action: 'msgvault build-cache' + }, + onRetry + }); + expect(screen.getByRole('alert').textContent).toContain('msgvault build-cache'); + expect(grid.getAttribute('aria-rowcount')).toBeNull(); + await fireEvent.click(screen.getByRole('button', { name: 'Retry cache check' })); + expect(onRetry).toHaveBeenCalledOnce(); + expect(screen.queryByText('No items match this view.')).toBeNull(); + }); + + it('renders request errors as an exclusive state rather than an empty result', () => { + render(EverythingTable, { + rows: [], + selection: new ExploreSelectionState(), + error: 'The query could not be completed.' + }); + + expect(screen.getByRole('alert').textContent).toContain('The query could not be completed.'); + expect(screen.queryByText('No items match this view.')).toBeNull(); + expect(screen.getByRole('grid', { name: 'Everything results' }).getAttribute('aria-rowcount')).toBeNull(); + }); +}); diff --git a/web/src/lib/components/explore/FilesPresentation.svelte b/web/src/lib/components/explore/FilesPresentation.svelte new file mode 100644 index 000000000..c73272899 --- /dev/null +++ b/web/src/lib/components/explore/FilesPresentation.svelte @@ -0,0 +1,348 @@ + + +
+
Files in context{totalCount?.toLocaleString() ?? '—'} files
+
+
+ ReceivedFilename + Containing itemSource + Size +
+
+ {#if unavailable} +
+ {:else if error && files.length === 0} +
+ {:else if loading && files.length === 0} +
Loading files in this context…
+ {:else if files.length === 0} +
No files match this view.
+ {:else if !slice || rowHeight === undefined} +
Preparing files layout…
+ {:else} +
+
+ {#each renderedFiles as file, offset (file.key)} + {@const index = slice.start + offset} +
{ activeKey = file.key; onActiveKey?.(file.key); grid?.focus(); }} + ondblclick={() => onOpenFile?.(file)} + > + {formatTime(file.occurred_at)} + {file.filename || 'Unnamed file'} + + {file.source_identifier} + {formatBytes(file.size)} +
+ {/each} +
+
+ {#if error} +
+ {:else if loadingMore} +
Loading more… {files.length.toLocaleString()} loaded
+ {:else if hasMore} +
+ {/if} + {/if} +
+
+
+ + diff --git a/web/src/lib/components/explore/FilesPresentation.test.ts b/web/src/lib/components/explore/FilesPresentation.test.ts new file mode 100644 index 000000000..e2bb63659 --- /dev/null +++ b/web/src/lib/components/explore/FilesPresentation.test.ts @@ -0,0 +1,123 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import type { ExploreFileFact } from '../../explore/models'; +import FilesPresentation from './FilesPresentation.svelte'; + +function file(index: number): ExploreFileFact { + return { + id: index, + key: `message:${index}:file:${index}`, + entry_key: `message:${index}`, + message_id: index, + conversation_id: index + 1000, + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + title: `Containing item ${index}`, + filename: `file-${index}.pdf`, + mime_type: 'application/pdf', + size: 2048 + }; +} + +describe('FilesPresentation', () => { + it('virtualizes a large loaded slice and implements bounded table navigation', async () => { + const files = Array.from({ length: 1_000 }, (_, index) => file(index + 1)); + const onActiveKey = vi.fn(); + const onLoadMore = vi.fn(); + render(FilesPresentation, { + files, hasMore: true, totalCount: 100_000, onActiveKey, onLoadMore + }); + const grid = screen.getByRole('grid', { name: 'Files in current context' }); + grid.focus(); + + await waitFor(() => expect(screen.getAllByRole('row').length).toBeLessThan(80)); + await fireEvent.keyDown(grid, { key: 'PageDown' }); + expect(onActiveKey).toHaveBeenLastCalledWith('message:10:file:10'); + await fireEvent.keyDown(grid, { key: 'PageUp' }); + expect(onActiveKey).toHaveBeenLastCalledWith('message:1:file:1'); + await fireEvent.keyDown(grid, { key: 'End' }); + expect(onActiveKey).toHaveBeenLastCalledWith('message:1000:file:1000'); + expect(onLoadMore).not.toHaveBeenCalled(); + }); + + it('loads at most one next page when keyboard movement crosses the loaded boundary', async () => { + let files = Array.from({ length: 600 }, (_, index) => file(index + 1)); + const onActiveKey = vi.fn(); + let rendered: ReturnType; + const onLoadMore = vi.fn(async () => { + files = Array.from({ length: 700 }, (_, index) => file(index + 1)); + await rendered.rerender({ + files, hasMore: true, totalCount: 10_000, + focusedKey: 'message:600:file:600', onActiveKey, onLoadMore + }); + return { status: 'advanced' }; + }); + rendered = render(FilesPresentation, { + files, hasMore: true, totalCount: 10_000, + focusedKey: 'message:600:file:600', onActiveKey, onLoadMore + }); + const grid = screen.getByRole('grid', { name: 'Files in current context' }); + grid.focus(); + + await fireEvent.keyDown(grid, { key: 'ArrowDown' }); + + expect(onLoadMore).toHaveBeenCalledTimes(1); + await waitFor(() => expect(onActiveKey).toHaveBeenLastCalledWith('message:601:file:601')); + expect(screen.getAllByRole('row').length).toBeLessThan(80); + }); + + it('keeps loaded files visible and exposes an accessible paging retry', async () => { + const files = Array.from({ length: 600 }, (_, index) => file(index + 1)); + const onLoadMore = vi.fn(async () => ({ status: 'advanced' })); + render(FilesPresentation, { + files, hasMore: true, totalCount: 10_000, + error: 'The next file page could not be loaded.', onLoadMore + }); + + expect(screen.getByText('file-1.pdf')).toBeDefined(); + await fireEvent.click(screen.getByRole('button', { name: 'Retry loading more files' })); + expect(onLoadMore).toHaveBeenCalledTimes(1); + }); + + it('restores a keyed scroll anchor and reports user scroll authority', async () => { + const onScrollAnchor = vi.fn(); + const files = Array.from({ length: 200 }, (_, index) => file(index + 1)); + const rendered = render(FilesPresentation, { + files, + focusedKey: 'message:150:file:150', + scrollAnchor: { key: 'message:140:file:140', offset: 5 }, + restoring: true, + onScrollAnchor + }); + const grid = screen.getByRole('grid', { name: 'Files in current context' }) as HTMLDivElement; + await waitFor(() => expect(grid.scrollTop).toBe(139 * 36 + 5)); + expect(grid.getAttribute('aria-activedescendant')).toContain('message-3a-150'); + + await rendered.rerender({ + files, focusedKey: 'message:150:file:150', + scrollAnchor: { key: 'message:140:file:140', offset: 5 }, + restoring: false, onScrollAnchor + }); + grid.scrollTop = 72; + await fireEvent.scroll(grid); + expect(onScrollAnchor).toHaveBeenLastCalledWith('message:3:file:3', 0); + }); + + it('opens the attachment while keeping the containing item a separate action', async () => { + const onOpenFile = vi.fn(); + const onOpenItem = vi.fn(); + const selected = file(7); + render(FilesPresentation, { files: [selected], onOpenFile, onOpenItem }); + const grid = screen.getByRole('grid', { name: 'Files in current context' }); + grid.focus(); + + await fireEvent.keyDown(grid, { key: 'Enter' }); + expect(onOpenFile).toHaveBeenCalledWith(selected); + expect(onOpenItem).not.toHaveBeenCalled(); + + await fireEvent.click(screen.getByRole('button', { name: 'Open containing item 7' })); + expect(onOpenItem).toHaveBeenCalledWith('message:7'); + }); +}); diff --git a/web/src/lib/components/explore/GroupTable.svelte b/web/src/lib/components/explore/GroupTable.svelte new file mode 100644 index 000000000..fafeff0ae --- /dev/null +++ b/web/src/lib/components/explore/GroupTable.svelte @@ -0,0 +1,505 @@ + + +
+
= 0 && rows[activeIndex] ? rowId(rows[activeIndex]!) : undefined} + tabindex="0" + onkeydown={handleKeydown} + onscroll={handleScroll} + > +
+ Group + Items + Estimated + Latest + Action +
+
+ {#if unavailable} +
+ {:else if error} +
+ {:else if loading && rows.length === 0} +
Loading grouped results…
+ {:else if rows.length === 0} +
No groups match this view.
+ {:else if !slice || rowHeight === undefined} +
Preparing grouped layout…
+ {:else} +
+
+ {#each renderedRows as row, offset (row.key)} + {@const index = slice.start + offset} +
{ + activeKey = stableKey(row); + onActiveKey?.(activeKey); + gridElement?.focus(); + if (!(event.target as Element).closest('button')) onInspect?.(row); + }} + ondblclick={() => { if (drillable) onDrill(row); }} + > +
{row.label}
+ {row.count.toLocaleString()} + {bytes(row.estimated_bytes)} +
+ + {#if drillable} +
+ {/each} +
+
+ {/if} + {#if loadingMore}
Loading more groups…
{/if} +
+
+
+ + diff --git a/web/src/lib/components/explore/GroupTable.test.ts b/web/src/lib/components/explore/GroupTable.test.ts new file mode 100644 index 000000000..3905948e4 --- /dev/null +++ b/web/src/lib/components/explore/GroupTable.test.ts @@ -0,0 +1,261 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import GroupTable from './GroupTable.svelte'; + +describe('GroupTable', () => { + const rows = [ + { key: '7', label: 'Example source', count: 12, estimated_bytes: 2048, latest_at: '2026-07-18T12:00:00Z' }, + { key: '8', label: 'Second source', count: 5, estimated_bytes: 512, latest_at: '2026-07-17T12:00:00Z' } + ]; + + it('owns headers, virtual rows, and named states in one focusable grid', async () => { + const rendered = render(GroupTable, { rows, dimension: 'source', onDrill: vi.fn() }); + const grid = screen.getByRole('grid', { name: 'Everything grouped by source' }); + expect(screen.getAllByRole('grid')).toHaveLength(1); + expect(grid.contains(screen.getByRole('columnheader', { name: 'Group' }))).toBe(true); + expect(grid.contains(screen.getByRole('row', { name: /Example source/ }))).toBe(true); + + await rendered.rerender({ rows: [], dimension: 'source', onDrill: vi.fn(), error: 'Synthetic failure' }); + const alert = screen.getByRole('alert'); + expect(alert.closest('[role="gridcell"]')?.closest('[role="row"]')).not.toBeNull(); + expect(grid.getAttribute('aria-rowcount')).toBeNull(); + + await rendered.rerender({ rows: [], dimension: 'source', onDrill: vi.fn(), loading: true, error: '' }); + expect(screen.getByRole('status').textContent).toContain('Loading grouped results'); + expect(grid.getAttribute('aria-rowcount')).toBeNull(); + + await rendered.rerender({ rows: [], dimension: 'source', onDrill: vi.fn(), loading: false, error: '' }); + expect(screen.getByText('No groups match this view.')).toBeDefined(); + expect(grid.getAttribute('aria-rowcount')).toBe('2'); + }); + + it('leaves aggregate row height to CSS and drills with keyboard focus retained', async () => { + const onDrill = vi.fn(); + render(GroupTable, { rows, dimension: 'source', onDrill }); + const grid = screen.getByRole('grid', { name: 'Everything grouped by source' }); + const first = screen.getByRole('row', { name: /Example source/ }); + + expect((first as HTMLElement).style.height).toBe(''); + expect(getComputedStyle(document.documentElement).getPropertyValue('--row-height').trim()).toBe('36px'); + grid.focus(); + await fireEvent.keyDown(grid, { key: 'j' }); + await fireEvent.keyDown(grid, { key: 'Enter' }); + + expect(document.activeElement).toBe(grid); + expect(onDrill).toHaveBeenCalledWith(expect.objectContaining({ key: '8' })); + expect(first.getAttribute('aria-rowindex')).toBe('2'); + expect(screen.getByRole('button', { name: 'Drill into Example source' }).textContent?.trim()).toBe('Drill'); + }); + + it('does not let a focused drill control bubble Enter into grid navigation', async () => { + const onDrill = vi.fn(); + render(GroupTable, { rows, dimension: 'source', onDrill }); + const drill = screen.getByRole('button', { name: 'Drill into Example source' }); + + drill.focus(); + await fireEvent.keyDown(drill, { key: 'Enter' }); + expect(onDrill).not.toHaveBeenCalled(); + await fireEvent.click(drill); + expect(onDrill).toHaveBeenCalledOnce(); + }); + + it('renders cache and request failures exclusively with retry', async () => { + const onRetry = vi.fn(); + const rendered = render(GroupTable, { + rows: [], + dimension: 'source', + onDrill: vi.fn(), + unavailable: { + error: 'cache_unavailable', + message: 'The analytical cache has not been built.', + readiness: 'absent', + recovery_action: 'msgvault build-cache' + }, + onRetry + }); + + expect(screen.getByRole('alert').textContent).toContain('msgvault build-cache'); + expect(screen.getByRole('grid', { name: 'Everything grouped by source' }).getAttribute('aria-rowcount')).toBeNull(); + expect(screen.queryByText('No groups match this view.')).toBeNull(); + await fireEvent.click(screen.getByRole('button', { name: 'Retry cache check' })); + expect(onRetry).toHaveBeenCalledOnce(); + + await rendered.rerender({ + rows: [], dimension: 'source', onDrill: vi.fn(), error: 'Grouped query failed.', + unavailable: undefined, onRetry + }); + expect(screen.getByRole('alert').textContent).toContain('Grouped query failed.'); + expect(screen.queryByText('No groups match this view.')).toBeNull(); + await fireEvent.click(screen.getByRole('button', { name: 'Retry request' })); + expect(onRetry).toHaveBeenCalledTimes(2); + }); + + it('uses the observed viewport, durable group key and one-based virtual row indices', async () => { + let resizeCallback: ResizeObserverCallback | undefined; + const observe = vi.fn(); + const disconnect = vi.fn(); + vi.stubGlobal('ResizeObserver', class { + constructor(callback: ResizeObserverCallback) { resizeCallback = callback; } + observe = observe; + disconnect = disconnect; + }); + const onActiveKey = vi.fn(); + const onScrollAnchor = vi.fn(); + const manyRows = Array.from({ length: 200 }, (_, index) => ({ + key: String(index + 1), label: `Source ${index + 1}`, count: index + 1, + estimated_bytes: 10, latest_at: '2026-07-18T12:00:00Z' + })); + render(GroupTable, { + rows: manyRows, + dimension: 'source', + onDrill: vi.fn(), + focusedKey: 'group:source:150', + scrollAnchor: { key: 'group:source:150', offset: 5 }, + onActiveKey, + onScrollAnchor + }); + const grid = screen.getByRole('grid', { name: 'Everything grouped by source' }) as HTMLDivElement; + Object.defineProperty(grid, 'clientHeight', { configurable: true, value: 720 }); + resizeCallback?.([], {} as ResizeObserver); + + await waitFor(() => expect(grid.scrollTop).toBe(149 * 36 + 5)); + expect(observe).toHaveBeenCalledWith(grid); + expect(grid.querySelectorAll('[role="row"]').length).toBeLessThan(40); + expect(grid.getAttribute('aria-activedescendant')).toContain('150'); + expect(onActiveKey).not.toHaveBeenCalled(); + vi.unstubAllGlobals(); + }); + + it('reapplies an exact anchor when a retained same-count result gets a new generation', async () => { + const onActiveKey = vi.fn(); + const onScrollAnchor = vi.fn(); + const manyRows = Array.from({ length: 200 }, (_, index) => ({ + key: String(index + 1), label: `Source ${index + 1}`, count: index + 1, + estimated_bytes: 10, latest_at: '2026-07-18T12:00:00Z' + })); + const rendered = render(GroupTable, { + rows: manyRows, + dimension: 'source', + generation: 1, + restoring: true, + focusedKey: 'group:source:150', + scrollAnchor: { key: 'group:source:140', offset: 5 }, + onDrill: vi.fn(), + onActiveKey, + onScrollAnchor + }); + const grid = screen.getByRole('grid', { name: 'Everything grouped by source' }) as HTMLDivElement; + await waitFor(() => expect(grid.scrollTop).toBe(139 * 36 + 5)); + await fireEvent.scroll(grid); + expect(onActiveKey).not.toHaveBeenCalled(); + expect(onScrollAnchor).not.toHaveBeenCalled(); + + grid.scrollTop = 0; + await rendered.rerender({ + rows: manyRows.map((row) => ({ ...row, label: `Rebuilt ${row.label}` })), + dimension: 'source', + generation: 2, + restoring: true, + focusedKey: 'group:source:150', + scrollAnchor: { key: 'group:source:140', offset: 5 }, + onDrill: vi.fn(), + onActiveKey, + onScrollAnchor + }); + + await waitFor(() => expect(grid.scrollTop).toBe(139 * 36 + 5)); + await fireEvent.scroll(grid); + expect(grid.getAttribute('aria-activedescendant')).toContain('source-3A150'); + expect(onActiveKey).not.toHaveBeenCalled(); + expect(onScrollAnchor).not.toHaveBeenCalled(); + }); + + it('keeps a multi-dimension anchor authoritative when restoration completes', async () => { + const onActiveKey = vi.fn(); + const onScrollAnchor = vi.fn(); + const manyRows = Array.from({ length: 200 }, (_, index) => ({ + key: String(index + 1), label: `Group ${index + 1}`, count: index + 1, + estimated_bytes: 10, latest_at: '2026-07-18T12:00:00Z' + })); + const rendered = render(GroupTable, { + rows: manyRows, + dimension: 'source', + generation: 1, + restoring: true, + focusedKey: 'group:source:150', + scrollAnchor: { key: 'group:source:140', offset: 5 }, + onDrill: vi.fn(), + onActiveKey, + onScrollAnchor + }); + const grid = screen.getByRole('grid', { name: 'Everything grouped by source' }) as HTMLDivElement; + await waitFor(() => expect(grid.scrollTop).toBe(139 * 36 + 5)); + + await rendered.rerender({ + rows: manyRows, + dimension: 'month', + generation: 2, + restoring: true, + focusedKey: 'group:month:150', + scrollAnchor: { key: 'group:month:140', offset: 5 }, + onDrill: vi.fn(), + onActiveKey, + onScrollAnchor + }); + await waitFor(() => expect(grid.scrollTop).toBe(139 * 36 + 5)); + await fireEvent.scroll(grid); + expect(onActiveKey).not.toHaveBeenCalled(); + expect(onScrollAnchor).not.toHaveBeenCalled(); + + await rendered.rerender({ + rows: manyRows, + dimension: 'month', + generation: 2, + restoring: false, + focusedKey: 'group:month:150', + scrollAnchor: { key: 'group:month:140', offset: 5 }, + onDrill: vi.fn(), + onActiveKey, + onScrollAnchor + }); + + await waitFor(() => expect(grid.scrollTop).toBe(139 * 36 + 5)); + expect(grid.getAttribute('aria-activedescendant')).toContain('month-3A150'); + }); + + it('removes the drill affordance for a non-filterable group dimension', async () => { + const onDrill = vi.fn(); + render(GroupTable, { rows, dimension: 'kind', drillable: false, onDrill }); + const grid = screen.getByRole('grid', { name: 'Everything grouped by kind' }); + grid.focus(); + await fireEvent.keyDown(grid, { key: 'Enter' }); + + expect(screen.queryByRole('button', { name: /Drill into/ })).toBeNull(); + expect(onDrill).not.toHaveBeenCalled(); + }); + + it('opens aggregate inspection from a focused pointer row without drilling', async () => { + const onInspect = vi.fn(); + const onDrill = vi.fn(); + render(GroupTable, { rows, dimension: 'source', onDrill, onInspect }); + + await fireEvent.pointerDown(screen.getByRole('row', { name: /Example source/ })); + + expect(onInspect).toHaveBeenCalledWith(rows[0]); + expect(onDrill).not.toHaveBeenCalled(); + }); + + it('marks the inspected aggregate independently from keyboard focus', () => { + render(GroupTable, { + rows, + dimension: 'source', + focusedKey: 'group:source:7', + inspectedKey: 'group:source:8', + onDrill: vi.fn() + }); + + expect(screen.getByRole('row', { name: /Example source/ }).getAttribute('aria-current')).toBeNull(); + expect(screen.getByRole('row', { name: /Second source/ }).getAttribute('aria-current')).toBe('true'); + }); +}); diff --git a/web/src/lib/components/explore/RowKind.svelte b/web/src/lib/components/explore/RowKind.svelte new file mode 100644 index 000000000..26d0c9eaa --- /dev/null +++ b/web/src/lib/components/explore/RowKind.svelte @@ -0,0 +1,50 @@ + + + + + {presentation.label.replace(' item', '').replace('Archive', 'Item')} + + + diff --git a/web/src/lib/components/explore/RowKind.test.ts b/web/src/lib/components/explore/RowKind.test.ts new file mode 100644 index 000000000..4aaa4bb66 --- /dev/null +++ b/web/src/lib/components/explore/RowKind.test.ts @@ -0,0 +1,17 @@ +import { render, screen } from '@testing-library/svelte'; +import { describe, expect, it } from 'vitest'; + +import RowKind from './RowKind.svelte'; + +describe('RowKind', () => { + it.each([ + ['conversation', 'sms', 'Conversation item'], + ['event', 'calendar_event', 'Calendar event'], + ['meeting', 'meeting_transcript', 'Meeting item'], + ['email', 'unknown', 'Email item'], + ['unknown', 'unknown', 'Archive item'] + ])('presents server kind %s before message type %s', (kind, messageType, label) => { + render(RowKind, { kind, messageType }); + expect(screen.getByLabelText(label)).toBeDefined(); + }); +}); diff --git a/web/src/lib/components/explore/SelectionBar.svelte b/web/src/lib/components/explore/SelectionBar.svelte new file mode 100644 index 000000000..01a9b6637 --- /dev/null +++ b/web/src/lib/components/explore/SelectionBar.svelte @@ -0,0 +1,116 @@ + + +
0}> + {message} + toggle + visible + {#if allMatching && selection.mode === 'explicit' && selection.count > 0} +
+ + diff --git a/web/src/lib/components/explore/SelectionBar.test.ts b/web/src/lib/components/explore/SelectionBar.test.ts new file mode 100644 index 000000000..4401cec8c --- /dev/null +++ b/web/src/lib/components/explore/SelectionBar.test.ts @@ -0,0 +1,97 @@ +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { describe, expect, it } from 'vitest'; + +import type { components } from '../../api/generated/schema'; + +import { ExploreSelectionState } from '../../explore/state.svelte'; +import { predicateFingerprint } from '../../explore/selection'; +import SelectionBar from './SelectionBar.svelte'; + +describe('SelectionBar', () => { + const preflight = (unavailable_actions: components['schemas']['ExploreUnavailableAction'][] = []): components['schemas']['ExplorePreflightResponse'] => ({ + count: 2, + estimated_bytes: 20, + cache_revision: 'cache-1', + search_provenance: {}, + unavailable_actions, + action_targets: [{ action: 'export', message_id: 1, filename: 'message-1.eml' }], + operation_token: 'token-1', + expires_at: '2026-07-19T10:00:00Z' + }); + + it('announces explicit selection and clears it', async () => { + const selection = new ExploreSelectionState(); + selection.selectVisible(['message:1', 'message:2']); + render(SelectionBar, { selection, totalCount: 8 }); + + expect(screen.getByRole('status').textContent).toContain('2 selected'); + await fireEvent.click(screen.getByRole('button', { name: 'Clear selection' })); + expect(selection.count).toBe(0); + }); + + it('labels predicate selection as all matching rather than a finite URL selection', () => { + const selection = new ExploreSelectionState(); + selection.selectAllMatching({ + mode: 'all_matching', + predicate: { query: 'synthetic', search_mode: 'full_text' }, + exclusions: [], + cacheRevision: 'cache-1', + searchProvenance: { lexical_index_revision: 'fts-1' }, + predicateFingerprint: predicateFingerprint({ query: 'synthetic', search_mode: 'full_text' }), + resultGeneration: 1 + }); + render(SelectionBar, { selection, totalCount: 50000 }); + + expect(screen.getByRole('status').textContent).toContain('All 50,000 matching items selected'); + }); + + it('promotes visible selection to a pinned all-matching selection', async () => { + const selection = new ExploreSelectionState(); + selection.selectVisible(['message:1', 'message:2']); + render(SelectionBar, { + selection, + totalCount: 50, + allMatching: { + mode: 'all_matching', + predicate: { query: 'synthetic', search_mode: 'full_text' }, + exclusions: [], + cacheRevision: 'cache-1', + searchProvenance: { lexical_index_revision: 'fts-1' }, + predicateFingerprint: predicateFingerprint({ query: 'synthetic', search_mode: 'full_text' }), + resultGeneration: 1 + } + }); + + await fireEvent.click(screen.getByRole('button', { name: 'Select all 50 matching items' })); + + expect(selection.mode).toBe('all_matching'); + expect(selection.snapshot()).toMatchObject({ cacheRevision: 'cache-1' }); + }); + + it('shows executable actions only when preflight supplies a target and a handler', () => { + const selection = new ExploreSelectionState(); + selection.selectVisible(['message:1', 'message:2']); + render(SelectionBar, { selection, totalCount: 2, preflight: preflight(), onExport: () => undefined }); + + expect(screen.getByRole('button', { name: 'Export selection' })).toBeDefined(); + expect(screen.queryByRole('button', { name: 'Open selection in source' })).toBeNull(); + }); + + it('shows preflight-supplied per-action reasons without inferring support from rows', () => { + const selection = new ExploreSelectionState(); + selection.selectVisible(['message:1', 'message:2']); + render(SelectionBar, { + selection, + totalCount: 2, + preflight: preflight([ + { action: 'export', reason: 'selection_contains_items_without_exportable_files' }, + { action: 'open_in_source', reason: 'selection_contains_items_that_cannot_be_opened_in_source' } + ]) + }); + + expect(screen.queryByRole('button', { name: 'Export selection' })).toBeNull(); + expect(screen.queryByRole('button', { name: 'Open selection in source' })).toBeNull(); + expect(screen.getByText('Export: selection_contains_items_without_exportable_files')).toBeDefined(); + expect(screen.getByText('Open in source: selection_contains_items_that_cannot_be_opened_in_source')).toBeDefined(); + }); +}); diff --git a/web/src/lib/components/files/FileViewer.browser.svelte.ts b/web/src/lib/components/files/FileViewer.browser.svelte.ts new file mode 100644 index 000000000..3bc1dcbff --- /dev/null +++ b/web/src/lib/components/files/FileViewer.browser.svelte.ts @@ -0,0 +1,110 @@ +import { GlobalWorkerOptions, getDocument, version } from 'pdfjs-dist'; +import workerURL from 'pdfjs-dist/build/pdf.worker.min.mjs?url'; + +export const PDFJS_PINNED_VERSION = '5.4.296'; +export const MAX_PDF_BYTES = 25 * 1024 * 1024; +export const MAX_PDF_PAGES = 40; +const MAX_CANVAS_PIXELS = 16_000_000; +export const MAX_TOTAL_CANVAS_PIXELS = 64_000_000; + +export interface PDFRenderHandle { + pages: number; + destroy(): void; +} + +GlobalWorkerOptions.workerSrc = workerURL; + +export async function renderPDF( + bytes: Uint8Array, + host: HTMLElement, + signal: AbortSignal +): Promise { + if (version !== PDFJS_PINNED_VERSION) { + throw new Error(`PDF renderer version mismatch: expected ${PDFJS_PINNED_VERSION}, got ${version}`); + } + if (bytes.byteLength > MAX_PDF_BYTES) throw new Error('PDF exceeds the preview byte limit.'); + if (signal.aborted) throw new DOMException('Preview cancelled', 'AbortError'); + + const loadingTask = getDocument({ data: bytes }); + let activeRender: { cancel(): void; promise: Promise } | undefined; + let activePage: { cleanup(): void } | undefined; + let destroyed = false; + const cleanup = async (): Promise => { + if (destroyed) return; + destroyed = true; + signal.removeEventListener('abort', abortLoading); + activeRender?.cancel(); + activeRender = undefined; + activePage?.cleanup(); + activePage = undefined; + host.replaceChildren(); + await loadingTask.destroy(); + }; + const abortLoading = (): void => { void cleanup(); }; + signal.addEventListener('abort', abortLoading, { once: true }); + try { + const pdfDocument = await loadingTask.promise; + if (signal.aborted) throw new DOMException('Preview cancelled', 'AbortError'); + if (pdfDocument.numPages > MAX_PDF_PAGES) { + throw new Error(`PDF exceeds the ${MAX_PDF_PAGES}-page preview limit.`); + } + + host.replaceChildren(); + let totalCanvasPixels = 0; + for (let pageNumber = 1; pageNumber <= pdfDocument.numPages; pageNumber += 1) { + if (signal.aborted) throw new DOMException('Preview cancelled', 'AbortError'); + const page = await pdfDocument.getPage(pageNumber); + if (destroyed || signal.aborted) { + page.cleanup(); + throw new DOMException('Preview cancelled', 'AbortError'); + } + activePage = page; + const viewport = page.getViewport({ scale: 1.25 }); + const pagePixels = Math.ceil(viewport.width) * Math.ceil(viewport.height); + if (pagePixels > MAX_CANVAS_PIXELS) { + throw new Error(`PDF page ${pageNumber} exceeds the preview canvas limit.`); + } + totalCanvasPixels += pagePixels; + if (totalCanvasPixels > MAX_TOTAL_CANVAS_PIXELS) { + throw new Error('PDF exceeds the aggregate preview canvas limit.'); + } + + const section = host.ownerDocument.createElement('section'); + section.className = 'pdf-page'; + const canvas = host.ownerDocument.createElement('canvas'); + canvas.className = 'pdf-canvas'; + canvas.width = Math.ceil(viewport.width); + canvas.height = Math.ceil(viewport.height); + canvas.setAttribute('aria-label', `PDF page ${pageNumber}`); + const context = canvas.getContext('2d'); + if (!context) throw new Error('Canvas rendering is unavailable.'); + section.append(canvas); + + activeRender = page.render({ canvas, canvasContext: context, viewport }); + await activeRender.promise; + activeRender = undefined; + if (destroyed || signal.aborted) throw new DOMException('Preview cancelled', 'AbortError'); + + const text = await page.getTextContent(); + if (destroyed || signal.aborted) throw new DOMException('Preview cancelled', 'AbortError'); + const textLayer = host.ownerDocument.createElement('div'); + textLayer.className = 'pdf-text'; + textLayer.setAttribute('aria-label', `Text from PDF page ${pageNumber}`); + textLayer.textContent = text.items + .map((item) => ('str' in item ? item.str : '')) + .filter(Boolean) + .join(' '); + section.append(textLayer); + host.append(section); + page.cleanup(); + activePage = undefined; + } + return { + pages: pdfDocument.numPages, + destroy(): void { void cleanup(); } + }; + } catch (error) { + await cleanup(); + throw error; + } +} diff --git a/web/src/lib/components/files/FileViewer.browser.test.ts b/web/src/lib/components/files/FileViewer.browser.test.ts new file mode 100644 index 000000000..cd8164a91 --- /dev/null +++ b/web/src/lib/components/files/FileViewer.browser.test.ts @@ -0,0 +1,109 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const pdf = vi.hoisted(() => ({ + getDocument: vi.fn(), + workerOptions: { workerSrc: '' } +})); + +vi.mock('pdfjs-dist', () => ({ + getDocument: pdf.getDocument, + GlobalWorkerOptions: pdf.workerOptions, + version: '5.4.296' +})); +vi.mock('pdfjs-dist/build/pdf.worker.min.mjs?url', () => ({ default: '/pdf.worker.min.mjs' })); + +import { MAX_PDF_PAGES, renderPDF } from './FileViewer.browser.svelte'; + +function loadingTask(pages: Array<{ width: number; height: number }>) { + const destroy = vi.fn(async () => undefined); + const cleanup = vi.fn(); + const document = { + numPages: pages.length, + getPage: vi.fn(async (number: number) => ({ + getViewport: () => pages[number - 1]!, + render: () => ({ cancel: vi.fn(), promise: Promise.resolve() }), + getTextContent: async () => ({ items: [] }), + cleanup + })) + }; + return { promise: Promise.resolve(document), destroy, cleanup }; +} + +describe('PDF preview resource bounds', () => { + beforeEach(() => { + pdf.getDocument.mockReset(); + vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue({} as CanvasRenderingContext2D); + }); + + it('destroys a document with too many pages exactly once', async () => { + const task = loadingTask(Array.from({ length: MAX_PDF_PAGES + 1 }, () => ({ width: 10, height: 10 }))); + pdf.getDocument.mockReturnValue(task); + const host = document.createElement('div'); + + await expect(renderPDF(new TextEncoder().encode('%PDF-1.4'), host, new AbortController().signal)) + .rejects.toThrow(/page preview limit/i); + expect(task.destroy).toHaveBeenCalledOnce(); + expect(host.childElementCount).toBe(0); + }); + + it('cleans a huge page and an aggregate overflow without retaining canvases', async () => { + for (const pages of [ + [{ width: 4_001, height: 4_000 }], + Array.from({ length: 5 }, () => ({ width: 3_500, height: 4_000 })) + ]) { + const task = loadingTask(pages); + pdf.getDocument.mockReturnValue(task); + const host = document.createElement('div'); + await expect(renderPDF(new TextEncoder().encode('%PDF-1.4'), host, new AbortController().signal)) + .rejects.toThrow(/canvas limit/i); + expect(task.cleanup).toHaveBeenCalled(); + expect(task.destroy).toHaveBeenCalledOnce(); + expect(host.childElementCount).toBe(0); + } + }); + + it('returns an idempotent handle that releases all rendered DOM', async () => { + const task = loadingTask([{ width: 10, height: 10 }]); + pdf.getDocument.mockReturnValue(task); + const host = document.createElement('div'); + const handle = await renderPDF(new TextEncoder().encode('%PDF-1.4'), host, new AbortController().signal); + expect(host.querySelectorAll('canvas')).toHaveLength(1); + + handle.destroy(); + handle.destroy(); + await vi.waitFor(() => expect(task.destroy).toHaveBeenCalledOnce()); + expect(host.childElementCount).toBe(0); + }); + + it('cleans a page acquired after aborting an in-flight getPage call', async () => { + let resolvePage: ((page: ReturnType) => void) | undefined; + const page = pageFixture(); + const task = { + promise: Promise.resolve({ + numPages: 1, + getPage: vi.fn(() => new Promise>((resolve) => { resolvePage = resolve; })) + }), + destroy: vi.fn(async () => undefined) + }; + pdf.getDocument.mockReturnValue(task); + const host = document.createElement('div'); + const controller = new AbortController(); + const pending = renderPDF(new TextEncoder().encode('%PDF-1.4'), host, controller.signal); + await vi.waitFor(() => expect(task.promise).resolves.toBeDefined()); + controller.abort(); + resolvePage?.(page); + + await expect(pending).rejects.toMatchObject({ name: 'AbortError' }); + expect(page.cleanup).toHaveBeenCalledOnce(); + expect(host.childElementCount).toBe(0); + }); +}); + +function pageFixture() { + return { + getViewport: () => ({ width: 10, height: 10 }), + render: () => ({ cancel: vi.fn(), promise: Promise.resolve() }), + getTextContent: async () => ({ items: [] }), + cleanup: vi.fn() + }; +} diff --git a/web/src/lib/components/files/FileViewer.svelte b/web/src/lib/components/files/FileViewer.svelte new file mode 100644 index 000000000..3ec7f9a96 --- /dev/null +++ b/web/src/lib/components/files/FileViewer.svelte @@ -0,0 +1,318 @@ + + + { void close(); }} +> +
+
+
+

File preview

+

{displayFilename}

+
+ +
+ +
+ {#if loading} +

Loading file metadata…

+ {:else if metadata} + {#if stateMessage(metadata)} +

{stateMessage(metadata)}

+ {:else if isImage(metadata)} + {#if imageURL} + {`Preview + {:else} +

Loading image preview…

+ {/if} + {:else if isPDF(metadata)} +
+ {/if} + {/if} + {#if error}{/if} +
+ +
+ +
+ + + {#if metadata?.content_state === 'local_content'} + + {/if} +
+
+
+
+ + + + diff --git a/web/src/lib/components/files/FileViewer.test.ts b/web/src/lib/components/files/FileViewer.test.ts new file mode 100644 index 000000000..1b7ac3409 --- /dev/null +++ b/web/src/lib/components/files/FileViewer.test.ts @@ -0,0 +1,223 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { appShortcuts } from '@kenn-io/kit-ui'; + +import { createAPIClient } from '../../api/client'; +import type { FileSearchRow } from '../../explore/models'; +import FileViewer from './FileViewer.svelte'; + +const { renderPDF } = vi.hoisted(() => ({ + renderPDF: vi.fn(async (_bytes: Uint8Array, host: HTMLElement) => { + const canvas = document.createElement('canvas'); + canvas.setAttribute('aria-label', 'PDF page 1'); + host.append(canvas); + return { pages: 1, destroy: vi.fn() }; + }) +})); + +vi.mock('./FileViewer.browser.svelte', () => ({ + MAX_PDF_BYTES: 25 * 1024 * 1024, + renderPDF +})); + +function file(overrides: Partial = {}): FileSearchRow { + return { + id: 7, + key: 'file:7', + entry_key: 'message:11', + message_id: 11, + conversation_id: 21, + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_type: 'synthetic', + source_identifier: 'archive@example.com', + containing_title: 'Containing item', + filename: 'preview.png', + mime_type: 'image/png', + mime_family: 'image', + size_bytes: 68, + content_state: 'local_content', + content_available: true, + ...overrides + }; +} + +function viewerFetch(metadata: Record, content = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10])) { + return vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (new URL(request.url).pathname === '/api/v1/files/7') return Response.json(metadata); + return new Response(content, { headers: { 'Content-Type': String(metadata.mime_type ?? 'application/octet-stream') } }); + }); +} + +describe('FileViewer', () => { + const createObjectURL = vi.fn(() => 'blob:synthetic-preview'); + const revokeObjectURL = vi.fn(); + + beforeEach(() => { + renderPDF.mockClear(); + createObjectURL.mockClear(); + revokeObjectURL.mockClear(); + Object.defineProperty(URL, 'createObjectURL', { configurable: true, value: createObjectURL }); + Object.defineProperty(URL, 'revokeObjectURL', { configurable: true, value: revokeObjectURL }); + }); + + afterEach(() => vi.useRealTimers()); + + it('fetches authenticated image bytes in the shell and revokes the preview URL on close', async () => { + const opener = document.createElement('button'); + document.body.append(opener); + opener.focus(); + const onClose = vi.fn(); + const fetchFn = viewerFetch({ + id: 7, message_id: 11, conversation_id: 21, filename: 'preview.png', mime_type: 'image/png', + size_bytes: 68, content_hash: 'a'.repeat(64), content_state: 'local_content', content_available: true + }); + render(FileViewer, { client: createAPIClient(fetchFn), file: file(), returnFocus: opener, onClose }); + + const image = await screen.findByRole('img', { name: 'Preview preview.png' }); + expect(image.getAttribute('src')).toBe('blob:synthetic-preview'); + expect(fetchFn).toHaveBeenCalledTimes(2); + await fireEvent.click(screen.getByRole('button', { name: 'Close file viewer' })); + expect(onClose).toHaveBeenCalledOnce(); + await waitFor(() => expect(document.activeElement).toBe(opener)); + expect(revokeObjectURL).toHaveBeenCalledWith('blob:synthetic-preview'); + }); + + it('lets Modal own Escape, suspends background shortcuts, and closes idempotently', async () => { + const opener = document.createElement('button'); + document.body.append(opener); + opener.focus(); + const onClose = vi.fn(); + const background = vi.fn(); + const unregister = appShortcuts.register('escape', background); + const view = render(FileViewer, { + client: createAPIClient(viewerFetch({ + id: 7, message_id: 11, conversation_id: 21, filename: 'preview.png', mime_type: 'image/png', + size_bytes: 8, content_hash: 'a'.repeat(64), content_state: 'local_content', content_available: true + })), + file: file(), returnFocus: opener, onClose + }); + await screen.findByRole('img'); + expect(appShortcuts.activeScope()).toBe('file-viewer'); + appShortcuts.handleKeydown(new KeyboardEvent('keydown', { key: 'Escape', cancelable: true })); + expect(background).not.toHaveBeenCalled(); + + const escape = new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }); + window.dispatchEvent(escape); + window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true })); + expect(escape.defaultPrevented).toBe(true); + expect(onClose).toHaveBeenCalledOnce(); + await waitFor(() => expect(document.activeElement).toBe(opener)); + + view.unmount(); + expect(appShortcuts.activeScope()).toBe('root'); + unregister(); + }); + + it('rejects malformed image bytes before creating a URL', async () => { + const fetchFn = viewerFetch({ + id: 7, message_id: 11, conversation_id: 21, filename: 'fake.png', mime_type: 'image/png', + size_bytes: 13, content_hash: 'a'.repeat(64), content_state: 'local_content', content_available: true + }, new TextEncoder().encode('')); + render(FileViewer, { client: createAPIClient(fetchFn), file: file({ filename: 'fake.png' }) }); + + expect((await screen.findByRole('alert')).textContent).toMatch(/image preview was rejected/i); + expect(createObjectURL).not.toHaveBeenCalled(); + }); + + it('revokes a decoded image URL exactly once when the browser reports an image error', async () => { + const fetchFn = viewerFetch({ + id: 7, message_id: 11, conversation_id: 21, filename: 'broken.png', mime_type: 'image/png', + size_bytes: 8, content_hash: 'a'.repeat(64), content_state: 'local_content', content_available: true + }); + render(FileViewer, { client: createAPIClient(fetchFn), file: file({ filename: 'broken.png' }) }); + const image = await screen.findByRole('img'); + + await fireEvent.error(image); + + expect((await screen.findByRole('alert')).textContent).toMatch(/browser could not decode/i); + expect(revokeObjectURL).toHaveBeenCalledTimes(1); + }); + + it('renders PDF bytes through the application renderer instead of a native document element', async () => { + const fetchFn = viewerFetch({ + id: 7, message_id: 11, conversation_id: 21, filename: 'fixture.pdf', mime_type: 'application/pdf', + size_bytes: 32, content_hash: 'b'.repeat(64), content_state: 'local_content', content_available: true + }, new TextEncoder().encode('%PDF-1.4 synthetic')); + render(FileViewer, { + client: createAPIClient(fetchFn), + file: file({ filename: 'fixture.pdf', mime_type: 'application/pdf', mime_family: 'pdf' }) + }); + + expect(await screen.findByRole('region', { name: 'PDF preview fixture.pdf' })).toBeDefined(); + await waitFor(() => expect(renderPDF).toHaveBeenCalledOnce()); + expect(document.querySelector('iframe, embed, object')).toBeNull(); + }); + + it.each([ + ['missing_blob', 'Archived bytes are missing.'], + ['metadata_only', 'This attachment has metadata only.'], + ['url_only', 'This attachment is URL-only and is not fetched as local content.'] + ] as const)('names %s without fetching content bytes', async (contentState, message) => { + const fetchFn = viewerFetch({ + id: 7, message_id: 11, conversation_id: 21, filename: 'unavailable.bin', mime_type: '', + size_bytes: 12, content_state: contentState, content_available: false + }); + render(FileViewer, { client: createAPIClient(fetchFn), file: file({ content_state: contentState, content_available: false }) }); + expect(await screen.findByText(message)).toBeDefined(); + expect(fetchFn).toHaveBeenCalledOnce(); + }); + + it('falls back to the attachment ID heading when the filename is empty or whitespace-only', async () => { + const fetchFn = viewerFetch({ + id: 7, message_id: 11, conversation_id: 21, filename: ' ', mime_type: '', + size_bytes: 12, content_state: 'missing_blob', content_available: false + }); + render(FileViewer, { + client: createAPIClient(fetchFn), + file: file({ filename: '', content_state: 'missing_blob', content_available: false }) + }); + + expect(await screen.findByRole('heading', { name: 'attachment 7' })).toBeDefined(); + expect(screen.getByRole('button', { name: 'Close file viewer' })).toBeDefined(); + }); + + it('applies the attachment ID fallback to image alt text, download label, and the saved filename', async () => { + const fetchFn = viewerFetch({ + id: 7, message_id: 11, conversation_id: 21, filename: '', mime_type: 'image/png', + size_bytes: 8, content_hash: 'a'.repeat(64), content_state: 'local_content', content_available: true + }); + render(FileViewer, { client: createAPIClient(fetchFn), file: file({ filename: '' }) }); + + expect(await screen.findByRole('heading', { name: 'attachment 7' })).toBeDefined(); + expect(await screen.findByRole('img', { name: 'Preview attachment 7' })).toBeDefined(); + const downloadButton = screen.getByRole('button', { name: 'Download attachment 7' }); + + let capturedDownload: string | undefined; + const originalClick = HTMLAnchorElement.prototype.click; + HTMLAnchorElement.prototype.click = function capture(this: HTMLAnchorElement) { + capturedDownload = this.download; + }; + try { + await fireEvent.click(downloadButton); + await waitFor(() => expect(capturedDownload).toBe('attachment 7')); + } finally { + HTMLAnchorElement.prototype.click = originalClick; + } + }); + + it('keeps unsupported local content to metadata and an explicit download', async () => { + const fetchFn = viewerFetch({ + id: 7, message_id: 11, conversation_id: 21, filename: 'archive.zip', mime_type: 'application/zip', + size_bytes: 12, content_hash: 'c'.repeat(64), content_state: 'local_content', content_available: true + }); + render(FileViewer, { + client: createAPIClient(fetchFn), + file: file({ filename: 'archive.zip', mime_type: 'application/zip', mime_family: 'archive' }) + }); + expect(await screen.findByText('Preview is not supported for this file type.')).toBeDefined(); + expect(screen.getByRole('button', { name: 'Download archive.zip' })).toBeDefined(); + expect(fetchFn).toHaveBeenCalledOnce(); + }); +}); diff --git a/web/src/lib/components/files/FilesWorkspace.svelte b/web/src/lib/components/files/FilesWorkspace.svelte new file mode 100644 index 000000000..709e4a614 --- /dev/null +++ b/web/src/lib/components/files/FilesWorkspace.svelte @@ -0,0 +1,590 @@ + + +
+
+

Archive workspace

Files

+ {totalCount.toLocaleString()} files +
+ +
+ +
+ {#each ['image', 'pdf', 'audio', 'video', 'text', 'document', 'archive', 'other'] as family} + + {/each} +
+
+ +
+
+
+ + + Type + + People / domain + Source + Containing item + Availability +
+
+ {#if unavailable} +
+ {:else if error && rows.length === 0} +
+ {:else if loading && rows.length === 0} +
Loading files…
+ {:else if rows.length === 0} +
No files match this view.
+ {:else if !slice || rowHeight === undefined} +
Preparing files layout…
+ {:else} +
+
+ {#each renderedRows as row, offset (row.key)} + {@const index = slice.start + offset} +
{ activeKey = row.key; onActiveKey?.(row.key); grid?.focus(); viewerReturnFocus = event.currentTarget as HTMLElement; }} + ondblclick={(event) => open(row, event.currentTarget as HTMLElement)} + > + + {row.filename || '(unnamed)'} + {row.mime_type || row.mime_family} + {formatBytes(row.size_bytes)} + {people(row)} + {row.source_identifier} + {row.containing_title || row.entry_key} + {availability(row)} +
+ {/each} +
+
+ {/if} + {#if pageError} +
+ {/if} + {#if loadingMore}
Loading more…
{/if} +
+
+
+
+ +{#if viewerFile} + { viewerFile = undefined; onSelectedKey?.(null); }} + {onOpenItem} + {onOpenConversation} + /> +{/if} + + + + diff --git a/web/src/lib/components/files/FilesWorkspace.test.ts b/web/src/lib/components/files/FilesWorkspace.test.ts new file mode 100644 index 000000000..a44131e3c --- /dev/null +++ b/web/src/lib/components/files/FilesWorkspace.test.ts @@ -0,0 +1,270 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import FilesWorkspace from './FilesWorkspace.svelte'; + +function response() { + return { + files: [{ + id: 7, key: 'file:7', entry_key: 'message:11', message_id: 11, conversation_id: 21, + occurred_at: '2026-07-18T12:00:00Z', source_id: 1, source_type: 'synthetic', + source_identifier: 'archive@example.com', containing_title: 'Containing item', + filename: 'fixture.pdf', mime_type: 'application/pdf', mime_family: 'pdf', size_bytes: 2048, + participant_labels: ['Alice Example'], participant_domains: ['example.com'], + content_state: 'local_content', content_available: true + }], + total_count: 1, cache_revision: 'cache-files', search_provenance: {} + }; +} + +describe('FilesWorkspace', () => { + it.each([ + { + state: 'loading', + fetchFn: vi.fn(() => new Promise(() => {})), + expected: null, + rendered: 'Loading files…' + }, + { + state: 'empty', + fetchFn: vi.fn(async () => Response.json({ + files: [], total_count: 0, cache_revision: 'empty', search_provenance: {} + })), + expected: '2', + rendered: 'No files match this view.' + }, + { + state: 'error', + fetchFn: vi.fn(async () => Response.json( + { error: 'internal_error', message: 'Synthetic file failure.' }, { status: 500 } + )), + expected: null, + rendered: 'Synthetic file failure.' + }, + { + state: 'degraded', + fetchFn: vi.fn(async () => Response.json({ + error: 'analytical_cache_unavailable', message: 'Synthetic cache unavailable.', + readiness: 'absent', recovery_action: 'msgvault build-cache' + }, { status: 503 })), + expected: null, + rendered: 'Synthetic cache unavailable.' + } + ])('reports an honest row count for the $state state', async ({ fetchFn, expected, rendered }) => { + render(FilesWorkspace, { + client: createAPIClient(fetchFn), predicate: { filters: [], presentation: 'table' }, + sort: { field: 'occurred_at', direction: 'desc' } + }); + + const grid = await screen.findByRole('grid', { name: 'Files results' }); + await screen.findByText(rendered); + expect(grid.getAttribute('aria-rowcount')).toBe(expected); + }); + + it('owns headers and virtual rows in one focusable grid', async () => { + const fetchFn = vi.fn(async () => Response.json(response())); + render(FilesWorkspace, { + client: createAPIClient(fetchFn), predicate: { filters: [], presentation: 'table' }, + sort: { field: 'occurred_at', direction: 'desc' } + }); + + const grid = await screen.findByRole('grid', { name: 'Files results' }); + expect(screen.getAllByRole('grid')).toHaveLength(1); + expect(grid.contains(screen.getByRole('columnheader', { name: 'Filename' }))).toBe(true); + expect(grid.contains(await screen.findByRole('row', { name: /fixture.pdf/ }))).toBe(true); + }); + + afterEach(() => vi.unstubAllGlobals()); + + it('requests a bounded canonical page and commits stable sortable headers', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return Response.json(response()); + }); + const onSortChange = vi.fn(); + render(FilesWorkspace, { + client: createAPIClient(fetchFn), + predicate: { + query: 'quarterly', search_mode: 'full_text', + filters: [{ dimension: 'participant', values: ['42'] }], presentation: 'table' + }, + sort: { field: 'occurred_at', direction: 'desc' }, + filenameQuery: 'invoice', + mimeFamilies: ['pdf'], + onSortChange + }); + + expect(await screen.findByText('fixture.pdf')).toBeDefined(); + await expect(requests[0]!.clone().json()).resolves.toMatchObject({ + predicate: { query: 'quarterly', search_mode: 'full_text', filters: [{ dimension: 'participant', values: ['42'] }] }, + sort: { field: 'occurred_at', direction: 'desc' }, limit: 500, + filename_query: 'invoice', mime_families: ['pdf'] + }); + await fireEvent.click(screen.getByRole('button', { name: 'Sort by date' })); + expect(onSortChange).toHaveBeenCalledWith({ field: 'occurred_at', direction: 'asc' }); + await fireEvent.click(screen.getByRole('button', { name: 'Sort by filename' })); + expect(onSortChange).toHaveBeenCalledWith({ field: 'filename', direction: 'asc' }); + await fireEvent.click(screen.getByRole('button', { name: 'Sort by size' })); + expect(onSortChange).toHaveBeenCalledWith({ field: 'size', direction: 'asc' }); + }); + + it('does not open the active file when Enter activates a focused sort control', async () => { + const onSortChange = vi.fn(); + const onSelectedKey = vi.fn(); + render(FilesWorkspace, { + client: createAPIClient(vi.fn(async () => Response.json(response()))), + predicate: { filters: [], presentation: 'table' }, + sort: { field: 'occurred_at', direction: 'desc' }, + onSortChange, + onSelectedKey + }); + + await screen.findByText('fixture.pdf'); + const sort = screen.getByRole('button', { name: 'Sort by date' }); + sort.focus(); + await fireEvent.keyDown(sort, { key: 'Enter' }); + + expect(onSelectedKey).not.toHaveBeenCalled(); + expect(screen.queryByRole('dialog')).toBeNull(); + await fireEvent.click(sort); + expect(onSortChange).toHaveBeenCalledOnce(); + }); + + it('loads bounded cursor pages until a durable selected file is restored', async () => { + document.documentElement.style.removeProperty('--row-height'); + const frames = new Map(); + let nextFrame = 1; + vi.stubGlobal('requestAnimationFrame', vi.fn((callback: FrameRequestCallback) => { + const frame = nextFrame++; + frames.set(frame, callback); + return frame; + })); + vi.stubGlobal('cancelAnimationFrame', vi.fn((frame: number) => frames.delete(frame))); + const requests: Request[] = []; + const first = response(); + first.files[0]!.key = 'file:1'; + first.files[0]!.id = 1; + first.files[0]!.filename = 'first.pdf'; + first.total_count = 50_000; + Object.assign(first, { next_cursor: 'page-2' }); + const second = response(); + second.files[0]!.key = 'file:900'; + second.files[0]!.id = 900; + second.files[0]!.filename = 'deep.pdf'; + second.total_count = 50_000; + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const path = new URL(request.url, document.baseURI).pathname; + if (path === '/api/v1/files/900') return Response.json({ + id: 900, message_id: 11, conversation_id: 21, filename: 'deep.pdf', mime_type: 'application/pdf', + size_bytes: 2048, content_state: 'missing_blob', content_available: false + }); + const body = await request.clone().json() as { cursor?: string }; + return Response.json(body.cursor ? second : first); + }); + const onRestorationComplete = vi.fn(); + + const { container } = render(FilesWorkspace, { + client: createAPIClient(fetchFn), predicate: { filters: [], presentation: 'table' }, + sort: { field: 'occurred_at', direction: 'desc' }, selectedKey: 'file:900', + activeKey: 'file:900', restorationEpoch: 7, onRestorationComplete + }); + + expect(await screen.findByRole('dialog', { name: 'View deep.pdf' })).toBeDefined(); + expect(requests.filter((request) => new URL(request.url).pathname === '/api/v1/files/search')).toHaveLength(2); + const grid = screen.getByRole('grid', { name: 'Files results' }); + expect(await screen.findByText('Preparing files layout…')).toBeDefined(); + expect(onRestorationComplete).not.toHaveBeenCalled(); + expect(grid.getAttribute('aria-activedescendant')).toBeNull(); + + document.documentElement.style.setProperty('--row-height', '36px'); + const [frame, callback] = [...frames.entries()][0]!; + frames.delete(frame); + callback(performance.now()); + + await waitFor(() => expect(onRestorationComplete).toHaveBeenCalledWith(7)); + expect(grid.getAttribute('aria-activedescendant')).toBe('file-row-900'); + expect(container.querySelector('#file-row-900')).not.toBeNull(); + expect(screen.getAllByRole('row')).toHaveLength(3); + }); + + it('opens a file from keyboard focus and preserves containing navigation authority', async () => { + const fetchFn = vi.fn(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input), document.baseURI).pathname; + if (path === '/api/v1/files/search') return Response.json(response()); + return Response.json({ + id: 7, message_id: 11, conversation_id: 21, filename: 'fixture.pdf', mime_type: 'application/pdf', + size_bytes: 2048, content_state: 'missing_blob', content_available: false + }); + }); + const onOpenItem = vi.fn(); + const onOpenConversation = vi.fn(); + const onSelectedKey = vi.fn(); + render(FilesWorkspace, { + client: createAPIClient(fetchFn), predicate: { filters: [], presentation: 'table' }, + sort: { field: 'occurred_at', direction: 'desc' }, onOpenItem, onOpenConversation, onSelectedKey + }); + + const grid = await screen.findByRole('grid', { name: 'Files results' }); + await screen.findByRole('row', { name: /fixture.pdf/ }); + grid.focus(); + await fireEvent.keyDown(grid, { key: 'Enter' }); + expect(onSelectedKey).toHaveBeenCalledWith('file:7'); + expect(await screen.findByRole('dialog', { name: 'View fixture.pdf' })).toBeDefined(); + await fireEvent.click(screen.getByRole('button', { name: 'Open containing item' })); + expect(onOpenItem).toHaveBeenCalledWith('message:11'); + await fireEvent.click(screen.getByRole('button', { name: 'Open containing conversation' })); + expect(onOpenConversation).toHaveBeenCalledWith('message:11', 11, 21); + await waitFor(() => expect(fetchFn).toHaveBeenCalledTimes(2)); + }); + + it('restores ascending date sort and toggles it back to descending', async () => { + const fetchFn = vi.fn(async () => Response.json(response())); + const onSortChange = vi.fn(); + render(FilesWorkspace, { + client: createAPIClient(fetchFn), predicate: { filters: [], presentation: 'table' }, + sort: { field: 'occurred_at', direction: 'asc' }, onSortChange + }); + await screen.findByText('fixture.pdf'); + + await fireEvent.click(screen.getByRole('button', { name: 'Sort by date' })); + + expect(onSortChange).toHaveBeenCalledWith({ field: 'occurred_at', direction: 'desc' }); + await expect((fetchFn.mock.calls[0]![0] as Request).clone().json()).resolves.toMatchObject({ + sort: { field: 'occurred_at', direction: 'asc' } + }); + }); + + it('retries a cursor after a transient network failure', async () => { + const first = response(); + Object.assign(first, { total_count: 2, next_cursor: 'page-2' }); + const second = response(); + second.files[0] = { ...second.files[0]!, id: 8, key: 'file:8', filename: 'recovered.pdf' }; + second.total_count = 2; + let cursorCalls = 0; + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const body = await request.clone().json() as { cursor?: string }; + if (!body.cursor) return Response.json(first); + cursorCalls += 1; + if (cursorCalls === 1) throw new TypeError('temporary network failure'); + return Response.json(second); + }); + render(FilesWorkspace, { + client: createAPIClient(fetchFn), predicate: { filters: [], presentation: 'table' }, + sort: { field: 'occurred_at', direction: 'desc' } + }); + const grid = await screen.findByRole('grid', { name: 'Files results' }); + await screen.findByRole('row', { name: /fixture.pdf/ }); + await fireEvent.scroll(grid); + expect((await screen.findByRole('alert')).textContent).toContain('temporary network failure'); + expect(screen.getByRole('row', { name: /fixture.pdf/ })).toBeDefined(); + await fireEvent.click(screen.getByRole('button', { name: 'Retry loading more files' })); + expect(await screen.findByText('recovered.pdf')).toBeDefined(); + expect(cursorCalls).toBe(2); + }); +}); diff --git a/web/src/lib/components/files/preview-bytes.test.ts b/web/src/lib/components/files/preview-bytes.test.ts new file mode 100644 index 000000000..f3fc4f3a4 --- /dev/null +++ b/web/src/lib/components/files/preview-bytes.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { readBoundedStream, validatedImageBlob } from './preview-bytes'; + +const imageCases = [ + ['image/png', [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]], + ['image/jpeg', [0xff, 0xd8, 0xff, 0xe0]], + ['image/gif', [...new TextEncoder().encode('GIF89a')]], + ['image/webp', [...new TextEncoder().encode('RIFF'), 4, 0, 0, 0, ...new TextEncoder().encode('WEBP')]] +] as const; + +describe('preview byte validation', () => { + it.each(imageCases)('accepts %s only when metadata, response MIME, and magic agree', (mime, bytes) => { + const blob = validatedImageBlob(new Uint8Array(bytes), mime, `${mime}; charset=binary`); + expect(blob.type).toBe(mime); + }); + + it.each([ + ['SVG', 'image/svg+xml', new TextEncoder().encode('')], + ['HTML', 'image/png', new TextEncoder().encode('')], + ['mismatched metadata', 'image/jpeg', new Uint8Array(imageCases[0]![1])] + ])('rejects %s preview bytes', (_name, mime, bytes) => { + expect(() => validatedImageBlob(bytes, mime, 'image/png')).toThrow(/image preview/i); + }); + + it('enforces Content-Length before reading and a byte limit while streaming', async () => { + await expect(readBoundedStream( + new Response(new Uint8Array([1])).body, + new Headers({ 'Content-Length': '5' }), + new AbortController().signal, + 4 + )).rejects.toThrow(/byte limit/i); + + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array([1, 2, 3])); + controller.enqueue(new Uint8Array([4, 5])); + controller.close(); + } + }); + await expect(readBoundedStream(stream, new Headers(), new AbortController().signal, 4)) + .rejects.toThrow(/byte limit/i); + }); + + it('cancels an oversized declared response body exactly once before reporting the size error', async () => { + const cancel = vi.fn(async () => undefined); + const stream = new ReadableStream({ cancel }); + + await expect(readBoundedStream( + stream, new Headers({ 'Content-Length': '5' }), new AbortController().signal, 4 + )).rejects.toThrow(/byte limit/i); + + expect(cancel).toHaveBeenCalledOnce(); + }); + + it('keeps the declared size error when the body is absent or cancellation rejects', async () => { + await expect(readBoundedStream( + null, new Headers({ 'Content-Length': '5' }), new AbortController().signal, 4 + )).rejects.toThrow(/byte limit/i); + + const cancel = vi.fn(async () => { throw new Error('cancel failed'); }); + const stream = new ReadableStream({ cancel }); + await expect(readBoundedStream( + stream, new Headers({ 'Content-Length': '5' }), new AbortController().signal, 4 + )).rejects.toThrow(/byte limit/i); + expect(cancel).toHaveBeenCalledOnce(); + }); +}); diff --git a/web/src/lib/components/files/preview-bytes.ts b/web/src/lib/components/files/preview-bytes.ts new file mode 100644 index 000000000..51ffbf6fe --- /dev/null +++ b/web/src/lib/components/files/preview-bytes.ts @@ -0,0 +1,86 @@ +const IMAGE_SIGNATURES = { + 'image/png': (bytes: Uint8Array) => startsWith(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), + 'image/jpeg': (bytes: Uint8Array) => startsWith(bytes, [0xff, 0xd8, 0xff]), + 'image/gif': (bytes: Uint8Array) => startsWith(bytes, [...new TextEncoder().encode('GIF87a')]) || + startsWith(bytes, [...new TextEncoder().encode('GIF89a')]), + 'image/webp': (bytes: Uint8Array) => startsWith(bytes, [...new TextEncoder().encode('RIFF')]) && + bytes.length >= 12 && startsWith(bytes.subarray(8), [...new TextEncoder().encode('WEBP')]) +} as const; + +type SupportedImageMIME = keyof typeof IMAGE_SIGNATURES; + +function startsWith(bytes: Uint8Array, prefix: readonly number[]): boolean { + return bytes.length >= prefix.length && prefix.every((value, index) => bytes[index] === value); +} + +function normalizedMIME(value: string | null | undefined): string { + return (value ?? '').split(';', 1)[0]!.trim().toLowerCase(); +} + +export function isSupportedImageMIME(value: string): value is SupportedImageMIME { + return normalizedMIME(value) in IMAGE_SIGNATURES; +} + +export function validatedImageBlob( + bytes: Uint8Array, + metadataMIME: string, + responseMIME: string | null +): Blob { + const metadata = normalizedMIME(metadataMIME); + const response = normalizedMIME(responseMIME); + if (!isSupportedImageMIME(metadata) || response !== metadata || !IMAGE_SIGNATURES[metadata](bytes)) { + throw new Error('Image preview was rejected because its MIME type and file signature do not agree.'); + } + return new Blob([new Uint8Array(bytes).buffer], { type: metadata }); +} + +export async function readBoundedStream( + stream: ReadableStream | null, + headers: Headers, + signal: AbortSignal, + maxBytes: number +): Promise { + const declared = headers.get('Content-Length'); + if (declared !== null) { + const length = Number(declared); + if (Number.isFinite(length) && length > maxBytes) { + if (stream) { + try { + await stream.cancel(); + } catch { + // The stable size error is authoritative even when transport cleanup fails. + } + } + throw new Error('File exceeds the preview byte limit.'); + } + } + if (!stream) return new Uint8Array(); + const reader = stream.getReader(); + const cancel = (): void => { void reader.cancel(); }; + signal.addEventListener('abort', cancel, { once: true }); + const chunks: Uint8Array[] = []; + let length = 0; + try { + while (true) { + if (signal.aborted) throw new DOMException('Preview cancelled', 'AbortError'); + const { done, value } = await reader.read(); + if (done) break; + length += value.byteLength; + if (length > maxBytes) { + await reader.cancel(); + throw new Error('File exceeds the preview byte limit.'); + } + chunks.push(value); + } + } finally { + signal.removeEventListener('abort', cancel); + reader.releaseLock(); + } + const bytes = new Uint8Array(length); + let offset = 0; + for (const chunk of chunks) { + bytes.set(chunk, offset); + offset += chunk.byteLength; + } + return bytes; +} diff --git a/web/src/lib/components/inspector/AttachmentRail.svelte b/web/src/lib/components/inspector/AttachmentRail.svelte new file mode 100644 index 000000000..afc256980 --- /dev/null +++ b/web/src/lib/components/inspector/AttachmentRail.svelte @@ -0,0 +1,124 @@ + + +
+
+

Files

+ {#if totalCount > 0}{Math.min(files.length, totalCount)} of {totalCount.toLocaleString()}{/if} +
+ {#if loading} +

Loading file facts…

+ {:else if error} +

{error}

+ {:else if chronological.length === 0} +

No files in this context.

+ {:else} +
    + {#each chronological as file (file.key)} +
  1. +
    {file.filename || '(unnamed file)'}{bytes(file.size)}
    +
    {file.title || file.source_identifier}
    +
  2. + {/each} +
+ {#if totalCount > files.length} +

+ Showing a bounded sample of {files.length.toLocaleString()} of {totalCount.toLocaleString()} files. +

+ {/if} + {/if} +
+ + diff --git a/web/src/lib/components/inspector/Inspector.svelte b/web/src/lib/components/inspector/Inspector.svelte new file mode 100644 index 000000000..d77a04432 --- /dev/null +++ b/web/src/lib/components/inspector/Inspector.svelte @@ -0,0 +1,465 @@ + + + + +{#snippet header()} +
+
Inspector{title}
+
+ {#if onOpenRelationship && counterpartParticipantId !== undefined} +
+
+{/snippet} + +{#snippet contents()} + +
+ {#if conversationAnchorId !== undefined && conversationRow} + onBackConversation?.()} + onAnchorChange={(anchorId) => onConversationAnchorChange?.(anchorId)} + {onContentFocusChange} + /> + {:else if !selection} +
+ {#if status === 'loading'} +

Restoring selected detail…

+ {:else if status === 'unavailable' && unavailable} +

+ {unavailable.message} Rebuild the cache with {unavailable.recovery_action}. +

+ {:else} +

{statusMessage || 'The selected result is no longer available in this context.'}

+ {/if} +
+ {:else if selection.kind === 'entry'} +
+

{selection.row.preview || 'No preview is available.'}

+
+
Kind
{selection.row.message_type}
+
Source
{selection.row.source_identifier}
+
Occurred
{formatDate(selection.row.occurred_at)}
+
Items
{selection.row.message_count.toLocaleString()}
+
Files
{selection.row.attachment_count.toLocaleString()}
+
+ {#if conversationRow} + +
+ {#if selection.row.message_type === 'email' && selection.row.anchor_message_id} + + {/if} + {:else} +
+

Grouped by {selection.dimension}

+
+ {#if selection.count !== undefined}
Items
{selection.count.toLocaleString()} items
{/if} + {#if selection.estimatedBytes !== undefined}
Estimated
{formatBytes(selection.estimatedBytes)}
{/if} + {#if selection.latestAt}
Latest
{formatDate(selection.latestAt)}
{/if} +
+
+ {/if} + {#if showFiles} + + {/if} +
+{/snippet} + +
+ + +
+ + diff --git a/web/src/lib/components/inspector/Inspector.test.ts b/web/src/lib/components/inspector/Inspector.test.ts new file mode 100644 index 000000000..4443fe659 --- /dev/null +++ b/web/src/lib/components/inspector/Inspector.test.ts @@ -0,0 +1,274 @@ +import { fireEvent, render, screen, waitFor, within } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import type { EntryRow } from '../../explore/models'; +import Inspector from './Inspector.svelte'; + +function entry(key = 'message:1'): EntryRow { + return { + key, + kind: 'message', + message_type: 'email', + conversation_type: 'email', + title: 'Synthetic subject', + preview: 'Synthetic excerpt', + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + source_type: 'synthetic', + participant_labels: ['Example Person'], + participant_ids: [42], + counterpart_participant_id: 99, + attachment_count: 1, + attachment_size: 42, + has_attachments: true, + deleted_from_source: false, + message_count: 1, + anchor_message_id: 42, + conversation_id: 7, + match: {} + }; +} + +describe('Inspector', () => { + it('shows a concrete row in the pinned side panel and exposes close', async () => { + const onClose = vi.fn(); + render(Inspector, { + client: createAPIClient(vi.fn()), + selection: { kind: 'entry', row: entry() }, + predicate: { filters: [], presentation: 'table' }, + width: 380, + onClose + }); + + const aside = screen.getByRole('complementary', { name: 'Inspect Synthetic subject' }); + expect(within(aside).getByText('Synthetic excerpt')).toBeDefined(); + expect(document.querySelector('.kit-detail-drawer-overlay')).toBeNull(); + await fireEvent.click(within(aside).getByRole('button', { name: 'Close inspector' })); + expect(onClose).toHaveBeenCalledOnce(); + }); + + it('opens the entry\'s server-computed counterpart into the Relationships hub from the header action', async () => { + const onOpenRelationship = vi.fn(); + render(Inspector, { + client: createAPIClient(vi.fn()), + selection: { kind: 'entry', row: entry() }, + predicate: { filters: [], presentation: 'table' }, + width: 380, + onOpenRelationship + }); + + await fireEvent.click(screen.getByRole('button', { name: 'Open relationship' })); + // counterpart_participant_id (99) is asserted, not participant_ids[0] + // (42) — the entry fixture deliberately sets them to different values so + // this test fails if the component regresses to the old + // participant_ids[0] heuristic, which frequently resolved to the + // archive owner rather than the other side of the conversation. + expect(onOpenRelationship).toHaveBeenCalledWith(99); + }); + + it('hides the Open relationship action when the caller has no destination for it, or the entry carries no counterpart', async () => { + render(Inspector, { + client: createAPIClient(vi.fn()), + selection: { kind: 'entry', row: entry() }, + predicate: { filters: [], presentation: 'table' }, + width: 380 + }); + expect(screen.queryByRole('button', { name: 'Open relationship' })).toBeNull(); + + const onOpenRelationship = vi.fn(); + render(Inspector, { + client: createAPIClient(vi.fn()), + selection: { kind: 'entry', row: { ...entry(), counterpart_participant_id: undefined } }, + predicate: { filters: [], presentation: 'table' }, + width: 380, + onOpenRelationship + }); + expect(screen.queryByRole('button', { name: 'Open relationship' })).toBeNull(); + }); + + it('shows selected group details and chronological files from one bounded request', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return Response.json({ + files: [ + { + key: 'file:new', entry_key: 'message:2', message_id: 2, + occurred_at: '2026-07-18T12:00:00Z', source_id: 1, + source_identifier: 'archive@example.com', title: 'Newer entry', + filename: 'new.txt', size: 20 + }, + { + key: 'file:old', entry_key: 'message:1', message_id: 1, + occurred_at: '2026-07-17T12:00:00Z', source_id: 1, + source_identifier: 'archive@example.com', title: 'Older entry', + filename: 'old.txt', size: 10 + } + ], + total_count: 3, cache_revision: 'cache-1', search_provenance: {} + }); + }); + render(Inspector, { + client: createAPIClient(fetchFn), + selection: { + kind: 'group', dimension: 'participant', key: '42', label: 'Example Person', + count: 12, estimatedBytes: 30, latestAt: '2026-07-18T12:00:00Z' + }, + predicate: { query: 'pasta', search_mode: 'semantic', filters: [], presentation: 'table' }, + width: 380 + }); + + await screen.findByText('new.txt'); + const facts = screen.getAllByRole('listitem').map((item) => item.textContent); + expect(facts[0]).toContain('old.txt'); + expect(facts[1]).toContain('new.txt'); + expect(screen.getByText('Showing a bounded sample of 2 of 3 files.')).toBeDefined(); + expect(screen.queryByText(/Open Files/)).toBeNull(); + expect(screen.getByText('12 items')).toBeDefined(); + expect(requests).toHaveLength(1); + await expect(requests[0]!.clone().json()).resolves.toMatchObject({ + limit: 100, + predicate: { + query: 'pasta', search_mode: 'semantic', + filters: [{ dimension: 'participant', values: ['42'] }], + presentation: 'table' + } + }); + expect((await requests[0]!.clone().json()).predicate).not.toHaveProperty('grouping'); + }); + + it('aborts superseded file facts and ignores a stale response that still settles', async () => { + const signals: AbortSignal[] = []; + const resolvers: Array<(response: Response) => void> = []; + const fetchFn = vi.fn((input) => { + const request = input instanceof Request ? input : new Request(input); + signals.push(request.signal); + return new Promise((resolve) => resolvers.push(resolve)); + }); + const rendered = render(Inspector, { + client: createAPIClient(fetchFn), + selection: { kind: 'group', dimension: 'participant', key: '1', label: 'First person' }, + predicate: { filters: [], presentation: 'table' }, + width: 380 + }); + await waitFor(() => expect(signals).toHaveLength(1)); + + await rendered.rerender({ + client: createAPIClient(fetchFn), + selection: { kind: 'group', dimension: 'domain', key: 'example.com', label: 'example.com' }, + predicate: { filters: [], presentation: 'table' }, + width: 380 + }); + await waitFor(() => expect(signals).toHaveLength(2)); + expect(signals[0]!.aborted).toBe(true); + + resolvers[1]!(Response.json({ + files: [{ + key: 'file:second', entry_key: 'message:2', message_id: 2, + occurred_at: '2026-07-18T12:00:00Z', source_id: 1, + source_identifier: 'archive@example.com', title: 'Second entry', + filename: 'second.txt', size: 20 + }], + total_count: 1, cache_revision: 'cache-2', search_provenance: {} + })); + expect(await screen.findByText('second.txt')).toBeDefined(); + + resolvers[0]!(Response.json({ + files: [{ + key: 'file:first', entry_key: 'message:1', message_id: 1, + occurred_at: '2026-07-17T12:00:00Z', source_id: 1, + source_identifier: 'archive@example.com', title: 'First entry', + filename: 'stale.txt', size: 10 + }], + total_count: 1, cache_revision: 'cache-1', search_provenance: {} + })); + await Promise.resolve(); + expect(screen.queryByText('stale.txt')).toBeNull(); + }); + + it('resizes a pinned inspector through the kit handle', async () => { + const onWidthChange = vi.fn(); + render(Inspector, { + client: createAPIClient(vi.fn()), + selection: { kind: 'entry', row: entry() }, + predicate: { filters: [], presentation: 'table' }, + width: 400, + onWidthChange + }); + + await fireEvent.keyDown(screen.getByRole('button', { name: 'Resize inspector' }), { + key: 'ArrowLeft' + }); + expect(onWidthChange).toHaveBeenCalledWith(424); + }); + + it('enters and exits an explicit content-focus mode', async () => { + const onContentFocusChange = vi.fn(); + render(Inspector, { + client: createAPIClient(vi.fn()), + selection: { kind: 'entry', row: entry() }, + predicate: { filters: [], presentation: 'table' }, + width: 400, + onContentFocusChange + }); + + await fireEvent.click(screen.getByRole('button', { name: 'Focus inspector content' })); + const content = screen.getByRole('region', { name: 'Inspector content' }); + expect(document.activeElement).toBe(content); + expect(onContentFocusChange).toHaveBeenCalledWith(true); + + await fireEvent.keyDown(content, { key: 'Escape' }); + expect(onContentFocusChange).toHaveBeenLastCalledWith(false); + expect(document.activeElement).toBe(screen.getByRole('button', { name: 'Focus inspector content' })); + }); + + it('opens the containing conversation from an email or chat row', async () => { + const onViewConversation = vi.fn(); + const rendered = render(Inspector, { + client: createAPIClient(vi.fn()), + selection: { kind: 'entry', row: entry() }, + predicate: { filters: [], presentation: 'table' }, + width: 400, + onViewConversation + }); + + await fireEvent.click(screen.getByRole('button', { name: 'View conversation' })); + expect(onViewConversation).toHaveBeenCalledWith(7, 42); + + await rendered.rerender({ + client: createAPIClient(vi.fn()), + selection: { kind: 'entry', row: { ...entry(), kind: 'conversation', message_type: 'imessage' } }, + predicate: { filters: [], presentation: 'table' }, + width: 400, + onViewConversation + }); + await fireEvent.click(screen.getByRole('button', { name: 'View conversation' })); + expect(onViewConversation).toHaveBeenLastCalledWith(7, 42); + }); + + it('threads optional conversation start/end bounds to the conversation fetch', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return Response.json({ id: 7, anchor_id: 42, messages: [], has_before: false, has_after: false, total: 0 }); + }); + render(Inspector, { + client: createAPIClient(fetchFn), + selection: { kind: 'entry', row: entry() }, + predicate: { filters: [], presentation: 'table' }, + width: 400, + conversationAnchorId: 42, + conversationStart: '2026-07-19T00:00:00.000Z', + conversationEnd: '2026-07-20T00:00:00.000Z' + }); + + await waitFor(() => expect(requests).toHaveLength(1)); + const url = new URL(requests[0]!.url); + expect(url.searchParams.get('start')).toBe('2026-07-19T00:00:00.000Z'); + expect(url.searchParams.get('end')).toBe('2026-07-20T00:00:00.000Z'); + }); +}); diff --git a/web/src/lib/components/layout/SplitPane.svelte b/web/src/lib/components/layout/SplitPane.svelte new file mode 100644 index 000000000..d878e1e0b --- /dev/null +++ b/web/src/lib/components/layout/SplitPane.svelte @@ -0,0 +1,127 @@ + + +
+
+ {#if primary}{@render primary()}{/if} +
+ +
+ {#if secondary}{@render secondary()}{/if} +
+
+ + diff --git a/web/src/lib/components/layout/SplitPane.test.ts b/web/src/lib/components/layout/SplitPane.test.ts new file mode 100644 index 000000000..e3229ad28 --- /dev/null +++ b/web/src/lib/components/layout/SplitPane.test.ts @@ -0,0 +1,109 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import SplitPane from './SplitPane.svelte'; + +const observers = new Map(); + +class ResizeObserverMock { + constructor(private callback: ResizeObserverCallback) {} + + observe(target: Element): void { + observers.set(target, this.callback); + } + + unobserve(target: Element): void { + observers.delete(target); + } + + disconnect(): void { + observers.clear(); + } +} + +vi.stubGlobal('ResizeObserver', ResizeObserverMock); + +afterEach(() => { + localStorage.clear(); + observers.clear(); +}); + +function reportWidth(target: Element, width: number): void { + const callback = observers.get(target); + if (!callback) throw new Error('split pane was not observed'); + callback( + [{ target, contentRect: { width } } as unknown as ResizeObserverEntry], + {} as ResizeObserver + ); +} + +describe('SplitPane', () => { + it('remains usable when the storage accessor throws during reads and writes', async () => { + const storageDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'localStorage'); + let container: HTMLElement | undefined; + + Object.defineProperty(globalThis, 'localStorage', { + configurable: true, + get() { + throw new Error('storage is unavailable'); + } + }); + + try { + expect(() => { + ({ container } = render(SplitPane, { + props: { storageKey: 'archive:test-split', ariaLabel: 'Resize result list' } + })); + }).not.toThrow(); + + const primary = container!.querySelector('[data-pane="primary"]') as HTMLElement; + expect(primary.style.flexBasis).toBe('360px'); + + await fireEvent.keyDown(screen.getByRole('button', { name: 'Resize result list' }), { + key: 'ArrowRight' + }); + expect(primary.style.flexBasis).toBe('384px'); + } finally { + if (storageDescriptor) { + Object.defineProperty(globalThis, 'localStorage', storageDescriptor); + } + } + }); + + it('restores, resizes, and persists the primary pane size', async () => { + localStorage.setItem('archive:test-split', '480'); + const { container } = render(SplitPane, { + props: { storageKey: 'archive:test-split', ariaLabel: 'Resize result list' } + }); + const primary = container.querySelector('[data-pane="primary"]') as HTMLElement; + + expect(primary.style.flexBasis).toBe('480px'); + await fireEvent.keyDown(screen.getByRole('button', { name: 'Resize result list' }), { + key: 'ArrowRight' + }); + + expect(primary.style.flexBasis).toBe('504px'); + expect(localStorage.getItem('archive:test-split')).toBe('504'); + }); + + it('clamps restored and resized sizes as available width changes', async () => { + localStorage.setItem('archive:test-split', '900'); + const { container } = render(SplitPane, { + props: { storageKey: 'archive:test-split', ariaLabel: 'Resize result list' } + }); + const host = container.querySelector('[data-split-pane]')!; + const primary = container.querySelector('[data-pane="primary"]') as HTMLElement; + + reportWidth(host, 700); + await waitFor(() => expect(primary.style.flexBasis).toBe('376px')); + + await fireEvent.keyDown(screen.getByRole('button', { name: 'Resize result list' }), { + key: 'ArrowRight' + }); + expect(primary.style.flexBasis).toBe('376px'); + + reportWidth(host, 500); + await waitFor(() => expect(primary.style.flexBasis).toBe('176px')); + expect(localStorage.getItem('archive:test-split')).toBe('176'); + }); +}); diff --git a/web/src/lib/components/people/PersonTimeline.svelte b/web/src/lib/components/people/PersonTimeline.svelte new file mode 100644 index 000000000..9bf0999f2 --- /dev/null +++ b/web/src/lib/components/people/PersonTimeline.svelte @@ -0,0 +1,56 @@ + + +
+
+

Canonical context

Activity

+ {totalCount === undefined ? 'Bounded timeline' : `${totalCount.toLocaleString()} items`} +
+ +
+ + diff --git a/web/src/lib/components/reader/ContentFrame.browser.svelte.ts b/web/src/lib/components/reader/ContentFrame.browser.svelte.ts new file mode 100644 index 000000000..d679e029f --- /dev/null +++ b/web/src/lib/components/reader/ContentFrame.browser.svelte.ts @@ -0,0 +1,48 @@ +import { + ARCHIVED_CONTENT_CHANNEL, + MAX_ARCHIVED_SCROLL_DELTA +} from '../../content/frame-document'; + +const FRAME_KEYS = new Set(['Escape', 'PageUp', 'PageDown', 'Home', 'End', 'ArrowUp', 'ArrowDown']); + +interface ArchivedContentHandlerOptions { + frameWindow: () => Window | null; + nonce: () => string; + onKey: (key: string) => void; + onScroll: (deltaY: number) => void; +} + +function exactKeys(value: Record, expected: string[]): boolean { + const keys = Object.keys(value).sort(); + return keys.length === expected.length && expected.sort().every((key, index) => keys[index] === key); +} + +export function createArchivedContentMessageHandler( + options: ArchivedContentHandlerOptions +): (event: MessageEvent) => void { + return (event) => { + if (event.source !== options.frameWindow() || event.origin !== 'null') return; + if (typeof event.data !== 'object' || event.data === null || Array.isArray(event.data)) return; + const data = event.data as Record; + if (data.channel !== ARCHIVED_CONTENT_CHANNEL || data.nonce !== options.nonce()) return; + if (data.type === 'key') { + if (!exactKeys(data, ['channel', 'nonce', 'type', 'key'])) return; + if (typeof data.key !== 'string' || !FRAME_KEYS.has(data.key)) return; + options.onKey(data.key); + return; + } + if (data.type === 'scroll') { + if (!exactKeys(data, ['channel', 'nonce', 'type', 'deltaY'])) return; + if (typeof data.deltaY !== 'number' || !Number.isFinite(data.deltaY) || + Math.abs(data.deltaY) > MAX_ARCHIVED_SCROLL_DELTA) return; + options.onScroll(data.deltaY); + } + }; +} + +export function createFrameNonce(): string { + const bytes = crypto.getRandomValues(new Uint8Array(32)); + let binary = ''; + for (const byte of bytes) binary += String.fromCharCode(byte); + return btoa(binary).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, ''); +} diff --git a/web/src/lib/components/reader/ContentFrame.browser.test.ts b/web/src/lib/components/reader/ContentFrame.browser.test.ts new file mode 100644 index 000000000..38657aea8 --- /dev/null +++ b/web/src/lib/components/reader/ContentFrame.browser.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { createArchivedContentMessageHandler } from './ContentFrame.browser.svelte'; + +function event(source: Window, data: unknown, origin = 'null'): MessageEvent { + return { source, data, origin } as MessageEvent; +} + +describe('archived content frame message boundary', () => { + it('accepts only the expected opaque frame window, nonce, and exact schema', () => { + const frameWindow = {} as Window; + const otherWindow = {} as Window; + const onKey = vi.fn(); + const onScroll = vi.fn(); + const handle = createArchivedContentMessageHandler({ + frameWindow: () => frameWindow, + nonce: () => 'secret-nonce', + onKey, + onScroll + }); + const valid = { + channel: 'msgvault-archived-content', + nonce: 'secret-nonce', + type: 'key', + key: 'Escape' + }; + + handle(event(frameWindow, valid)); + handle(event(otherWindow, valid)); + handle(event(frameWindow, valid, 'https://archive.example')); + handle(event(frameWindow, { ...valid, nonce: 'wrong' })); + handle(event(frameWindow, { ...valid, type: 'close' })); + handle(event(frameWindow, { ...valid, extra: true })); + handle(event(frameWindow, { ...valid, key: 'x' })); + handle(event(frameWindow, ['msgvault-archived-content', 'secret-nonce'])); + + expect(onKey).toHaveBeenCalledTimes(1); + expect(onKey).toHaveBeenCalledWith('Escape'); + expect(onScroll).not.toHaveBeenCalled(); + }); + + it('accepts finite scroll deltas only through the exact scroll schema', () => { + const frameWindow = {} as Window; + const onScroll = vi.fn(); + const handle = createArchivedContentMessageHandler({ + frameWindow: () => frameWindow, + nonce: () => 'nonce', + onKey: vi.fn(), + onScroll + }); + const base = { channel: 'msgvault-archived-content', nonce: 'nonce', type: 'scroll' }; + + handle(event(frameWindow, { ...base, deltaY: 12.5 })); + handle(event(frameWindow, { ...base, deltaY: Number.POSITIVE_INFINITY })); + handle(event(frameWindow, { ...base, deltaY: 10_001 })); + handle(event(frameWindow, { ...base, deltaY: -10_001 })); + handle(event(frameWindow, { ...base, deltaY: '12' })); + handle(event(frameWindow, { ...base, deltaY: 1, key: 'Escape' })); + + expect(onScroll).toHaveBeenCalledTimes(1); + expect(onScroll).toHaveBeenCalledWith(12.5); + }); +}); diff --git a/web/src/lib/components/reader/ContentFrame.svelte b/web/src/lib/components/reader/ContentFrame.svelte new file mode 100644 index 000000000..e9b769feb --- /dev/null +++ b/web/src/lib/components/reader/ContentFrame.svelte @@ -0,0 +1,283 @@ + + +
+
+ {#if contentEntered} + Archived content active +
+
+ {#if !contentEntered} + + {/if} + {#if frameDocument} + {#key frameDocument.generation} + + {/key} + {/if} +
+
+ + diff --git a/web/src/lib/components/reader/ContentFrame.test.ts b/web/src/lib/components/reader/ContentFrame.test.ts new file mode 100644 index 000000000..860200c57 --- /dev/null +++ b/web/src/lib/components/reader/ContentFrame.test.ts @@ -0,0 +1,265 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import ContentFrame from './ContentFrame.svelte'; + +const onePixelPNG = Uint8Array.from([ + 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82 +]); + +function deferred(): { + promise: Promise; + resolve: (value: T) => void; + reject: (reason: unknown) => void; +} { + let resolve!: (value: T) => void; + let reject!: (reason: unknown) => void; + const promise = new Promise((resolvePromise, rejectPromise) => { + resolve = resolvePromise; + reject = rejectPromise; + }); + return { promise, resolve, reject }; +} + +describe('ContentFrame', () => { + it('uses an opaque scripted sandbox without same-origin authority', async () => { + const { container } = render(ContentFrame, { + props: { messageId: 42, html: '

Archived content

', title: 'Archived message' } + }); + + await waitFor(() => expect(container.querySelector('iframe')).not.toBeNull()); + const frame = container.querySelector('iframe'); + expect(frame?.getAttribute('sandbox')).toBe('allow-scripts'); + expect(frame?.getAttribute('sandbox')).not.toContain('allow-same-origin'); + expect(frame?.getAttribute('tabindex')).toBe('-1'); + expect((frame as HTMLIFrameElement & { inert: boolean }).inert).toBe(true); + await waitFor(() => + expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain('Archived content') + ); + }); + + it('keeps focus in the shell until explicit content entry', async () => { + render(ContentFrame, { + props: { messageId: 42, html: '

Archived content

', title: 'Archived message' } + }); + const enter = screen.getByRole('button', { name: 'Enter archived content' }); + await waitFor(() => expect(document.querySelector('iframe')).not.toBeNull()); + await fireEvent.load(document.querySelector('iframe')!); + enter.focus(); + + expect(screen.queryByText('Archived content active')).toBeNull(); + await fireEvent.click(enter); + + expect(screen.getByText('Archived content active')).toBeTruthy(); + expect(screen.getByRole('button', { name: 'Exit archived content' })).toBeTruthy(); + expect((document.querySelector('iframe') as HTMLIFrameElement & { inert: boolean }).inert).toBe(false); + }); + + it('keeps entry unavailable until delayed CID resolution and the final iframe load', async () => { + const response = deferred(); + const fetchFn = vi.fn(async () => await response.promise); + const { container } = render(ContentFrame, { + props: { + client: createAPIClient(fetchFn), + messageId: 42, + html: 'Slow', + title: 'Archived message' + } + }); + + await waitFor(() => expect(fetchFn).toHaveBeenCalledOnce()); + const enter = screen.getByRole('button', { name: 'Enter archived content' }); + expect((enter as HTMLButtonElement).disabled).toBe(true); + expect(container.querySelector('iframe')).toBeNull(); + + response.resolve(new Response(onePixelPNG, { headers: { 'Content-Type': 'image/png' } })); + await waitFor(() => expect(container.querySelector('iframe')).not.toBeNull()); + expect((enter as HTMLButtonElement).disabled).toBe(true); + await fireEvent.load(container.querySelector('iframe')!); + expect((enter as HTMLButtonElement).disabled).toBe(false); + }); + + it('synchronously exits and focuses stable shell chrome before consent replaces an entered frame', async () => { + const onContentFocusChange = vi.fn(); + const { container } = render(ContentFrame, { + props: { + messageId: 42, + html: '

Words

Chart', + title: 'Archived message', + onContentFocusChange + } + }); + await waitFor(() => expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain('Words')); + await fireEvent.load(container.querySelector('iframe')!); + await fireEvent.click(screen.getByRole('button', { name: 'Enter archived content' })); + expect(screen.getByText('Archived content active')).toBeTruthy(); + + await fireEvent.click(screen.getByRole('button', { name: 'Load 1 remote image' })); + + expect(screen.queryByText('Archived content active')).toBeNull(); + expect(onContentFocusChange).toHaveBeenLastCalledWith(false); + expect(container.querySelector('iframe')).toBeNull(); + expect(document.activeElement).toBe(screen.getByLabelText('Archived content controls')); + const enter = screen.getByRole('button', { name: 'Enter archived content' }); + expect((enter as HTMLButtonElement).disabled).toBe(true); + await waitFor(() => expect(container.querySelector('iframe')).not.toBeNull()); + expect((enter as HTMLButtonElement).disabled).toBe(true); + await fireEvent.load(container.querySelector('iframe')!); + expect((enter as HTMLButtonElement).disabled).toBe(false); + }); + + it('publishes a failed CID placeholder but still requires the final iframe load before entry', async () => { + const { container } = render(ContentFrame, { + props: { + client: createAPIClient(vi.fn(async () => new Response('missing', { status: 404 }))), + messageId: 42, + html: 'Missing', + title: 'Archived message' + } + }); + + const enter = screen.getByRole('button', { name: 'Enter archived content' }); + expect((enter as HTMLButtonElement).disabled).toBe(true); + await waitFor(() => { + expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain( + 'Inline image unavailable: Missing' + ); + }); + expect((enter as HTMLButtonElement).disabled).toBe(true); + await fireEvent.load(container.querySelector('iframe')!); + expect((enter as HTMLButtonElement).disabled).toBe(false); + }); + + it('aborts old message work and ignores its stale completion after a replacement is ready', async () => { + const firstResponse = deferred(); + let firstRequest: Request | undefined; + const fetchFn = vi.fn(async (input) => { + const request = input as Request; + if (new URL(request.url).searchParams.get('cid') === 'old@example.com') { + firstRequest = request; + return await firstResponse.promise; + } + return new Response(onePixelPNG, { headers: { 'Content-Type': 'image/png' } }); + }); + const client = createAPIClient(fetchFn); + const rendered = render(ContentFrame, { + props: { + client, messageId: 1, html: '', title: 'Archived message' + } + }); + await waitFor(() => expect(firstRequest).toBeDefined()); + + await rendered.rerender({ + client, messageId: 2, html: 'New', title: 'Archived message' + }); + expect(firstRequest?.signal.aborted).toBe(true); + await waitFor(() => { + const srcdoc = rendered.container.querySelector('iframe')?.getAttribute('srcdoc') ?? ''; + expect(srcdoc).toContain('data:image/png;base64,'); + }); + const replacementDocument = rendered.container.querySelector('iframe')?.getAttribute('srcdoc'); + + firstResponse.resolve(new Response(onePixelPNG, { headers: { 'Content-Type': 'image/png' } })); + await Promise.resolve(); + await Promise.resolve(); + expect(rendered.container.querySelector('iframe')?.getAttribute('srcdoc')).toBe(replacementDocument); + }); + + it('fetches CID images in the authenticated shell and embeds validated bytes as data', async () => { + const fetchFn = vi.fn(async () => new Response(onePixelPNG, { + headers: { 'Content-Type': 'image/png' } + })); + const { container } = render(ContentFrame, { + props: { + client: createAPIClient(fetchFn), + messageId: 42, + html: 'Logo', + title: 'Archived message' + } + }); + + await waitFor(() => expect(fetchFn).toHaveBeenCalledOnce()); + const request = fetchFn.mock.calls[0]?.[0] as Request; + expect(new URL(request.url).pathname).toBe('/api/v1/messages/42/inline'); + expect(new URL(request.url).searchParams.get('cid')).toBe('logo@example.com'); + expect(request.headers.has('Authorization')).toBe(false); + await waitFor(() => { + const srcdoc = container.querySelector('iframe')?.getAttribute('srcdoc') ?? ''; + expect(srcdoc).toContain('src="data:image/png;base64,'); + expect(srcdoc).not.toMatch(/\/api\/v1\/messages|cid:/); + }); + }); + + it.each([ + ['non-image MIME', new Response(onePixelPNG, { headers: { 'Content-Type': 'text/html' } })], + ['oversized bytes', new Response(new Uint8Array(5 * 1024 * 1024 + 1), { headers: { 'Content-Type': 'image/png' } })], + ['HTTP failure', new Response('missing', { status: 404, headers: { 'Content-Type': 'application/json' } })] + ])('keeps a URL-free CID failure placeholder for %s', async (_name, response) => { + const fetchFn = vi.fn(async () => response); + const { container } = render(ContentFrame, { + props: { + client: createAPIClient(fetchFn), + messageId: 42, + html: 'Logo', + title: 'Archived message' + } + }); + + await waitFor(() => { + const srcdoc = container.querySelector('iframe')?.getAttribute('srcdoc') ?? ''; + expect(srcdoc).toContain('Inline image unavailable: Logo'); + expect(srcdoc).not.toMatch(/\/api\/v1\/messages|cid:/); + }); + }); + + it('aborts shell-owned CID work when the frame is destroyed', async () => { + let request: Request | undefined; + const response = deferred(); + const fetchFn = vi.fn(async (input) => { + request = input as Request; + return await response.promise; + }); + const rendered = render(ContentFrame, { + props: { + client: createAPIClient(fetchFn), + messageId: 42, + html: '', + title: 'Archived message' + } + }); + + await waitFor(() => expect(request).toBeDefined()); + rendered.unmount(); + + expect(request?.signal.aborted).toBe(true); + response.resolve(new Response(onePixelPNG, { headers: { 'Content-Type': 'image/png' } })); + await Promise.resolve(); + await Promise.resolve(); + }); + + it('keeps remote image consent in the shell and rebuilds only after consent', async () => { + const { container } = render(ContentFrame, { + props: { + messageId: 42, + html: 'Chart', + title: 'Archived message' + } + }); + const consent = await screen.findByRole('button', { name: 'Load 1 remote image' }); + await waitFor(() => { + const srcdoc = container.querySelector('iframe')?.getAttribute('srcdoc'); + expect(srcdoc).toContain('Remote image blocked: Chart'); + expect(srcdoc).not.toContain('images.example'); + }); + await fireEvent.load(container.querySelector('iframe')!); + + await fireEvent.click(consent); + + await waitFor(() => + expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain( + 'https://images.example/chart.png' + ) + ); + }); +}); diff --git a/web/src/lib/components/reader/ConversationView.svelte b/web/src/lib/components/reader/ConversationView.svelte new file mode 100644 index 000000000..2693bccd5 --- /dev/null +++ b/web/src/lib/components/reader/ConversationView.svelte @@ -0,0 +1,252 @@ + + +
+
+
+ + {#if loading && messages.length === 0} +

Loading conversation…

+ {:else if error} +

{error}

+ {:else} + {#if hasBefore}

Earlier messages are outside this bounded window.

{/if} +
+ {#each messages as message (message.id)} + {#if message.id === activeAnchor} +
+ +
+ {:else} +
+ {#if hasAfter}

Later messages are outside this bounded window.

{/if} + {#if !selected}

The selected message is not available in this conversation window.

{/if} + {/if} +
+ + diff --git a/web/src/lib/components/reader/ConversationView.test.ts b/web/src/lib/components/reader/ConversationView.test.ts new file mode 100644 index 000000000..38d693692 --- /dev/null +++ b/web/src/lib/components/reader/ConversationView.test.ts @@ -0,0 +1,204 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import ConversationView from './ConversationView.svelte'; + +function message(id: number, type = 'email') { + return { + id, + conversation_id: 7, + subject: `Message ${id}`, + message_type: type, + from: 'alice@example.com', + to: ['bob@example.com'], + sent_at: `2026-01-0${id}T12:00:00Z`, + snippet: `Preview ${id}`, + labels: [], + has_attachments: false, + size_bytes: 10, + body: `Body ${id}`, + body_html: `

Body ${id}

`, + attachments: [] + }; +} + +describe('ConversationView', () => { + it('loads the anchored chronological email thread and highlights the selected item', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return Response.json({ + id: 7, anchor_id: 2, messages: [message(1), message(2), message(3)], + has_before: false, has_after: false, total: 3 + }); + }); + + render(ConversationView, { + props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack: vi.fn() } + }); + + expect(await screen.findByRole('heading', { name: 'Conversation' })).toBeTruthy(); + expect((await screen.findByRole('article', { name: 'Selected message 2' })).getAttribute('aria-current')).toBe('true'); + expect(screen.getByRole('button', { name: /Open message 1/ })).toBeTruthy(); + const url = new URL(requests[0]!.url); + expect(url.pathname).toBe('/api/v1/conversations/7'); + expect(url.searchParams.get('anchor')).toBe('2'); + }); + + it('sends optional start/end bounds when scoping to a chat burst day', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return Response.json({ + id: 7, anchor_id: 2, messages: [message(2)], has_before: false, has_after: false, total: 1 + }); + }); + + render(ConversationView, { + props: { + client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack: vi.fn(), + start: '2026-07-19T00:00:00.000Z', end: '2026-07-20T00:00:00.000Z' + } + }); + + await screen.findByRole('article', { name: 'Selected message 2' }); + const url = new URL(requests[0]!.url); + expect(url.searchParams.get('start')).toBe('2026-07-19T00:00:00.000Z'); + expect(url.searchParams.get('end')).toBe('2026-07-20T00:00:00.000Z'); + }); + + it('switches the selected conversation message to plain text', async () => { + const fetchFn = vi.fn(async () => Response.json({ + id: 7, anchor_id: 2, messages: [message(2)], has_before: false, has_after: false, total: 1 + })); + render(ConversationView, { + props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack: vi.fn() } + }); + + await screen.findByRole('article', { name: 'Selected message 2' }); + await fireEvent.click(screen.getByRole('button', { name: 'Text' })); + expect(screen.getByRole('button', { name: 'Text' }).getAttribute('aria-pressed')).toBe('true'); + expect(screen.getByText('Body 2')).toBeDefined(); + }); + + it('drills chat conversation rows to individual messages without losing conversation context', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const anchor = Number(new URL(request.url).searchParams.get('anchor')); + return Response.json({ + id: 7, anchor_id: anchor, messages: [message(1, 'imessage'), message(2, 'imessage')], + has_before: false, has_after: false, total: 2 + }); + }); + const client = createAPIClient(fetchFn); + const onBack = vi.fn(); + let rendered: ReturnType; + const onAnchorChange = vi.fn((nextAnchor: number) => { + void rendered.rerender({ + client, conversationId: 7, anchorId: nextAnchor, onBack, onAnchorChange + }); + }); + rendered = render(ConversationView, { + props: { client, conversationId: 7, anchorId: 2, onBack, onAnchorChange } + }); + + await fireEvent.click(await screen.findByRole('button', { name: /Open message 1/ })); + + await waitFor(() => expect(onAnchorChange).toHaveBeenCalledWith(1)); + await waitFor(() => expect(requests).toHaveLength(2)); + expect(new URL(requests[1]!.url).searchParams.get('anchor')).toBe('1'); + }); + + it('aborts superseded anchors and ignores stale resolve or reject settlements', async () => { + const pending = new Map void; + reject: (error: unknown) => void; + }>(); + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const anchor = Number(new URL(request.url).searchParams.get('anchor')); + if (anchor === 2) return Response.json({ + id: 7, anchor_id: 2, messages: [message(1), message(2), message(3)], + has_before: false, has_after: false, total: 3 + }); + return await new Promise((resolve, reject) => { + pending.set(anchor, { request, resolve, reject }); + }); + }); + const client = createAPIClient(fetchFn); + const onBack = vi.fn(); + let rendered: ReturnType; + const onAnchorChange = (nextAnchor: number): void => { + void rendered.rerender({ + client, conversationId: 7, anchorId: nextAnchor, onBack, onAnchorChange + }); + }; + rendered = render(ConversationView, { + props: { client, conversationId: 7, anchorId: 2, onBack, onAnchorChange } + }); + await screen.findByRole('article', { name: 'Selected message 2' }); + + await fireEvent.click(screen.getByRole('button', { name: /Open message 1/ })); + await waitFor(() => expect(pending.get(1)).toBeDefined()); + await fireEvent.click(screen.getByRole('button', { name: /Open message 3/ })); + await waitFor(() => expect(pending.get(3)).toBeDefined()); + + expect(pending.get(1)?.request.signal.aborted).toBe(true); + pending.get(3)?.resolve(Response.json({ + id: 7, anchor_id: 3, messages: [message(1), message(2), message(3)], + has_before: false, has_after: false, total: 3 + })); + pending.get(1)?.reject(new TypeError('stale network failure')); + + expect(await screen.findByRole('article', { name: 'Selected message 3' })).toBeTruthy(); + expect(screen.queryByRole('alert')).toBeNull(); + expect(fetchFn).toHaveBeenCalledTimes(3); + }); + + it('surfaces a named network failure without an unhandled rejection', async () => { + const fetchFn = vi.fn(async () => { + throw new TypeError('connection refused'); + }); + render(ConversationView, { + props: { + client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack: vi.fn() + } + }); + + expect((await screen.findByRole('alert')).textContent).toContain('Conversation network error'); + }); + + it('silently ignores AbortError request settlements', async () => { + const fetchFn = vi.fn(async () => { + throw new DOMException('superseded', 'AbortError'); + }); + render(ConversationView, { + props: { + client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack: vi.fn() + } + }); + + await waitFor(() => expect(fetchFn).toHaveBeenCalledOnce()); + expect(screen.queryByRole('alert')).toBeNull(); + }); + + it('keeps Back in the shell-owned sticky toolbar and names unavailable states', async () => { + const onBack = vi.fn(); + const fetchFn = vi.fn(async () => Response.json({ + error: 'conversation_unavailable', message: 'Conversation details are unavailable' + }, { status: 503 })); + render(ConversationView, { + props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack } + }); + + expect((await screen.findByRole('alert')).textContent).toContain('Conversation details are unavailable'); + await fireEvent.click(screen.getByRole('button', { name: 'Back from conversation' })); + expect(onBack).toHaveBeenCalledOnce(); + }); +}); diff --git a/web/src/lib/components/reader/MessageReader.svelte b/web/src/lib/components/reader/MessageReader.svelte new file mode 100644 index 000000000..37ff7c7be --- /dev/null +++ b/web/src/lib/components/reader/MessageReader.svelte @@ -0,0 +1,118 @@ + + +
+ {#if loading} +

Loading message…

+ {:else if error} +

{error}

+ {:else if message === null} +

Select a message to read it.

+ {:else} +
+

{message.subject}

+

{message.sender}

+ {#if hasRenderableHTML} +
+ + +
+ {/if} +
+ + {#if sanitizationFailed} +

Could not render HTML formatting. Showing plain text.

+ {/if} + +
+ {#if renderAsHTML} + + {:else} +
{message.body}
+ {/if} +
+ {/if} +
+ + diff --git a/web/src/lib/components/reader/MessageReader.test.ts b/web/src/lib/components/reader/MessageReader.test.ts new file mode 100644 index 000000000..8bcc580cb --- /dev/null +++ b/web/src/lib/components/reader/MessageReader.test.ts @@ -0,0 +1,129 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import type { ArchiveMessageDetail } from '../../archive/types'; +import MessageReader from './MessageReader.svelte'; + +function detail(overrides: Partial = {}): ArchiveMessageDetail { + return { + id: 42, + conversationId: 7, + subject: 'Quarterly plan', + sender: 'alice@example.com', + recipients: ['bob@example.com'], + sentAt: '2026-01-01T12:00:00Z', + snippet: 'Plan preview', + body: 'Plain text fallback.', + attachments: [], + ...overrides + }; +} + +describe('MessageReader', () => { + it('renders plain text when text mode is selected', () => { + const { container } = render(MessageReader, { + props: { message: detail({ bodyHtml: '

Formatted body

' }), viewMode: 'text' } + }); + + expect(container.querySelector('pre')?.textContent).toBe('Plain text fallback.'); + expect(container.querySelector('iframe')).toBeNull(); + }); + + it('renders non-empty HTML in an isolated frame when HTML mode is selected', async () => { + const { container } = render(MessageReader, { + props: { message: detail({ bodyHtml: '

Formatted body

' }), viewMode: 'html' } + }); + + await waitFor(() => expect(container.querySelector('iframe')).not.toBeNull()); + const frame = container.querySelector('iframe'); + expect(frame?.getAttribute('title')).toBe('Message body'); + expect(frame?.hasAttribute('sandbox')).toBe(true); + await waitFor(() => + expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain( + '

Formatted body

' + ) + ); + expect(container.querySelector('pre')).toBeNull(); + }); + + it('sanitizes framed HTML and blocks sender-controlled network requests', async () => { + const { container } = render(MessageReader, { + props: { + message: detail({ + bodyHtml: + '' + + '' + + 'Open' + + '

Safe text

' + }), + viewMode: 'html' + } + }); + + await waitFor(() => expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain('Safe text')); + const srcdoc = container.querySelector('iframe')?.getAttribute('srcdoc') ?? ''; + expect(srcdoc).toContain('Content-Security-Policy'); + expect(srcdoc).toContain("default-src 'none'"); + expect(srcdoc).toContain('Safe text'); + expect(srcdoc.match(/ + + + + {#snippet footer()} + + {/if} + {/if} + + {/each} + {#each unrepresentedMembers as memberID (memberID)} + + identity #{memberID} · No stored identifier evidence · linked identity #{memberID} + {#if confirmingParticipantID === memberID} + + Detach from cluster? + + {/if} + + {/each} + + {#if unlinkError} + + {/if} + {/if} + {#if dialogOpen && isPersonDetail(detail)} + (dialogOpen = false)} + /> + {/if} + {/if} + + + diff --git a/web/src/lib/components/relationships/RelationshipHeader.test.ts b/web/src/lib/components/relationships/RelationshipHeader.test.ts new file mode 100644 index 000000000..543c0599f --- /dev/null +++ b/web/src/lib/components/relationships/RelationshipHeader.test.ts @@ -0,0 +1,369 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import type { DomainSummary, PersonSummary } from '../../explore/models'; +import type { LinkOutcome } from '../../relationships/controller.svelte'; +import RelationshipHeader from './RelationshipHeader.svelte'; + +const when = '2026-07-19T10:00:00Z'; + +function person(): PersonSummary { + return { + id: 12, display_label: 'Alice Example', partial_label: false, + identifiers: [ + { type: 'email', value: 'alice@example.com', display_value: 'Alice', participant_id: 12, is_primary: true, provenance: 'participant_identifiers' }, + { type: 'phone', value: '+15550100001', participant_id: 12, is_primary: false, provenance: 'participant_identifiers' } + ], + activity_count: 42, file_count: 3, source_counts: [{ source_type: 'gmail', count: 42 }], + first_at: when, last_at: when, cache_revision: 'cache-rel' + }; +} + +function domain(): DomainSummary { + return { + domain: 'example.com', activity_count: 100, file_count: 5, person_count: 8, + first_at: when, last_at: when, source_counts: [], cache_revision: 'cache-rel' + }; +} + +function searchResult(): PersonSummary { + return { + id: 99, display_label: 'Bob Example', partial_label: false, + identifiers: [{ type: 'email', value: 'bob@example.com', participant_id: 99, is_primary: true, provenance: 'participant_identifiers' }], + activity_count: 7, file_count: 0, source_counts: [], first_at: when, last_at: when, cache_revision: 'cache-rel' + }; +} + +/** A linked cluster spanning three participants in a chain (12–34–56, not a + * star): 34 is a cut vertex, so detaching it must remove both incident + * edges, not just one. */ +function clusteredPerson(): PersonSummary { + return { + id: 12, display_label: 'Alice Example', partial_label: false, + identifiers: [ + { type: 'email', value: 'alice@example.com', display_value: 'Alice', participant_id: 12, is_primary: true, provenance: 'participant_identifiers' }, + { type: 'phone', value: '+15550100002', participant_id: 34, is_primary: true, provenance: 'participant_identifiers' }, + { type: 'email', value: 'carol@example.com', participant_id: 56, is_primary: true, provenance: 'participant_identifiers' } + ], + activity_count: 42, file_count: 3, source_counts: [{ source_type: 'gmail', count: 42 }], + first_at: when, last_at: when, cache_revision: 'cache-rel', + cluster: { + canonical_id: 12, member_ids: [12, 34, 56], + edges: [{ participant_a: 12, participant_b: 34 }, { participant_a: 34, participant_b: 56 }] + } + }; +} + +/** A cluster where member 78 was joined by a manual participant link with + * no stored identifier evidence at all (78 has no row in `identifiers`), + * unlike 34/56 above which each have their own identifier row. */ +function clusteredPersonWithBareMember(): PersonSummary { + const base = clusteredPerson(); + return { + ...base, + cluster: { + canonical_id: 12, member_ids: [12, 34, 56, 78], + edges: [...(base.cluster?.edges ?? []), { participant_a: 12, participant_b: 78 }] + } + }; +} + +function searchClient(): ReturnType { + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (new URL(request.url).pathname === '/api/v1/people/search') { + return Response.json({ rows: [searchResult()], total_count: 1, cache_revision: 'cache-rel', search_provenance: {} }); + } + throw new Error(`unexpected fetch to ${request.url}`); + }); + return createAPIClient(fetchFn); +} + +function baseProps(overrides: Record = {}) { + return { + detail: person(), + filesOpen: false, + onFilesToggle: vi.fn(), + client: searchClient(), + onLinkParticipants: vi.fn(async (): Promise => ({ ok: true, identityRevision: 2, cacheState: 'ready' })), + onUnlinkParticipants: vi.fn(async (): Promise => ({ ok: true, identityRevision: 3, cacheState: 'ready' })), + ...overrides + }; +} + +/** Opens the Link identity dialog, searches for "Bob", selects the stubbed + * search result (participant #99), and clicks Link to confirm. Exercises the + * real LinkIdentityDialog end to end; its own search/select mechanics are + * covered directly in LinkIdentityDialog.test.ts. */ +async function linkToSearchResult(): Promise { + await fireEvent.click(screen.getByRole('button', { name: 'Link identity' })); + await fireEvent.input(screen.getByRole('searchbox', { name: 'Search people to link' }), { target: { value: 'Bob' } }); + await fireEvent.click(await screen.findByRole('option', { name: /Bob Example/ })); + await fireEvent.click(screen.getByRole('button', { name: 'Link' })); +} + +describe('RelationshipHeader', () => { + it('shows a placeholder status when nothing is selected', () => { + render(RelationshipHeader, baseProps({ detail: null })); + expect(screen.getByRole('status').textContent).toContain('Select a person or domain'); + }); + + it('renders a person: display name, identity chips, item counts, and the Files toggle', async () => { + const onFilesToggle = vi.fn(); + render(RelationshipHeader, baseProps({ onFilesToggle })); + + expect(screen.getByRole('heading', { name: 'Alice Example' })).toBeDefined(); + expect(screen.getByText(/42 items/)).toBeDefined(); + expect(screen.getByText(/3 files/)).toBeDefined(); + expect(screen.getByText('Alice')).toBeDefined(); + expect(screen.getByText('alice@example.com')).toBeDefined(); + expect(screen.getByText(/email · Primary · participant_identifiers/)).toBeDefined(); + expect(screen.getByText('+15550100001')).toBeDefined(); + expect(screen.getByText(/phone · Secondary · participant_identifiers/)).toBeDefined(); + + await fireEvent.click(screen.getByRole('button', { name: 'Files 3' })); + expect(onFilesToggle).toHaveBeenCalledWith(true); + }); + + it('renders a domain by domain name and person count, without identity chips or a Link identity button', () => { + render(RelationshipHeader, baseProps({ detail: domain() })); + + expect(screen.getByRole('heading', { name: 'example.com' })).toBeDefined(); + expect(screen.getByText(/100 items/)).toBeDefined(); + expect(screen.getByText(/8 people/)).toBeDefined(); + expect(screen.queryByLabelText('Archive-wide identity evidence')).toBeNull(); + expect(screen.queryByRole('button', { name: 'Link identity' })).toBeNull(); + }); + + it('toggles Files off when already open', async () => { + const onFilesToggle = vi.fn(); + render(RelationshipHeader, baseProps({ filesOpen: true, onFilesToggle })); + + await fireEvent.click(screen.getByRole('button', { name: 'Files 3' })); + expect(onFilesToggle).toHaveBeenCalledWith(false); + }); + + it('opens the Link identity dialog for a person, and Esc closes it', async () => { + render(RelationshipHeader, baseProps()); + + await fireEvent.click(screen.getByRole('button', { name: 'Link identity' })); + expect(screen.getByRole('dialog', { name: 'Link identity' })).toBeDefined(); + + await fireEvent.keyDown(window, { key: 'Escape' }); + expect(screen.queryByRole('dialog', { name: 'Link identity' })).toBeNull(); + }); + + it('links against the open cluster member ID; an ok/ready outcome closes the dialog silently', async () => { + const onLinkParticipants = vi.fn(async (): Promise => ({ ok: true, identityRevision: 2, cacheState: 'ready' })); + render(RelationshipHeader, baseProps({ onLinkParticipants })); + + await linkToSearchResult(); + + await waitFor(() => expect(onLinkParticipants).toHaveBeenCalledWith(12, 99)); + expect(screen.queryByRole('dialog', { name: 'Link identity' })).toBeNull(); + expect(screen.queryByRole('alert')).toBeNull(); + }); + + it('shows already_linked as an inline dialog error and keeps the dialog open', async () => { + const onLinkParticipants = vi.fn(async (): Promise => ( + { ok: false, code: 'already_linked', message: 'these participants are already connected through other links' } + )); + render(RelationshipHeader, baseProps({ onLinkParticipants })); + + await linkToSearchResult(); + + expect((await screen.findByRole('alert')).textContent).toContain( + 'These identities are already linked through another identifier.' + ); + expect(screen.getByRole('dialog', { name: 'Link identity' })).toBeDefined(); + }); + + it('raises the identity_cache_stale banner on an ok/stale outcome, and Retry re-invokes the same pair', async () => { + const onLinkParticipants = vi.fn(async (): Promise => ({ ok: true, identityRevision: 2, cacheState: 'stale' })); + render(RelationshipHeader, baseProps({ onLinkParticipants })); + + await linkToSearchResult(); + + expect((await screen.findByRole('alert')).textContent).toContain( + 'Identity saved; the cache refresh failed — groupings may be stale until a rebuild. Retrying is safe.' + ); + expect(onLinkParticipants).toHaveBeenCalledWith(12, 99); + expect(screen.queryByRole('dialog', { name: 'Link identity' })).toBeNull(); + + await fireEvent.click(screen.getByRole('button', { name: 'Retry' })); + await waitFor(() => expect(onLinkParticipants).toHaveBeenCalledTimes(2)); + expect(onLinkParticipants).toHaveBeenLastCalledWith(12, 99); + }); + + it('clears the stale banner after a later ready outcome, and it persists across a dialog close in the meantime', async () => { + const onLinkParticipants = vi.fn(async (): Promise => ({ ok: true, identityRevision: 2, cacheState: 'stale' })); + render(RelationshipHeader, baseProps({ onLinkParticipants })); + + await linkToSearchResult(); + await screen.findByRole('alert'); + + // The dialog closed itself on the ok outcome; the banner must still show. + expect(screen.queryByRole('dialog', { name: 'Link identity' })).toBeNull(); + expect(screen.getByRole('alert')).toBeDefined(); + + // Reopening and closing the dialog without linking again must not + // disturb the still-pending stale banner. + await fireEvent.click(screen.getByRole('button', { name: 'Link identity' })); + await fireEvent.keyDown(window, { key: 'Escape' }); + expect(screen.getByRole('alert')).toBeDefined(); + + onLinkParticipants.mockResolvedValueOnce({ ok: true, identityRevision: 3, cacheState: 'ready' }); + await fireEvent.click(screen.getByRole('button', { name: 'Retry' })); + await waitFor(() => expect(screen.queryByRole('alert')).toBeNull()); + }); + + it('shows an unlink × only on chips for other cluster members, never on the open cluster\'s own identifiers', () => { + render(RelationshipHeader, baseProps({ detail: clusteredPerson() })); + + expect(screen.queryByRole('button', { name: /Detach identity #12/ })).toBeNull(); + expect(screen.getByRole('button', { name: 'Detach identity #34 from this cluster' })).toBeDefined(); + expect(screen.getByRole('button', { name: 'Detach identity #56 from this cluster' })).toBeDefined(); + }); + + it('does not show unlink controls when the person has no cluster', () => { + render(RelationshipHeader, baseProps()); + expect(screen.queryByRole('button', { name: /Detach identity/ })).toBeNull(); + }); + + it('renders a fallback chip with its own detach control for a cluster member with no identifier rows', async () => { + const onUnlinkParticipants = vi.fn(async (): Promise => ({ ok: true, identityRevision: 4, cacheState: 'ready' })); + render(RelationshipHeader, baseProps({ detail: clusteredPersonWithBareMember(), onUnlinkParticipants })); + + expect(screen.getByText(/identity #78/)).toBeDefined(); + const detachButton = screen.getByRole('button', { name: 'Detach identity #78 from this cluster' }); + await fireEvent.click(detachButton); + await fireEvent.click(screen.getByRole('button', { name: 'Detach' })); + + await waitFor(() => expect(onUnlinkParticipants).toHaveBeenCalledWith(12, 78)); + }); + + it('confirming a cut-vertex member\'s unlink removes every edge incident to it, not just one', async () => { + const onUnlinkParticipants = vi.fn(async (): Promise => ({ ok: true, identityRevision: 4, cacheState: 'ready' })); + render(RelationshipHeader, baseProps({ detail: clusteredPerson(), onUnlinkParticipants })); + + await fireEvent.click(screen.getByRole('button', { name: 'Detach identity #34 from this cluster' })); + expect(screen.getByRole('group', { name: 'Confirm detaching identity #34' })).toBeDefined(); + await fireEvent.click(screen.getByRole('button', { name: 'Detach' })); + + await waitFor(() => expect(onUnlinkParticipants).toHaveBeenCalledTimes(2)); + expect(onUnlinkParticipants).toHaveBeenCalledWith(12, 34); + expect(onUnlinkParticipants).toHaveBeenCalledWith(34, 56); + expect(screen.queryByRole('group', { name: 'Confirm detaching identity #34' })).toBeNull(); + expect(screen.queryByRole('alert')).toBeNull(); + }); + + it('cancelling an unlink confirm leaves the chip untouched and calls nothing', async () => { + const onUnlinkParticipants = vi.fn(); + render(RelationshipHeader, baseProps({ detail: clusteredPerson(), onUnlinkParticipants })); + + await fireEvent.click(screen.getByRole('button', { name: 'Detach identity #56 from this cluster' })); + await fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + + expect(screen.queryByRole('group', { name: 'Confirm detaching identity #56' })).toBeNull(); + expect(screen.getByRole('button', { name: 'Detach identity #56 from this cluster' })).toBeDefined(); + expect(onUnlinkParticipants).not.toHaveBeenCalled(); + }); + + it('treats an already-clean edge (idempotent 200) as success and closes the confirm', async () => { + // Simulates confirming after the edge was already removed elsewhere: + // UnlinkParticipants is idempotent, so the store 200s without error. + const onUnlinkParticipants = vi.fn(async (): Promise => ({ ok: true, identityRevision: 5, cacheState: 'ready' })); + render(RelationshipHeader, baseProps({ detail: clusteredPerson(), onUnlinkParticipants })); + + await fireEvent.click(screen.getByRole('button', { name: 'Detach identity #34 from this cluster' })); + await fireEvent.click(screen.getByRole('button', { name: 'Detach' })); + + await waitFor(() => expect(onUnlinkParticipants).toHaveBeenCalledTimes(2)); + expect(screen.queryByRole('alert')).toBeNull(); + expect(screen.queryByRole('group', { name: 'Confirm detaching identity #34' })).toBeNull(); + }); + + it('shows an inline error and keeps the confirm state on an unlink failure', async () => { + const onUnlinkParticipants = vi.fn(async (): Promise => ({ ok: false, code: 'error', message: 'Request failed (500)' })); + render(RelationshipHeader, baseProps({ detail: clusteredPerson(), onUnlinkParticipants })); + + await fireEvent.click(screen.getByRole('button', { name: 'Detach identity #34 from this cluster' })); + await fireEvent.click(screen.getByRole('button', { name: 'Detach' })); + + expect((await screen.findByRole('alert')).textContent).toContain('Request failed (500)'); + expect(onUnlinkParticipants).toHaveBeenCalledTimes(1); + }); + + it('clears the stale banner and any pending unlink confirm when navigating to a different person', async () => { + const onLinkParticipants = vi.fn(async (): Promise => ({ ok: true, identityRevision: 2, cacheState: 'stale' })); + const { rerender } = render(RelationshipHeader, baseProps({ onLinkParticipants })); + + await linkToSearchResult(); + await screen.findByRole('alert'); + expect(screen.getByRole('alert')).toBeDefined(); + + const otherPerson = { ...clusteredPerson(), id: 200 }; + await rerender(baseProps({ onLinkParticipants, detail: otherPerson })); + expect(screen.queryByRole('alert')).toBeNull(); + }); + + it('a link outcome that resolves after navigating away does not repopulate the banner for the new person', async () => { + let resolveLink: ((outcome: LinkOutcome) => void) | undefined; + const onLinkParticipants = vi.fn( + () => new Promise((resolve) => { resolveLink = resolve; }) + ); + const { rerender } = render(RelationshipHeader, baseProps({ onLinkParticipants })); + + await fireEvent.click(screen.getByRole('button', { name: 'Link identity' })); + await fireEvent.input(screen.getByRole('searchbox', { name: 'Search people to link' }), { target: { value: 'Bob' } }); + await fireEvent.click(await screen.findByRole('option', { name: /Bob Example/ })); + await fireEvent.click(screen.getByRole('button', { name: 'Link' })); + await waitFor(() => expect(onLinkParticipants).toHaveBeenCalledWith(12, 99)); + + // Navigate to a different person while the link call for Alice (id 12) + // is still in flight. + const otherPerson = { ...clusteredPerson(), id: 200 }; + await rerender(baseProps({ onLinkParticipants, detail: otherPerson })); + expect(screen.queryByRole('alert')).toBeNull(); + + // The stale outcome resolves for the person that's no longer open. + // LinkIdentityDialog's own confirmLink closes the dialog once + // RelationshipHeader's confirmLink (which this awaits) returns — waiting + // for that close is what actually proves the full continuation, + // including any (skipped) applyOutcome call, ran to completion. + resolveLink?.({ ok: true, identityRevision: 9, cacheState: 'stale' }); + await waitFor(() => expect(screen.queryByRole('dialog', { name: 'Link identity' })).toBeNull()); + + expect(screen.queryByRole('alert'), 'must not resurrect the banner for person 200').toBeNull(); + }); + + it('an unlink outcome for a later edge that resolves after navigating away stops touching this component\'s state', async () => { + let resolveSecondEdge: ((outcome: LinkOutcome) => void) | undefined; + let callCount = 0; + const onUnlinkParticipants = vi.fn((): Promise => { + callCount += 1; + if (callCount === 1) return Promise.resolve({ ok: true, identityRevision: 3, cacheState: 'ready' }); + return new Promise((resolve) => { resolveSecondEdge = resolve; }); + }); + const { rerender } = render(RelationshipHeader, baseProps({ detail: clusteredPerson(), onUnlinkParticipants })); + + await fireEvent.click(screen.getByRole('button', { name: 'Detach identity #34 from this cluster' })); + await fireEvent.click(screen.getByRole('button', { name: 'Detach' })); + await waitFor(() => expect(onUnlinkParticipants).toHaveBeenCalledWith(12, 34)); + await waitFor(() => expect(onUnlinkParticipants).toHaveBeenCalledWith(34, 56)); + + // Navigate away while the second edge's unlink call is still pending. + const otherPerson = { ...clusteredPerson(), id: 200 }; + await rerender(baseProps({ detail: otherPerson, onUnlinkParticipants })); + + resolveSecondEdge?.({ ok: false, code: 'error', message: 'Request failed (500)' }); + // A macrotask, not a fixed count of microtask flushes: drains the + // continuation after the awaited unlink call regardless of how many + // .then hops it takes to reach the (skipped, once fixed) unlinkError + // write and the finally block's `unlinking = false`. + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(screen.queryByRole('alert'), 'a stale failure must not surface on the wrong person').toBeNull(); + }); +}); diff --git a/web/src/lib/components/relationships/RelationshipList.svelte b/web/src/lib/components/relationships/RelationshipList.svelte new file mode 100644 index 000000000..1f5193e36 --- /dev/null +++ b/web/src/lib/components/relationships/RelationshipList.svelte @@ -0,0 +1,338 @@ + + + + + diff --git a/web/src/lib/components/relationships/RelationshipList.test.ts b/web/src/lib/components/relationships/RelationshipList.test.ts new file mode 100644 index 000000000..cb5e47d60 --- /dev/null +++ b/web/src/lib/components/relationships/RelationshipList.test.ts @@ -0,0 +1,141 @@ +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import type { DomainSummary, PersonSummary } from '../../explore/models'; +import type { RelationshipRow } from '../../relationships/models'; +import RelationshipList from './RelationshipList.svelte'; + +const when = '2026-07-19T10:00:00Z'; + +function relationshipRow(id: number, label: string, overrides: Partial = {}): RelationshipRow { + return { + canonical_id: id, + display_label: label, + last_at: when, + member_ids: [id], + score: id, + signals: { + last_interaction_at: when, + meeting_count: 0, + meetings_together: 0, + modalities: id, + received_from_them: 1, + sent_count: id * 3, + sent_to_them: 1 + }, + ...overrides + }; +} + +function person(id: number, label: string): PersonSummary { + return { + id, display_label: label, partial_label: false, identifiers: [], + activity_count: 4, file_count: 0, source_counts: [], first_at: when, last_at: when, cache_revision: 'cache-rel' + }; +} + +function domain(name: string): DomainSummary { + return { + domain: name, activity_count: 9, file_count: 1, person_count: 2, + first_at: when, last_at: when, source_counts: [], cache_revision: 'cache-rel' + }; +} + +function baseProps() { + return { + rows: [relationshipRow(1, 'Alice Example'), relationshipRow(2, 'Bob Example')], + loading: false, + error: null, + degraded: null, + facet: 'people' as const, + query: '', + showAll: false, + onQueryChange: vi.fn(), + onFacetChange: vi.fn(), + onShowAllChange: vi.fn(), + onSelect: vi.fn() + }; +} + +describe('RelationshipList', () => { + it('renders ranked rows with label, modality badge, last-interaction date, and a score-proportional bar', () => { + render(RelationshipList, baseProps()); + + const grid = screen.getByRole('grid', { name: 'Relationship results' }); + expect(screen.getByText('Alice Example')).toBeDefined(); + expect(screen.getByText('Bob Example')).toBeDefined(); + expect(screen.getByLabelText('1 modalities')).toBeDefined(); + expect(screen.getByLabelText('2 modalities')).toBeDefined(); + const bars = grid.querySelectorAll('.activity-bar'); + expect(bars).toHaveLength(2); + expect(bars[0]!.style.width).toBe('50%'); + expect(bars[1]!.style.width).toBe('100%'); + }); + + it('fires onFacetChange when the People/Domains toggle changes', async () => { + const props = baseProps(); + render(RelationshipList, props); + + await fireEvent.click(screen.getByRole('radio', { name: 'Domains' })); + expect(props.onFacetChange).toHaveBeenCalledWith('domains'); + }); + + it('fires onShowAllChange from the show-all checkbox', async () => { + const props = baseProps(); + render(RelationshipList, props); + + await fireEvent.click(screen.getByRole('checkbox', { name: 'Show all senders' })); + expect(props.onShowAllChange).toHaveBeenCalledWith(true); + }); + + it('switches from ranked rows to search results as the query and rows props change', async () => { + const props = baseProps(); + const view = render(RelationshipList, props); + + await fireEvent.input(screen.getByRole('searchbox', { name: 'Search people and domains' }), { target: { value: 'ali' } }); + expect(props.onQueryChange).toHaveBeenCalledWith('ali'); + + await view.rerender({ ...props, query: 'ali', rows: [person(3, 'Alicia Searched')] }); + expect(screen.getByText('Alicia Searched')).toBeDefined(); + expect(screen.queryByText('Alice Example')).toBeNull(); + }); + + it('renders domain rows by domain name and activity count', () => { + render(RelationshipList, { ...baseProps(), facet: 'domains', rows: [domain('example.com')] }); + expect(screen.getByText('example.com')).toBeDefined(); + expect(screen.getByText('9 items')).toBeDefined(); + }); + + it('moves the active row with j/k and opens it with Enter', async () => { + const props = baseProps(); + render(RelationshipList, props); + const grid = screen.getByRole('grid', { name: 'Relationship results' }); + grid.focus(); + + await fireEvent.keyDown(grid, { key: 'j' }); + await fireEvent.keyDown(grid, { key: 'Enter' }); + expect(props.onSelect).toHaveBeenCalledWith('cluster:2'); + + await fireEvent.keyDown(grid, { key: 'k' }); + await fireEvent.keyDown(grid, { key: 'Enter' }); + expect(props.onSelect).toHaveBeenLastCalledWith('cluster:1'); + }); + + it('selects a row by clicking it', async () => { + const props = baseProps(); + render(RelationshipList, props); + + await fireEvent.pointerDown(screen.getByText('Bob Example').closest('[role="row"]')!); + expect(props.onSelect).toHaveBeenCalledWith('cluster:2'); + }); + + it('renders the named degraded state with an Open Everything action instead of the grid', async () => { + const onOpenEverything = vi.fn(); + render(RelationshipList, { ...baseProps(), degraded: 'cache_unavailable', onOpenEverything }); + + expect(screen.getByText('Relationship ranking needs the analytical cache/engine')).toBeDefined(); + expect(screen.queryByRole('grid')).toBeNull(); + await fireEvent.click(screen.getByRole('button', { name: 'Open Everything' })); + expect(onOpenEverything).toHaveBeenCalledOnce(); + }); +}); diff --git a/web/src/lib/components/relationships/RelationshipTimeline.svelte b/web/src/lib/components/relationships/RelationshipTimeline.svelte new file mode 100644 index 000000000..98b58106b --- /dev/null +++ b/web/src/lib/components/relationships/RelationshipTimeline.svelte @@ -0,0 +1,340 @@ + + +
+ {#if restartNotice}

{restartNotice}

{/if} + {#if error}{/if} +
+
+ {#if loading && rows.length === 0} +

Loading activity…

+ {:else if rows.length === 0} +

No activity yet.

+ {:else if !slice || rowHeight === undefined} +

Preparing timeline layout…

+ {:else} +
+
+ {#each renderedItems as item (item.key)} + {#if item.type === 'header'} +
+ {item.label} +
+ {:else} +
selectRow(item.row)} + ondblclick={() => selectRow(item.row)} + > +
+ {timelineRowTitle(item.row)} + {item.row.preview} + +
+
+ {/if} + {/each} +
+
+ {/if} + {#if loadingMore} +

Loading more…

+ {/if} +
+
+
+ + diff --git a/web/src/lib/components/relationships/RelationshipTimeline.test.ts b/web/src/lib/components/relationships/RelationshipTimeline.test.ts new file mode 100644 index 000000000..761a1251e --- /dev/null +++ b/web/src/lib/components/relationships/RelationshipTimeline.test.ts @@ -0,0 +1,127 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import type { RelationshipTimelineRow } from '../../relationships/models'; +import RelationshipTimeline from './RelationshipTimeline.svelte'; + +function emailRow(key: string, occurredAt: string, title = key): RelationshipTimelineRow { + return { + key, kind: 'email', occurred_at: occurredAt, preview: `Preview for ${key}`, + source_id: 1, title, has_attachments: false, message_count: 1, + anchor_message_id: 90, conversation_id: 70 + }; +} + +function burstRow(key: string, occurredAt: string, messageCount: number): RelationshipTimelineRow { + return { + key, kind: 'chat_burst', occurred_at: occurredAt, first_at: occurredAt, preview: 'Latest chat snippet', + source_id: 2, title: 'Team Chat', has_attachments: false, message_count: messageCount, + anchor_message_id: 500, conversation_id: 71 + }; +} + +describe('RelationshipTimeline', () => { + it('groups rows by month header and renders chat bursts as "N messages in "', () => { + render(RelationshipTimeline, { + rows: [ + emailRow('message:1', '2026-07-18T12:00:00Z', 'July email'), + burstRow('burst:1', '2026-07-05T12:00:00Z', 6), + emailRow('message:2', '2026-06-20T12:00:00Z', 'June email') + ], + onRowOpen: vi.fn() + }); + + const grid = screen.getByRole('grid', { name: 'Relationship activity' }); + const headers = screen.getAllByRole('gridcell', { name: /^Month: / }); + expect(headers).toHaveLength(2); + expect(headers[0]!.closest('[role="row"]')).toBe(grid.querySelector('.month-header')); + expect(screen.getByText('6 messages in Team Chat')).toBeDefined(); + expect(screen.getByText('July email')).toBeDefined(); + expect(screen.getByText('June email')).toBeDefined(); + }); + + it('shows a selection ring on the row matching selectedKey', () => { + render(RelationshipTimeline, { + rows: [emailRow('message:1', '2026-07-18T12:00:00Z'), emailRow('message:2', '2026-07-17T12:00:00Z')], + selectedKey: 'message:2', + onRowOpen: vi.fn() + }); + + const selectedRow = document.querySelector('[data-row-key="message:2"]'); + expect(selectedRow?.classList.contains('selected')).toBe(true); + expect(document.querySelector('[data-row-key="message:1"]')?.classList.contains('selected')).toBe(false); + }); + + it('moves the keyboard cursor with j/k and opens the active row with Enter', async () => { + const onRowOpen = vi.fn(); + render(RelationshipTimeline, { + rows: [emailRow('message:1', '2026-07-18T12:00:00Z'), emailRow('message:2', '2026-07-17T12:00:00Z')], + onRowOpen + }); + const grid = screen.getByRole('grid', { name: 'Relationship activity' }); + grid.focus(); + + await fireEvent.keyDown(grid, { key: 'j' }); + await fireEvent.keyDown(grid, { key: 'Enter' }); + expect(onRowOpen).toHaveBeenCalledWith(expect.objectContaining({ key: 'message:2' })); + }); + + it('opens a row by clicking it', async () => { + const onRowOpen = vi.fn(); + render(RelationshipTimeline, { + rows: [emailRow('message:1', '2026-07-18T12:00:00Z', 'Click target')], + onRowOpen + }); + + await fireEvent.pointerDown(screen.getByText('Click target').closest('[role="row"]')!); + expect(onRowOpen).toHaveBeenCalledWith(expect.objectContaining({ key: 'message:1' })); + }); + + it('requests more rows once scroll nears the loaded end, but never on End alone', async () => { + const onLoadMore = vi.fn(); + render(RelationshipTimeline, { + rows: [emailRow('message:1', '2026-07-18T12:00:00Z')], + hasMore: true, + onLoadMore, + onRowOpen: vi.fn() + }); + const grid = screen.getByRole('grid', { name: 'Relationship activity' }); + grid.focus(); + + await fireEvent.keyDown(grid, { key: 'End' }); + expect(onLoadMore).not.toHaveBeenCalled(); + + await fireEvent.scroll(grid); + await waitFor(() => expect(onLoadMore).toHaveBeenCalled()); + }); + + it('does not request more rows while a page is already loading', async () => { + const onLoadMore = vi.fn(); + render(RelationshipTimeline, { + rows: [emailRow('message:1', '2026-07-18T12:00:00Z')], + hasMore: true, + loadingMore: true, + onLoadMore, + onRowOpen: vi.fn() + }); + + await fireEvent.scroll(screen.getByRole('grid', { name: 'Relationship activity' })); + expect(onLoadMore).not.toHaveBeenCalled(); + expect(screen.getByText('Loading more…')).toBeDefined(); + }); + + it('shows the 409-restart notice as a persistent one-line status', () => { + render(RelationshipTimeline, { + rows: [emailRow('message:1', '2026-07-18T12:00:00Z')], + restartNotice: 'Timeline restarted: the archive changed.', + onRowOpen: vi.fn() + }); + + expect(screen.getByText('Timeline restarted: the archive changed.')).toBeDefined(); + }); + + it('shows an empty state when there is no activity', () => { + render(RelationshipTimeline, { rows: [], onRowOpen: vi.fn() }); + expect(screen.getByText('No activity yet.')).toBeDefined(); + }); +}); diff --git a/web/src/lib/components/relationships/RelationshipsWorkspace.svelte b/web/src/lib/components/relationships/RelationshipsWorkspace.svelte new file mode 100644 index 000000000..b540ac387 --- /dev/null +++ b/web/src/lib/components/relationships/RelationshipsWorkspace.svelte @@ -0,0 +1,521 @@ +<script module lang="ts"> + export type HubLayout = 'wide' | 'stacked' | 'narrow'; + + /** Pure container-width → layout classification, kept outside the + * component so it's unit-testable without a real ResizeObserver (jsdom + * doesn't fire one). Below 1100px the reading pane stacks over the + * timeline; below 720px the list additionally becomes a slide-in drawer. */ + export function computeHubLayout(width: number): HubLayout { + if (width < 720) return 'narrow'; + if (width < 1100) return 'stacked'; + return 'wide'; + } +</script> + +<script lang="ts"> + import { appShortcuts, Button, ROOT_SCOPE } from '@kenn-io/kit-ui'; + import { onDestroy, onMount, tick, untrack } from 'svelte'; + + import type { APIClient } from '../../api/client'; + import type { ExplorePredicate, FileMIMEFamily, FileSearchSort } from '../../explore/models'; + import type { RelationshipsController } from '../../relationships/controller.svelte'; + import type { RelationshipFacet, RelationshipTimelineRow } from '../../relationships/models'; + import { debounce } from '../../util/debounce'; + import FilesWorkspace from '../files/FilesWorkspace.svelte'; + import Inspector, { type InspectorSelection } from '../inspector/Inspector.svelte'; + import RelationshipHeader from './RelationshipHeader.svelte'; + import RelationshipList from './RelationshipList.svelte'; + import RelationshipTimeline from './RelationshipTimeline.svelte'; + import { localDayBoundsUTC, timelineRowToSelection } from './timeline-support'; + + const QUERY_DEBOUNCE_MS = 250; + + interface Props { + client: APIClient; + controller: RelationshipsController; + facet: RelationshipFacet; + target: string | null; + showAll: boolean; + filesOpen: boolean; + predicate: ExplorePredicate; + onFacetChange: (facet: RelationshipFacet) => void; + onTargetChange: (target: string | null) => void; + onShowAllChange: (value: boolean) => void; + onFilesToggle: (value: boolean) => void; + /** Degraded-state escape hatch: switches the parent workspace to + * Everything. Not part of the frozen Task 4 Props contract — AppShell + * (Task 6) wires it to its own workspace-change callback. */ + onOpenEverything?: () => void; + /** Opening a file (or its containing conversation) from the hub's own + * embedded Files pane has no reading pane of its own to resolve a full + * EntryRow into — AppShell wires these to the same openFileItem/ + * openFileConversation it uses for the Files sibling workspace, which + * navigate to Everything with the item selected. Omitted callbacks + * leave FileViewer's open buttons as a no-op, same as before this prop + * existed. */ + onOpenFileItem?: (entryKey: string) => void; + onOpenFileConversation?: (entryKey: string, messageID: number, conversationID: number) => void; + } + + let { + client, + controller, + facet, + target, + showAll, + filesOpen, + predicate, + onFacetChange, + onTargetChange, + onShowAllChange, + onFilesToggle, + onOpenEverything = undefined, + onOpenFileItem = undefined, + onOpenFileConversation = undefined + }: Props = $props(); + + let selection = $state<InspectorSelection | undefined>(); + let conversationAnchorId = $state<number | undefined>(); + let conversationBounds = $state<{ start: string; end: string } | undefined>(); + let inspectorWidth = $state(380); + let mobileListOpen = $state(false); + let fileSort = $state<FileSearchSort>({ field: 'occurred_at', direction: 'desc' }); + let fileFilenameQuery = $state(''); + let fileMIMEFamilies = $state<FileMIMEFamily[]>([]); + let rootElement = $state<HTMLElement>(); + let listPaneElement = $state<HTMLDivElement>(); + let containerWidth = $state(1200); + // Mirrors controller.query for immediate display: the debounce below only + // delays *writing* controller.query (and so the search fetch it drives), + // never the text shown in the search box, matching LinkIdentityDialog's + // "local state for display, debounced write for the network call" split. + let queryInput = $state(untrack(() => controller.query)); + + // A dedicated instance per component, never shared — LinkIdentityDialog's + // own debounced search is a separate instance for the same reason; sharing + // one across components previously caused a cross-component bug. + const debouncedSetQuery = debounce((value: string) => { controller.query = value; }, QUERY_DEBOUNCE_MS); + + // Flush, not cancel: the controller (owned by AppShell) outlives this + // component across a workspace round-trip, so a flushed write is not + // lost — it just sits on controller.query until the hub remounts and its + // own load effect below fires again on mount, refetching with the typed + // text intact. Cancelling here would silently drop what the user typed. + onDestroy(() => debouncedSetQuery.flush()); + + function handleQueryChange(value: string): void { + queryInput = value; + debouncedSetQuery(value); + } + + // Stable selectors into panes that never unmount when the reading pane or + // drawer closes, so focus always lands somewhere real instead of falling + // to <body> (mirrors AppShell's currentGrid()/document.querySelector + // pattern rather than needing bind:this on every target). + const TIMELINE_GRID_SELECTOR = '[role="grid"][aria-label="Relationship activity"]'; + const FILES_GRID_SELECTOR = '[role="grid"][aria-label="Files results"]'; + const LIST_GRID_SELECTOR = '[role="grid"][aria-label="Relationship results"]'; + // Excludes tabindex="-1" on every branch, not just the catch-all, so + // roving-tabindex widgets (e.g. the facet SegmentedControl) only + // contribute their one active segment as a stop. + const DRAWER_FOCUSABLE_SELECTOR = [ + 'a[href]:not([tabindex="-1"])', + 'button:not([disabled]):not([tabindex="-1"])', + 'input:not([disabled]):not([tabindex="-1"])', + 'select:not([disabled]):not([tabindex="-1"])', + 'textarea:not([disabled]):not([tabindex="-1"])', + '[tabindex]:not([tabindex="-1"])' + ].join(', '); + + const layout = $derived(computeHubLayout(containerWidth)); + const predicateFingerprint = $derived(JSON.stringify(predicate)); + const selectedRowKey = $derived(selection?.kind === 'entry' ? selection.row.key : null); + + // One effect ties facet/showAll/query/predicate to a single loadList call + // — mirrors PeopleWorkspace's search effect (the fingerprint sweep is + // Task 7's job across the whole app, not a new convention to invent here). + $effect(() => { + controller.facet = facet; + controller.showAll = showAll; + const query = controller.query; + void query; + void predicateFingerprint; + void controller.loadList(untrack(() => predicate)); + }); + + onMount(() => { + if (!rootElement) return; + containerWidth = rootElement.clientWidth || containerWidth; + if (typeof ResizeObserver === 'undefined') return; + const observer = new ResizeObserver((entries) => { + const measured = entries[0]?.contentRect.width; + if (measured !== undefined) containerWidth = measured; + }); + observer.observe(rootElement); + return () => observer.disconnect(); + }); + + function contextPredicate(value: ExplorePredicate): ExplorePredicate { + const { cursor: _cursor, grouping: _grouping, candidate_snapshot_id: _snapshot, ...context } = value; + return { ...context, presentation: 'table' }; + } + + function domainOf(value: string | null): string | undefined { + return value?.startsWith('domain:') ? value.slice('domain:'.length) : undefined; + } + + function identityScopeFor(value: string | null): { kind: 'person'; id: number } | { kind: 'domain'; domain: string } | undefined { + const domain = domainOf(value); + if (domain) return { kind: 'domain', domain }; + return controller.canonicalID !== null ? { kind: 'person', id: controller.canonicalID } : undefined; + } + + // Domain targets resolve identityScopeFor synchronously (the domain name + // comes straight from the target string), but a cluster target only knows + // its canonicalID once the timeline response for openTarget lands. Until + // then, identityScopeFor(target) is undefined — which FilesWorkspace reads + // as "no identity scope, search the whole archive" (its correct meaning + // for Everything/Files). Mounting FilesWorkspace in that gap would fire an + // unscoped whole-archive search and flash the wrong rows, so the hub keeps + // showing the timeline's own loading state until the scope resolves. A + // null target (Esc/Back cleared it) always counts as files-closed too. + // The controller.target !== target check covers a fast target switch: the + // `target` prop can update to the new cluster before controller.openTarget + // for it has run its synchronous reset, leaving controller.canonicalID + // briefly holding the PREVIOUS cluster's id — without this check that + // stale id would flow straight into identityScopeFor and FilesWorkspace + // would mount scoped to the wrong person. + const clusterScopePending = $derived( + target !== null && domainOf(target) === undefined && + (controller.target !== target || controller.canonicalID === null) + ); + const filesReady = $derived(target !== null && !clusterScopePending); + + function focusPane(selector: string): boolean { + const element = rootElement?.querySelector<HTMLElement>(selector); + if (!element) return false; + element.focus(); + return true; + } + + // The timeline/files grid never unmounts when the reading pane closes, so + // this always has somewhere real to land focus — falls back to the files + // grid when the center pane is showing FilesWorkspace instead. + async function focusTimelinePane(): Promise<void> { + await tick(); + if (!focusPane(TIMELINE_GRID_SELECTOR)) focusPane(FILES_GRID_SELECTOR); + } + + // In narrow layout with the drawer closed, the list grid is inert (see + // .pane-list's `inert` binding below) and cannot actually receive focus — + // the browser silently refuses .focus() on it. Land on the drawer toggle + // instead, the one focusable stand-in for "the list" in that state. + async function focusListPane(): Promise<void> { + await tick(); + if (layout === 'narrow' && !mobileListOpen) { + rootElement?.querySelector<HTMLButtonElement>('.drawer-toggle')?.focus(); + return; + } + focusPane(LIST_GRID_SELECTOR); + } + + // Closing the reading pane leaves the hub's own keydown handler as the + // next-highest focus target (AppShell.closeInspector follows the same + // shape): only re-focus the timeline when a pane was actually open, so + // this stays a no-op when called defensively (e.g. from selectListRow). + async function closeReadingPane(): Promise<void> { + const wasOpen = selection !== undefined; + selection = undefined; + conversationAnchorId = undefined; + conversationBounds = undefined; + if (wasOpen) await focusTimelinePane(); + } + + function selectListRow(nextTarget: string): void { + void closeReadingPane(); + const wasDrawerOpen = mobileListOpen; + mobileListOpen = false; + onTargetChange(nextTarget); + void controller.openTarget(nextTarget, predicate); + // Closing the drawer makes it inert; if focus was inside it (e.g. the + // search input auto-focused on open), the browser blurs it to <body>. + // Re-home focus on the timeline so Esc-chaining keeps working. + if (wasDrawerOpen) void focusTimelinePane(); + } + + // Esc closes the reading pane before it ever clears `target` (see + // handleEscape below), so an in-component walk-back never reaches here + // with a pane still open. An EXTERNAL clear — browser Back/Forward, which + // drives `target` straight to null via the URL without going through + // handleEscape at all — skips that ordering, so without this the reading + // pane could keep showing a message from the cluster/domain that just + // closed underneath it. Plain state reset only, no focus side effect: + // AppShell owns focus restoration for history navigation already. + $effect(() => { + if (target !== null) return; + if (selection === undefined && conversationAnchorId === undefined && conversationBounds === undefined) return; + selection = undefined; + conversationAnchorId = undefined; + conversationBounds = undefined; + }); + + function drawerFocusables(): HTMLElement[] { + return listPaneElement ? Array.from(listPaneElement.querySelectorAll<HTMLElement>(DRAWER_FOCUSABLE_SELECTOR)) : []; + } + + function closeDrawer(): void { + mobileListOpen = false; + rootElement?.querySelector<HTMLButtonElement>('.drawer-toggle')?.focus(); + } + + function handleDrawerKeydown(event: KeyboardEvent): void { + if (event.key === 'Escape') { + event.preventDefault(); + event.stopPropagation(); + closeDrawer(); + return; + } + if (event.key !== 'Tab') return; + const focusables = drawerFocusables(); + if (focusables.length === 0) return; + const first = focusables[0]!; + const last = focusables[focusables.length - 1]!; + if (event.shiftKey && document.activeElement === first) { + event.preventDefault(); + last.focus(); + } else if (!event.shiftKey && document.activeElement === last) { + event.preventDefault(); + first.focus(); + } + } + + // Moves focus into the drawer's first focusable (the search input) the + // moment it opens; the corresponding trap/Esc-close lives in + // handleDrawerKeydown above. + $effect(() => { + if (layout === 'narrow' && mobileListOpen) { + void tick().then(() => drawerFocusables()[0]?.focus()); + } + }); + + // chat_burst rows open the conversation directly, bounded to the burst's + // local day; other rows show the entry summary (its own "View conversation" + // opens the full, unbounded window via viewFullConversation below). + function openTimelineRow(row: RelationshipTimelineRow): void { + selection = timelineRowToSelection(row); + if (row.kind === 'chat_burst' && row.conversation_id !== undefined && row.anchor_message_id !== undefined) { + conversationAnchorId = row.anchor_message_id; + conversationBounds = localDayBoundsUTC(row.first_at ?? row.occurred_at); + } else { + conversationAnchorId = undefined; + conversationBounds = undefined; + } + } + + function viewFullConversation(_conversationId: number, anchorId: number): void { + conversationAnchorId = anchorId; + conversationBounds = undefined; + } + + function backFromConversation(): void { + conversationAnchorId = undefined; + conversationBounds = undefined; + } + + function editableTarget(value: EventTarget | null): boolean { + const element = value as HTMLElement | null; + return Boolean(element?.closest('input, textarea, select, [contenteditable]:not([contenteditable="false"])')); + } + + // Esc walks back exactly one layer: reading pane → timeline → list → + // nothing (bubbles further for the parent, AppShell in Task 6, to handle). + // Bails out while a scope is pushed (e.g. LinkIdentityDialog, rendered + // inside this same <main> and so a DOM descendant of it) so this raw + // keydown handler doesn't stopPropagation the Escape before it reaches + // the dialog's own Modal, which closes itself via a window-level + // listener further up the bubble chain. + function handleEscape(event: KeyboardEvent): void { + if (event.key !== 'Escape' || editableTarget(event.target)) return; + if (appShortcuts.activeScope() !== ROOT_SCOPE) return; + if (selection !== undefined) { + void closeReadingPane(); + event.preventDefault(); + event.stopPropagation(); + return; + } + if (target !== null) { + onTargetChange(null); + event.preventDefault(); + event.stopPropagation(); + void focusListPane(); + } + } +</script> + +<!-- svelte-ignore a11y_no_noninteractive_element_interactions -- landmark container scoping the hub's own Esc-layering; not a control itself. --> +<main + class="relationships-hub" + aria-label="Relationships" + class:layout-stacked={layout !== 'wide'} + class:layout-narrow={layout === 'narrow'} + bind:this={rootElement} + onkeydown={handleEscape} +> + <h1 class="kit-sr-only">Relationships</h1> + {#if layout === 'narrow'} + <Button + class="drawer-toggle" + label="Contacts" + ariaLabel="Show relationship list" + ariaExpanded={mobileListOpen} + onclick={() => (mobileListOpen = !mobileListOpen)} + /> + {/if} + <!-- svelte-ignore a11y_no_static_element_interactions -- drawer-mode focus trap/inert host; not a control itself. --> + <div + class="pane-list" + class:drawer={layout === 'narrow'} + class:drawer-open={layout === 'narrow' && mobileListOpen} + inert={layout === 'narrow' && !mobileListOpen} + bind:this={listPaneElement} + onkeydown={layout === 'narrow' ? handleDrawerKeydown : undefined} + > + <RelationshipList + rows={controller.listRows} + loading={controller.listLoading} + error={controller.listError} + degraded={controller.degraded} + {facet} + query={queryInput} + {showAll} + activeTarget={target} + onQueryChange={handleQueryChange} + {onFacetChange} + {onShowAllChange} + onSelect={selectListRow} + {onOpenEverything} + /> + </div> + + <div class="pane-center-and-reading" class:stacked={layout !== 'wide'}> + <div class="pane-center"> + <RelationshipHeader + detail={controller.detail} + loading={controller.timelineLoading} + {filesOpen} + {onFilesToggle} + {client} + onLinkParticipants={(a, b) => controller.linkParticipants(a, b)} + onUnlinkParticipants={(a, b) => controller.unlinkParticipants(a, b)} + /> + {#if filesOpen && filesReady} + <FilesWorkspace + {client} + predicate={contextPredicate(predicate)} + identityScope={identityScopeFor(target)} + sort={fileSort} + filenameQuery={fileFilenameQuery} + mimeFamilies={fileMIMEFamilies} + onSortChange={(value) => (fileSort = value)} + onFilenameQueryChange={(value) => (fileFilenameQuery = value)} + onMIMEFamiliesChange={(value) => (fileMIMEFamilies = value)} + onOpenItem={onOpenFileItem} + onOpenConversation={onOpenFileConversation} + /> + {:else} + <RelationshipTimeline + rows={controller.timelineRows} + loading={controller.timelineLoading} + loadingMore={controller.timelineLoadingMore} + hasMore={Boolean(controller.timelineCursor)} + error={controller.timelineError} + restartNotice={controller.timelineRestartNotice} + selectedKey={selectedRowKey} + onRowOpen={openTimelineRow} + onLoadMore={() => { void controller.loadMoreTimeline(); }} + /> + {/if} + </div> + {#if selection !== undefined} + <div class="pane-reading"> + <Inspector + {client} + {selection} + predicate={contextPredicate(predicate)} + width={inspectorWidth} + onClose={() => { void closeReadingPane(); }} + onWidthChange={(width) => (inspectorWidth = width)} + {conversationAnchorId} + conversationStart={conversationBounds?.start} + conversationEnd={conversationBounds?.end} + onViewConversation={viewFullConversation} + onBackConversation={backFromConversation} + onConversationAnchorChange={(anchorId) => (conversationAnchorId = anchorId)} + /> + </div> + {/if} + </div> +</main> + +<style> + .relationships-hub { + position: relative; + display: flex; + min-height: 0; + height: 100%; + gap: var(--space-4); + } + + .pane-list { + display: flex; + flex: 0 0 280px; + flex-direction: column; + overflow: hidden; + } + + .pane-list.drawer { + position: absolute; + z-index: var(--z-popover, 20); + inset: 0 auto 0 0; + width: 280px; + transform: translateX(-100%); + background: var(--bg-surface); + border-right: 1px solid var(--border-default); + box-shadow: var(--shadow-md); + transition: transform var(--transition-fast); + } + + .pane-list.drawer.drawer-open { + transform: translateX(0); + } + + .pane-center-and-reading { + display: flex; + min-width: 0; + flex: 1; + gap: var(--space-4); + overflow: hidden; + } + + .pane-center-and-reading.stacked { + flex-direction: column; + } + + .pane-center { + display: flex; + min-width: 0; + flex: 1; + flex-direction: column; + gap: var(--space-3); + overflow: hidden; + } + + .pane-reading { + flex: none; + } + + .pane-center-and-reading.stacked .pane-reading { + flex: 1; + min-height: 0; + } +</style> diff --git a/web/src/lib/components/relationships/RelationshipsWorkspace.test.ts b/web/src/lib/components/relationships/RelationshipsWorkspace.test.ts new file mode 100644 index 000000000..5e7e9f525 --- /dev/null +++ b/web/src/lib/components/relationships/RelationshipsWorkspace.test.ts @@ -0,0 +1,548 @@ +import { fireEvent, render, screen, waitFor, within } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import { RelationshipsController } from '../../relationships/controller.svelte'; +import { computeHubLayout } from './RelationshipsWorkspace.svelte'; +import RelationshipsWorkspace from './RelationshipsWorkspace.svelte'; + +const when = '2026-07-19T10:00:00Z'; + +function person(id: number, label: string) { + return { + id, display_label: label, partial_label: false, identifiers: [], + activity_count: 4, file_count: 2, source_counts: [], first_at: when, last_at: when, cache_revision: 'cache-rel' + }; +} + +function pathOf(request: Request): string { + return new URL(request.url, document.baseURI).pathname; +} + +/** Forces computeHubLayout into 'narrow' by making the hub root report a + * sub-720px clientWidth on mount — jsdom's own layout engine always reports + * 0, and the TestResizeObserver in src/test/setup.ts never fires, so this is + * the only way to reach drawer mode from a render test. Restore in the same + * test via the returned cleanup. + * + * jsdom defines `clientWidth` on Element.prototype, not HTMLElement.prototype + * (verified: `getOwnPropertyDescriptor(HTMLElement.prototype, 'clientWidth')` + * is `undefined`), so `original` here is always `undefined` — the override + * always creates a new own property on HTMLElement.prototype that shadows + * the inherited one. The restore must `delete` that own property in that + * case; a bare `if (original)` guard never fires and leaks the 400 override + * into every later test in this file. */ +function forceNarrowContainer(): () => void { + const proto = window.HTMLElement.prototype; + const original = Object.getOwnPropertyDescriptor(proto, 'clientWidth'); + Object.defineProperty(proto, 'clientWidth', { configurable: true, get: () => 400 }); + return () => { + if (original) Object.defineProperty(proto, 'clientWidth', original); + else delete (proto as { clientWidth?: unknown }).clientWidth; + }; +} + +function baseProps(fetchFn: typeof fetch) { + return { + client: createAPIClient(fetchFn), + controller: new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'), + facet: 'people' as const, + target: null, + showAll: false, + filesOpen: false, + predicate: { filters: [], presentation: 'table' as const }, + onFacetChange: vi.fn(), + onTargetChange: vi.fn(), + onShowAllChange: vi.fn(), + onFilesToggle: vi.fn() + }; +} + +function fetchHandler(overrides: Record<string, (request: Request) => Promise<Response> | Response> = {}) { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const handler = overrides[pathOf(request)]; + if (handler) return handler(request); + if (pathOf(request) === '/api/v1/relationships') { + return Response.json({ + rows: [{ + canonical_id: 1, display_label: 'Alice Example', last_at: when, member_ids: [1], score: 2, + signals: { last_interaction_at: when, meeting_count: 0, meetings_together: 0, modalities: 2, received_from_them: 1, sent_count: 3, sent_to_them: 1 } + }] + }); + } + throw new Error(`unexpected path ${pathOf(request)}`); + }); + return { fetchFn, requests }; +} + +describe('RelationshipsWorkspace', () => { + it('renders the ranked list and the empty-state header with no reading pane open', async () => { + const { fetchFn } = fetchHandler(); + render(RelationshipsWorkspace, { props: baseProps(fetchFn) }); + + expect(await screen.findByText('Alice Example')).toBeDefined(); + expect(screen.getByText('Select a person or domain to see activity.')).toBeDefined(); + expect(screen.queryByRole('complementary', { name: /Inspect/ })).toBeNull(); + }); + + it('selecting a list row calls onTargetChange and opens it through the controller', async () => { + const { fetchFn, requests } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), + '/api/v1/relationships/1/timeline': async () => Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', + rows: [{ key: 'message:1', kind: 'email', occurred_at: when, preview: 'Preview', source_id: 1, title: 'Subject', has_attachments: false, message_count: 1 }], + total_count: 1 + }) + }); + const props = baseProps(fetchFn); + render(RelationshipsWorkspace, { props }); + + await fireEvent.pointerDown((await screen.findByText('Alice Example')).closest('[role="row"]')!); + expect(props.onTargetChange).toHaveBeenCalledWith('cluster:1'); + + await waitFor(() => expect(requests.some((request) => pathOf(request) === '/api/v1/people/1')).toBe(true)); + expect(await screen.findByRole('heading', { name: 'Alice Example' })).toBeDefined(); + expect(await screen.findByText('Subject')).toBeDefined(); + }); + + it('selecting a timeline row shows the Inspector reading pane', async () => { + const { fetchFn } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), + '/api/v1/relationships/1/timeline': async () => Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', + rows: [{ key: 'message:1', kind: 'email', occurred_at: when, preview: 'Preview text', source_id: 1, title: 'Subject line', has_attachments: false, message_count: 1, anchor_message_id: 9, conversation_id: 70 }], + total_count: 1 + }) + }); + const props = { ...baseProps(fetchFn), target: 'cluster:1' }; + render(RelationshipsWorkspace, { props }); + await props.controller.openTarget('cluster:1', props.predicate); + + await fireEvent.pointerDown((await screen.findByText('Subject line')).closest('[role="row"]')!); + const reading = await screen.findByRole('complementary', { name: /Inspect/ }); + expect(within(reading).getByText('Preview text')).toBeDefined(); + }); + + it('opens a chat_burst directly into the conversation window bounded to the local day', async () => { + const requestedConversations: Request[] = []; + const { fetchFn } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), + '/api/v1/relationships/1/timeline': async () => Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', + rows: [{ + key: 'burst:2:70:2026-07-18', kind: 'chat_burst', occurred_at: '2026-07-18T20:00:00Z', + first_at: '2026-07-18T08:00:00Z', preview: 'Latest', source_id: 2, title: 'Team Chat', + has_attachments: false, message_count: 6, anchor_message_id: 500, conversation_id: 70 + }], + total_count: 1 + }), + '/api/v1/conversations/70': async (request) => { + requestedConversations.push(request); + return Response.json({ id: 70, anchor_id: 500, messages: [], has_before: false, has_after: false, total: 0 }); + } + }); + const props = { ...baseProps(fetchFn), target: 'cluster:1' }; + render(RelationshipsWorkspace, { props }); + await props.controller.openTarget('cluster:1', props.predicate); + + await fireEvent.pointerDown((await screen.findByText('6 messages in Team Chat')).closest('[role="row"]')!); + await waitFor(() => expect(requestedConversations).toHaveLength(1)); + const url = new URL(requestedConversations[0]!.url); + expect(url.searchParams.get('anchor')).toBe('500'); + const start = new Date(url.searchParams.get('start')!); + const end = new Date(url.searchParams.get('end')!); + expect(start.getHours()).toBe(0); + expect(end.getTime() - start.getTime()).toBe(24 * 60 * 60 * 1000); + expect(new Date('2026-07-18T08:00:00Z').getTime()).toBeGreaterThanOrEqual(start.getTime()); + expect(new Date('2026-07-18T08:00:00Z').getTime()).toBeLessThan(end.getTime()); + }); + + it('swaps the center pane to FilesWorkspace when filesOpen is true', async () => { + const { fetchFn } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), + '/api/v1/relationships/1/timeline': async () => Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', rows: [], total_count: 0 + }), + '/api/v1/people/1/files/search': async () => Response.json({ + files: [], total_count: 0, cache_revision: 'cache-rel', search_provenance: {} + }) + }); + const props = { ...baseProps(fetchFn), target: 'cluster:1', filesOpen: true }; + render(RelationshipsWorkspace, { props }); + await props.controller.openTarget('cluster:1', props.predicate); + + expect(await screen.findByRole('grid', { name: 'Files results' })).toBeDefined(); + expect(screen.queryByRole('grid', { name: 'Relationship activity' })).toBeNull(); + }); + + it('debounces identity search typing into one /api/v1/people/search fetch', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + try { + const searchRequests: Request[] = []; + const { fetchFn } = fetchHandler({ + '/api/v1/people/search': async (request) => { + searchRequests.push(request); + return Response.json({ rows: [], total_count: 0, cache_revision: 'cache-rel', search_provenance: {} }); + } + }); + const props = baseProps(fetchFn); + render(RelationshipsWorkspace, { props }); + await screen.findByText('Alice Example'); + + const search = screen.getByRole('searchbox', { name: 'Search people and domains' }); + await fireEvent.input(search, { target: { value: 'a' } }); + await fireEvent.input(search, { target: { value: 'al' } }); + await fireEvent.input(search, { target: { value: 'ali' } }); + + // The typed text shows immediately even though the search fetch it + // drives is still debounced. + expect((search as HTMLInputElement).value).toBe('ali'); + expect(searchRequests).toHaveLength(0); + + await vi.advanceTimersByTimeAsync(250); + + expect(searchRequests).toHaveLength(1); + await expect(searchRequests[0]!.clone().json()).resolves.toMatchObject({ identity_query: 'ali' }); + } finally { + vi.useRealTimers(); + } + }); + + it('flushes a pending debounced query write on unmount instead of dropping it', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + try { + const searchRequests: Request[] = []; + const { fetchFn } = fetchHandler({ + '/api/v1/people/search': async (request) => { + searchRequests.push(request); + return Response.json({ rows: [], total_count: 0, cache_revision: 'cache-rel', search_provenance: {} }); + } + }); + const props = baseProps(fetchFn); + const { unmount } = render(RelationshipsWorkspace, { props }); + await screen.findByText('Alice Example'); + + const search = screen.getByRole('searchbox', { name: 'Search people and domains' }); + await fireEvent.input(search, { target: { value: 'ali' } }); + expect(props.controller.query, 'not flushed yet').toBe(''); + + unmount(); + expect(props.controller.query, 'unmounting must flush, not drop, the pending write').toBe('ali'); + + // The controller is owned by AppShell and outlives the round-trip: + // remounting re-reads controller.query fresh and refetches with it. + render(RelationshipsWorkspace, { props }); + await waitFor(() => expect(searchRequests).toHaveLength(1)); + await expect(searchRequests[0]!.clone().json()).resolves.toMatchObject({ identity_query: 'ali' }); + } finally { + vi.useRealTimers(); + } + }); + + it('clears the reading pane when the target is cleared externally, not through this component\'s own Esc handler', async () => { + const { fetchFn } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), + '/api/v1/relationships/1/timeline': async () => Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', + rows: [{ key: 'message:1', kind: 'email', occurred_at: when, preview: 'Preview', source_id: 1, title: 'Subject', has_attachments: false, message_count: 1 }], + total_count: 1 + }) + }); + const props = { ...baseProps(fetchFn), target: 'cluster:1' }; + const { rerender } = render(RelationshipsWorkspace, { props }); + await props.controller.openTarget('cluster:1', props.predicate); + + await fireEvent.pointerDown((await screen.findByText('Subject')).closest('[role="row"]')!); + await screen.findByRole('complementary', { name: /Inspect/ }); + + // AppShell's own hydration effect clears the target directly on a + // Back/Forward popstate — never through this component's handleEscape. + await rerender({ ...props, target: null }); + + expect(screen.queryByRole('complementary', { name: /Inspect/ })).toBeNull(); + }); + + it('does not scope the embedded FilesWorkspace to the previous cluster while a fast target switch is still resolving', async () => { + const { fetchFn } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), + '/api/v1/relationships/1/timeline': async () => Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', rows: [], total_count: 0 + }), + '/api/v1/people/1/files/search': async () => Response.json({ + files: [], total_count: 0, cache_revision: 'cache-rel', search_provenance: {} + }) + }); + const props = { ...baseProps(fetchFn), target: 'cluster:1', filesOpen: true }; + const { rerender } = render(RelationshipsWorkspace, { props }); + await props.controller.openTarget('cluster:1', props.predicate); + expect(await screen.findByRole('grid', { name: 'Files results' })).toBeDefined(); + + // Simulate AppShell's own timing: the `target` prop (driven by the URL) + // advances to the new cluster before the controller's own openTarget for + // it has run — controller.canonicalID still holds cluster 1's id here. + await rerender({ ...props, target: 'cluster:2' }); + + expect(props.controller.target).toBe('cluster:1'); + expect(props.controller.canonicalID).toBe(1); + expect(screen.queryByRole('grid', { name: 'Files results' })).toBeNull(); + }); + + it('wires the embedded FilesWorkspace onOpenItem/onOpenConversation callbacks when provided', async () => { + const fileRow = { + id: 9, key: 'file:9', entry_key: 'message:42', message_id: 42, conversation_id: 70, + occurred_at: when, source_id: 1, source_type: 'gmail', source_identifier: 'alice@example.com', + containing_title: 'Subject', filename: 'report.pdf', mime_type: 'application/pdf', mime_family: 'pdf', + size_bytes: 100, content_state: 'local_content', content_available: true + }; + const { fetchFn } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), + '/api/v1/relationships/1/timeline': async () => Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', rows: [], total_count: 0 + }), + '/api/v1/people/1/files/search': async () => Response.json({ + files: [fileRow], total_count: 1, cache_revision: 'cache-rel', search_provenance: {} + }), + '/api/v1/files/9': async () => Response.json({ + id: 9, filename: 'report.pdf', mime_type: 'application/pdf', content_state: 'local_content', + message_id: 42, conversation_id: 70 + }) + }); + const onOpenFileItem = vi.fn(); + const onOpenFileConversation = vi.fn(); + const props = { ...baseProps(fetchFn), target: 'cluster:1', filesOpen: true, onOpenFileItem, onOpenFileConversation }; + render(RelationshipsWorkspace, { props }); + await props.controller.openTarget('cluster:1', props.predicate); + + const row = (await screen.findByText('report.pdf')).closest('[role="row"]')!; + await fireEvent.dblClick(row); + + await fireEvent.click(await screen.findByRole('button', { name: 'Open containing item' })); + expect(onOpenFileItem).toHaveBeenCalledWith('message:42'); + + await fireEvent.click(await screen.findByRole('button', { name: 'Open containing conversation' })); + expect(onOpenFileConversation).toHaveBeenCalledWith('message:42', 42, 70); + }); + + it('does not act on Esc while another scope (e.g. the Link identity dialog) is active, letting the Modal handle it', async () => { + const { fetchFn } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')) + }); + const props = { ...baseProps(fetchFn), target: 'cluster:1' }; + render(RelationshipsWorkspace, { props }); + await props.controller.openTarget('cluster:1', props.predicate); + await screen.findByRole('heading', { name: 'Alice Example' }); + + await fireEvent.click(screen.getByRole('button', { name: 'Link identity' })); + expect(screen.getByRole('dialog', { name: 'Link identity' })).toBeDefined(); + + await fireEvent.keyDown(screen.getByRole('grid', { name: 'Relationship activity' }), { key: 'Escape' }); + + // The hub's own Esc handling must not have fired: the target is still + // open. Escape instead bubbles untouched to the Modal's own window-level + // listener, which closes the dialog. + expect(props.onTargetChange).not.toHaveBeenCalled(); + await waitFor(() => expect(screen.queryByRole('dialog', { name: 'Link identity' })).toBeNull()); + }); + + it('calls the domain files endpoint, not the person one, when the open target is a domain', async () => { + const filesRequests: Request[] = []; + const { fetchFn } = fetchHandler({ + '/api/v1/domains/example.com': async () => Response.json({ + domain: 'example.com', activity_count: 3, file_count: 1, person_count: 2, + first_at: when, last_at: when, source_counts: [], cache_revision: 'cache-rel' + }), + '/api/v1/domains/example.com/timeline': async () => Response.json({ rows: [], total_count: 0 }), + '/api/v1/domains/example.com/files/search': async (request) => { + filesRequests.push(request); + return Response.json({ files: [], total_count: 0, cache_revision: 'cache-rel', search_provenance: {} }); + } + }); + const props = { ...baseProps(fetchFn), facet: 'domains' as const, target: 'domain:example.com', filesOpen: true }; + render(RelationshipsWorkspace, { props }); + await props.controller.openTarget('domain:example.com', props.predicate); + + expect(await screen.findByRole('grid', { name: 'Files results' })).toBeDefined(); + expect(filesRequests).toHaveLength(1); + }); + + it('does not mount FilesWorkspace for a cluster target until its scope resolves, then issues exactly one scoped fetch', async () => { + let resolveTimeline: ((response: Response) => void) | undefined; + const filesRequests: Request[] = []; + const { fetchFn } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), + '/api/v1/relationships/1/timeline': async () => new Promise<Response>((resolve) => { resolveTimeline = resolve; }), + '/api/v1/people/1/files/search': async (request) => { + filesRequests.push(request); + return Response.json({ files: [], total_count: 0, cache_revision: 'cache-rel', search_provenance: {} }); + } + }); + const props = { ...baseProps(fetchFn), target: 'cluster:1', filesOpen: true }; + render(RelationshipsWorkspace, { props }); + const openPromise = props.controller.openTarget('cluster:1', props.predicate); + + await screen.findByText('Loading activity…'); + expect(screen.queryByRole('grid', { name: 'Files results' })).toBeNull(); + expect(filesRequests).toHaveLength(0); + + resolveTimeline?.(Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', rows: [], total_count: 0 + })); + await openPromise; + + expect(await screen.findByRole('grid', { name: 'Files results' })).toBeDefined(); + await waitFor(() => expect(filesRequests).toHaveLength(1)); + }); + + it('treats files as closed when the target is null, even if relationshipFiles is still true', async () => { + const { fetchFn } = fetchHandler(); + const props = { ...baseProps(fetchFn), target: null, filesOpen: true }; + render(RelationshipsWorkspace, { props }); + + await screen.findByText('Alice Example'); + expect(screen.queryByRole('grid', { name: 'Files results' })).toBeNull(); + expect(screen.getByText('Select a person or domain to see activity.')).toBeDefined(); + }); + + it('walks Esc back one layer at a time: reading pane, then the open target', async () => { + const { fetchFn } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), + '/api/v1/relationships/1/timeline': async () => Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', + rows: [{ key: 'message:1', kind: 'email', occurred_at: when, preview: 'Preview', source_id: 1, title: 'Subject', has_attachments: false, message_count: 1 }], + total_count: 1 + }) + }); + const props = { ...baseProps(fetchFn), target: 'cluster:1' }; + render(RelationshipsWorkspace, { props }); + await props.controller.openTarget('cluster:1', props.predicate); + + await fireEvent.pointerDown((await screen.findByText('Subject')).closest('[role="row"]')!); + const reading = await screen.findByRole('complementary', { name: /Inspect/ }); + + await fireEvent.keyDown(reading, { key: 'Escape' }); + await waitFor(() => expect(screen.queryByRole('complementary', { name: /Inspect/ })).toBeNull()); + + await fireEvent.keyDown(screen.getByRole('grid', { name: 'Relationship activity' }), { key: 'Escape' }); + expect(props.onTargetChange).toHaveBeenCalledWith(null); + }); + + it('moves focus to the timeline, then the list, as Esc walks back each layer', async () => { + const { fetchFn } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), + '/api/v1/relationships/1/timeline': async () => Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', + rows: [{ key: 'message:1', kind: 'email', occurred_at: when, preview: 'Preview', source_id: 1, title: 'Subject', has_attachments: false, message_count: 1 }], + total_count: 1 + }) + }); + const props = { ...baseProps(fetchFn), target: 'cluster:1' }; + render(RelationshipsWorkspace, { props }); + await props.controller.openTarget('cluster:1', props.predicate); + + await fireEvent.pointerDown((await screen.findByText('Subject')).closest('[role="row"]')!); + const reading = await screen.findByRole('complementary', { name: /Inspect/ }); + + await fireEvent.keyDown(reading, { key: 'Escape' }); + const timelineGrid = screen.getByRole('grid', { name: 'Relationship activity' }); + await waitFor(() => expect(document.activeElement).toBe(timelineGrid)); + + await fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); + expect(props.onTargetChange).toHaveBeenCalledWith(null); + const listGrid = screen.getByRole('grid', { name: 'Relationship results' }); + await waitFor(() => expect(document.activeElement).toBe(listGrid)); + }); +}); + +describe('RelationshipsWorkspace drawer (narrow layout)', () => { + it('is inert while closed; opening focuses the search input and traps Tab; Esc returns focus to the toggle', async () => { + const restoreContainer = forceNarrowContainer(); + try { + const { fetchFn } = fetchHandler(); + render(RelationshipsWorkspace, { props: baseProps(fetchFn) }); + await screen.findByText('Alice Example'); + + const listPane = document.querySelector<HTMLElement & { inert: boolean }>('.pane-list')!; + expect(listPane.inert).toBe(true); + + const toggle = screen.getByRole('button', { name: 'Show relationship list' }); + await fireEvent.click(toggle); + expect(listPane.inert).toBe(false); + + const searchInput = screen.getByRole('searchbox', { name: 'Search people and domains' }); + await waitFor(() => expect(document.activeElement).toBe(searchInput)); + + const resultsGrid = screen.getByRole('grid', { name: 'Relationship results' }); + resultsGrid.focus(); + await fireEvent.keyDown(resultsGrid, { key: 'Tab' }); + expect(document.activeElement).toBe(searchInput); + + await fireEvent.keyDown(searchInput, { key: 'Tab', shiftKey: true }); + expect(document.activeElement).toBe(resultsGrid); + + await fireEvent.keyDown(document.activeElement!, { key: 'Escape' }); + expect(listPane.inert).toBe(true); + expect(document.activeElement).toBe(toggle); + } finally { + restoreContainer(); + } + }); + + it('Esc clearing the open target focuses the drawer toggle, not the still-inert list grid', async () => { + const restoreContainer = forceNarrowContainer(); + try { + const { fetchFn } = fetchHandler({ + '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), + '/api/v1/relationships/1/timeline': async () => Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', rows: [], total_count: 0 + }) + }); + const props = { ...baseProps(fetchFn), target: 'cluster:1' }; + render(RelationshipsWorkspace, { props }); + await props.controller.openTarget('cluster:1', props.predicate); + await screen.findByRole('heading', { name: 'Alice Example' }); + + // The drawer starts closed (selecting a row closes it), so the list + // pane is inert; without the fix, Esc would call .focus() on it and + // land nowhere real. + const listPane = document.querySelector<HTMLElement & { inert: boolean }>('.pane-list')!; + expect(listPane.inert).toBe(true); + + await fireEvent.keyDown(screen.getByRole('grid', { name: 'Relationship activity' }), { key: 'Escape' }); + expect(props.onTargetChange).toHaveBeenCalledWith(null); + + const toggle = screen.getByRole('button', { name: 'Show relationship list' }); + await waitFor(() => expect(document.activeElement).toBe(toggle)); + } finally { + restoreContainer(); + } + }); +}); + +describe('forceNarrowContainer test helper', () => { + it('fully restores clientWidth on cleanup instead of leaking the 400px override', () => { + const probe = document.createElement('div'); + document.body.appendChild(probe); + const baseline = probe.clientWidth; + + const restore = forceNarrowContainer(); + expect(probe.clientWidth).toBe(400); + restore(); + + expect(probe.clientWidth).toBe(baseline); + probe.remove(); + }); +}); + +describe('computeHubLayout', () => { + it('classifies wide, stacked, and narrow by container width', () => { + expect(computeHubLayout(1300)).toBe('wide'); + expect(computeHubLayout(1100)).toBe('wide'); + expect(computeHubLayout(1099)).toBe('stacked'); + expect(computeHubLayout(720)).toBe('stacked'); + expect(computeHubLayout(719)).toBe('narrow'); + expect(computeHubLayout(400)).toBe('narrow'); + }); +}); diff --git a/web/src/lib/components/relationships/timeline-support.test.ts b/web/src/lib/components/relationships/timeline-support.test.ts new file mode 100644 index 000000000..9485f5383 --- /dev/null +++ b/web/src/lib/components/relationships/timeline-support.test.ts @@ -0,0 +1,119 @@ +import { describe, expect, it } from 'vitest'; + +import type { RelationshipTimelineRow } from '../../relationships/models'; +import { + localDayBoundsUTC, + monthGroupKey, + monthGroupLabel, + timelineRowTitle, + timelineRowToSelection +} from './timeline-support'; + +function emailRow(overrides: Partial<RelationshipTimelineRow> = {}): RelationshipTimelineRow { + return { + key: 'message:1', + kind: 'email', + occurred_at: '2026-07-18T12:00:00Z', + preview: 'Preview text', + source_id: 4, + title: 'Subject line', + has_attachments: false, + message_count: 1, + anchor_message_id: 91, + conversation_id: 71, + ...overrides + }; +} + +function burstRow(overrides: Partial<RelationshipTimelineRow> = {}): RelationshipTimelineRow { + return { + key: 'burst:2:71:2026-07-18', + kind: 'chat_burst', + occurred_at: '2026-07-18T20:00:00Z', + first_at: '2026-07-18T08:00:00Z', + preview: 'Latest chat snippet', + source_id: 2, + title: 'Team Chat', + has_attachments: true, + message_count: 14, + anchor_message_id: 500, + conversation_id: 71, + ...overrides + }; +} + +describe('timelineRowTitle', () => { + it('renders single-message rows with their own title', () => { + expect(timelineRowTitle(emailRow())).toBe('Subject line'); + }); + + it('renders chat bursts as "N messages in <title>"', () => { + expect(timelineRowTitle(burstRow())).toBe('14 messages in Team Chat'); + }); + + it('falls back to a generic conversation label when a burst has no title', () => { + expect(timelineRowTitle(burstRow({ title: '' }))).toBe('14 messages in Conversation'); + }); + + it('falls back to "(untitled)" for an untitled single-message row', () => { + expect(timelineRowTitle(emailRow({ title: '' }))).toBe('(untitled)'); + }); +}); + +describe('timelineRowToSelection', () => { + it('adapts a single-message row into an entry selection', () => { + const selection = timelineRowToSelection(emailRow()); + expect(selection.kind).toBe('entry'); + if (selection.kind !== 'entry') throw new Error('expected entry selection'); + expect(selection.row).toMatchObject({ + key: 'message:1', + title: 'Subject line', + preview: 'Preview text', + message_type: 'email', + conversation_type: 'email', + conversation_id: 71, + anchor_message_id: 91, + source_id: 4, + source_identifier: '4', + attachment_count: 0, + has_attachments: false + }); + }); + + it('adapts a chat burst into an entry selection carrying the burst title and a chat conversation type', () => { + const selection = timelineRowToSelection(burstRow()); + if (selection.kind !== 'entry') throw new Error('expected entry selection'); + expect(selection.row.title).toBe('14 messages in Team Chat'); + expect(selection.row.conversation_type).toBe('chat'); + expect(selection.row.message_type).toBe('chat_burst'); + expect(selection.row.attachment_count).toBe(1); + expect(selection.row.anchor_message_id).toBe(500); + expect(selection.row.conversation_id).toBe(71); + }); +}); + +describe('localDayBoundsUTC', () => { + it('returns the local midnight-to-midnight UTC bounds containing the instant', () => { + const bounds = localDayBoundsUTC('2026-07-18T08:00:00Z'); + const start = new Date(bounds.start); + const end = new Date(bounds.end); + expect(start.getHours()).toBe(0); + expect(end.getTime() - start.getTime()).toBe(24 * 60 * 60 * 1000); + expect(new Date('2026-07-18T08:00:00Z').getTime()).toBeGreaterThanOrEqual(start.getTime()); + expect(new Date('2026-07-18T08:00:00Z').getTime()).toBeLessThan(end.getTime()); + }); +}); + +describe('monthGroupKey / monthGroupLabel', () => { + // Noon-UTC, mid-month instants stay within the same calendar day/month for + // any real-world timezone offset (-12h..+14h), so these assertions hold + // regardless of the test runner's local TZ. + it('buckets same-month instants under one key and formats a readable label', () => { + expect(monthGroupKey('2026-07-10T12:00:00Z')).toBe(monthGroupKey('2026-07-20T12:00:00Z')); + expect(monthGroupLabel('2026-07-18T12:00:00Z')).toContain('2026'); + }); + + it('distinguishes adjacent months', () => { + expect(monthGroupKey('2026-07-15T12:00:00Z')).not.toBe(monthGroupKey('2026-08-15T12:00:00Z')); + }); +}); diff --git a/web/src/lib/components/relationships/timeline-support.ts b/web/src/lib/components/relationships/timeline-support.ts new file mode 100644 index 000000000..e3c3ab6c4 --- /dev/null +++ b/web/src/lib/components/relationships/timeline-support.ts @@ -0,0 +1,76 @@ +import type { InspectorSelection } from '../inspector/Inspector.svelte'; +import type { RelationshipTimelineRow } from '../../relationships/models'; + +/** + * Pure helpers shared by RelationshipTimeline and RelationshipsWorkspace: + * turning a TimelineRow into display text, an Inspector entry selection, and + * (for chat bursts) the UTC bounds of the burst's local calendar day. Kept + * dependency-free so they're testable without mounting Svelte. + */ + +/** "N messages in <title>" for chat_burst rows; the row's own title otherwise. */ +export function timelineRowTitle(row: RelationshipTimelineRow): string { + if (row.kind !== 'chat_burst') return row.title || '(untitled)'; + return `${row.message_count.toLocaleString()} messages in ${row.title || 'Conversation'}`; +} + +/** + * Adapts a TimelineRow into the EntryRow shape Inspector's `{kind: 'entry'}` + * selection expects. TimelineRow is a leaner shape (no message_type, + * source_type/identifier, attachment counts, participants) than the + * Everything-table EntryRow it's borrowing the reading pane from — fields + * absent from the timeline response fall back to values derived from what + * IS present (source_id as a string identifier, has_attachments as a 0/1 + * attachment count) rather than being fabricated. + */ +export function timelineRowToSelection(row: RelationshipTimelineRow): InspectorSelection { + const isBurst = row.kind === 'chat_burst'; + return { + kind: 'entry', + row: { + key: row.key, + kind: row.kind, + title: timelineRowTitle(row), + preview: row.preview, + occurred_at: row.occurred_at, + message_type: row.kind, + source_type: '', + source_identifier: String(row.source_id), + source_id: row.source_id, + conversation_type: isBurst ? 'chat' : row.kind, + conversation_id: row.conversation_id, + anchor_message_id: row.anchor_message_id, + message_count: row.message_count, + attachment_count: row.has_attachments ? 1 : 0, + attachment_size: 0, + has_attachments: row.has_attachments, + deleted_from_source: false, + match: {} + } + }; +} + +/** + * UTC [start, end) bounds of the local calendar day containing `instant`, + * per the host's local timezone (the same zone RelationshipsController + * sends as `timezone()` — the day boundary the backend already bucketed the + * burst on). + */ +export function localDayBoundsUTC(instant: string): { start: string; end: string } { + const date = new Date(instant); + const startOfDay = new Date(date.getFullYear(), date.getMonth(), date.getDate()); + const endOfDay = new Date(startOfDay); + endOfDay.setDate(endOfDay.getDate() + 1); + return { start: startOfDay.toISOString(), end: endOfDay.toISOString() }; +} + +/** Sortable "YYYY-MM" bucket key for `instant`, in the local timezone. */ +export function monthGroupKey(instant: string): string { + const date = new Date(instant); + return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; +} + +/** Human month header label for `instant`, in the local timezone. */ +export function monthGroupLabel(instant: string): string { + return new Intl.DateTimeFormat(undefined, { month: 'long', year: 'numeric' }).format(new Date(instant)); +} diff --git a/web/src/lib/components/saved-views/SavedViewsWorkspace.svelte b/web/src/lib/components/saved-views/SavedViewsWorkspace.svelte new file mode 100644 index 000000000..1b84065e1 --- /dev/null +++ b/web/src/lib/components/saved-views/SavedViewsWorkspace.svelte @@ -0,0 +1,296 @@ +<script lang="ts"> + import { Button, Modal } from '@kenn-io/kit-ui'; + import { onMount } from 'svelte'; + + import type { APIClient } from '../../api/client'; + import type { components } from '../../api/generated/schema'; + import { + DEFAULT_EXPLORE_COLUMNS, + type ExploreColumn, + type ExploreURLState + } from '../../explore/models'; + import { isGroupingDimension } from '../../grouping/catalog'; + + type SavedView = components['schemas']['SavedView']; + type CanonicalState = components['schemas']['SavedViewStateEnvelope']; + const CURRENT_SCHEMA_VERSION = 1; + + let { + client, + currentState, + selection = undefined, + onOpen = () => undefined + }: { + client: APIClient; + currentState: ExploreURLState; + selection?: unknown; + onOpen?: (state: Partial<ExploreURLState>) => void; + } = $props(); + + let views = $state<SavedView[]>([]); + let loading = $state(true); + let saving = $state(false); + let error = $state(''); + let name = $state(''); + let description = $state(''); + let editing = $state<SavedView>(); + let editName = $state(''); + let editDescription = $state(''); + let deleting = $state<SavedView>(); + + onMount(() => void load()); + + async function load(): Promise<void> { + loading = true; + error = ''; + try { + const { data, error: responseError } = await client.GET('/api/v1/saved-views'); + if (!data) throw new Error(messageFor(responseError, 'Unable to load Saved Views.')); + views = data.saved_views ?? []; + } catch (cause) { + error = cause instanceof Error ? cause.message : 'Unable to load Saved Views.'; + } finally { + loading = false; + } + } + + function canonicalState(): CanonicalState { + return { + ...(currentState.query ? { query: currentState.query } : {}), + search_mode: currentState.searchMode, + filters: currentState.filters.map((filter) => ({ + field: filter.dimension, operator: 'in', values: [...filter.values] + })), + grouping: [...currentState.groupingChain], + presentation: currentState.presentation, + sort: currentState.sort.map((sort) => ({ field: sort.field, direction: sort.direction })), + columns: [...currentState.columns] + }; + } + + async function createView(): Promise<void> { + if (!name.trim()) return; + saving = true; + error = ''; + try { + const { data, error: responseError } = await client.POST('/api/v1/saved-views', { + body: { + name: name.trim(), + ...(description.trim() ? { description: description.trim() } : {}), + canonical_state: canonicalState(), + schema_version: CURRENT_SCHEMA_VERSION + } + }); + if (!data) throw new Error(messageFor(responseError, 'Unable to save this view.')); + views = [...views, data].sort((left, right) => left.name.localeCompare(right.name)); + name = ''; + description = ''; + } catch (cause) { + error = cause instanceof Error ? cause.message : 'Unable to save this view.'; + } finally { + saving = false; + } + } + + function beginEdit(view: SavedView): void { + editing = view; + editName = view.name; + editDescription = view.description ?? ''; + } + + async function saveEdit(): Promise<void> { + if (!editing || !editName.trim()) return; + saving = true; + error = ''; + const target = editing; + try { + const { data, error: responseError, response } = await client.PATCH('/api/v1/saved-views/{id}', { + params: { + path: { id: target.id }, + header: { 'If-Match': `"saved-view-${target.id}-r${target.revision}"` } + }, + body: { + name: editName.trim(), + description: editDescription.trim() + } + }); + if (!data) { + if (response.status === 409) throw new Error('This Saved View changed in another session. Reload and review the latest revision.'); + throw new Error(messageFor(responseError, 'Unable to update this view.')); + } + views = views.map((view) => view.id === data.id ? data : view); + editing = undefined; + } catch (cause) { + error = cause instanceof Error ? cause.message : 'Unable to update this view.'; + } finally { + saving = false; + } + } + + async function confirmDelete(): Promise<void> { + if (!deleting) return; + const target = deleting; + saving = true; + error = ''; + try { + const { response, error: responseError } = await client.DELETE('/api/v1/saved-views/{id}', { + params: { + path: { id: target.id }, + header: { 'If-Match': `"saved-view-${target.id}-r${target.revision}"` } + } + }); + if (!response.ok) throw new Error(messageFor(responseError, 'Unable to delete this view.')); + views = views.filter((view) => view.id !== target.id); + deleting = undefined; + } catch (cause) { + error = cause instanceof Error ? cause.message : 'Unable to delete this view.'; + } finally { + saving = false; + } + } + + function open(view: SavedView): void { + const incompatibility = incompatibilityFor(view); + if (incompatibility) return; + const saved = view.canonical_state; + const filters = (saved.filters ?? []).map((filter) => { + const aliases: Record<string, ExploreURLState['filters'][number]['dimension']> = { + source_id: 'source', participant_id: 'participant' + }; + return { + dimension: aliases[filter.field] ?? filter.field as ExploreURLState['filters'][number]['dimension'], + values: [...filter.values] + }; + }); + onOpen({ + workspace: 'everything', + query: saved.query ?? '', + searchMode: saved.search_mode === 'semantic' || saved.search_mode === 'hybrid' + ? saved.search_mode : 'full_text', + filters, + groupingChain: [...(saved.grouping ?? [])] as ExploreURLState['groupingChain'], + presentation: saved.presentation ?? 'table', + sort: (saved.sort ?? [{ field: 'occurred_at', direction: 'desc' }]) as ExploreURLState['sort'], + columns: (saved.columns ?? DEFAULT_EXPLORE_COLUMNS) as ExploreURLState['columns'], + activeRow: null, + selectedRow: null, + conversationAnchor: null, + scrollAnchor: null + }); + } + + function incompatibilityFor(view: SavedView): string { + if (view.schema_version !== CURRENT_SCHEMA_VERSION) { + return `This view uses schema version ${view.schema_version}. Automatic migration is not supported; remove it and save the current view again.`; + } + const saved = view.canonical_state; + const filterDimensions = new Set(['source', 'participant', 'domain', 'message_type', 'after', 'before', 'deletion']); + const filterAliases = new Set(['source_id', 'participant_id']); + for (const filter of saved.filters ?? []) { + if (!filterDimensions.has(filter.field) && !filterAliases.has(filter.field)) { + return `This view has an unsupported v1 filter field: ${filter.field}.`; + } + if (filter.operator !== 'eq' && filter.operator !== 'in') { + return `This view has an unsupported v1 filter operator: ${filter.operator}.`; + } + if (!Array.isArray(filter.values) || filter.values.length === 0 || filter.values.some((value) => !value)) { + return `This view has an unsupported v1 filter value for ${filter.field}.`; + } + } + if (saved.search_mode && !['full_text', 'semantic', 'hybrid'].includes(saved.search_mode)) { + return `This view has an unsupported v1 search mode: ${saved.search_mode}.`; + } + if ((saved.grouping ?? []).some((dimension) => !isGroupingDimension(dimension))) { + return 'This view has an unsupported v1 grouping dimension.'; + } + if ((saved.sort ?? []).some((sort) => sort.field !== 'occurred_at' || sort.direction !== 'desc')) { + return 'This view has an unsupported v1 sort.'; + } + const columns = new Set<ExploreColumn>(['kind', 'people', 'title', 'excerpt', 'time', 'attachments', 'size']); + if ((saved.columns ?? []).some((column) => !columns.has(column as ExploreColumn))) { + return 'This view has an unsupported v1 column.'; + } + return ''; + } + + function messageFor(value: unknown, fallback: string): string { + return typeof value === 'object' && value !== null && 'message' in value && typeof value.message === 'string' + ? value.message : fallback; + } +</script> + +<main class="saved-views" aria-label="Saved Views"> + <header><div><p>Archive workspace</p><h1>Saved Views</h1></div><span>Shared daemon state</span></header> + + {#if error}<p class="notice notice--error" role="alert">{error}</p>{/if} + + <form class="create" onsubmit={(event) => { event.preventDefault(); void createView(); }}> + <label>Name<input bind:value={name} autocomplete="off" /></label> + <label>Description<input bind:value={description} autocomplete="off" /></label> + <Button type="submit" tone="workflow" surface="solid" label="Save current view" disabled={saving || !name.trim()} /> + </form> + + {#if loading} + <p role="status">Loading Saved Views…</p> + {:else if views.length === 0} + <p class="empty">No Saved Views yet.</p> + {:else} + <section class="view-list" aria-label="Saved View library"> + {#each views as view (view.id)} + {@const incompatibility = incompatibilityFor(view)} + <article> + {#if editing?.id === view.id} + <label>Edit name<input aria-label="Edit name" bind:value={editName} /></label> + <label>Edit description<input aria-label="Edit description" bind:value={editDescription} /></label> + <div class="actions"> + <Button size="sm" tone="info" surface="solid" label="Save changes" onclick={() => void saveEdit()} disabled={saving} /> + <Button size="sm" surface="soft" label="Cancel edit" onclick={() => { editing = undefined; }} /> + </div> + {:else} + <div class="view-copy"><h2>{view.name}</h2><p>{view.description ?? 'No description'}</p></div> + {#if incompatibility} + <p class="notice" role="alert">{incompatibility}</p> + {/if} + <div class="actions"> + <Button size="sm" tone="info" surface="soft" label={`Open ${view.name}`} disabled={Boolean(incompatibility)} onclick={() => open(view)} /> + {#if !incompatibility} + <Button size="sm" surface="soft" label={`Edit ${view.name}`} onclick={() => beginEdit(view)} /> + <Button size="sm" tone="danger" surface="soft" label={`Delete ${view.name}`} onclick={() => { deleting = view; }} /> + {:else} + <Button size="sm" tone="danger" surface="soft" label={`Remove incompatible ${view.name}`} onclick={() => { deleting = view; }} /> + {/if} + </div> + {/if} + </article> + {/each} + </section> + {/if} +</main> + +{#if deleting} + <Modal title="Delete Saved View?" tone="danger" onclose={() => { deleting = undefined; }}> + <p>Delete “{deleting.name}” from every authenticated browser session?</p> + {#snippet footer()} + <Button surface="soft" label="Cancel" onclick={() => { deleting = undefined; }} /> + <Button tone="danger" surface="solid" label="Confirm delete" disabled={saving} onclick={() => void confirmDelete()} /> + {/snippet} + </Modal> +{/if} + +<style> + .saved-views { display: flex; min-height: 0; flex: 1; flex-direction: column; gap: var(--space-4); padding: var(--space-5) var(--space-6); } + header, .create, article, .actions { display: flex; align-items: center; gap: var(--space-3); } + header { justify-content: space-between; } + header p, h1, h2, article p { margin: 0; } + header p { color: var(--accent-amber); font-size: var(--font-size-2xs); font-weight: 800; letter-spacing: .1em; text-transform: uppercase; } + header span, article p, .empty { color: var(--text-muted); font-size: var(--font-size-xs); } + .create { padding: var(--space-3); border: 1px solid var(--border-muted); border-radius: var(--radius-md); background: var(--bg-surface); } + label { display: grid; gap: var(--space-1); color: var(--text-muted); font-size: var(--font-size-xs); } + input { min-width: 12rem; padding: var(--space-2); border: 1px solid var(--border-default); border-radius: var(--radius-sm); background: var(--bg-canvas); color: var(--text-primary); } + .view-list { display: grid; gap: var(--space-2); } + article { justify-content: space-between; padding: var(--space-3); border-bottom: 1px solid var(--border-muted); } + .view-copy { display: grid; min-width: 12rem; gap: var(--space-1); } + .notice { padding: var(--space-2) var(--space-3); border-left: 3px solid var(--accent-amber); background: var(--bg-subtle); } + .notice--error { border-color: var(--accent-red); color: var(--text-danger); } + @media (max-width: 760px) { .create, article { align-items: stretch; flex-direction: column; } input { min-width: 0; width: 100%; } } +</style> diff --git a/web/src/lib/components/saved-views/SavedViewsWorkspace.test.ts b/web/src/lib/components/saved-views/SavedViewsWorkspace.test.ts new file mode 100644 index 000000000..12cec5eaf --- /dev/null +++ b/web/src/lib/components/saved-views/SavedViewsWorkspace.test.ts @@ -0,0 +1,181 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import type { ExploreURLState } from '../../explore/models'; +import SavedViewsWorkspace from './SavedViewsWorkspace.svelte'; + +const currentState: ExploreURLState = { + schemaVersion: 2, + workspace: 'everything', + query: 'invoice', + searchMode: 'full_text', + filters: [{ dimension: 'source', values: ['1'] }], + groupingChain: ['domain'], + presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + fileFilenameQuery: '', fileMIMEFamilies: [], columns: ['kind', 'title'], columnWidths: {}, + activeRow: null, selectedRow: null, inspectorPinned: true, inspectorWidth: 380, + conversationAnchor: null, scrollAnchor: null, + relationshipFacet: 'people', relationshipTarget: null, + relationshipShowAll: false, relationshipFiles: false +}; + +function savedView(overrides: Record<string, unknown> = {}) { + return { + id: 7, name: 'Invoices', description: 'Quarterly review', + canonical_state: { + query: 'invoice', search_mode: 'full_text', + filters: [{ field: 'source', operator: 'in', values: ['1'] }], + grouping: ['domain'], presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'title'], inspector_pinned: true + }, + schema_version: 1, revision: 3, + created_at: '2026-07-19T10:00:00Z', updated_at: '2026-07-19T11:00:00Z', + ...overrides + }; +} + +describe('SavedViewsWorkspace', () => { + it('creates from canonical analytical state without persisting selection tokens', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (request.method === 'GET') return Response.json({ saved_views: [] }); + return Response.json(savedView(), { status: 201 }); + }); + render(SavedViewsWorkspace, { + client: createAPIClient(fetchFn), currentState, + selection: { mode: 'all_matching', operationToken: 'session-secret' } + }); + + await screen.findByText('No Saved Views yet.'); + await fireEvent.input(screen.getByLabelText('Name'), { target: { value: 'Invoices' } }); + await fireEvent.input(screen.getByLabelText('Description'), { target: { value: 'Quarterly review' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Save current view' })); + + await screen.findByRole('heading', { name: 'Invoices' }); + const body = await requests[1]!.clone().json(); + expect(body).toEqual({ + name: 'Invoices', description: 'Quarterly review', schema_version: 1, + canonical_state: { + query: 'invoice', search_mode: 'full_text', + filters: [{ field: 'source', operator: 'in', values: ['1'] }], + grouping: ['domain'], presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'title'] + } + }); + expect(JSON.stringify(body)).not.toContain('session-secret'); + expect(JSON.stringify(body)).not.toContain('selection'); + expect(JSON.stringify(body)).not.toContain('inspector_pinned'); + }); + + it('opens by replacing the exact analytical URL state and clears transient focus', async () => { + const onOpen = vi.fn(); + render(SavedViewsWorkspace, { + client: createAPIClient(vi.fn<typeof fetch>(async () => Response.json({ saved_views: [savedView()] }))), + currentState: { ...currentState, activeRow: 'message:9', selectedRow: 'message:9' }, onOpen + }); + + await fireEvent.click(await screen.findByRole('button', { name: 'Open Invoices' })); + + expect(onOpen).toHaveBeenCalledWith(expect.objectContaining({ + workspace: 'everything', query: 'invoice', searchMode: 'full_text', + filters: [{ dimension: 'source', values: ['1'] }], groupingChain: ['domain'], + activeRow: null, selectedRow: null, scrollAnchor: null + })); + expect(onOpen.mock.calls[0]![0]).not.toHaveProperty('selection'); + expect(onOpen.mock.calls[0]![0]).not.toHaveProperty('inspectorPinned'); + }); + + it('translates persisted v1 source identifiers and equality operators into current filters', async () => { + const onOpen = vi.fn(); + const legacy = savedView({ + canonical_state: { + query: 'invoice', search_mode: 'full_text', + filters: [{ field: 'source_id', operator: 'eq', values: ['1'] }], + grouping: [], presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'title'], inspector_pinned: false + } + }); + render(SavedViewsWorkspace, { + client: createAPIClient(vi.fn<typeof fetch>(async () => Response.json({ saved_views: [legacy] }))), + currentState, onOpen + }); + + await fireEvent.click(await screen.findByRole('button', { name: 'Open Invoices' })); + + expect(onOpen).toHaveBeenCalledWith(expect.objectContaining({ + filters: [{ dimension: 'source', values: ['1'] }] + })); + }); + + it('keeps compatible-schema views with unsupported fields or operators visibly blocked', async () => { + render(SavedViewsWorkspace, { + client: createAPIClient(vi.fn<typeof fetch>(async () => Response.json({ saved_views: [savedView({ + canonical_state: { + filters: [{ field: 'subject', operator: 'contains', values: ['invoice'] }], + presentation: 'table' + } + })] }))), + currentState + }); + + expect((await screen.findByRole('alert')).textContent).toContain('unsupported v1 filter'); + expect((screen.getByRole('button', { name: 'Open Invoices' }) as HTMLButtonElement).disabled).toBe(true); + }); + + it('updates with optimistic revision truth and explicitly confirms delete', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (request.method === 'GET') return Response.json({ saved_views: [savedView()] }); + if (request.method === 'PATCH') return Response.json(savedView({ name: 'Invoices 2026', revision: 4 })); + return new Response(null, { status: 204 }); + }); + render(SavedViewsWorkspace, { client: createAPIClient(fetchFn), currentState }); + + await fireEvent.click(await screen.findByRole('button', { name: 'Edit Invoices' })); + await fireEvent.input(screen.getByLabelText('Edit name'), { target: { value: 'Invoices 2026' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Save changes' })); + await screen.findByRole('heading', { name: 'Invoices 2026' }); + expect(requests[1]!.headers.get('If-Match')).toBe('"saved-view-7-r3"'); + + await fireEvent.click(screen.getByRole('button', { name: 'Delete Invoices 2026' })); + expect(screen.getByRole('dialog', { name: 'Delete Saved View?' })).toBeDefined(); + expect(requests).toHaveLength(2); + await fireEvent.click(screen.getByRole('button', { name: 'Confirm delete' })); + await waitFor(() => expect(requests).toHaveLength(3)); + expect(requests[2]!.headers.get('If-Match')).toBe('"saved-view-7-r4"'); + expect(await screen.findByText('No Saved Views yet.')).toBeDefined(); + }); + + it('keeps incompatible schema records visible and offers confirmed removal, not migration', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (request.method === 'DELETE') return new Response(null, { status: 204 }); + return Response.json({ saved_views: [savedView({ schema_version: 99 })] }); + }); + render(SavedViewsWorkspace, { + client: createAPIClient(fetchFn), + currentState + }); + + expect((await screen.findByRole('alert')).textContent).toContain('schema version 99'); + expect(screen.getByRole('alert').textContent).toContain('Automatic migration is not supported'); + expect((screen.getByRole('button', { name: 'Open Invoices' }) as HTMLButtonElement).disabled).toBe(true); + await fireEvent.click(screen.getByRole('button', { name: 'Remove incompatible Invoices' })); + expect(screen.getByRole('dialog', { name: 'Delete Saved View?' })).toBeDefined(); + expect(requests).toHaveLength(1); + await fireEvent.click(screen.getByRole('button', { name: 'Confirm delete' })); + await waitFor(() => expect(requests).toHaveLength(2)); + expect(requests[1]!.method).toBe('DELETE'); + }); +}); diff --git a/web/src/lib/components/search/SearchBar.svelte b/web/src/lib/components/search/SearchBar.svelte new file mode 100644 index 000000000..ae8b22049 --- /dev/null +++ b/web/src/lib/components/search/SearchBar.svelte @@ -0,0 +1,71 @@ +<script lang="ts"> + import { SearchInput } from '@kenn-io/kit-ui'; + import { untrack } from 'svelte'; + + import type { ArchiveSearchMode } from '../../archive/types'; + import SearchModeControl from './SearchModeControl.svelte'; + + interface Props { + initialQuery: string; + initialMode: ArchiveSearchMode; + onSubmit: (query: string, mode: ArchiveSearchMode) => void; + } + + let { initialQuery, initialMode, onSubmit }: Props = $props(); + + let query = $state(untrack(() => initialQuery)); + let mode = $state<ArchiveSearchMode>(untrack(() => initialMode)); + + $effect(() => { + query = initialQuery; + mode = initialMode; + }); + + function handleSubmit(event: SubmitEvent): void { + event.preventDefault(); + onSubmit(query.trim(), mode); + } +</script> + +<form role="search" aria-label="Search archive" onsubmit={handleSubmit}> + <div class="query"> + <SearchInput + id="archive-search-input" + name="q" + bind:value={query} + block + placeholder="Search the archive…" + ariaLabel="Search the archive" + /> + </div> + <SearchModeControl + requestedMode={mode === 'fts' ? 'full_text' : mode === 'vector' ? 'semantic' : 'hybrid'} + onchange={(next) => { + mode = next === 'full_text' ? 'fts' : next === 'semantic' ? 'vector' : 'hybrid'; + }} + /> + <button type="submit">Search</button> +</form> + +<style> + form { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + } + + .query { + min-width: 0; + flex: 1; + } + + button[type='submit'] { + padding: 5px 12px; + border: 1px solid var(--border-default); + border-radius: 4px; + background: var(--bg-surface); + color: var(--text-primary); + cursor: pointer; + } +</style> diff --git a/web/src/lib/components/search/SearchBar.test.ts b/web/src/lib/components/search/SearchBar.test.ts new file mode 100644 index 000000000..1c0527e4f --- /dev/null +++ b/web/src/lib/components/search/SearchBar.test.ts @@ -0,0 +1,52 @@ +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import SearchBar from './SearchBar.svelte'; + +describe('SearchBar', () => { + it('shows every explicit search mode and submits the selected mode', async () => { + const onSubmit = vi.fn(); + render(SearchBar, { + props: { initialQuery: '', initialMode: 'fts', onSubmit } + }); + + expect(screen.getByRole('radio', { name: 'Full text' }).getAttribute('aria-checked')).toBe('true'); + expect(screen.getByRole('radio', { name: 'Semantic' })).toBeDefined(); + expect(screen.getByRole('radio', { name: 'Hybrid' })).toBeDefined(); + + await fireEvent.click(screen.getByRole('radio', { name: 'Semantic' })); + await fireEvent.input(screen.getByRole('searchbox'), { + target: { value: ' quarterly plan ' } + }); + await fireEvent.submit(screen.getByRole('search', { name: 'Search archive' })); + + expect(onSubmit).toHaveBeenCalledWith('quarterly plan', 'vector'); + }); + + it('submits an empty query so the owner can clear URL state', async () => { + const onSubmit = vi.fn(); + render(SearchBar, { + props: { initialQuery: 'old query', initialMode: 'hybrid', onSubmit } + }); + + await fireEvent.input(screen.getByRole('searchbox'), { target: { value: ' ' } }); + await fireEvent.submit(screen.getByRole('search', { name: 'Search archive' })); + + expect(onSubmit).toHaveBeenCalledWith('', 'hybrid'); + }); + + it('restores query and mode when URL-owned props change', async () => { + const { rerender } = render(SearchBar, { + props: { initialQuery: 'first', initialMode: 'fts', onSubmit: vi.fn() } + }); + + await rerender({ + initialQuery: 'restored', + initialMode: 'hybrid', + onSubmit: vi.fn() + }); + + expect((screen.getByRole('searchbox') as HTMLInputElement).value).toBe('restored'); + expect(screen.getByRole('radio', { name: 'Hybrid' }).getAttribute('aria-checked')).toBe('true'); + }); +}); diff --git a/web/src/lib/components/search/SearchCoverage.svelte b/web/src/lib/components/search/SearchCoverage.svelte new file mode 100644 index 000000000..864fb3005 --- /dev/null +++ b/web/src/lib/components/search/SearchCoverage.svelte @@ -0,0 +1,79 @@ +<script lang="ts"> + import { Button } from '@kenn-io/kit-ui'; + + import type { ExploreSearchMode } from '../../explore/models'; + import type { SearchCoverageAction, SearchCoverageValue } from '../../search/modes'; + + interface Props { + requestedMode?: ExploreSearchMode; + coverage: SearchCoverageValue; + onaction?: (action: SearchCoverageAction) => void; + } + + let { requestedMode = 'full_text', coverage, onaction = undefined }: Props = $props(); + + let confirmingBuild = $state(false); + + const count = $derived(coverage.eligible_count.toLocaleString()); + const percentage = $derived(Math.round(coverage.percentage).toLocaleString()); + const actions = $derived(coverage.actions ?? []); + const summary = $derived.by(() => { + switch (coverage.status) { + case 'disabled': return 'Semantic search is disabled'; + case 'initializing': return 'Semantic index is initializing'; + case 'stale': return 'Semantic index is stale'; + case 'unavailable': return 'Semantic index is unavailable'; + case 'incomplete': + case 'ready': return `Semantic index: ${percentage}% of ${count} items`; + } + }); + + function requestAction(action: SearchCoverageAction): void { + if (action === 'build_index') { + confirmingBuild = true; + return; + } + onaction?.(action); + } + + function cancelBuild(): void { + confirmingBuild = false; + } + + function confirmBuild(): void { + confirmingBuild = false; + onaction?.('build_index'); + } +</script> + +<div class="coverage" role="status" aria-live="polite"> + <span>{summary}.</span> + {#if coverage.detail}<span>{coverage.detail}</span>{/if} + {#if requestedMode === 'semantic' && coverage.status === 'incomplete'} + <span>Unembedded items cannot appear in Semantic results.</span> + {/if} + {#if actions.includes('retry')} + <Button label="Retry" tone="info" surface="outline" onclick={() => requestAction('retry')} /> + {/if} + {#if actions.includes('build_index')} + {#if confirmingBuild} + <span>Start a full rebuild of the semantic index?</span> + <Button label="Cancel" tone="neutral" surface="outline" onclick={cancelBuild} /> + <Button label="Confirm full rebuild" tone="info" surface="solid" onclick={confirmBuild} /> + {:else} + <Button label="Build index" tone="info" surface="outline" onclick={() => requestAction('build_index')} /> + {/if} + {/if} +</div> + +<style> + .coverage { + display: flex; + min-height: 28px; + align-items: center; + gap: var(--space-3); + color: var(--text-muted); + font-size: var(--font-size-xs); + font-variant-numeric: tabular-nums; + } +</style> diff --git a/web/src/lib/components/search/SearchCoverage.test.ts b/web/src/lib/components/search/SearchCoverage.test.ts new file mode 100644 index 000000000..370178c58 --- /dev/null +++ b/web/src/lib/components/search/SearchCoverage.test.ts @@ -0,0 +1,69 @@ +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import SearchCoverage from './SearchCoverage.svelte'; +import type { SearchCoverageValue } from '../../search/modes'; + +const base: SearchCoverageValue = { + eligible_count: 12_430, embedded_count: 10_441, percentage: 84, + cache_revision: 'cache-1', actions: [], status: 'incomplete' +}; + +describe('SearchCoverage', () => { + it.each([ + ['disabled', 'Semantic search is disabled'], + ['initializing', 'Semantic index is initializing'], + ['stale', 'Semantic index is stale'], + ['incomplete', 'Semantic index: 84% of 12,430 items'], + ['unavailable', 'Semantic index is unavailable'], + ['ready', 'Semantic index: 100% of 12,430 items'] + ] as const)('renders honest %s copy', (status, copy) => { + render(SearchCoverage, { + props: { + coverage: { + ...base, + status, + embedded_count: status === 'ready' ? 12_430 : base.embedded_count, + percentage: status === 'ready' ? 100 : base.percentage + } + } + }); + expect(screen.getByRole('status').textContent).toContain(copy); + }); + + it('states that semantic-only results exclude unembedded content', () => { + render(SearchCoverage, { props: { requestedMode: 'semantic', coverage: { ...base, status: 'incomplete' } } }); + expect(screen.getByRole('status').textContent).toContain('Unembedded items cannot appear'); + }); + + it('offers only daemon-supported actions', async () => { + const onaction = vi.fn(); + render(SearchCoverage, { + props: { coverage: { ...base, status: 'unavailable', actions: ['retry'] }, onaction } + }); + + expect(screen.queryByRole('button', { name: 'Build index' })).toBeNull(); + await fireEvent.click(screen.getByRole('button', { name: 'Retry' })); + expect(onaction).toHaveBeenCalledWith('retry'); + }); + + it('requires explicit confirmation before requesting a full rebuild', async () => { + const onaction = vi.fn(); + render(SearchCoverage, { + props: { coverage: { ...base, status: 'stale', actions: ['build_index'] }, onaction } + }); + + await fireEvent.click(screen.getByRole('button', { name: 'Build index' })); + expect(onaction).not.toHaveBeenCalled(); + expect(screen.getByText('Start a full rebuild of the semantic index?')).toBeDefined(); + + await fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + expect(onaction).not.toHaveBeenCalled(); + expect(screen.queryByRole('button', { name: 'Confirm full rebuild' })).toBeNull(); + + await fireEvent.click(screen.getByRole('button', { name: 'Build index' })); + await fireEvent.click(screen.getByRole('button', { name: 'Confirm full rebuild' })); + expect(onaction).toHaveBeenCalledOnce(); + expect(onaction).toHaveBeenCalledWith('build_index'); + }); +}); diff --git a/web/src/lib/components/search/SearchModeControl.svelte b/web/src/lib/components/search/SearchModeControl.svelte new file mode 100644 index 000000000..72aa489ca --- /dev/null +++ b/web/src/lib/components/search/SearchModeControl.svelte @@ -0,0 +1,28 @@ +<script lang="ts"> + import { SegmentedControl, type SegmentedControlOption } from '@kenn-io/kit-ui'; + + import type { ExploreSearchMode } from '../../explore/models'; + import type { SearchCoverageStatus } from '../../search/modes'; + + interface Props { + requestedMode: ExploreSearchMode; + status?: SearchCoverageStatus; + error?: string; + onchange?: (mode: ExploreSearchMode) => void; + } + + let { requestedMode, status: _status = undefined, error: _error = '', onchange = undefined }: Props = $props(); + + const options: SegmentedControlOption[] = [ + { value: 'full_text', label: 'Full text' }, + { value: 'semantic', label: 'Semantic' }, + { value: 'hybrid', label: 'Hybrid' } + ]; +</script> + +<SegmentedControl + ariaLabel="Search mode" + {options} + value={requestedMode} + onchange={(mode) => onchange?.(mode as ExploreSearchMode)} +/> diff --git a/web/src/lib/components/search/SearchModeControl.test.ts b/web/src/lib/components/search/SearchModeControl.test.ts new file mode 100644 index 000000000..f835680de --- /dev/null +++ b/web/src/lib/components/search/SearchModeControl.test.ts @@ -0,0 +1,30 @@ +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import SearchModeControl from './SearchModeControl.svelte'; + +describe('SearchModeControl', () => { + it('labels all three modes and emits explicit choices', async () => { + const onchange = vi.fn(); + render(SearchModeControl, { props: { requestedMode: 'full_text', onchange } }); + + expect(screen.getByRole('radio', { name: 'Full text' }).getAttribute('aria-checked')).toBe('true'); + expect(screen.getByRole('radio', { name: 'Semantic' })).toBeDefined(); + expect(screen.getByRole('radio', { name: 'Hybrid' })).toBeDefined(); + await fireEvent.click(screen.getByRole('radio', { name: 'Semantic' })); + expect(onchange).toHaveBeenCalledWith('semantic'); + }); + + it.each(['disabled', 'initializing', 'stale', 'incomplete', 'unavailable', 'ready'] as const)( + 'preserves the requested mode through %s coverage', + async (status) => { + const { rerender } = render(SearchModeControl, { + props: { requestedMode: 'hybrid', status, error: status === 'unavailable' ? 'backend failed' : '' } + }); + + expect(screen.getByRole('radio', { name: 'Hybrid' }).getAttribute('aria-checked')).toBe('true'); + await rerender({ requestedMode: 'hybrid', status, error: 'new request failure' }); + expect(screen.getByRole('radio', { name: 'Hybrid' }).getAttribute('aria-checked')).toBe('true'); + } + ); +}); diff --git a/web/src/lib/components/settings/SettingsWorkspace.svelte b/web/src/lib/components/settings/SettingsWorkspace.svelte new file mode 100644 index 000000000..8a9723da4 --- /dev/null +++ b/web/src/lib/components/settings/SettingsWorkspace.svelte @@ -0,0 +1,311 @@ +<script lang="ts"> + import { onMount } from 'svelte'; + + import type { APIClient } from '../../api/client'; + import type { components } from '../../api/generated/schema'; + import { + groupSettings, + settingsCatalog, + type SettingState, + type SettingValue + } from '../../settings/catalog'; + + type SecretUpdate = { action: 'set'; value: string } | { action: 'clear' }; + type SettingUpdate = components['schemas']['SettingUpdate']; + + let { + client, + plainHTTPWarning = false, + onTestConnection + }: { + client: APIClient; + plainHTTPWarning?: boolean; + onTestConnection?: (key: string) => void | Promise<void>; + } = $props(); + + let settings = $state<SettingState[]>([]); + let etag = $state(''); + let drafts = $state<Record<string, unknown>>({}); + let secretUpdates = $state<Record<string, SecretUpdate>>({}); + let secretValues = $state<Record<string, string>>({}); + let pendingRestart = $state(false); + let loading = $state(true); + let saving = $state(false); + let error = $state(''); + let confirmAPIKeyRestart = $state(false); + + onMount(() => { + void loadSettings(false); + }); + + async function loadSettings(retainDrafts: boolean) { + loading = true; + try { + const { data: document, error: responseError, response } = await client.GET('/api/v1/settings'); + if (!document) throw new Error(apiErrorMessage(responseError, 'Unable to load settings.')); + settings = document.settings; + pendingRestart = document.pending_restart; + etag = response.headers.get('ETag') ?? ''; + if (!retainDrafts) { + drafts = {}; + secretUpdates = {}; + secretValues = {}; + confirmAPIKeyRestart = false; + } + } catch (cause) { + error = cause instanceof Error ? cause.message : 'Unable to load settings.'; + } finally { + loading = false; + } + } + + function currentValue(setting: SettingState): unknown { + if (Object.hasOwn(drafts, setting.key)) return drafts[setting.key]; + const value = setting.value; + if (!value) return undefined; + if ('string' in value) return value.string; + if ('integer' in value) return value.integer; + if ('number' in value) return value.number; + if ('boolean' in value) return value.boolean; + return value.strings; + } + + function setDraft(key: string, value: unknown) { + drafts = { ...drafts, [key]: value }; + } + + function setSecret(key: string, value: string) { + secretValues = { ...secretValues, [key]: value }; + if (value === '') { + const next = { ...secretUpdates }; + delete next[key]; + secretUpdates = next; + return; + } + secretUpdates = { ...secretUpdates, [key]: { action: 'set', value } }; + } + + function clearSecret(key: string) { + secretValues = { ...secretValues, [key]: '' }; + secretUpdates = { ...secretUpdates, [key]: { action: 'clear' } }; + } + + async function saveSettings() { + const updates: SettingUpdate[] = [ + ...Object.entries(drafts).map(([key, value]) => ({ + key, + value: typedValue(settings.find((setting) => setting.key === key), value) + })), + ...Object.entries(secretUpdates).map(([key, secret]) => ({ key, secret })) + ]; + if (updates.length === 0) return; + if (Object.hasOwn(secretUpdates, 'server.api_key') && !confirmAPIKeyRestart) { + error = 'You must confirm that the API key changes after restart.'; + return; + } + + saving = true; + error = ''; + try { + const { data: result, error: responseError, response } = await client.PATCH('/api/v1/settings', { + params: { header: { 'If-Match': etag } }, + body: { + updates, + confirm_api_key_restart: confirmAPIKeyRestart + } + }); + if (response.status === 412) { + await loadSettings(true); + error = 'The configuration changed on disk. Latest settings were loaded; review your local changes and save again.'; + return; + } + if (!result) { + error = apiErrorMessage(responseError, 'Unable to save settings.'); + return; + } + + settings = result.settings; + pendingRestart = result.pending_restart; + etag = response.headers.get('ETag') ?? etag; + drafts = {}; + secretUpdates = {}; + secretValues = {}; + confirmAPIKeyRestart = false; + } catch (cause) { + error = cause instanceof Error ? cause.message : 'Unable to save settings.'; + } finally { + saving = false; + } + } + + function apiErrorMessage(responseError: unknown, fallback: string): string { + if (typeof responseError === 'object' && responseError !== null && 'message' in responseError) { + const message = (responseError as { message?: unknown }).message; + if (typeof message === 'string' && message) return message; + } + return fallback; + } + + function stringValue(setting: SettingState): string { + const value = currentValue(setting); + if (Array.isArray(value)) return value.join(', '); + return value == null ? '' : String(value); + } + + function optionValues(setting: SettingState): string[] { + return setting.options ?? settingsCatalog[setting.key]?.options ?? []; + } + + function typedValue(setting: SettingState | undefined, value: unknown): SettingValue { + switch (setting?.kind) { + case 'boolean': return { boolean: Boolean(value) }; + case 'integer': return { integer: Number(value) }; + case 'number': return { number: Number(value) }; + case 'string_array': return { strings: Array.isArray(value) ? value.map(String) : [] }; + default: return { string: String(value ?? '') }; + } + } + + function sentenceLabel(label: string): string { + return label.charAt(0).toLowerCase() + label.slice(1); + } +</script> + +<main class="settings" aria-label="Settings"> + <header> + <p class="eyebrow">msgvault</p> + <h1>Settings</h1> + <p>Changes are written to config.toml and use optimistic concurrency.</p> + </header> + + {#if plainHTTPWarning} + <p class="warning" role="alert"> + This browser session uses plain HTTP, so its cookie cannot use the Secure flag. Prefer HTTPS for remote access. + </p> + {/if} + + {#if error} + <p class="error" role="alert">{error}</p> + {/if} + + {#if pendingRestart} + <p class="pending" role="status">Changes are pending restart.</p> + {/if} + + {#if loading} + <p role="status">Loading settings…</p> + {:else} + <form onsubmit={(event) => { event.preventDefault(); void saveSettings(); }}> + {#each groupSettings(settings) as group (group.id)} + <section aria-labelledby={`settings-${group.id}`}> + <h2 id={`settings-${group.id}`}>{group.label}</h2> + {#each group.settings as setting (setting.key)} + {@const catalog = settingsCatalog[setting.key]} + <div class="field"> + <div class="field-copy"> + <strong>{catalog.label}</strong> + <span>{catalog.description}</span> + {#if setting.restart_required}<small>Restart required</small>{/if} + </div> + + {#if setting.kind === 'secret'} + <div class="secret-control"> + <span>{setting.secret?.configured ? 'Set' : 'Not set'}</span> + <label> + New {sentenceLabel(catalog.label)} + <input + type="password" + autocomplete="new-password" + value={secretValues[setting.key] ?? ''} + oninput={(event) => setSecret(setting.key, event.currentTarget.value)} + /> + </label> + <button type="button" onclick={() => clearSecret(setting.key)}> + Clear {sentenceLabel(catalog.label)} + </button> + </div> + {:else if optionValues(setting).length > 0} + <label> + <span class="sr-only">{catalog.label}</span> + <select + value={stringValue(setting)} + onchange={(event) => setDraft(setting.key, event.currentTarget.value)} + > + {#each optionValues(setting) as option} + <option value={option}>{option}</option> + {/each} + </select> + </label> + {:else if setting.kind === 'boolean'} + <label> + <span class="sr-only">{catalog.label}</span> + <input + type="checkbox" + checked={Boolean(currentValue(setting))} + onchange={(event) => setDraft(setting.key, event.currentTarget.checked)} + /> + </label> + {:else if setting.kind === 'integer' || setting.kind === 'number'} + <label> + <span class="sr-only">{catalog.label}</span> + <input + type="number" + value={stringValue(setting)} + step={setting.kind === 'integer' ? '1' : 'any'} + oninput={(event) => setDraft(setting.key, Number(event.currentTarget.value))} + /> + </label> + {:else} + <label> + <span class="sr-only">{catalog.label}</span> + <input + type="text" + value={stringValue(setting)} + oninput={(event) => + setDraft( + setting.key, + setting.kind === 'string_array' + ? event.currentTarget.value.split(',').map((item) => item.trim()).filter(Boolean) + : event.currentTarget.value + )} + /> + </label> + {/if} + + {#if onTestConnection && (setting.testable || catalog.testable)} + <button + type="button" + aria-label={`Test ${sentenceLabel(catalog.label)} connection`} + onclick={() => void onTestConnection(setting.key)} + >Test connection</button> + {/if} + </div> + {/each} + </section> + {/each} + + {#if Object.hasOwn(secretUpdates, 'server.api_key')} + <label class="confirmation"> + <input type="checkbox" bind:checked={confirmAPIKeyRestart} /> + I understand the API key changes after restart + </label> + {/if} + + <button type="submit" disabled={saving}>{saving ? 'Saving…' : 'Save settings'}</button> + </form> + {/if} +</main> + +<style> + .settings { max-width: 72rem; margin: 0 auto; } + section { margin-block: 2rem; } + .field { display: grid; grid-template-columns: minmax(16rem, 1fr) minmax(14rem, 1fr) auto; gap: 1rem; align-items: center; padding: 1rem 0; border-top: 1px solid var(--border-muted); } + .field-copy { display: grid; gap: 0.25rem; } + .field-copy span, small { color: var(--text-muted); } + input:not([type='checkbox']), select { width: 100%; min-height: 2.25rem; } + .secret-control { display: grid; gap: 0.5rem; } + .warning, .pending { padding: 0.75rem 1rem; border-left: 0.25rem solid var(--status-warning-ink); background: var(--status-warning-bg); color: var(--status-warning-ink); } + .error { padding: 0.75rem 1rem; border-left: 0.25rem solid var(--status-error-ink); background: var(--status-error-bg); color: var(--status-error-ink); } + .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } + .confirmation { display: block; margin-block: 1rem; } +</style> diff --git a/web/src/lib/components/settings/SettingsWorkspace.test.ts b/web/src/lib/components/settings/SettingsWorkspace.test.ts new file mode 100644 index 000000000..90ae95179 --- /dev/null +++ b/web/src/lib/components/settings/SettingsWorkspace.test.ts @@ -0,0 +1,186 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import SettingsWorkspace from './SettingsWorkspace.svelte'; +import { createAPIClient } from '../../api/client'; + +const initialSettings = { + settings: [ + setting('web.theme', 'system', { options: ['system', 'light', 'dark'] }), + setting('server.api_key', undefined, { kind: 'secret', secret: { configured: true } }), + setting('vector.embeddings.endpoint', 'http://127.0.0.1:11434', { testable: true }), + setting('integrations.tasks.api_key', undefined, { + kind: 'secret', + secret: { configured: false } + }) + ], + pending_restart: false +}; + +describe('SettingsWorkspace', () => { + it('groups fields, redacts secrets, labels restart posture and warns on plain HTTP', async () => { + render(SettingsWorkspace, { + client: createAPIClient(vi.fn<typeof fetch>(async () => settingsResponse(initialSettings, '"etag-a"'))), + plainHTTPWarning: true + }); + + expect(await screen.findByRole('heading', { name: 'Browser experience' })).toBeDefined(); + expect(screen.getByRole('main', { name: 'Settings' })).toBeDefined(); + expect(screen.getByText('Set')).toBeDefined(); + expect(screen.getByText('Not set')).toBeDefined(); + expect(screen.getAllByText('Restart required').length).toBeGreaterThan(0); + expect(screen.getByRole('alert').textContent).toContain('plain HTTP'); + }); + + it('patches only changed values with If-Match and shows pending restart', async () => { + const fetchFn = vi + .fn<typeof fetch>() + .mockResolvedValueOnce(settingsResponse(initialSettings, '"etag-a"')) + .mockResolvedValueOnce( + settingsResponse( + { + ...initialSettings, + settings: initialSettings.settings.map((item) => + item.key === 'web.theme' ? { ...item, value: { string: 'dark' } } : item + ), + pending_restart: true + }, + '"etag-b"' + ) + ); + render(SettingsWorkspace, { client: createAPIClient(fetchFn) }); + + await fireEvent.change(await screen.findByLabelText('Theme'), { target: { value: 'dark' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Save settings' })); + + await waitFor(() => expect(fetchFn).toHaveBeenCalledTimes(2)); + const request = fetchFn.mock.calls[1]?.[0] as Request; + expect(request.method).toBe('PATCH'); + expect(request.headers.get('If-Match')).toBe('"etag-a"'); + await expect(request.clone().json()).resolves.toEqual({ + updates: [{ key: 'web.theme', value: { string: 'dark' } }], + confirm_api_key_restart: false + }); + expect(await screen.findByText('Changes are pending restart.')).toBeDefined(); + }); + + it('reloads the latest ETag after a conflict while retaining the local draft', async () => { + const latest = { + ...initialSettings, + settings: initialSettings.settings.map((item) => + item.key === 'web.theme' ? { ...item, value: { string: 'light' } } : item + ) + }; + const fetchFn = vi + .fn<typeof fetch>() + .mockResolvedValueOnce(settingsResponse(initialSettings, '"etag-a"')) + .mockResolvedValueOnce(Response.json({ error: 'settings_conflict' }, { status: 412 })) + .mockResolvedValueOnce(settingsResponse(latest, '"etag-latest"')); + render(SettingsWorkspace, { client: createAPIClient(fetchFn) }); + + const theme = await screen.findByLabelText('Theme'); + await fireEvent.change(theme, { target: { value: 'dark' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Save settings' })); + + expect((await screen.findByRole('alert')).textContent).toContain('changed on disk'); + expect(fetchFn).toHaveBeenCalledTimes(3); + expect((screen.getByLabelText('Theme') as HTMLSelectElement).value).toBe('dark'); + }); + + it('requires explicit API-key restart confirmation before sending the secret', async () => { + const fetchFn = vi + .fn<typeof fetch>() + .mockResolvedValueOnce(settingsResponse(initialSettings, '"etag-a"')) + .mockResolvedValueOnce(settingsResponse({ ...initialSettings, pending_restart: true }, '"etag-b"')); + render(SettingsWorkspace, { client: createAPIClient(fetchFn) }); + + await fireEvent.input(await screen.findByLabelText('New daemon API key'), { + target: { value: 'replacement-key' } + }); + await fireEvent.click(screen.getByRole('button', { name: 'Save settings' })); + expect(fetchFn).toHaveBeenCalledTimes(1); + expect(screen.getByRole('alert').textContent).toContain('confirm'); + + await fireEvent.click(screen.getByLabelText('I understand the API key changes after restart')); + await fireEvent.click(screen.getByRole('button', { name: 'Save settings' })); + await waitFor(() => expect(fetchFn).toHaveBeenCalledTimes(2)); + const request = fetchFn.mock.calls[1]?.[0] as Request; + await expect(request.clone().json()).resolves.toEqual({ + updates: [ + { + key: 'server.api_key', + secret: { action: 'set', value: 'replacement-key' } + } + ], + confirm_api_key_restart: true + }); + }); + + it('hides the Test connection button when no handler is provided', async () => { + render(SettingsWorkspace, { + client: createAPIClient(vi.fn<typeof fetch>(async () => settingsResponse(initialSettings, '"etag-a"'))) + }); + + await screen.findByRole('heading', { name: 'Browser experience' }); + expect(screen.queryByRole('button', { name: 'Test embedding endpoint connection' })).toBeNull(); + }); + + it('offers secret clearing and test-connection hooks without exposing values', async () => { + const onTestConnection = vi.fn(async () => undefined); + const fetchFn = vi + .fn<typeof fetch>() + .mockResolvedValueOnce(settingsResponse(initialSettings, '"etag-a"')) + .mockResolvedValueOnce(settingsResponse({ ...initialSettings, pending_restart: true }, '"etag-b"')); + render(SettingsWorkspace, { client: createAPIClient(fetchFn), onTestConnection }); + + await fireEvent.click(await screen.findByRole('button', { name: 'Clear task integration API key' })); + await fireEvent.click(screen.getByRole('button', { name: 'Test embedding endpoint connection' })); + expect(onTestConnection).toHaveBeenCalledWith('vector.embeddings.endpoint'); + await fireEvent.click(screen.getByRole('button', { name: 'Save settings' })); + const request = fetchFn.mock.calls[1]?.[0] as Request; + await expect(request.clone().json()).resolves.toEqual({ + updates: [{ key: 'integrations.tasks.api_key', secret: { action: 'clear' } }], + confirm_api_key_restart: false + }); + }); + + it('recovers from a rejected save without leaving the form stuck', async () => { + const fetchFn = vi + .fn<typeof fetch>() + .mockResolvedValueOnce(settingsResponse(initialSettings, '"etag-a"')) + .mockRejectedValueOnce(new Error('network unavailable')); + render(SettingsWorkspace, { client: createAPIClient(fetchFn) }); + + await fireEvent.change(await screen.findByLabelText('Theme'), { target: { value: 'dark' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Save settings' })); + + expect((await screen.findByRole('alert')).textContent).toContain('network unavailable'); + expect((screen.getByRole('button', { name: 'Save settings' }) as HTMLButtonElement).disabled).toBe(false); + }); +}); + +function setting( + key: string, + value: unknown, + overrides: Record<string, unknown> = {} +): Record<string, unknown> { + return { + key, + group: 'ignored', + kind: 'string', + value: value === undefined ? undefined : typedValue(value), + restart_required: true, + ...overrides + }; +} + +function typedValue(value: unknown): Record<string, unknown> { + if (typeof value === 'boolean') return { boolean: value }; + if (typeof value === 'number') return Number.isInteger(value) ? { integer: value } : { number: value }; + if (Array.isArray(value)) return { strings: value }; + return { string: value }; +} + +function settingsResponse(body: object, etag: string): Response { + return Response.json(body, { headers: { ETag: etag } }); +} diff --git a/web/src/lib/components/shell/AppShell.svelte b/web/src/lib/components/shell/AppShell.svelte new file mode 100644 index 000000000..affd5da61 --- /dev/null +++ b/web/src/lib/components/shell/AppShell.svelte @@ -0,0 +1,1132 @@ +<script lang="ts"> + import { + CommandPalette, + TopBar, + appShortcuts, + initShortcuts, + type PaletteCommand + } from '@kenn-io/kit-ui'; + import { onDestroy, onMount, tick, type Snippet, untrack } from 'svelte'; + + import type { APIClient } from '../../api/client'; + import type { components } from '../../api/generated/schema'; + import type { + EntryRow, + ExploreColumn, + ExploreGroupDimension, + ExploreGroupRow, + ExploreFileFact, + ExploreSearchMode, + ExploreURLState, + ExploreWorkspace, + FileViewerTarget, + FileSearchSort + } from '../../explore/models'; + import { attachmentSelection, parseAttachmentSelection } from '../../explore/attachment-authority'; + import { filtersForGroup, parseGroupSelection } from '../../explore/group-context'; + import { ExploreLoader } from '../../explore/loader.svelte'; + import { GROUPING_CATALOG, groupingByDimension } from '../../grouping/catalog'; + import { canonicalFingerprint, predicateFingerprint } from '../../explore/selection'; + import { ExploreSelectionState, ExploreState } from '../../explore/state.svelte'; + import { RelationshipsController } from '../../relationships/controller.svelte'; + import { createCommandRegistry, type CommandHandlers } from '../../commands/registry'; + import { + createAppearancePreferences, + type AppearanceDefaults, + type DensityPreference, + type ThemePreference + } from '../../theme/preferences.svelte'; + import ContextBar from '../explore/ContextBar.svelte'; + import GroupTable from '../explore/GroupTable.svelte'; + import SavedViewsWorkspace from '../saved-views/SavedViewsWorkspace.svelte'; + import SourcesWorkspace from '../sources/SourcesWorkspace.svelte'; + import DeletionsWorkspace from '../deletions/DeletionsWorkspace.svelte'; + import FilesWorkspace from '../files/FilesWorkspace.svelte'; + import FileViewer from '../files/FileViewer.svelte'; + import RelationshipsWorkspace from '../relationships/RelationshipsWorkspace.svelte'; + import KeyboardHelp from './KeyboardHelp.svelte'; + import EverythingWorkspace from './EverythingWorkspace.svelte'; + import { EverythingSessionState } from './EverythingSessionState.svelte'; + import { debounce } from '../../util/debounce'; + + interface Props { + client: APIClient; + state?: ExploreState; + enabled?: boolean; + settings?: Snippet; + appearanceDefaults?: AppearanceDefaults; + } + + let { + client, + state: providedState = undefined, + enabled = true, + settings = undefined, + appearanceDefaults = { theme: 'system', density: 'compact' } + }: Props = $props(); + + const ownsState = untrack(() => providedState === undefined); + const exploreState = untrack(() => providedState ?? new ExploreState()); + const ATTACHMENT_HISTORY_MARKER = 'msgvaultAttachmentViewer'; + const DEFAULT_SORT_NOTICE = 'Newest first is the canonical Everything order.'; + + const SEARCH_TYPING_DEBOUNCE_MS = 250; + const debouncedSearchPatch = debounce( + (patch: Partial<ExploreURLState>) => { + // Typing a search query is itself a user-initiated interaction, even + // though it only ever writes committed *draft* state (never a + // commit* wrapper) — see the disarming note below. Firing on its own + // 250ms after the last keystroke, with no navigation ever committing, + // must disarm the one-shot landing fallback exactly like beforeCommit + // does; only beforeCommit's own explicit flush of THIS callback (a + // navigation committing while the patch is still pending) reached it + // before. + arrivedWithoutExploreParam = false; + exploreState.replaceCommittedDraft(patch); + }, + SEARCH_TYPING_DEBOUNCE_MS + ); + + // A pending debounced search patch (People/Domains identity search, Files + // filename search) applies to whatever state is current when it eventually + // fires. If a navigation commits while the patch is still pending, flushing + // it first applies the typed text immediately so it is not silently + // dropped; the navigation patch is then committed on top and wins for the + // fields it touches. Every write to ExploreState funnels through these + // wrappers so the pending patch is flushed before any navigation commits. + // This shared path is also where the one-shot landing fallback (see + // `arrivedWithoutExploreParam` below) gets invalidated: any user-initiated + // navigation past the initial landing means a later explicit visit to the + // Relationships hub must show its own degraded state, not bounce away. + function beforeCommit(): void { + debouncedSearchPatch.flush(); + arrivedWithoutExploreParam = false; + } + + function commitNavigation(patch: Partial<ExploreURLState>): void { + beforeCommit(); + exploreState.commitNavigation(patch); + } + + function replaceCommittedNavigation(patch: Partial<ExploreURLState>): void { + beforeCommit(); + exploreState.replaceCommittedNavigation(patch); + } + + function commitRestorableNavigation(patch: Partial<ExploreURLState>): void { + beforeCommit(); + exploreState.commitRestorableNavigation(patch); + } + + function replaceCommittedRestorableNavigation(patch: Partial<ExploreURLState>): void { + beforeCommit(); + exploreState.replaceCommittedRestorableNavigation(patch); + } + + function commitWorkspace(workspace: ExploreWorkspace): void { + beforeCommit(); + exploreState.commitWorkspace(workspace); + } + + function commitGrouping(dimension: ExploreGroupDimension): void { + beforeCommit(); + exploreState.commitGrouping(dimension); + } + + function commitUngroup(): void { + beforeCommit(); + exploreState.commitUngroup(); + } + + function commitSearch(query: string, mode: ExploreSearchMode): void { + beforeCommit(); + exploreState.commitSearch(query, mode); + } + + const selection = new ExploreSelectionState(); + const appearance = createAppearancePreferences(untrack(() => appearanceDefaults)); + const relationshipsController = new RelationshipsController( + untrack(() => client), + () => Intl.DateTimeFormat().resolvedOptions().timeZone + ); + type APIExploreSelection = components['schemas']['ExploreSelection']; + type ExplorePreflight = components['schemas']['ExplorePreflightResponse']; + const tabs = [ + { id: 'relationships', label: 'Relationships' }, + { id: 'everything', label: 'Everything' }, + { id: 'files', label: 'Files' }, + { id: 'saved_views', label: 'Saved Views' }, + { id: 'sources', label: 'Sources' }, + { id: 'deletions', label: 'Deletions' }, + { id: 'settings', label: 'Settings' } + ]; + // A default landing (no `explore` param at all — the very first visit, + // not a URL that named a workspace) starts on the Relationships hub. If + // the analytical engine turns out to be unavailable, that default silently + // steps down to Everything instead of leaving a first-time visitor on a + // hub that can't rank anything. An explicit URL naming (or renaming, via + // the legacy people/domains rewrite) relationships is a deliberate choice + // and keeps showing the hub's own degraded state instead. + // + // This is a one-shot allowance for the INITIAL landing only: it is + // invalidated (set false) on the first user-initiated navigation — any + // commit* wrapper call (see `beforeCommit` above) or a Back/Forward + // popstate (see `handleHistoryFocus` below) — so a user who lands by + // default, navigates elsewhere, then explicitly clicks back into + // Relationships later is never silently bounced away again. + let arrivedWithoutExploreParam = untrack( + () => new URLSearchParams(window.location.search).get('explore') === null + ); + let landingFallbackApplied = false; + let contextualViewerFile = $state<FileViewerTarget>(); + let contextualViewerReturnFocus = $state<HTMLElement>(); + let previousAttachmentID: number | undefined; + let selectionPreflight = $state<ExplorePreflight>(); + let selectionPreflightController: AbortController | undefined; + let pendingDeletionReview = $state<'explicit' | 'all_matching'>(); + let searchInput = $state<HTMLInputElement>(); + // The loader also drives the Files-shell grouped view (AppShell gates its + // internal load effect on `workspace === 'files' && groupingChain.length > + // 0` in addition to `workspace === 'everything'`), so it is owned here + // rather than by EverythingWorkspace alone. `sortNotice`, `selection`, and + // grid focus restoration are shared shell-wide state the loader cannot own + // itself; it reaches them only through these callbacks, called at the + // exact points the original inline effect wrote to that shared state. + const loader = new ExploreLoader(untrack(() => client), exploreState, { + isEnabled: () => enabled, + onPredicateChange: () => selection.clear(), + onPagingNotice: (message) => { sortNotice = message ?? DEFAULT_SORT_NOTICE; }, + onRestorationFocus: () => focusGrid() + }); + // Owned here (not by EverythingWorkspace) so coverage-poll backoff, the + // exact lexical match-count cache, and loaded inspector group detail + // survive a workspace round-trip: AppShell renders EverythingWorkspace + // behind an {#if}, so it is destroyed and recreated on every switch away + // from and back to 'everything'. + const everythingSession = new EverythingSessionState(); + let paletteOpen = $state(false); + let keyboardHelpOpen = $state(false); + let keyboardHelpScopeCleanup: (() => void) | undefined; + let sortNotice = $state(DEFAULT_SORT_NOTICE); + let editableScopeCleanup: (() => void) | undefined; + let inspectorContentScopeCleanup: (() => void) | undefined; + let previousConversationAnchor = untrack(() => exploreState.current.conversationAnchor); + let conversationFocusGeneration = 0; + let previousSortNoticeWorkspace = untrack(() => exploreState.current.workspace); + + $effect(() => { + const defaults = appearanceDefaults; + untrack(() => appearance.setDefaults(defaults)); + }); + + // sortNotice is shared across workspaces (Everything, Files, etc.). A + // workspace-specific notice (e.g. the Files grouped End-cap pause message) + // must not leak into another workspace after switching — for example via + // the ContextBar "Show as" presentation control, which changes the + // workspace through commitNavigation rather than commitWorkspace. Resetting + // here, keyed off the actual workspace value rather than the commit path, + // covers every route that changes it. Ordinary paging/sorting within the + // same workspace must not clear the notice, so this only fires on a change. + $effect(() => { + const workspace = exploreState.current.workspace; + untrack(() => { + if (workspace !== previousSortNoticeWorkspace) { + previousSortNoticeWorkspace = workspace; + sortNotice = DEFAULT_SORT_NOTICE; + } + }); + }); + + const selectedAttachmentID = $derived(parseAttachmentSelection(exploreState.current.selectedRow)); + const inspectorTargetKey = $derived( + selectedAttachmentID === undefined ? exploreState.current.selectedRow : null + ); + + const apiSelection = $derived.by((): APIExploreSelection | undefined => { + const snapshot = selection.snapshot(); + const authority = loader.result; + if (snapshot.mode === 'explicit') { + if (snapshot.rowKeys.length === 0 || !authority) return undefined; + return { + mode: 'explicit', + predicate: exploreState.predicate(), + row_keys: snapshot.rowKeys, + cache_revision: authority.cacheRevision, + search_provenance: authority.searchProvenance, + ...(authority.candidateSnapshotId + ? { candidate_snapshot_id: authority.candidateSnapshotId } + : {}) + }; + } + return { + mode: 'all_matching', + predicate: snapshot.predicate, + exclusions: snapshot.exclusions, + cache_revision: snapshot.cacheRevision, + search_provenance: snapshot.searchProvenance, + ...(snapshot.candidateSnapshotId + ? { candidate_snapshot_id: snapshot.candidateSnapshotId } + : {}) + }; + }); + + const apiSelectionFingerprint = $derived(canonicalFingerprint(apiSelection)); + + $effect(() => { + const workspace = exploreState.current.workspace; + void apiSelectionFingerprint; + const candidate = untrack(() => apiSelection); + selectionPreflightController?.abort(); + selectionPreflightController = undefined; + selectionPreflight = undefined; + if (!candidate || workspace !== 'everything') return; + const controller = new AbortController(); + selectionPreflightController = controller; + void client.POST('/api/v1/explore/preflight', { + body: { selection: candidate }, + signal: controller.signal + }).then(({ data }) => { + if (!controller.signal.aborted) selectionPreflight = data; + }).catch(() => undefined); + }); + + async function exportSelection(): Promise<void> { + const target = selectionPreflight?.action_targets.find((item) => item.action === 'export'); + if (!target || !apiSelection || selectionPreflight?.count !== 1) return; + try { + const { data, response } = await client.GET('/api/v1/cli/message/raw', { + params: { query: { id: String(target.message_id) } }, + parseAs: 'blob' + }); + if (!response.ok || !(data instanceof Blob)) throw new Error('The authorized raw message export is no longer available.'); + const objectURL = URL.createObjectURL(data); + const anchor = document.createElement('a'); + anchor.href = objectURL; + anchor.download = target.filename; + anchor.click(); + URL.revokeObjectURL(objectURL); + } catch (cause) { + loader.error = cause instanceof Error ? cause.message : 'Unable to export the selected message.'; + } + } + + function openDeletionReview(mode: 'explicit' | 'all_matching'): void { + const candidate = apiSelection; + if (exploreState.current.workspace !== 'everything' || !candidate || candidate.mode !== mode) return; + pendingDeletionReview = mode; + commitWorkspace('deletions'); + } + + async function openSavedView(state: Partial<ExploreURLState>): Promise<void> { + selection.clear(); + replaceCommittedNavigation(state); + await tick(); + const grid = currentGrid(); + if (grid) grid.focus(); + else searchInput?.focus(); + } + + function viewerTargetFromFact(file: ExploreFileFact): FileViewerTarget { + return { + id: file.id, + key: file.key, + entry_key: file.entry_key, + message_id: file.message_id, + conversation_id: file.conversation_id, + filename: file.filename, + mime_type: file.mime_type, + size_bytes: file.size + }; + } + + $effect(() => { + const attachmentID = selectedAttachmentID; + const facts = loader.fileFacts; + if (attachmentID === undefined) { + contextualViewerFile = undefined; + if (previousAttachmentID !== undefined) { + void tick().then(() => (contextualViewerReturnFocus ?? currentGrid())?.focus()); + } + previousAttachmentID = undefined; + return; + } + previousAttachmentID = attachmentID; + if (!untrack(() => contextualViewerReturnFocus)) { + contextualViewerReturnFocus = currentGrid() ?? undefined; + } + const local = facts.find((file) => file.id === attachmentID); + const existing = untrack(() => contextualViewerFile); + if (local) { + if (existing?.key !== local.key) contextualViewerFile = viewerTargetFromFact(local); + } else if (existing?.id !== attachmentID) contextualViewerFile = { id: attachmentID }; + }); + + const conversationAnchorId = $derived.by(() => { + const anchor = exploreState.current.conversationAnchor; + if (anchor === null) return undefined; + const parsed = Number(anchor); + return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined; + }); + + $effect(() => { + const currentAnchor = exploreState.current.conversationAnchor; + const priorAnchor = previousConversationAnchor; + previousConversationAnchor = currentAnchor; + if (priorAnchor === null || currentAnchor !== null) return; + conversationFocusGeneration += 1; + const generation = conversationFocusGeneration; + void tick().then(() => { + if (generation !== conversationFocusGeneration || + exploreState.current.conversationAnchor !== null) return; + const returnControl = document.querySelector<HTMLButtonElement>( + '[data-conversation-return] button' + ); + (returnControl ?? currentGrid())?.focus(); + }); + }); + + function editableTarget(target: EventTarget | null): boolean { + if (!(target instanceof Element)) return false; + const element = target as HTMLElement; + return Boolean( + element?.closest('input, textarea, select, [contenteditable]:not([contenteditable="false"]), iframe') + ); + } + + function preserveNativeControlKey(event: KeyboardEvent): void { + if (!(event.target instanceof Element)) return; + const target = event.target; + const activationControl = target.closest('button, a[href], summary, [role="button"], [role="option"]'); + const directionalControl = target.closest('[role="radio"], [role="option"], select'); + if ((activationControl && (event.key === 'Enter' || event.key === ' ')) || + (directionalControl && event.key.startsWith('Arrow'))) { + // Local control handlers and browser defaults run before this document + // listener; stop only the app-wide shortcut listener on window. + event.stopPropagation(); + } + } + + function syncEditableShortcutScope(target: EventTarget | null): void { + const focused = editableTarget(target); + if (focused && appShortcuts.activeScope() === 'everything-editable') return; + if (!focused && !editableScopeCleanup) return; + editableScopeCleanup?.(); + editableScopeCleanup = focused + ? appShortcuts.pushScope('everything-editable') + : undefined; + } + + function focusGrid(): void { + currentGrid()?.focus(); + } + + async function restoreHistoryFocus(): Promise<void> { + await tick(); + if (exploreState.current.workspace === 'everything') { + if (exploreState.current.selectedRow === null) focusGrid(); + return; + } + document.querySelector<HTMLButtonElement>('button[aria-current="page"]')?.focus(); + } + + async function focusGridAfterUpdate(): Promise<void> { + await tick(); + focusGrid(); + } + + function currentGrid(): HTMLElement | null { + return document.querySelector<HTMLElement>( + '[role="grid"][aria-label="Everything results"], [role="grid"][aria-label^="Everything grouped by"], [role="grid"][aria-label="Files in current context"]' + ); + } + + function relayGridKey(event: KeyboardEvent, key: string): void { + if (event.target instanceof Element && event.target.closest('button, a, summary, [role="button"]')) return; + const grid = currentGrid(); + if (!grid || event.target === grid) return; + grid.focus(); + grid.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: false, cancelable: true })); + } + + async function closeInspector(): Promise<void> { + setInspectorContentFocus(false); + commitNavigation({ selectedRow: null }); + await tick(); + focusGrid(); + } + + function openContainingConversation(_conversationId: number, anchorId: number): void { + setInspectorContentFocus(false); + commitNavigation({ conversationAnchor: String(anchorId) }); + } + + function openFileItem(entryKey: string): void { + if (selectedAttachmentID !== undefined) { + replaceCommittedRestorableNavigation({ + workspace: 'everything', presentation: 'table', selectedRow: entryKey, conversationAnchor: null, + activeRow: null, scrollAnchor: null + }); + return; + } + commitRestorableNavigation({ + workspace: 'everything', presentation: 'table', selectedRow: entryKey, conversationAnchor: null, + activeRow: null, scrollAnchor: null + }); + } + + function openFileConversation(entryKey: string, messageID: number, _conversationID: number): void { + if (selectedAttachmentID !== undefined) { + replaceCommittedRestorableNavigation({ + workspace: 'everything', presentation: 'table', selectedRow: entryKey, + conversationAnchor: String(messageID), + activeRow: null, scrollAnchor: null + }); + return; + } + commitRestorableNavigation({ + workspace: 'everything', presentation: 'table', selectedRow: entryKey, conversationAnchor: String(messageID), + activeRow: null, scrollAnchor: null + }); + } + + function openContextualFile(file: ExploreFileFact): void { + contextualViewerReturnFocus = currentGrid() ?? undefined; + contextualViewerFile = viewerTargetFromFact(file); + commitNavigation({ + selectedRow: attachmentSelection(file.id), + conversationAnchor: null + }); + window.history.replaceState( + { ...(window.history.state && typeof window.history.state === 'object' ? window.history.state : {}), + [ATTACHMENT_HISTORY_MARKER]: file.id }, + '', window.location.href + ); + } + + async function closeContextualViewer(): Promise<void> { + if (window.history.state?.[ATTACHMENT_HISTORY_MARKER] === selectedAttachmentID) { + window.history.back(); + return; + } + replaceCommittedNavigation({ selectedRow: null }); + contextualViewerFile = undefined; + await tick(); + (contextualViewerReturnFocus ?? currentGrid())?.focus(); + } + + function backFromConversation(): void { + setInspectorContentFocus(false); + window.history.back(); + } + + function changeConversationAnchor(anchorId: number): void { + replaceCommittedNavigation({ conversationAnchor: String(anchorId) }); + } + + function setInspectorContentFocus(focused: boolean): void { + inspectorContentScopeCleanup?.(); + inspectorContentScopeCleanup = focused + ? appShortcuts.pushScope('everything-inspector-content') + : undefined; + } + + // The Relationships hub owns its own Esc layering (reading pane → timeline + // → list) and only lets an Esc it didn't consume bubble here once it has + // nothing left to close. This function's branches read/write state that + // belongs to Everything/Files (selectedRow, groupingChain, the conversation + // and attachment viewers) — none of it relevant to the hub — so a bubbled + // Esc must not fall through into clearing leftover state from a workspace + // the user isn't even in (e.g. a groupingChain left behind by + // commitWorkspace, which does not reset it). + function handleEscape(event: KeyboardEvent): void { + if (editableTarget(event.target)) return; + if (exploreState.current.workspace === 'relationships') return; + if (exploreState.current.conversationAnchor !== null) { + backFromConversation(); + return; + } else if (selectedAttachmentID !== undefined) { + void closeContextualViewer(); + } else if (exploreState.current.selectedRow !== null) { + void closeInspector(); + } else if (exploreState.current.groupingChain.length > 0) { + commitUngroup(); + } + focusGrid(); + } + + function openContextControl(kind: 'filters' | 'grouping' | 'sort'): void { + const selector = kind === 'filters' + ? 'button[aria-label="Filters"]' + : kind === 'grouping' + ? '[data-group-picker] button' + : 'button[aria-label="Sort: newest first"]'; + const control = document.querySelector<HTMLButtonElement>(selector); + control?.focus(); + control?.click(); + } + + function fixedSortNotice(): void { + sortNotice = 'Everything remains newest first; reverse order is not supported by the canonical entry API.'; + document.querySelector<HTMLButtonElement>('button[aria-label="Sort: newest first"]')?.focus(); + } + + function navigateInspector(delta: number): void { + if (!exploreState.current.selectedRow || loader.rows.length === 0) return; + const index = loader.rows.findIndex((row) => row.key === exploreState.current.selectedRow); + if (index < 0) return; + const next = loader.rows[Math.max(0, Math.min(loader.rows.length - 1, index + delta))]; + if (next && next.key !== exploreState.current.selectedRow) openRow(next); + } + + function relay(event: KeyboardEvent | undefined, key: string | undefined = undefined): void { + const resolvedKey = key ?? event?.key; + if (!resolvedKey) return; + if (event) { + relayGridKey(event, resolvedKey); + return; + } + queueMicrotask(() => { + const grid = currentGrid(); + if (!grid) return; + grid.focus(); + grid.dispatchEvent(new KeyboardEvent('keydown', { key: resolvedKey, bubbles: false, cancelable: true })); + }); + } + + const commandHandlers: CommandHandlers = { + 'move-next': (event) => relay(event, 'j'), + 'move-previous': (event) => relay(event, 'k'), + 'reader-previous': () => navigateInspector(-1), + 'reader-next': () => navigateInspector(1), + 'page-up': (event) => relay(event, 'PageUp'), + 'page-down': (event) => relay(event, 'PageDown'), + 'first-row': (event) => relay(event, 'Home'), + 'last-row': (event) => relay(event, 'End'), + 'open-row': (event) => relay(event, 'Enter'), + 'close-layer': (event) => handleEscape(event ?? new KeyboardEvent('keydown', { key: 'Escape' })), + 'focus-search': (event) => { if (!editableTarget(event?.target ?? null)) searchInput?.focus(); }, + 'toggle-selection': (event) => relay(event, ' '), + 'select-visible': (event) => relay(event, 'A'), + 'clear-selection': (event) => { + if (event) relay(event, 'x'); + else { + selection.clear(); + queueMicrotask(focusGrid); + } + }, + 'review-delete-selected': () => openDeletionReview('explicit'), + 'review-delete-matching': () => openDeletionReview('all_matching'), + 'open-filters': () => openContextControl('filters'), + 'open-grouping': () => openContextControl('grouping'), + 'change-sort': () => openContextControl('sort'), + 'reverse-sort': fixedSortNotice, + 'open-keyboard-help': () => { keyboardHelpOpen = true; }, + 'open-command-palette': (event) => { if (!editableTarget(event?.target ?? null)) paletteOpen = true; } + }; + const groupingCommands = $derived(GROUPING_CATALOG.flatMap((entry) => { + if (!entry.requestable) return [{ + id: `unavailable:${entry.concept}`, + label: `${entry.label} — unavailable: ${entry.unavailableReason}`, + section: 'Group by', + keywords: `${entry.keywords} ${entry.unavailableReason ?? ''}`, + keys: [], + combos: [], + destructive: false, + review: false, + disabled: true, + run: () => undefined + }]; + return entry.requestDimensions.map((dimension) => ({ + id: `group:${dimension}`, + label: `Group by ${dimension === 'year' ? 'Year' : dimension === 'month' ? 'Month' : entry.label}`, + section: 'Group by', + keywords: entry.keywords, + keys: [], + combos: [], + destructive: false, + review: false, + disabled: exploreState.current.groupingChain.includes(dimension), + run: () => { + commitGrouping(dimension); + void focusGridAfterUpdate(); + } + })); + })); + const commandRegistry = $derived([...createCommandRegistry(commandHandlers), ...groupingCommands]); + const paletteCommands = $derived(commandRegistry.map((command): PaletteCommand => ({ + id: command.id, + label: command.label, + section: command.section, + keywords: command.keywords, + combo: command.combos[0], + disabled: command.disabled + }))); + + function runPalette(command: PaletteCommand): void { + commandRegistry.find(({ id }) => id === command.id)?.run(); + } + + function applyTemporaryTheme(value: string): void { + if (value === 'daemon') appearance.clearTemporary('theme'); + else appearance.setTemporary({ theme: value as ThemePreference }); + } + + function applyTemporaryDensity(value: string): void { + if (value === 'daemon') appearance.clearTemporary('density'); + else appearance.setTemporary({ density: value as DensityPreference }); + } + + function openRow(row: EntryRow): void { + commitNavigation({ selectedRow: row.key }); + } + + function drillGroup(row: ExploreGroupRow): void { + const [dimension, ...remaining] = exploreState.current.groupingChain; + if (!dimension) return; + const filters = filtersForGroup(exploreState.current.filters, dimension, row.key); + if (!filters) { + commitNavigation({ selectedRow: `group:${dimension}:${row.key}` }); + return; + } + commitNavigation({ + filters, + groupingChain: remaining, + selectedRow: exploreState.current.workspace === 'files' + ? null + : `group:${dimension}:${row.key}`, + activeRow: null, + scrollAnchor: null + }); + } + + $effect(() => { + keyboardHelpScopeCleanup?.(); + keyboardHelpScopeCleanup = keyboardHelpOpen + ? appShortcuts.pushScope('everything-keyboard-help') + : undefined; + }); + + // Hydrates the Relationships hub's detail pane from a URL-carried target + // (initial load, or Back/Forward restoring a different one). The hub + // itself never opens a target on its own — RelationshipsWorkspace's own + // tests drive controller.openTarget explicitly — so AppShell, as the + // controller's owner, is the one place that reacts to the URL field. + // Tracked (not untracked) so a predicate change alone — e.g. filters set + // in Everything/Files that carry over when the URL-carried target stays + // the same — re-opens the target too, mirroring how Everything's own + // inspector detail fingerprints include the predicate. Still guarded + // against re-fetching: the ordinary in-hub click path (which calls + // onTargetChange and controller.openTarget together, synchronously, + // before this effect can flush) already opened this exact pair — that + // guard reads controller.lastPredicateFingerprint (set by openTarget + // itself) rather than a copy tracked here, so it stays correct no matter + // which caller opened the target. + $effect(() => { + const workspace = exploreState.current.workspace; + const target = exploreState.current.relationshipTarget; + if (workspace !== 'relationships') return; + if (target === null) { + // URL is truth: once the target is gone (Esc, Back/Forward), drop the + // hub's detail/timeline state too, so the center pane falls back to + // its own empty placeholder instead of keeping the previous person or + // domain on screen. + if (relationshipsController.target !== null) relationshipsController.clearTarget(); + return; + } + const predicate = exploreState.predicate(); + if (relationshipsController.target === target && + relationshipsController.lastPredicateFingerprint === predicateFingerprint(predicate)) { + return; + } + void relationshipsController.openTarget(target, predicate); + }); + + $effect(() => { + if (landingFallbackApplied || !arrivedWithoutExploreParam) return; + if (exploreState.current.workspace !== 'relationships') return; + if (relationshipsController.degraded !== 'cache_unavailable') return; + landingFallbackApplied = true; + // A committed replace, not a transient one: `committed` is what the + // next push rewrites the current history entry from (see + // ExploreState.navigate's 'push' branch). Leaving `committed` behind at + // the degraded 'relationships' landing would mean the very next push + // silently rewrites this entry back to a state the user never actually + // saw, so Back would return to the degraded hub instead of wherever + // they actually came from. + exploreState.replaceCommittedNavigation({ workspace: 'everything' }); + }); + + function openRelationship(participantID: number): void { + commitNavigation({ + workspace: 'relationships', + relationshipFacet: 'people', + relationshipTarget: `cluster:${participantID}`, + relationshipShowAll: false, + analysisTarget: null, + selectedIdentifier: null, + selectedRow: null, + activeRow: null, + conversationAnchor: null, + scrollAnchor: null + }); + } + + onMount(() => { + const detachShortcuts = initShortcuts(); + let disposed = false; + const resyncEditableScope = (): void => { + if (!disposed) syncEditableShortcutScope(document.activeElement); + }; + const handleFocusIn = (event: FocusEvent): void => syncEditableShortcutScope(event.target); + const handleFocusOut = (): void => queueMicrotask(resyncEditableScope); + const handleHistoryFocus = (): void => { + // Back/Forward restores committed state synchronously (ExploreState's + // own popstate listener). A still-pending debounced typing patch must + // not survive to later clobber that restored state, so it is discarded + // rather than flushed. + debouncedSearchPatch.cancel(); + // A Back/Forward navigation is user-initiated, same as any commit* + // wrapper call — it ends the one-shot landing-fallback allowance (see + // `arrivedWithoutExploreParam` above). + arrivedWithoutExploreParam = false; + void restoreHistoryFocus(); + }; + const editableObserver = new MutationObserver(() => queueMicrotask(resyncEditableScope)); + document.addEventListener('focusin', handleFocusIn, true); + document.addEventListener('focusout', handleFocusOut, true); + document.addEventListener('keydown', preserveNativeControlKey); + window.addEventListener('popstate', handleHistoryFocus); + editableObserver.observe(document.documentElement, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['contenteditable'] + }); + resyncEditableScope(); + const unregister = commandRegistry.flatMap((command) => command.combos.map((combo) => + appShortcuts.register(combo, command.run, { description: command.label }) + )); + return () => { + disposed = true; + document.removeEventListener('focusin', handleFocusIn, true); + document.removeEventListener('focusout', handleFocusOut, true); + document.removeEventListener('keydown', preserveNativeControlKey); + window.removeEventListener('popstate', handleHistoryFocus); + editableObserver.disconnect(); + editableScopeCleanup?.(); + editableScopeCleanup = undefined; + keyboardHelpScopeCleanup?.(); + keyboardHelpScopeCleanup = undefined; + for (const remove of unregister.reverse()) remove(); + detachShortcuts(); + }; + }); + + onDestroy(() => { + debouncedSearchPatch.cancel(); + loader.destroy(); + selectionPreflightController?.abort(); + editableScopeCleanup?.(); + editableScopeCleanup = undefined; + keyboardHelpScopeCleanup?.(); + keyboardHelpScopeCleanup = undefined; + setInspectorContentFocus(false); + appearance.destroy(); + relationshipsController.destroy(); + if (ownsState) exploreState.destroy(); + }); +</script> + +<div class="app-shell"> + <TopBar + {tabs} + active={exploreState.current.workspace} + centerTabs + onchange={(workspace) => commitWorkspace(workspace as ExploreWorkspace)} + > + {#snippet left()} + <div class="brand" aria-label="msgvault home"><span aria-hidden="true">◇</span> msgvault</div> + {/snippet} + {#snippet right()} + <div class="appearance-controls" aria-label="Temporary appearance override"> + <label> + <span class="kit-sr-only">Temporary theme</span> + <select + aria-label="Temporary theme" + value={appearance.temporary.theme ?? 'daemon'} + onchange={(event) => applyTemporaryTheme(event.currentTarget.value)} + > + <option value="daemon">Theme: daemon</option> + <option value="system">Theme: system</option> + <option value="light">Theme: light</option> + <option value="dark">Theme: dark</option> + </select> + </label> + <label> + <span class="kit-sr-only">Temporary density</span> + <select + aria-label="Temporary density" + value={appearance.temporary.density ?? 'daemon'} + onchange={(event) => applyTemporaryDensity(event.currentTarget.value)} + > + <option value="daemon">Density: daemon</option> + <option value="compact">Density: compact</option> + <option value="comfortable">Density: comfortable</option> + </select> + </label> + </div> + <span class="archive-state" class:archive-state--error={Boolean(loader.error || loader.unavailable)}> + <span aria-hidden="true">●</span> + {loader.loading ? 'Searching' : loader.error || loader.unavailable ? 'Attention' : 'Local archive'} + </span> + {/snippet} + </TopBar> + + {#if exploreState.current.workspace === 'settings'} + {#if settings}{@render settings()}{/if} + {:else if exploreState.current.workspace === 'saved_views'} + <SavedViewsWorkspace + {client} + currentState={exploreState.current} + selection={selection.snapshot()} + onOpen={(state) => { void openSavedView(state); }} + /> + {:else if exploreState.current.workspace === 'sources'} + <SourcesWorkspace {client} /> + {:else if exploreState.current.workspace === 'deletions'} + <DeletionsWorkspace + {client} + selection={apiSelection} + reviewOnMount={pendingDeletionReview === apiSelection?.mode} + onReviewStarted={() => { pendingDeletionReview = undefined; }} + /> + {:else if exploreState.current.workspace === 'relationships'} + <RelationshipsWorkspace + {client} + controller={relationshipsController} + facet={exploreState.current.relationshipFacet} + target={exploreState.current.relationshipTarget} + showAll={exploreState.current.relationshipShowAll} + filesOpen={exploreState.current.relationshipFiles} + predicate={exploreState.predicate()} + onFacetChange={(relationshipFacet) => commitNavigation({ relationshipFacet })} + onTargetChange={(relationshipTarget) => commitNavigation({ relationshipTarget })} + onShowAllChange={(relationshipShowAll) => commitNavigation({ relationshipShowAll })} + onFilesToggle={(relationshipFiles) => commitNavigation({ relationshipFiles })} + onOpenEverything={() => commitWorkspace('everything')} + onOpenFileItem={openFileItem} + onOpenFileConversation={openFileConversation} + /> + {:else if exploreState.current.workspace === 'files'} + <div class="files-shell"> + <ContextBar + query={exploreState.current.query} + searchMode={exploreState.current.searchMode} + filters={exploreState.current.filters} + groupingChain={exploreState.current.groupingChain} + totalCount={exploreState.current.groupingChain.length > 0 ? loader.result?.totalCount : undefined} + presentation="files" + onPresentationChange={(presentation) => { + if (presentation === 'files') return; + commitNavigation({ + workspace: 'everything', presentation, analysisTarget: null, selectedIdentifier: null, + activeRow: null, selectedRow: null, conversationAnchor: null, scrollAnchor: null + }); + }} + onAddGroup={(dimension) => commitGrouping(dimension)} + onRemoveGroup={(index) => commitNavigation({ + groupingChain: exploreState.current.groupingChain.filter((_, position) => position !== index), + activeRow: null, + scrollAnchor: null + })} + onClearFilters={() => commitNavigation({ filters: [], activeRow: null, scrollAnchor: null })} + /> + <span class="kit-sr-only" role="status" aria-label="Sort status" aria-live="polite">{sortNotice}</span> + {#if exploreState.current.groupingChain.length > 0} + <GroupTable + rows={loader.groupRows} + dimension={exploreState.current.groupingChain[0]!} + workspaceLabel="Files" + loading={loader.loading} + loadingMore={loader.loadingMore} + hasMore={Boolean(loader.nextCursor)} + totalCount={loader.result?.totalCount} + generation={loader.resultGeneration} + error={loader.error} + unavailable={loader.unavailable} + drillable={groupingByDimension(exploreState.current.groupingChain[0]!).requestable} + focusedKey={exploreState.current.activeRow} + inspectedKey={inspectorTargetKey} + scrollAnchor={exploreState.current.scrollAnchor} + restoring={loader.restoring} + onDrill={drillGroup} + onInspect={drillGroup} + onLoadMore={loader.loadMore} + onLoadThroughEnd={loader.loadThroughEnd} + onActiveKey={(activeRow) => exploreState.replaceTransient({ activeRow })} + onScrollAnchor={(key, offset) => exploreState.replaceTransient({ scrollAnchor: { key, offset } })} + onRetry={loader.retry} + /> + {:else} + <FilesWorkspace + {client} + predicate={{ ...exploreState.predicate(), grouping: undefined }} + sort={exploreState.current.fileSort ?? { field: 'occurred_at', direction: 'desc' }} + filenameQuery={exploreState.current.fileFilenameQuery} + mimeFamilies={exploreState.current.fileMIMEFamilies} + activeKey={exploreState.current.activeRow} + selectedKey={exploreState.current.selectedRow} + restorationEpoch={exploreState.restorationEpoch} + onRestorationComplete={(epoch) => { + exploreState.acknowledgeRestoration(epoch); + }} + onSortChange={(fileSort: FileSearchSort) => commitNavigation({ + fileSort, activeRow: null, scrollAnchor: null + })} + onFilenameQueryChange={(fileFilenameQuery) => debouncedSearchPatch({ + fileFilenameQuery, activeRow: null, selectedRow: null, scrollAnchor: null + })} + onMIMEFamiliesChange={(fileMIMEFamilies) => commitNavigation({ + fileMIMEFamilies, activeRow: null, selectedRow: null, scrollAnchor: null + })} + onActiveKey={(activeRow) => exploreState.replaceTransient({ activeRow })} + onSelectedKey={(selectedRow) => selectedRow + ? commitNavigation({ selectedRow }) + : replaceCommittedNavigation({ selectedRow: null })} + onOpenItem={openFileItem} + onOpenConversation={openFileConversation} + /> + {/if} + </div> + {:else} + <EverythingWorkspace + {client} + {exploreState} + {loader} + session={everythingSession} + {selection} + {enabled} + {inspectorTargetKey} + {conversationAnchorId} + {sortNotice} + bind:searchInput + {selectionPreflight} + exportSelection={() => void exportSelection()} + {commitNavigation} + {commitWorkspace} + {commitGrouping} + {commitSearch} + {fixedSortNotice} + {focusGrid} + {openRow} + {drillGroup} + {openFileItem} + {openContextualFile} + closeInspector={() => void closeInspector()} + {setInspectorContentFocus} + {openRelationship} + {openContainingConversation} + {backFromConversation} + {changeConversationAnchor} + /> + {/if} +</div> + +<CommandPalette + bind:open={paletteOpen} + commands={paletteCommands} + ariaLabel="Everything commands" + onrun={runPalette} +/> + +{#if keyboardHelpOpen} + <KeyboardHelp commands={commandRegistry} onclose={() => { keyboardHelpOpen = false; }} /> +{/if} + +{#if contextualViewerFile} + <FileViewer + {client} + file={contextualViewerFile} + returnFocus={contextualViewerReturnFocus} + onClose={() => { void closeContextualViewer(); }} + onOpenItem={(entryKey) => { contextualViewerFile = undefined; openFileItem(entryKey); }} + onOpenConversation={(entryKey, messageID, conversationID) => { + contextualViewerFile = undefined; + openFileConversation(entryKey, messageID, conversationID); + }} + /> +{/if} + +<style> + .app-shell { + display: flex; + min-width: 0; + min-height: 100vh; + height: 100vh; + flex-direction: column; + overflow: hidden; + background: + radial-gradient(circle at 86% -20%, color-mix(in srgb, var(--artifact-ink) 13%, transparent), transparent 34%), + var(--bg-primary); + color: var(--text-primary); + } + + .brand { + display: inline-flex; + align-items: center; + gap: var(--space-3); + color: var(--text-primary); + font-family: var(--font-sans); + font-size: var(--font-size-lg); + font-weight: 700; + letter-spacing: 0.02em; + } + + + .brand span { + color: var(--artifact-ink); + } + + .archive-state { + display: inline-flex; + align-items: center; + gap: var(--space-3); + color: var(--text-muted); + font-size: var(--font-size-xs); + } + + .archive-state span { + color: var(--accent-green); + /* Sizes the "●" status-dot glyph, not text content — intentionally outside the --font-size-* type scale. */ + font-size: 7px; + } + + .archive-state--error span { + color: var(--status-warning-ink); + } + + .appearance-controls { + display: inline-flex; + gap: var(--space-2); + } + + .appearance-controls select { + min-height: var(--control-height); + border: 1px solid var(--control-border); + border-radius: var(--radius-sm); + background: var(--control-bg); + color: var(--text-primary); + font-size: var(--font-size-xs); + } + + .files-shell { + display: flex; + min-height: 0; + flex: 1; + flex-direction: column; + } + + .files-shell > :global(.context-bar) { + margin: var(--space-4) var(--space-6) 0; + } +</style> diff --git a/web/src/lib/components/shell/AppShell.test.ts b/web/src/lib/components/shell/AppShell.test.ts new file mode 100644 index 000000000..2a832c558 --- /dev/null +++ b/web/src/lib/components/shell/AppShell.test.ts @@ -0,0 +1,898 @@ +import { fireEvent, render, screen, waitFor, within } from '@testing-library/svelte'; +import { appShortcuts } from '@kenn-io/kit-ui'; +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import { LOAD_THROUGH_END_MAX_PAGES } from '../../explore/paging'; +import { ExploreState, parseExploreURLState } from '../../explore/state.svelte'; +import AppShell from './AppShell.svelte'; + +function exploreResponse(overrides: Record<string, unknown> = {}) { + return { + rows: [], + total_count: 0, + cache_revision: 'cache-1', + search_provenance: {}, + ...overrides + }; +} + +describe('AppShell', () => { + function entry(index: number) { + return { + key: `message:${index}`, + kind: 'message', + message_type: 'email', + conversation_type: 'email', + title: `Synthetic subject ${index}`, + preview: `Synthetic excerpt ${index}`, + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + source_type: 'synthetic', + participant_labels: ['Example Person'], + participant_ids: [1], + attachment_count: 0, + attachment_size: 0, + has_attachments: false, + deleted_from_source: false, + message_count: 1, + match: {} + }; + } + + it('focuses search with slash and leaves Escape to the search input', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const state = new ExploreState(window); + render(AppShell, { client: createAPIClient(vi.fn()), state, enabled: false }); + + await fireEvent.keyDown(window, { key: '/' }); + const search = screen.getByRole('searchbox', { name: 'Search everything' }); + expect(document.activeElement).toBe(search); + await fireEvent.keyDown(search, { key: 'Escape' }); + expect(document.activeElement).toBe(search); + state.destroy(); + }); + + + it('suspends the shortcut registry without preventing defaults in every editable control', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(vi.fn()), state, enabled: false }); + const handleKeydown = vi.spyOn(appShortcuts, 'handleKeydown'); + const search = screen.getByRole('searchbox', { name: 'Search everything' }); + const textarea = document.createElement('textarea'); + const select = document.createElement('select'); + const editable = document.createElement('div'); + editable.setAttribute('contenteditable', 'plaintext-only'); + editable.tabIndex = 0; + const iframe = document.createElement('iframe'); + document.body.append(textarea, select, editable, iframe); + + for (const control of [search, textarea, select, editable, iframe]) { + control.focus(); + await waitFor(() => expect(appShortcuts.activeScope()).toBe('everything-editable')); + for (const init of [ + { key: 'k', ctrlKey: true }, + { key: 'k', metaKey: true }, + { key: 'd' }, + { key: 'D', shiftKey: true }, + { key: 'Escape' } + ]) { + const event = new KeyboardEvent('keydown', { ...init, bubbles: true, cancelable: true }); + control.dispatchEvent(event); + expect(handleKeydown.mock.results.at(-1)?.value).toBe(false); + expect(event.defaultPrevented).toBe(false); + } + } + + textarea.focus(); + textarea.remove(); + await waitFor(() => expect(appShortcuts.activeScope()).toBe('root')); + + const outside = document.createElement('button'); + document.body.append(outside); + outside.focus(); + const slash = new KeyboardEvent('keydown', { key: '/', bubbles: true, cancelable: true }); + outside.dispatchEvent(slash); + expect(handleKeydown.mock.results.at(-1)?.value).toBe(true); + expect(slash.defaultPrevented).toBe(true); + expect(document.activeElement).toBe(search); + + outside.focus(); + await fireEvent.keyDown(outside, { key: 'd' }); + expect(state.current.workspace).toBe('everything'); + + select.remove(); + editable.remove(); + iframe.remove(); + outside.remove(); + rendered.unmount(); + expect(appShortcuts.activeScope()).toBe('root'); + handleKeydown.mockRestore(); + state.destroy(); + }); + + + it('does not reinstall an editable shortcut scope from a queued callback after unmount', async () => { + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(vi.fn()), state, enabled: false }); + const textarea = document.createElement('textarea'); + document.body.append(textarea); + textarea.focus(); + await waitFor(() => expect(appShortcuts.activeScope()).toBe('everything-editable')); + textarea.dispatchEvent(new FocusEvent('focusout', { bubbles: true })); + rendered.unmount(); + await Promise.resolve(); + expect(appShortcuts.activeScope()).toBe('root'); + textarea.remove(); + state.destroy(); + }); + + + it('guards slash for modifiers and every editable contenteditable value except false', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const state = new ExploreState(window); + render(AppShell, { client: createAPIClient(vi.fn()), state, enabled: false }); + const search = screen.getByRole('searchbox', { name: 'Search everything' }); + const host = document.createElement('div'); + document.body.append(host); + + for (const value of ['', 'true', 'plaintext-only']) { + host.setAttribute('contenteditable', value); + host.focus(); + await fireEvent.keyDown(host, { key: '/' }); + expect(document.activeElement).toBe(host); + } + host.setAttribute('contenteditable', 'false'); + host.focus(); + await waitFor(() => expect(appShortcuts.activeScope()).toBe('root')); + await fireEvent.keyDown(host, { key: '/' }); + expect(document.activeElement).toBe(search); + + for (const modifier of ['ctrlKey', 'metaKey', 'altKey'] as const) { + host.focus(); + await fireEvent.keyDown(host, { key: '/', [modifier]: true }); + expect(document.activeElement).toBe(host); + } + await fireEvent.keyDown(host, { key: '?', shiftKey: true }); + expect(screen.getByRole('dialog', { name: 'Keyboard shortcuts' })).toBeDefined(); + await fireEvent.click(screen.getByRole('button', { name: 'Close' })); + host.remove(); + state.destroy(); + }); + + + it('does not steal table shortcuts from editable controls', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const state = new ExploreState(window); + render(AppShell, { client: createAPIClient(vi.fn()), state, enabled: false }); + const search = screen.getByRole('searchbox', { name: 'Search everything' }); + search.focus(); + + await fireEvent.keyDown(search, { key: 'j' }); + + expect(document.activeElement).toBe(search); + state.destroy(); + }); + + + it('commits workspace navigation to URL history', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const state = new ExploreState(window); + const push = vi.spyOn(window.history, 'pushState'); + render(AppShell, { client: createAPIClient(vi.fn()), state, enabled: false }); + + await fireEvent.click(screen.getByRole('button', { name: 'Settings' })); + + expect(state.current.workspace).toBe('settings'); + expect(push).toHaveBeenCalledOnce(); + state.destroy(); + }); + + + it('acknowledges history restoration immediately in a workspace without a result grid', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const state = new ExploreState(window); + state.replaceTransient({ + workspace: 'settings', activeRow: 'message:stale', + scrollAnchor: { key: 'message:stale', offset: 12 } + }); + const rendered = render(AppShell, { client: createAPIClient(vi.fn()), state, enabled: false }); + + await waitFor(() => expect(state.peekRestorationEpoch()).toBeUndefined()); + const theme = screen.getByRole('combobox', { name: 'Temporary theme' }); + theme.focus(); + await Promise.resolve(); + expect(document.activeElement).toBe(theme); + + rendered.unmount(); + state.destroy(); + }); + + + it('renders every archive management destination from primary navigation', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path.endsWith('/saved-views')) return Response.json({ saved_views: [] }); + if (path.endsWith('/sources/status')) return Response.json({ sources: [] }); + if (path.endsWith('/deletions')) return Response.json({ manifests: [] }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state, enabled: false }); + + for (const [tab, label, workspace] of [ + ['Saved Views', 'Saved Views', 'saved_views'], + ['Sources', 'Sources', 'sources'], + ['Deletions', 'Deletions', 'deletions'] + ] as const) { + await fireEvent.click(screen.getByRole('button', { name: tab })); + expect(await screen.findByRole('main', { name: label })).toBeDefined(); + expect(state.current.workspace).toBe(workspace); + } + + rendered.unmount(); + state.destroy(); + }); + + + it('presents the primary navigation tabs with Relationships first and People/Domains retired', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const state = new ExploreState(window); + const rendered = render(AppShell, { + client: createAPIClient(vi.fn<typeof fetch>(async () => Response.json(exploreResponse()))), + state, enabled: false + }); + + const nav = screen.getByRole('navigation', { name: 'Primary' }); + expect(within(nav).getAllByRole('button').map((button) => button.textContent)).toEqual([ + 'Relationships', 'Everything', 'Files', 'Saved Views', 'Sources', 'Deletions', 'Settings' + ]); + expect(screen.queryByRole('button', { name: 'People' })).toBeNull(); + expect(screen.queryByRole('button', { name: 'Domains' })).toBeNull(); + + rendered.unmount(); + state.destroy(); + }); + + + it('renders the Relationships hub for the default landing workspace', async () => { + window.history.replaceState(null, '', '/'); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path === '/api/v1/relationships') return Response.json({ + rows: [{ + canonical_id: 1, display_label: 'Alice Example', last_at: '2026-07-19T10:00:00Z', member_ids: [1], score: 1, + signals: { + last_interaction_at: '2026-07-19T10:00:00Z', meeting_count: 0, meetings_together: 0, modalities: 1, + received_from_them: 1, sent_count: 1, sent_to_them: 1 + } + }] + }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect(await screen.findByRole('main', { name: 'Relationships' })).toBeDefined(); + expect(await screen.findByText('Alice Example')).toBeDefined(); + expect(state.current.workspace).toBe('relationships'); + + rendered.unmount(); + state.destroy(); + }); + + + it('restores a legacy workspace=people URL into the hub with the facet set', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ + workspace: 'people', analysisTarget: 'person:42' + }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path === '/api/v1/people/42') return Response.json({ + id: 42, display_label: 'Legacy Person', partial_label: false, identifiers: [], + activity_count: 1, file_count: 0, source_counts: [], first_at: '2026-07-19T10:00:00Z', + last_at: '2026-07-19T10:00:00Z', cache_revision: 'cache-legacy' + }); + if (path === '/api/v1/relationships/42/timeline') return Response.json({ + canonical_id: 42, identity_revision: 1, cache_revision: 'cache-legacy', rows: [], total_count: 0 + }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect(await screen.findByRole('main', { name: 'Relationships' })).toBeDefined(); + expect(state.current.workspace).toBe('relationships'); + expect(state.current.relationshipFacet).toBe('people'); + expect(state.current.relationshipTarget).toBe('cluster:42'); + expect(screen.getByRole('radio', { name: 'People' }).getAttribute('aria-checked')).toBe('true'); + expect(await screen.findByRole('heading', { name: 'Legacy Person' })).toBeDefined(); + + rendered.unmount(); + state.destroy(); + }); + + + it('keeps Relationships and shows its degraded state when the URL explicitly names it', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'relationships' }))}`); + const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ + error: 'analytical_cache_unavailable', message: 'The committed analytical cache is unavailable', + readiness: 'stale_schema', recovery_action: 'Rebuild the analytical cache' + }, { status: 503 })); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect(await screen.findByText('Relationship ranking needs the analytical cache/engine')).toBeDefined(); + expect(screen.getByRole('main', { name: 'Relationships' })).toBeDefined(); + expect(state.current.workspace).toBe('relationships'); + expect(screen.queryByRole('main', { name: 'Everything' })).toBeNull(); + + rendered.unmount(); + state.destroy(); + }); + + + it('falls back to Everything from the default Relationships landing when the archive engine is unavailable', async () => { + window.history.replaceState(null, '', '/'); + const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ + error: 'analytical_cache_unavailable', message: 'The committed analytical cache is unavailable', + readiness: 'stale_schema', recovery_action: 'Rebuild the analytical cache' + }, { status: 503 })); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + await waitFor(() => expect(state.current.workspace).toBe('everything')); + expect(await screen.findByRole('main', { name: 'Everything' })).toBeDefined(); + expect(screen.queryByRole('main', { name: 'Relationships' })).toBeNull(); + + rendered.unmount(); + state.destroy(); + }); + + + it('a committed replace for the landing fallback keeps Back from resurrecting the degraded hub', async () => { + window.history.replaceState(null, '', '/'); + const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ + error: 'analytical_cache_unavailable', message: 'The committed analytical cache is unavailable', + readiness: 'stale_schema', recovery_action: 'Rebuild the analytical cache' + }, { status: 503 })); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + await waitFor(() => expect(state.current.workspace).toBe('everything')); + expect(await screen.findByRole('main', { name: 'Everything' })).toBeDefined(); + + // Any later push navigation (state.svelte.ts's `navigate()`, 'push' + // branch) rewrites the CURRENT history entry from `committed` before + // pushing the new one — a transient replace never updates `committed`, + // so this push would otherwise rewrite entry #1 back to the degraded + // 'relationships' landing the user never actually saw past. + state.commitSearch('synthetic', 'full_text'); + await waitFor(() => expect(state.current.query).toBe('synthetic')); + + window.history.back(); + await waitFor(() => expect(state.current.query).toBe('')); + expect(state.current.workspace).toBe('everything'); + expect(screen.queryByRole('main', { name: 'Relationships' })).toBeNull(); + + rendered.unmount(); + state.destroy(); + }); + + + it('keeps a later explicit Relationships visit degraded instead of bouncing, after the landing fallback allowance was already spent', async () => { + window.history.replaceState(null, '', '/'); + let relationshipsDegraded = false; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path === '/api/v1/relationships') { + if (relationshipsDegraded) return Response.json({ + error: 'analytical_cache_unavailable', message: 'The committed analytical cache is unavailable', + readiness: 'stale_schema', recovery_action: 'Rebuild the analytical cache' + }, { status: 503 }); + return Response.json({ + rows: [{ + canonical_id: 1, display_label: 'Alice Example', last_at: '2026-07-19T10:00:00Z', member_ids: [1], score: 1, + signals: { + last_interaction_at: '2026-07-19T10:00:00Z', meeting_count: 0, meetings_together: 0, modalities: 1, + received_from_them: 1, sent_count: 1, sent_to_them: 1 + } + }] + }); + } + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + // The default landing is healthy, so the one-shot landing fallback never + // fires: the hub stays on Relationships and the allowance is still + // unspent going into the next step. + expect(await screen.findByRole('main', { name: 'Relationships' })).toBeDefined(); + expect(await screen.findByText('Alice Example')).toBeDefined(); + expect(state.current.workspace).toBe('relationships'); + + // The user explicitly navigates to Everything via the tab. That + // user-initiated navigation spends the one-shot landing-fallback + // allowance, even though it never fired. + const nav = screen.getByRole('navigation', { name: 'Primary' }); + await fireEvent.click(within(nav).getByRole('button', { name: 'Everything' })); + expect(await screen.findByRole('main', { name: 'Everything' })).toBeDefined(); + + // Now the relationships list starts reporting the cache as unavailable, + // and the user explicitly returns to Relationships via the tab. + relationshipsDegraded = true; + await fireEvent.click(within(nav).getByRole('button', { name: 'Relationships' })); + + // It degrades, but this is no longer the initial landing, so it must + // show its own degraded state rather than bounce back to Everything. + expect(await screen.findByText('Relationship ranking needs the analytical cache/engine')).toBeDefined(); + expect(screen.getByRole('main', { name: 'Relationships' })).toBeDefined(); + expect(state.current.workspace).toBe('relationships'); + expect(screen.queryByRole('main', { name: 'Everything' })).toBeNull(); + + rendered.unmount(); + state.destroy(); + }); + + + it('clears the hub detail pane when the URL target becomes null (Esc / Back)', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ + workspace: 'relationships', relationshipTarget: 'cluster:1' + }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path === '/api/v1/people/1') return Response.json({ + id: 1, display_label: 'Alice Example', partial_label: false, identifiers: [], + activity_count: 1, file_count: 0, source_counts: [], first_at: '2026-07-19T10:00:00Z', + last_at: '2026-07-19T10:00:00Z', cache_revision: 'cache-rel' + }); + if (path === '/api/v1/relationships/1/timeline') return Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', rows: [], total_count: 0 + }); + if (path === '/api/v1/relationships') return Response.json({ rows: [] }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect(await screen.findByRole('heading', { name: 'Alice Example' })).toBeDefined(); + + state.commitNavigation({ relationshipTarget: null }); + + await waitFor(() => expect(screen.queryByRole('heading', { name: 'Alice Example' })).toBeNull()); + expect(screen.getByText('Select a person or domain to see activity.')).toBeDefined(); + expect(state.current.workspace).toBe('relationships'); + + rendered.unmount(); + state.destroy(); + }); + + + it('re-opens the hub target when the predicate changes but the target string does not', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ + workspace: 'relationships', relationshipTarget: 'cluster:1' + }))}`); + const timelineBodies: unknown[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path === '/api/v1/people/1') return Response.json({ + id: 1, display_label: 'Alice Example', partial_label: false, identifiers: [], + activity_count: 1, file_count: 0, source_counts: [], first_at: '2026-07-19T10:00:00Z', + last_at: '2026-07-19T10:00:00Z', cache_revision: 'cache-rel' + }); + if (path === '/api/v1/relationships/1/timeline') { + timelineBodies.push(await request.clone().json()); + return Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', rows: [], total_count: 0 + }); + } + if (path === '/api/v1/relationships') return Response.json({ rows: [] }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + await waitFor(() => expect(timelineBodies).toHaveLength(1)); + expect((timelineBodies[0] as { filters?: unknown[] }).filters).toEqual([]); + + // The target string is unchanged; only the predicate (a filter picked + // up from elsewhere in the app) changes. + state.commitNavigation({ filters: [{ dimension: 'source', values: ['1'] }] }); + + await waitFor(() => expect(timelineBodies).toHaveLength(2)); + expect((timelineBodies[1] as { filters?: unknown[] }).filters).toEqual([{ dimension: 'source', values: ['1'] }]); + + rendered.unmount(); + state.destroy(); + }); + + + it('opening a file from the hub\'s embedded Files pane navigates to Everything with the item selected', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ + workspace: 'relationships', relationshipTarget: 'cluster:1', relationshipFiles: true + }))}`); + const fileRow = { + id: 9, key: 'file:9', entry_key: 'message:42', message_id: 42, conversation_id: 70, + occurred_at: '2026-07-19T10:00:00Z', source_id: 1, source_type: 'gmail', + source_identifier: 'alice@example.com', containing_title: 'Subject', filename: 'report.pdf', + mime_type: 'application/pdf', mime_family: 'pdf', size_bytes: 100, + content_state: 'local_content', content_available: true + }; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path === '/api/v1/people/1') return Response.json({ + id: 1, display_label: 'Alice Example', partial_label: false, identifiers: [], + activity_count: 1, file_count: 1, source_counts: [], first_at: '2026-07-19T10:00:00Z', + last_at: '2026-07-19T10:00:00Z', cache_revision: 'cache-rel' + }); + if (path === '/api/v1/relationships/1/timeline') return Response.json({ + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', rows: [], total_count: 0 + }); + if (path === '/api/v1/people/1/files/search') return Response.json({ + files: [fileRow], total_count: 1, cache_revision: 'cache-rel', search_provenance: {} + }); + if (path === '/api/v1/files/9') return Response.json({ + id: 9, filename: 'report.pdf', mime_type: 'application/pdf', content_state: 'local_content', + message_id: 42, conversation_id: 70 + }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + const row = (await screen.findByText('report.pdf')).closest('[role="row"]')!; + await fireEvent.dblClick(row); + await fireEvent.click(await screen.findByRole('button', { name: 'Open containing item' })); + + await waitFor(() => expect(state.current.workspace).toBe('everything')); + expect(state.current.selectedRow).toBe('message:42'); + + rendered.unmount(); + state.destroy(); + }); + + + it('does not mutate Everything state when Escape bubbles up from an empty Relationships hub', async () => { + window.history.replaceState(null, '', '/'); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path === '/api/v1/relationships') return Response.json({ rows: [] }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + await screen.findByRole('main', { name: 'Relationships' }); + expect(state.current.workspace).toBe('relationships'); + + // A groupingChain left behind by a prior Everything session: commitWorkspace + // does not reset it, and Relationships never uses it — a bubbled Escape + // from the hub must not silently pop it. + state.replaceTransient({ groupingChain: ['source'], selectedRow: 'message:1' }); + + const grid = screen.getByRole('grid', { name: 'Relationship results' }); + grid.focus(); + await fireEvent.keyDown(grid, { key: 'Escape' }); + + expect(state.current.groupingChain).toEqual(['source']); + expect(state.current.selectedRow).toBe('message:1'); + expect(state.current.workspace).toBe('relationships'); + + rendered.unmount(); + state.destroy(); + }); + + + it('debounces filename search typing into one committed state write', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const searchRequests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (new URL(request.url).pathname === '/api/v1/files/search') { + searchRequests.push(request); + return Response.json({ files: [], total_count: 0, cache_revision: 'cache-1', search_provenance: {} }); + } + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceTransient({ workspace: 'files' }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + try { + const input = await screen.findByLabelText('Filter filename'); + await screen.findByRole('grid', { name: 'Files results' }); + const initialRequestCount = searchRequests.length; + await fireEvent.input(input, { target: { value: 'in' } }); + await fireEvent.input(input, { target: { value: 'invoice' } }); + expect(state.current.fileFilenameQuery).toBe(''); + expect(searchRequests.length).toBe(initialRequestCount); + await vi.advanceTimersByTimeAsync(250); + expect(state.current.fileFilenameQuery).toBe('invoice'); + expect(searchRequests.length).toBe(initialRequestCount + 1); + const lastRequest = searchRequests[searchRequests.length - 1]!; + await expect(lastRequest.clone().json()).resolves.toMatchObject({ filename_query: 'invoice' }); + } finally { + rendered.unmount(); + state.destroy(); + vi.useRealTimers(); + } + }); + + + it('flushes a pending debounced filename patch before a MIME-filter navigation commits', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const searchRequests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (new URL(request.url).pathname === '/api/v1/files/search') { + searchRequests.push(request); + return Response.json({ files: [], total_count: 0, cache_revision: 'cache-1', search_provenance: {} }); + } + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceTransient({ workspace: 'files' }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + try { + const input = await screen.findByLabelText('Filter filename'); + const pdfCheckbox = await screen.findByRole('checkbox', { name: 'pdf' }); + await screen.findByRole('grid', { name: 'Files results' }); + const initialRequestCount = searchRequests.length; + // Start a debounced filename-search patch (queued for 250ms) and then, + // still inside that window, commit a navigation. + await fireEvent.input(input, { target: { value: 'invoice' } }); + await fireEvent.click(pdfCheckbox); + // The pending patch flushes immediately so the typed text is not lost; + // the navigation commit applies on top and wins for fileMIMEFamilies. + expect(state.current.fileFilenameQuery).toBe('invoice'); + expect(state.current.fileMIMEFamilies).toEqual(['pdf']); + expect(searchRequests.length).toBe(initialRequestCount + 1); + const lastRequest = searchRequests[searchRequests.length - 1]!; + await expect(lastRequest.clone().json()).resolves.toMatchObject({ + filename_query: 'invoice', mime_families: ['pdf'] + }); + + await vi.advanceTimersByTimeAsync(250); + // Flushing cleared the pending timer, so nothing fires again later. + expect(state.current.fileFilenameQuery).toBe('invoice'); + expect(searchRequests.length).toBe(initialRequestCount + 1); + } finally { + rendered.unmount(); + state.destroy(); + vi.useRealTimers(); + } + }); + + + it('discards a pending debounced filename patch on Back instead of letting it clobber restored state', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const searchRequests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (new URL(request.url).pathname === '/api/v1/files/search') { + searchRequests.push(request); + return Response.json({ files: [], total_count: 0, cache_revision: 'cache-1', search_provenance: {} }); + } + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.commitNavigation({ workspace: 'files' }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + try { + const input = await screen.findByLabelText('Filter filename'); + await screen.findByRole('grid', { name: 'Files results' }); + const initialRequestCount = searchRequests.length; + // Queue a debounced filename-search patch, then navigate back before + // its timer fires. The restored state must not be clobbered later. + await fireEvent.input(input, { target: { value: 'invoice' } }); + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + + expect(state.current.fileFilenameQuery).toBe(''); + await vi.advanceTimersByTimeAsync(250); + expect(state.current.fileFilenameQuery).toBe(''); + expect(searchRequests.length).toBe(initialRequestCount); + } finally { + rendered.unmount(); + state.destroy(); + vi.useRealTimers(); + } + }); + + + it('drills a Files group into a canonical filter without opening a stale group selection', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const path = new URL(request.url).pathname; + if (path.endsWith('/groups')) return Response.json({ + rows: [{ key: '7', label: 'Example source', count: 12, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' }], + total_count: 1, cache_revision: 'cache-1', search_provenance: {} + }); + if (path.endsWith('/files/search')) return Response.json({ + files: [], total_count: 0, cache_revision: 'cache-1', search_provenance: {} + }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceTransient({ + workspace: 'files', groupingChain: ['source'], fileFilenameQuery: 'invoice', fileMIMEFamilies: ['pdf'] + }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + await screen.findByRole('grid', { name: 'Files grouped by source' }); + await screen.findByText('Example source'); + const groupRequest = requests.find((request) => new URL(request.url).pathname.endsWith('/groups'))!; + expect(new URL(groupRequest.url).pathname).toBe('/api/v1/files/groups'); + await expect(groupRequest.clone().json()).resolves.toMatchObject({ + filename_query: 'invoice', mime_families: ['pdf'], grouping: ['source'] + }); + await fireEvent.click(screen.getByRole('button', { name: 'Drill into Example source' })); + await screen.findByRole('grid', { name: 'Files results' }); + + expect(state.current.workspace).toBe('files'); + expect(state.current.filters).toEqual([{ dimension: 'source', values: ['7'] }]); + expect(state.current.groupingChain).toEqual([]); + expect(state.current.selectedRow).toBeNull(); + rendered.unmount(); + state.destroy(); + }); + + + it('clears a stale Everything sortNotice when the workspace changes', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path === '/api/v1/files/search') return Response.json({ + files: [], total_count: 0, cache_revision: 'cache-1', search_provenance: {} + }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await screen.findByRole('grid', { name: 'Everything results' }); + + await fireEvent.keyDown(window, { key: 'r' }); + expect(screen.getByRole('status', { name: 'Sort status' }).textContent) + .toContain('reverse order is not supported'); + + await fireEvent.click(screen.getByRole('button', { name: 'Files' })); + expect(screen.getByRole('status', { name: 'Sort status' }).textContent) + .toBe('Newest first is the canonical Everything order.'); + rendered.unmount(); + state.destroy(); + }); + + + it('announces the End cap outside Everything in the files-shell grouped workspace', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let groupPostCount = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path !== '/api/v1/files/groups') return Response.json(exploreResponse()); + groupPostCount += 1; + const page = groupPostCount; + return Response.json({ + rows: [{ key: String(page), label: `Source ${page}`, count: 1, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' }], + total_count: 10_000, cache_revision: 'cache-1', search_provenance: {}, next_cursor: `cursor-${page}` + }); + }); + const state = new ExploreState(window); + state.replaceTransient({ workspace: 'files', groupingChain: ['source'] }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Files grouped by source' }); + await screen.findByText('Source 1'); + + grid.focus(); + await fireEvent.keyDown(grid, { key: 'End' }); + + await waitFor(() => expect(groupPostCount).toBe(1 + LOAD_THROUGH_END_MAX_PAGES)); + await waitFor(() => + expect(screen.getByRole('status', { name: 'Sort status' }).textContent).toMatch(/press End again to continue/) + ); + rendered.unmount(); + state.destroy(); + }); + + + it('clears a stale Files End-cap notice when switching presentation to Everything, but not on mere paging', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let groupPostCount = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path === '/api/v1/files/groups') { + groupPostCount += 1; + const page = groupPostCount; + return Response.json({ + rows: [ + { key: `${page}a`, label: `Source ${page}a`, count: 1, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' }, + { key: `${page}b`, label: `Source ${page}b`, count: 1, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' } + ], + total_count: 10_000, cache_revision: 'cache-1', search_provenance: {}, next_cursor: `cursor-${page}` + }); + } + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceTransient({ workspace: 'files', groupingChain: ['source'] }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Files grouped by source' }); + await screen.findByText('Source 1a'); + + grid.focus(); + await fireEvent.keyDown(grid, { key: 'End' }); + await waitFor(() => expect(groupPostCount).toBe(1 + LOAD_THROUGH_END_MAX_PAGES)); + await waitFor(() => + expect(screen.getByRole('status', { name: 'Sort status' }).textContent).toMatch(/press End again to continue/) + ); + + // Paging/navigating within the same workspace must not clear the notice. + await fireEvent.keyDown(grid, { key: 'ArrowDown' }); + expect(screen.getByRole('status', { name: 'Sort status' }).textContent).toMatch(/press End again to continue/); + + await fireEvent.change(screen.getByRole('combobox', { name: 'Show as' }), { target: { value: 'table' } }); + await waitFor(() => expect(state.current.workspace).toBe('everything')); + expect(screen.getByRole('status', { name: 'Sort status' }).textContent) + .toBe('Newest first is the canonical Everything order.'); + rendered.unmount(); + state.destroy(); + }); + + + it('shares nested grouping between the context picker and command palette', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const state = new ExploreState(window); + const rendered = render(AppShell, { + client: createAPIClient(vi.fn()), state, enabled: false + }); + + await fireEvent.keyDown(window, { key: 'g' }); + const picker = screen.getByRole('combobox', { name: /Group by/ }); + expect(document.activeElement).toBe(picker); + expect(picker.getAttribute('aria-expanded')).toBe('true'); + await fireEvent.click(screen.getByRole('option', { name: 'People' })); + expect(state.current.groupingChain).toEqual(['participant']); + + await fireEvent.keyDown(window, { key: 'g' }); + await fireEvent.click(screen.getByRole('option', { name: 'Year' })); + expect(state.current.groupingChain).toEqual(['participant', 'year']); + expect(screen.getByLabelText('Active analytical context').textContent).toContain('Group People'); + expect(screen.getByLabelText('Active analytical context').textContent).toContain('Year'); + + await fireEvent.keyDown(window, { key: 'k', ctrlKey: true }); + const palette = screen.getByRole('dialog', { name: 'Everything commands' }); + expect(palette).toBeDefined(); + expect(within(palette).getByRole('option', { name: /Labels — unavailable/ }).getAttribute('aria-disabled')) + .toBe('true'); + const paletteInput = within(palette).getByRole('combobox'); + paletteInput.focus(); + await waitFor(() => expect(appShortcuts.activeScope()).toBe('everything-editable')); + await fireEvent.input(paletteInput, { target: { value: 'group' } }); + await fireEvent.keyDown(paletteInput, { key: 'Escape' }); + expect(screen.getByRole('dialog', { name: 'Everything commands' })).toBeDefined(); + expect((paletteInput as HTMLInputElement).value).toBe(''); + expect(appShortcuts.activeScope()).toBe('everything-editable'); + await fireEvent.keyDown(paletteInput, { key: 'Escape' }); + await waitFor(() => expect(screen.queryByRole('dialog', { name: 'Everything commands' })).toBeNull()); + await waitFor(() => expect(appShortcuts.activeScope()).toBe('root')); + + await fireEvent.keyDown(window, { key: 'f' }); + expect(screen.getByRole('button', { name: 'Filters' }).getAttribute('aria-expanded')).toBe('true'); + await fireEvent.keyDown(window, { key: 'r' }); + expect(screen.getByRole('status', { name: 'Sort status' }).textContent).toContain('newest first'); + rendered.unmount(); + state.destroy(); + }); +}); diff --git a/web/src/lib/components/shell/EverythingSessionState.svelte.ts b/web/src/lib/components/shell/EverythingSessionState.svelte.ts new file mode 100644 index 000000000..38b3b9374 --- /dev/null +++ b/web/src/lib/components/shell/EverythingSessionState.svelte.ts @@ -0,0 +1,36 @@ +import type { InspectorSelection } from '../inspector/Inspector.svelte'; +import { VisibleLexicalCountCache, type SearchCoverageValue } from '../../search/modes'; + +/** + * Session-scoped state for the Everything workspace that must survive a + * workspace round-trip (navigating away from 'everything' and back). + * + * AppShell renders EverythingWorkspace behind an `{#if}` chain alongside the + * other workspaces, so Svelte destroys and recreates it on every such + * switch. Before EverythingWorkspace was split out of AppShell, this state + * lived at AppShell's persistent top level and never lost continuity; this + * container restores that by living in AppShell (instantiated once, like the + * loader/controller) and being passed down as a prop. EverythingWorkspace + * reads and writes through it while keeping its own `$effect`s, in-flight + * `AbortController`s, and timer handles local to each mount. + */ +export class EverythingSessionState { + /** Latest semantic coverage snapshot for the active poll key. */ + coverage = $state<SearchCoverageValue>(); + /** Exponential-backoff attempt count for the current coverage poll key. */ + coveragePollAttempts = 0; + /** `workspace|mode|filtersFingerprint` identifying the active coverage poll. */ + coveragePollKey = ''; + /** Row keys the visible table/timeline currently has on screen, for exact lexical counts. */ + visibleLexicalRowKeys = $state<string[]>([]); + /** LRU cache of exact lexical match counts, keyed by query/revision/rows. */ + readonly lexicalCountCache = new VisibleLexicalCountCache(128); + /** Maps a (lexicalRevision, predicateFingerprint) pair to its last-seen canonical query hash. */ + readonly canonicalQueryHashes = new Map<string, string>(); + /** Loaded detail for the currently inspected group row, if any. */ + inspectorGroupDetail = $state<InspectorSelection>(); + /** Generation counter guarding the inspector-detail fetch against stale/aborted responses. */ + inspectorDetailGeneration = 0; + /** `predicateFingerprint|group:dimension:key` that `inspectorGroupDetail` was loaded under. */ + inspectorDetailFingerprint = ''; +} diff --git a/web/src/lib/components/shell/EverythingWorkspace.svelte b/web/src/lib/components/shell/EverythingWorkspace.svelte new file mode 100644 index 000000000..46c6596be --- /dev/null +++ b/web/src/lib/components/shell/EverythingWorkspace.svelte @@ -0,0 +1,794 @@ +<script lang="ts"> + import { Button, KbdBadge, SearchInput } from '@kenn-io/kit-ui'; + import { onDestroy, untrack } from 'svelte'; + + import type { APIClient } from '../../api/client'; + import type { components } from '../../api/generated/schema'; + import type { + EntryRow, + AllMatchingExploreSelection, + ExploreCacheUnavailable, + ExploreColumn, + ExploreFileFact, + ExploreGroupDimension, + ExploreGroupRow, + ExploreSearchMode, + ExploreURLState, + ExploreWorkspace + } from '../../explore/models'; + import { createExploreAPI } from '../../explore/api'; + import { filtersForGroup, parseGroupSelection } from '../../explore/group-context'; + import type { ExploreLoader } from '../../explore/loader.svelte'; + import { groupingByDimension } from '../../grouping/catalog'; + import { canonicalFingerprint, createAllMatchingSelection, predicateFingerprint } from '../../explore/selection'; + import type { ExploreSelectionState, ExploreState } from '../../explore/state.svelte'; + import ContextBar from '../explore/ContextBar.svelte'; + import EverythingTable from '../explore/EverythingTable.svelte'; + import FilesPresentation from '../explore/FilesPresentation.svelte'; + import GroupTable from '../explore/GroupTable.svelte'; + import SelectionBar from '../explore/SelectionBar.svelte'; + import PersonTimeline from '../people/PersonTimeline.svelte'; + import SearchCoverage from '../search/SearchCoverage.svelte'; + import SearchModeControl from '../search/SearchModeControl.svelte'; + import Inspector, { + type InspectorSelection, + type InspectorStatus + } from '../inspector/Inspector.svelte'; + import type { SearchCoverageAction } from '../../search/modes'; + import type { EverythingSessionState } from './EverythingSessionState.svelte'; + + type ExplorePreflight = components['schemas']['ExplorePreflightResponse']; + + interface Props { + client: APIClient; + exploreState: ExploreState; + loader: ExploreLoader; + session: EverythingSessionState; + selection: ExploreSelectionState; + enabled: boolean; + inspectorTargetKey: string | null; + conversationAnchorId: number | undefined; + sortNotice: string; + searchInput?: HTMLInputElement; + selectionPreflight: ExplorePreflight | undefined; + exportSelection: () => void; + commitNavigation: (patch: Partial<ExploreURLState>) => void; + commitWorkspace: (workspace: ExploreWorkspace) => void; + commitGrouping: (dimension: ExploreGroupDimension) => void; + commitSearch: (query: string, mode: ExploreSearchMode) => void; + fixedSortNotice: () => void; + focusGrid: () => void; + openRow: (row: EntryRow) => void; + drillGroup: (row: ExploreGroupRow) => void; + openFileItem: (entryKey: string) => void; + openContextualFile: (file: ExploreFileFact) => void; + closeInspector: () => void; + setInspectorContentFocus: (focused: boolean) => void; + openRelationship: (participantID: number) => void; + openContainingConversation: (conversationId: number, anchorId: number) => void; + backFromConversation: () => void; + changeConversationAnchor: (anchorId: number) => void; + } + + let { + client, + exploreState, + loader, + session, + selection, + enabled, + inspectorTargetKey, + conversationAnchorId, + sortNotice, + searchInput = $bindable(undefined), + selectionPreflight, + exportSelection, + commitNavigation, + commitWorkspace, + commitGrouping, + commitSearch, + fixedSortNotice, + focusGrid, + openRow, + drillGroup, + openFileItem, + openContextualFile, + closeInspector, + setInspectorContentFocus, + openRelationship, + openContainingConversation, + backFromConversation, + changeConversationAnchor + }: Props = $props(); + + const api = createExploreAPI(untrack(() => client)); + + // coverage, coveragePollAttempts/coveragePollKey, visibleLexicalRowKeys, + // lexicalCountCache/canonicalQueryHashes, and inspectorGroupDetail/ + // inspectorDetailGeneration/inspectorDetailFingerprint live on `session` + // (owned by AppShell) so they survive this component being destroyed and + // recreated on a workspace round-trip. Timer handles, AbortControllers, + // and other in-flight-request bookkeeping below stay local: they are + // cleaned up in `onDestroy` and re-armed fresh on the next mount. + let coverageRetryRevision = $state(0); + let coveragePollTimer: ReturnType<typeof setTimeout> | undefined; + let coverageRequestGeneration = 0; + let coverageController: AbortController | undefined; + let lexicalCountController: AbortController | undefined; + let lexicalCountTimer: ReturnType<typeof setTimeout> | undefined; + let lexicalCountRequestKey = ''; + let inspectorDetailLoading = $state(false); + let inspectorDetailError = $state(''); + let inspectorDetailUnavailable = $state<ExploreCacheUnavailable>(); + let inspectorDetailController: AbortController | undefined; + + const allMatchingSelection = $derived.by((): AllMatchingExploreSelection | undefined => { + if (!loader.result || loader.loading || loader.loadingMore) return undefined; + const predicate = exploreState.predicate(); + if (exploreState.current.presentation !== 'table' || exploreState.current.groupingChain.length > 0) return undefined; + if (loader.resultFingerprint !== predicateFingerprint(predicate)) return undefined; + // requestGeneration and resultGeneration are always equal while this + // workspace is mounted: the loader syncs them together at request + // start, and only its early-return branch (a different workspace) + // advances one without the other. + return createAllMatchingSelection(predicate, loader.result, loader.resultGeneration); + }); + + const inspectedSelection = $derived.by((): InspectorSelection | undefined => { + const selected = inspectorTargetKey; + if (!selected) return undefined; + const entry = loader.rows.find((row) => row.key === selected); + if (entry) return { kind: 'entry', row: entry }; + const group = parseGroupSelection(selected); + return group && session.inspectorGroupDetail?.kind === 'group' && + session.inspectorGroupDetail.dimension === group.dimension && session.inspectorGroupDetail.key === group.key + ? session.inspectorGroupDetail + : undefined; + }); + + const inspectorState = $derived.by((): { + status: InspectorStatus; + message: string; + unavailable?: ExploreCacheUnavailable; + } => { + const selected = inspectorTargetKey; + if (!selected || inspectedSelection) return { status: 'ready', message: '' }; + if (parseGroupSelection(selected)) { + if (inspectorDetailUnavailable) { + return { + status: 'unavailable', + message: inspectorDetailUnavailable.message, + unavailable: inspectorDetailUnavailable + }; + } + if (inspectorDetailLoading || !inspectorDetailError) return { status: 'loading', message: '' }; + return { + status: 'missing', + message: inspectorDetailError + }; + } + if (loader.unavailable) { + return { status: 'unavailable', message: loader.unavailable.message, unavailable: loader.unavailable }; + } + if (loader.loading || loader.restoring) return { status: 'loading', message: '' }; + return { + status: loader.error ? 'error' : 'missing', + message: loader.error || 'The selected entry is no longer available in this context.' + }; + }); + + const inspectorPredicateFingerprint = $derived(predicateFingerprint(exploreState.predicate())); + + $effect(() => { + const target = parseGroupSelection(exploreState.current.selectedRow); + const fingerprint = inspectorPredicateFingerprint; + // Tracked, not untracked like `predicate` below: a cache rebuild + // invalidates a stale detail whether it happened before this mount + // (`session` persisted the fingerprint across a prior workspace + // round-trip that destroyed and recreated this component) or resolves + // just after it, while this component was already waiting on it. Gated + // below on `cacheRevision === undefined` so this settles the reuse + // decision once per predicate/target rather than firing an extra, + // wasted request the instant the main list's own load transitions from + // "revision not yet known" (a fresh mount, before its own list request + // has resolved even once) to whatever revision it turns out to be. + const cacheRevision = loader.result?.cacheRevision; + const predicate = untrack(() => exploreState.predicate()); + if (target && cacheRevision === undefined) { + inspectorDetailLoading = true; + inspectorDetailError = ''; + inspectorDetailUnavailable = undefined; + return; + } + const loadedKey = target ? `${fingerprint}|${cacheRevision}|group:${target.dimension}:${target.key}` : ''; + // A workspace round-trip destroys and recreates this component, which + // re-creates this effect and would otherwise unconditionally discard and + // refetch the group detail below. If a previous mount already loaded + // this exact group under this exact predicate — persisted on `session`, + // which survives the round-trip — reuse it instead. Read through + // `untrack` so this effect's dependencies stay limited to the selection + // and predicate (matching the pre-persistence behavior); this effect + // also writes `session.inspectorGroupDetail`/`inspectorDetailFingerprint` + // below, and a tracked read of the same fields here would make the + // effect re-trigger itself on every write. + const canReuse = untrack(() => Boolean( + target && loadedKey === session.inspectorDetailFingerprint && session.inspectorGroupDetail + )); + if (canReuse) { + inspectorDetailLoading = false; + inspectorDetailError = ''; + inspectorDetailUnavailable = undefined; + return; + } + session.inspectorDetailGeneration += 1; + inspectorDetailController?.abort(); + inspectorDetailController = undefined; + session.inspectorGroupDetail = undefined; + session.inspectorDetailFingerprint = ''; + inspectorDetailError = ''; + inspectorDetailUnavailable = undefined; + if (!target) { + inspectorDetailLoading = false; + return; + } + const filters = filtersForGroup(predicate.filters ?? [], target.dimension, target.key); + if (!filters) { + inspectorDetailLoading = false; + inspectorDetailError = 'The selected group is not available in this context.'; + return; + } + const generation = session.inspectorDetailGeneration; + const controller = new AbortController(); + inspectorDetailController = controller; + inspectorDetailLoading = true; + const detailPredicate = { + ...predicate, + cursor: undefined, + candidate_snapshot_id: undefined, + grouping: undefined, + filters, + limit: 1, + presentation: 'table' as const + }; + void api.groups(detailPredicate, target.dimension, controller.signal) + .then((loaded) => { + if (generation !== session.inspectorDetailGeneration || controller.signal.aborted) return; + if (loaded.status === 'unavailable') { + inspectorDetailUnavailable = loaded.unavailable; + return; + } + const row = loaded.result.rows.find((candidate) => candidate.key === target.key); + if (!row) { + inspectorDetailError = 'The selected group is no longer available in this context.'; + return; + } + session.inspectorGroupDetail = { + kind: 'group', + dimension: target.dimension, + key: target.key, + label: row.label, + count: row.count, + estimatedBytes: row.estimated_bytes, + latestAt: row.latest_at + }; + session.inspectorDetailFingerprint = loadedKey; + }) + .catch((cause: unknown) => { + if (generation !== session.inspectorDetailGeneration || controller.signal.aborted) return; + inspectorDetailError = cause instanceof Error ? cause.message : 'Could not restore the selected group.'; + }) + .finally(() => { + if (generation === session.inspectorDetailGeneration) inspectorDetailLoading = false; + }); + }); + + const coverageFiltersFingerprint = $derived(canonicalFingerprint(exploreState.current.filters)); + + $effect(() => { + const workspace = exploreState.current.workspace; + const mode = exploreState.current.searchMode; + void coverageRetryRevision; + void coverageFiltersFingerprint; + const filters = untrack(() => exploreState.current.filters); + const pollKey = `${workspace}|${mode}|${coverageFiltersFingerprint}`; + if (pollKey !== session.coveragePollKey) { + session.coveragePollKey = pollKey; + session.coveragePollAttempts = 0; + } + if (coveragePollTimer !== undefined) { + clearTimeout(coveragePollTimer); + coveragePollTimer = undefined; + } + coverageController?.abort(); + const generation = ++coverageRequestGeneration; + if (!enabled || workspace !== 'everything' || mode === 'full_text') { + session.coverage = undefined; + return; + } + coverageController = new AbortController(); + const controller = coverageController; + void api.coverage(filters, controller.signal) + .then((loaded) => { + if (generation !== coverageRequestGeneration || controller.signal.aborted) return; + session.coverage = loaded; + if (loaded.status === 'initializing') { + const delay = Math.min(10_000, 1_000 * 2 ** session.coveragePollAttempts); + session.coveragePollAttempts += 1; + coveragePollTimer = setTimeout(() => { + coveragePollTimer = undefined; + if (generation === coverageRequestGeneration) coverageRetryRevision += 1; + }, delay); + } else { + session.coveragePollAttempts = 0; + } + }) + .catch((cause: unknown) => { + if (generation !== coverageRequestGeneration || controller.signal.aborted) return; + if (cause instanceof DOMException && cause.name === 'AbortError') return; + session.coveragePollAttempts = 0; + session.coverage = { + eligible_count: session.coverage?.eligible_count ?? 0, + embedded_count: session.coverage?.embedded_count ?? 0, + percentage: session.coverage?.percentage ?? 0, + cache_revision: session.coverage?.cache_revision ?? '', + status: 'unavailable', + detail: cause instanceof Error ? cause.message : 'Semantic coverage could not be loaded.', + actions: ['retry'] + }; + }); + }); + + $effect(() => { + const predicate = exploreState.predicate(); + const currentResult = loader.result; + const rowKeys = [...session.visibleLexicalRowKeys].sort(); + if (lexicalCountTimer !== undefined) { + clearTimeout(lexicalCountTimer); + lexicalCountTimer = undefined; + } + if (!currentResult || exploreState.current.presentation === 'files' || !predicate.query || + (predicate.search_mode !== 'full_text' && predicate.search_mode !== 'hybrid') || + loader.resultFingerprint !== predicateFingerprint(predicate) || rowKeys.length === 0) { + lexicalCountController?.abort(); + lexicalCountController = undefined; + lexicalCountRequestKey = ''; + return; + } + const lexicalRevision = currentResult.searchProvenance.lexical_index_revision; + if (!lexicalRevision) return; + const countPredicateFingerprint = predicateFingerprint(predicate); + const canonicalHashKey = `${lexicalRevision}\u0000${countPredicateFingerprint}`; + session.lexicalCountCache.invalidateRevision(currentResult.cacheRevision); + const canonicalQueryHash = session.canonicalQueryHashes.get(canonicalHashKey); + const cacheKey = session.lexicalCountCache.key({ + query: predicate.query, + ...(canonicalQueryHash ? { canonicalQueryHash } : {}), + cacheRevision: currentResult.cacheRevision, + lexicalRevision, + predicateFingerprint: countPredicateFingerprint, + rowKeys + }); + const cached = canonicalQueryHash ? session.lexicalCountCache.get(cacheKey) : undefined; + if (cached) { + lexicalCountController?.abort(); + lexicalCountController = undefined; + lexicalCountRequestKey = ''; + applyLexicalCounts(cached); + return; + } + if (lexicalCountRequestKey === cacheKey) return; + lexicalCountController?.abort(); + lexicalCountController = undefined; + lexicalCountRequestKey = cacheKey; + const countPredicate = { + ...predicate, + ...(currentResult.candidateSnapshotId + ? { candidate_snapshot_id: currentResult.candidateSnapshotId } + : {}) + }; + lexicalCountTimer = setTimeout(() => { + lexicalCountTimer = undefined; + const controller = new AbortController(); + lexicalCountController = controller; + void api.matchCounts(countPredicate, rowKeys, controller.signal) + .then((loaded) => { + if (controller.signal.aborted || loaded.cacheRevision !== currentResult.cacheRevision || + loaded.lexicalRevision !== lexicalRevision) return; + if (predicateFingerprint(exploreState.predicate()) !== countPredicateFingerprint) return; + session.canonicalQueryHashes.delete(canonicalHashKey); + while (session.canonicalQueryHashes.size >= 128) { + const oldest = session.canonicalQueryHashes.keys().next().value; + if (oldest === undefined) break; + session.canonicalQueryHashes.delete(oldest); + } + session.canonicalQueryHashes.set(canonicalHashKey, loaded.canonicalQueryHash); + const exactKey = session.lexicalCountCache.key({ + query: predicate.query!, canonicalQueryHash: loaded.canonicalQueryHash, + cacheRevision: loaded.cacheRevision, lexicalRevision: loaded.lexicalRevision, + predicateFingerprint: countPredicateFingerprint, rowKeys + }); + session.lexicalCountCache.set(exactKey, loaded.counts, loaded.cacheRevision); + applyLexicalCounts(loaded.counts); + }) + .catch((cause: unknown) => { + if (!(cause instanceof DOMException && cause.name === 'AbortError')) { + // Exact counts are supplementary. Search results retain their own named state. + } + }) + .finally(() => { + if (lexicalCountController === controller) lexicalCountController = undefined; + if (lexicalCountRequestKey === cacheKey) lexicalCountRequestKey = ''; + }); + }, 60); + }); + + function applyLexicalCounts(counts: Record<string, number>): void { + // A persisted cache hit calls this synchronously from inside the + // lexicalCount $effect below (the miss path only reaches it from an + // async `.then`, outside that effect's tracked window). Reading + // `loader.rows` untracked keeps this effect from depending on the very + // state it writes here, which would otherwise re-trigger itself forever. + const rows = untrack(() => loader.rows); + loader.rows = rows.map((row) => counts[row.key] === undefined ? row : { + ...row, + match: { ...row.match, lexical_match_count: counts[row.key] } + }); + } + + async function handleCoverageAction(action: SearchCoverageAction): Promise<void> { + if (action === 'retry') { + coverageRetryRevision += 1; + return; + } + try { + await api.runCoverageAction(action, session.coverage?.status ?? 'unavailable'); + coverageRetryRevision += 1; + } catch (cause) { + session.coverage = { + eligible_count: session.coverage?.eligible_count ?? 0, + embedded_count: session.coverage?.embedded_count ?? 0, + percentage: session.coverage?.percentage ?? 0, + cache_revision: session.coverage?.cache_revision ?? '', + status: 'unavailable', + detail: cause instanceof Error ? cause.message : 'The semantic index action failed.', + actions: ['retry'] + }; + } + } + + function submitSearch(event: SubmitEvent): void { + event.preventDefault(); + commitSearch(exploreState.current.query.trim(), exploreState.current.searchMode); + focusGrid(); + } + + function inspectGroup(row: ExploreGroupRow): void { + const dimension = exploreState.current.groupingChain[0]; + if (dimension) commitNavigation({ selectedRow: `group:${dimension}:${row.key}` }); + } + + onDestroy(() => { + coverageRequestGeneration += 1; + coverageController?.abort(); + if (coveragePollTimer !== undefined) clearTimeout(coveragePollTimer); + if (lexicalCountTimer !== undefined) clearTimeout(lexicalCountTimer); + lexicalCountController?.abort(); + session.inspectorDetailGeneration += 1; + inspectorDetailController?.abort(); + }); +</script> + +<main class="everything-workspace" aria-label="Everything"> + <header class="workspace-header"> + <div> + <p class="eyebrow">Analytical workspace</p> + <h1>Everything</h1> + </div> + <p class="result-count" aria-live="polite"> + {#if loader.result?.totalCount !== undefined} + {loader.result.totalCount.toLocaleString()} items + {:else if loader.result?.candidatePoolSaturated} + Candidate pool capped + {:else} + Modality-neutral archive + {/if} + </p> + </header> + + <form class="search-bar" role="search" aria-label="Search Everything" onsubmit={submitSearch}> + <div class="query-control"> + <SearchInput + id="everything-search" + bind:inputEl={searchInput} + value={exploreState.current.query} + ariaLabel="Search everything" + placeholder="Search people, conversations, events, and files…" + block + oninput={(value) => exploreState.replaceSearchDraft(value, exploreState.current.searchMode)} + /> + </div> + <SearchModeControl + requestedMode={exploreState.current.searchMode} + status={session.coverage?.status} + error={loader.error} + onchange={(mode: ExploreSearchMode) => exploreState.replaceSearchDraft( + exploreState.current.query, + mode + )} + /> + <Button type="submit" label="Search" tone="info" surface="solid" /> + </form> + + {#if session.coverage} + <SearchCoverage + requestedMode={exploreState.current.searchMode} + coverage={session.coverage} + onaction={handleCoverageAction} + /> + {/if} + + <ContextBar + query={exploreState.current.query} + searchMode={exploreState.current.searchMode} + filters={exploreState.current.filters} + groupingChain={exploreState.current.groupingChain} + totalCount={loader.result?.totalCount} + presentation={exploreState.current.presentation} + onPresentationChange={(presentation) => commitNavigation({ + presentation, activeRow: null, selectedRow: null, scrollAnchor: null + })} + onAddGroup={(dimension) => commitGrouping(dimension)} + onRemoveGroup={(index) => commitNavigation({ + groupingChain: exploreState.current.groupingChain.filter((_, position) => position !== index), + activeRow: null, + scrollAnchor: null + })} + onClearFilters={() => commitNavigation({ filters: [], activeRow: null, scrollAnchor: null })} + onSort={fixedSortNotice} + /> + <span class="kit-sr-only" role="status" aria-label="Sort status" aria-live="polite">{sortNotice}</span> + + <div + class="results-and-inspector" + class:results-and-inspector--pinned={Boolean(inspectorTargetKey)} + > + <div class="results-primary"> + {#if exploreState.current.groupingChain.length > 0} + <GroupTable + rows={loader.groupRows} + dimension={exploreState.current.groupingChain[0]!} + loading={loader.loading} + loadingMore={loader.loadingMore} + hasMore={Boolean(loader.nextCursor)} + totalCount={loader.result?.totalCount} + generation={loader.resultGeneration} + error={loader.error} + unavailable={loader.unavailable} + drillable={groupingByDimension(exploreState.current.groupingChain[0]!).requestable} + focusedKey={exploreState.current.activeRow} + inspectedKey={inspectorTargetKey} + scrollAnchor={exploreState.current.scrollAnchor} + restoring={loader.restoring} + onDrill={drillGroup} + onInspect={inspectGroup} + onLoadMore={loader.loadMore} + onLoadThroughEnd={loader.loadThroughEnd} + onActiveKey={(activeRow) => exploreState.replaceTransient({ activeRow })} + onScrollAnchor={(key, offset) => exploreState.replaceTransient({ scrollAnchor: { key, offset } })} + onRetry={loader.retry} + /> + {:else if exploreState.current.presentation === 'files'} + <FilesPresentation + files={loader.fileFacts} + loading={loader.loading} + loadingMore={loader.loadingMore} + hasMore={Boolean(loader.nextCursor)} + totalCount={loader.result?.totalCount} + generation={loader.resultGeneration} + error={loader.error} + unavailable={loader.unavailable} + focusedKey={exploreState.current.activeRow} + scrollAnchor={exploreState.current.scrollAnchor} + restoring={loader.restoring} + onOpenFile={openContextualFile} + onOpenItem={openFileItem} + onActiveKey={(activeRow) => exploreState.replaceTransient({ activeRow })} + onScrollAnchor={(key, offset) => exploreState.replaceTransient({ scrollAnchor: { key, offset } })} + onLoadMore={loader.loadMore} + onRetry={loader.retry} + /> + {:else} + <SelectionBar + {selection} + totalCount={loader.result?.totalCount} + allMatching={allMatchingSelection} + preflight={selectionPreflight} + onExport={exportSelection} + /> + {#if exploreState.current.presentation === 'timeline'} + <PersonTimeline + rows={loader.rows} + {selection} + loading={loader.loading} + loadingMore={loader.loadingMore} + hasMore={Boolean(loader.nextCursor)} + totalCount={loader.result?.totalCount} + generation={loader.resultGeneration} + unavailable={loader.unavailable} + error={loader.error} + focusedKey={exploreState.current.activeRow} + inspectedKey={inspectorTargetKey} + scrollAnchor={exploreState.current.scrollAnchor} + restoring={loader.restoring} + onOpen={openRow} + onScrollAnchor={(key, offset) => exploreState.replaceTransient({ scrollAnchor: { key, offset } })} + onLoadMore={loader.loadMore} + onLoadThroughEnd={loader.loadThroughEnd} + onActiveKey={(activeRow) => exploreState.replaceTransient({ activeRow })} + onVisibleRows={(rowKeys) => { session.visibleLexicalRowKeys = rowKeys; }} + onRetry={loader.retry} + /> + {:else} + <EverythingTable + rows={loader.rows} + {selection} + columns={exploreState.current.columns} + columnWidths={exploreState.current.columnWidths} + focusedKey={exploreState.current.activeRow} + inspectedKey={inspectorTargetKey} + scrollAnchor={exploreState.current.scrollAnchor} + restoring={loader.restoring} + loading={loader.loading} + loadingMore={loader.loadingMore} + hasMore={Boolean(loader.nextCursor)} + totalCount={loader.result?.totalCount} + generation={loader.resultGeneration} + unavailable={loader.unavailable} + error={loader.error} + onOpen={openRow} + onColumnsChange={(columns: ExploreColumn[]) => exploreState.replaceTransient({ columns })} + onScrollAnchor={(key, offset) => exploreState.replaceTransient({ scrollAnchor: { key, offset } })} + onLoadMore={loader.loadMore} + onLoadThroughEnd={loader.loadThroughEnd} + onActiveKey={(activeRow) => exploreState.replaceTransient({ activeRow })} + onVisibleRows={(rowKeys) => { session.visibleLexicalRowKeys = rowKeys; }} + onRetry={loader.retry} + /> + {/if} + {/if} + </div> + {#if inspectorTargetKey} + <Inspector + {client} + selection={inspectedSelection} + targetKey={inspectorTargetKey} + status={inspectorState.status} + statusMessage={inspectorState.message} + unavailable={inspectorState.unavailable} + predicate={exploreState.predicate()} + width={exploreState.current.inspectorWidth} + onClose={closeInspector} + onWidthChange={(inspectorWidth) => exploreState.replaceTransient({ inspectorWidth })} + onContentFocusChange={setInspectorContentFocus} + onOpenSettings={() => commitWorkspace('settings')} + onOpenRelationship={openRelationship} + {conversationAnchorId} + onViewConversation={openContainingConversation} + onBackConversation={backFromConversation} + onConversationAnchorChange={changeConversationAnchor} + /> + {/if} + </div> + + <footer class="keyboard-help" aria-label="Keyboard shortcuts"> + <span><KbdBadge keys={['J']} />/<KbdBadge keys={['K']} /> move</span> + <span><KbdBadge keys={['Enter']} /> open</span> + <span><KbdBadge keys={['Space']} /> select</span> + <span><KbdBadge keys={['Shift', 'Space']} /> range</span> + <span><KbdBadge keys={['A']} /> visible</span> + <span><KbdBadge keys={['X']} /> clear</span> + <span><KbdBadge keys={['/']} /> search</span> + <span><KbdBadge keys={['Esc']} /> back</span> + </footer> +</main> + +<style> + .everything-workspace { + display: flex; + min-height: 0; + flex: 1; + flex-direction: column; + gap: var(--space-4); + padding: var(--space-5) var(--space-6) var(--space-4); + } + + .workspace-header { + display: flex; + min-height: 42px; + align-items: end; + justify-content: space-between; + gap: var(--space-6); + } + + .eyebrow { + margin: 0 0 var(--space-1); + color: var(--artifact-ink); + font-size: var(--font-size-2xs); + font-weight: 800; + letter-spacing: 0.12em; + text-transform: uppercase; + } + + h1 { + margin: 0; + font-family: var(--font-sans); + font-size: var(--font-size-2xl); + line-height: 1; + } + + .result-count { + margin: 0; + color: var(--text-muted); + font-size: var(--font-size-xs); + font-variant-numeric: tabular-nums; + } + + .search-bar { + display: flex; + align-items: center; + gap: var(--space-4); + } + + .query-control { + min-width: 240px; + flex: 1; + } + + .results-and-inspector { + display: flex; + min-width: 0; + min-height: 200px; + flex: 1; + overflow: hidden; + } + + .results-primary { + display: flex; + min-width: 0; + min-height: 0; + flex: 1; + flex-direction: column; + } + + .keyboard-help { + display: flex; + min-height: 22px; + align-items: center; + gap: var(--space-5); + color: var(--text-muted); + font-size: var(--font-size-2xs); + } + + @media (max-width: 760px) { + .everything-workspace { + padding-inline: var(--space-4); + } + + .search-bar { + align-items: stretch; + flex-wrap: wrap; + } + + .query-control { + min-width: 100%; + } + + .results-and-inspector--pinned .results-primary { + display: none; + } + + .keyboard-help { + overflow-x: auto; + } + } +</style> diff --git a/web/src/lib/components/shell/EverythingWorkspace.test.ts b/web/src/lib/components/shell/EverythingWorkspace.test.ts new file mode 100644 index 000000000..a07b951bf --- /dev/null +++ b/web/src/lib/components/shell/EverythingWorkspace.test.ts @@ -0,0 +1,1999 @@ +import { fireEvent, render, screen, waitFor, within } from '@testing-library/svelte'; +import { appShortcuts } from '@kenn-io/kit-ui'; +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import { LOAD_THROUGH_END_MAX_PAGES } from '../../explore/paging'; +import { ExploreState, parseExploreURLState } from '../../explore/state.svelte'; +import AppShell from './AppShell.svelte'; + +function exploreResponse(overrides: Record<string, unknown> = {}) { + return { + rows: [], + total_count: 0, + cache_revision: 'cache-1', + search_provenance: {}, + ...overrides + }; +} + +describe('EverythingWorkspace', () => { + function entry(index: number) { + return { + key: `message:${index}`, + kind: 'message', + message_type: 'email', + conversation_type: 'email', + title: `Synthetic subject ${index}`, + preview: `Synthetic excerpt ${index}`, + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + source_type: 'synthetic', + participant_labels: ['Example Person'], + participant_ids: [1], + attachment_count: 0, + attachment_size: 0, + has_attachments: false, + deleted_from_source: false, + message_count: 1, + match: {} + }; + } + + it('keeps requested Semantic mode selected while showing incomplete coverage and a search error', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path.endsWith('/coverage')) return Response.json({ + status: 'incomplete', eligible_count: 2, embedded_count: 1, percentage: 50, + vector_generation: 7, cache_revision: 'cache-1', actions: [] + }); + return Response.json({ error: 'vector_initializing', message: 'Vector search is still building.' }, { status: 503 }); + }); + const state = new ExploreState(window); + state.replaceSearchDraft('alpha', 'semantic'); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect(await screen.findByText(/Semantic index: 50% of 2 items/)).toBeDefined(); + expect((await screen.findByRole('alert')).textContent).toContain('Vector search is still building.'); + expect(screen.getByRole('radio', { name: 'Semantic' }).getAttribute('aria-checked')).toBe('true'); + expect(state.current.searchMode).toBe('semantic'); + rendered.unmount(); + state.destroy(); + }); + + + it('polls initializing semantic coverage until it reaches a terminal state', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let coverageCalls = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path.endsWith('/coverage')) { + coverageCalls += 1; + return Response.json({ + status: coverageCalls === 1 ? 'initializing' : 'ready', eligible_count: 2, + embedded_count: coverageCalls === 1 ? 0 : 2, percentage: coverageCalls === 1 ? 0 : 100, + cache_revision: 'cache-1', actions: [] + }); + } + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceSearchDraft('', 'semantic'); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + try { + await screen.findByText('Semantic index is initializing.'); + await vi.advanceTimersByTimeAsync(2_000); + expect(await screen.findByText('Semantic index: 100% of 2 items.')).toBeDefined(); + expect(coverageCalls).toBeGreaterThanOrEqual(2); + } finally { + rendered.unmount(); + state.destroy(); + vi.useRealTimers(); + } + }); + + + it('backs off exponentially while semantic coverage stays initializing', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let coverageCalls = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path.endsWith('/coverage')) { + coverageCalls += 1; + return Response.json({ + status: 'initializing', eligible_count: 2, embedded_count: 0, percentage: 0, + cache_revision: 'cache-1', actions: [] + }); + } + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceSearchDraft('', 'semantic'); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + try { + await screen.findByText('Semantic index is initializing.'); + expect(coverageCalls).toBe(1); + + await vi.advanceTimersByTimeAsync(1_000); + expect(coverageCalls).toBe(2); + + await vi.advanceTimersByTimeAsync(2_000); + expect(coverageCalls).toBe(3); + + await vi.advanceTimersByTimeAsync(4_000); + expect(coverageCalls).toBe(4); + } finally { + rendered.unmount(); + state.destroy(); + vi.useRealTimers(); + } + }); + + + it('resets the poll backoff after a coverage error so a later retry starts at 1s again', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let coverageCalls = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path.endsWith('/coverage')) { + coverageCalls += 1; + // The first two calls back off normally, then fail once, then the + // manual retry (call 4) and its follow-up poll (call 5) resume + // initializing. + if (coverageCalls === 3) throw new Error('Coverage endpoint unavailable.'); + return Response.json({ + status: 'initializing', eligible_count: 2, embedded_count: 0, percentage: 0, + cache_revision: 'cache-1', actions: [] + }); + } + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceSearchDraft('', 'semantic'); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + try { + await screen.findByText('Semantic index is initializing.'); + expect(coverageCalls).toBe(1); + + await vi.advanceTimersByTimeAsync(1_000); + expect(coverageCalls).toBe(2); + + await vi.advanceTimersByTimeAsync(2_000); + expect(await screen.findByText('Coverage endpoint unavailable.')).toBeDefined(); + expect(coverageCalls).toBe(3); + + await fireEvent.click(screen.getByRole('button', { name: 'Retry' })); + await waitFor(() => expect(coverageCalls).toBe(4)); + await screen.findByText('Semantic index is initializing.'); + + // If the backoff counter had not been reset on the error, this poll + // would be scheduled several seconds out instead of at 1s. + await vi.advanceTimersByTimeAsync(1_000); + expect(coverageCalls).toBe(5); + } finally { + rendered.unmount(); + state.destroy(); + vi.useRealTimers(); + } + }); + + + it('does not install a coverage poll after an aborted request settles during teardown', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let resolveCoverage!: (response: Response) => void; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path.endsWith('/coverage')) return new Promise<Response>((resolve) => { resolveCoverage = resolve; }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceSearchDraft('', 'semantic'); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await waitFor(() => expect(resolveCoverage).toBeTypeOf('function')); + + rendered.unmount(); + resolveCoverage(Response.json({ + status: 'initializing', eligible_count: 2, embedded_count: 0, + percentage: 0, cache_revision: 'cache-1', actions: [] + })); + await Promise.resolve(); + await Promise.resolve(); + expect(vi.getTimerCount()).toBe(0); + await vi.advanceTimersByTimeAsync(2_000); + + expect(fetchFn.mock.calls.filter(([input]) => new URL(input instanceof Request ? input.url : String(input)).pathname.endsWith('/coverage'))).toHaveLength(1); + state.destroy(); + vi.useRealTimers(); + }); + + + it('runs a full rebuild for stale coverage and refreshes the named status after completion', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const requests: Request[] = []; + let coverageCalls = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const path = new URL(request.url).pathname; + if (path.endsWith('/coverage')) { + coverageCalls += 1; + return Response.json({ + status: coverageCalls === 1 ? 'stale' : 'ready', + eligible_count: 2, embedded_count: 2, percentage: 100, + vector_generation: coverageCalls === 1 ? 7 : 8, + cache_revision: 'cache-1', actions: coverageCalls === 1 ? ['build_index'] : [] + }); + } + if (path.endsWith('/cli/run')) { + return new Response(`${JSON.stringify({ type: 'complete' })}\n`, { + headers: { 'Content-Type': 'application/x-ndjson' } + }); + } + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceSearchDraft('', 'semantic'); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + await screen.findByText('Semantic index is stale.'); + await fireEvent.click(screen.getByRole('button', { name: 'Build index' })); + expect(requests.some((request) => new URL(request.url).pathname.endsWith('/cli/run'))).toBe(false); + await fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + expect(requests.some((request) => new URL(request.url).pathname.endsWith('/cli/run'))).toBe(false); + + await fireEvent.click(screen.getByRole('button', { name: 'Build index' })); + await fireEvent.click(screen.getByRole('button', { name: 'Confirm full rebuild' })); + await screen.findByText('Semantic index: 100% of 2 items.'); + + expect(coverageCalls).toBe(2); + const cliRequest = requests.find((request) => new URL(request.url).pathname.endsWith('/cli/run')); + await expect(cliRequest?.clone().json()).resolves.toEqual({ + args: ['embeddings', 'build', '--full-rebuild', '--yes'] + }); + expect(screen.getByRole('radio', { name: 'Semantic' }).getAttribute('aria-checked')).toBe('true'); + rendered.unmount(); + state.destroy(); + }); + + + it('surfaces a streamed build failure without switching the requested mode', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path.endsWith('/coverage')) return Response.json({ + status: 'stale', eligible_count: 2, embedded_count: 1, percentage: 50, + vector_generation: 7, cache_revision: 'cache-1', actions: ['build_index'] + }); + if (path.endsWith('/cli/run')) return new Response([ + JSON.stringify({ type: 'stdout', data: 'starting\n' }), + JSON.stringify({ type: 'error', error: 'embedding endpoint failed' }) + ].join('\n') + '\n', { headers: { 'Content-Type': 'application/x-ndjson' } }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceSearchDraft('', 'hybrid'); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + await screen.findByText('Semantic index is stale.'); + await fireEvent.click(screen.getByRole('button', { name: 'Build index' })); + await fireEvent.click(screen.getByRole('button', { name: 'Confirm full rebuild' })); + + expect(await screen.findByText('embedding endpoint failed')).toBeDefined(); + expect(screen.getByRole('radio', { name: 'Hybrid' }).getAttribute('aria-checked')).toBe('true'); + expect(state.current.searchMode).toBe('hybrid'); + rendered.unmount(); + state.destroy(); + }); + + + it('uses selection preflight as the sole authority for shell actions', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const preflightRequests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (new URL(request.url).pathname.endsWith('/explore/preflight')) { + preflightRequests.push(request); + return Response.json({ + count: 1, estimated_bytes: 10, cache_revision: 'cache-1', search_provenance: {}, + unavailable_actions: [ + { action: 'export', reason: 'selection_contains_items_without_exportable_files' }, + { action: 'open_in_source', reason: 'trusted_source_link_unavailable' } + ], + action_targets: [], + operation_token: 'operation-1', expires_at: '2026-07-19T10:05:00Z' + }); + } + return Response.json(exploreResponse({ rows: [entry(1)], total_count: 1 })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 1'); + grid.focus(); + await fireEvent.keyDown(grid, { key: ' ' }); + + expect(await screen.findByText('Export: selection_contains_items_without_exportable_files')).toBeDefined(); + expect(screen.getByText('Open in source: trusted_source_link_unavailable')).toBeDefined(); + expect(screen.queryByRole('button', { name: 'Export selection' })).toBeNull(); + const body = await preflightRequests[0]!.clone().json(); + expect(body.selection).toMatchObject({ + mode: 'explicit', row_keys: ['message:1'], cache_revision: 'cache-1' + }); + + rendered.unmount(); + state.destroy(); + }); + + + it('downloads the exact server-authorized raw message export target', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const requests: Request[] = []; + const createObjectURL = vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:raw-message'); + const revokeObjectURL = vi.spyOn(URL, 'revokeObjectURL').mockImplementation(() => undefined); + const anchorClick = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => undefined); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const path = new URL(request.url).pathname; + if (path.endsWith('/explore/preflight')) return Response.json({ + count: 1, estimated_bytes: 10, cache_revision: 'cache-1', search_provenance: {}, + unavailable_actions: [{ action: 'open_in_source', reason: 'trusted_source_link_unavailable' }], + action_targets: [{ action: 'export', message_id: 1, filename: 'message-1.eml' }], + operation_token: 'operation-1', expires_at: '2026-07-19T10:05:00Z' + }); + if (path.endsWith('/cli/message/raw')) { + return new Response('raw mime', { headers: { 'Content-Type': 'message/rfc822' } }); + } + return Response.json(exploreResponse({ rows: [entry(1)], total_count: 1 })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 1'); + grid.focus(); + await fireEvent.keyDown(grid, { key: ' ' }); + + await fireEvent.click(await screen.findByRole('button', { name: 'Export selection' })); + + await waitFor(() => expect(requests.some((request) => { + const url = new URL(request.url); + return url.pathname.endsWith('/cli/message/raw') && url.searchParams.get('id') === '1'; + })).toBe(true)); + await waitFor(() => expect(createObjectURL).toHaveBeenCalledOnce()); + expect(anchorClick).toHaveBeenCalledOnce(); + expect(revokeObjectURL).toHaveBeenCalledWith('blob:raw-message'); + rendered.unmount(); + state.destroy(); + }); + + + it('records a transient scroll anchor without refetching the durable predicate', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return Response.json({ + rows: [ + { + key: 'message:1', + kind: 'message', + message_type: 'email', + conversation_type: 'email', + title: 'Synthetic subject', + preview: 'Synthetic excerpt', + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + source_type: 'synthetic', + participant_labels: ['Example Person'], + participant_ids: [1], + attachment_count: 0, + attachment_size: 0, + has_attachments: false, + deleted_from_source: false, + message_count: 1, + match: {} + } + ], + total_count: 1, + cache_revision: 'cache-1', + search_provenance: { lexical_index_revision: 'fts-1' } + }); + }); + const state = new ExploreState(window); + render(AppShell, { client: createAPIClient(fetchFn), state, enabled: true }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await waitFor(() => expect(requests).toHaveLength(1)); + await screen.findByText('Synthetic subject'); + + Object.defineProperty(grid, 'scrollTop', { configurable: true, value: 12, writable: true }); + await fireEvent.scroll(grid); + await waitFor(() => expect(state.current.scrollAnchor).toEqual({ key: 'message:1', offset: 12 })); + + expect(requests).toHaveLength(1); + state.destroy(); + }); + + + it('does not refetch when an equal-content filters array replaces the current one by reference', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + render(AppShell, { client: createAPIClient(fetchFn), state, enabled: true }); + await screen.findByRole('grid', { name: 'Everything results' }); + await waitFor(() => expect(requests).toHaveLength(1)); + + state.replaceTransient({ filters: [{ dimension: 'domain', values: ['example.com'] }] }); + await waitFor(() => expect(requests).toHaveLength(2)); + + // A fresh array with the same filter content must not change the + // canonical predicate fingerprint, so no new request should fire. + state.replaceTransient({ filters: [{ dimension: 'domain', values: ['example.com'] }] }); + await new Promise((resolve) => setTimeout(resolve, 20)); + expect(requests).toHaveLength(2); + + // A genuine content change is a different fingerprint and must refetch. + state.replaceTransient({ filters: [{ dimension: 'domain', values: ['other.example'] }] }); + await waitFor(() => expect(requests).toHaveLength(3)); + state.destroy(); + }); + + + it('debounces rapid visible conversation churn into one exact-count request', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const countRequests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path.endsWith('/match-counts')) { + countRequests.push(request); + const body = await request.clone().json(); + return Response.json({ + counts: body.row_keys.map((row_key: string) => ({ row_key, count: 1 })), + cache_revision: 'cache-1', lexical_index_revision: 'fts-1', + canonical_query_hash: 'query-1' + }); + } + return Response.json(exploreResponse({ + rows: Array.from({ length: 100 }, (_, index) => ({ + ...entry(index + 1), kind: 'conversation', message_type: 'sms' + })), + total_count: 100, + search_provenance: { lexical_index_revision: 'fts-1' } + })); + }); + const state = new ExploreState(window); + state.replaceTransient({ query: 'alpha', searchMode: 'full_text' }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + try { + const grid = await screen.findByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + await screen.findByText('Synthetic subject 1'); + for (const scrollTop of [36, 72, 108, 144]) { + Object.defineProperty(grid, 'scrollTop', { configurable: true, value: scrollTop, writable: true }); + await fireEvent.scroll(grid); + } + + await vi.advanceTimersByTimeAsync(100); + await waitFor(() => expect(countRequests).toHaveLength(1)); + } finally { + rendered.unmount(); + state.destroy(); + vi.useRealTimers(); + } + }); + + + it('keeps a cached exact lexical match count across a workspace round-trip away from Everything and back', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let matchCountCalls = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path.endsWith('/match-counts')) { + matchCountCalls += 1; + return Response.json({ + counts: [{ row_key: 'message:1', count: 3 }], cache_revision: 'cache-1', + lexical_index_revision: 'fts-1', canonical_query_hash: 'query-1' + }); + } + return Response.json(exploreResponse({ + rows: [{ ...entry(1), kind: 'conversation', message_type: 'sms' }], total_count: 1, + search_provenance: { lexical_index_revision: 'fts-1' } + })); + }); + const state = new ExploreState(window); + state.replaceTransient({ query: 'alpha', searchMode: 'full_text' }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + try { + await screen.findByText('Synthetic subject 1'); + await vi.advanceTimersByTimeAsync(100); + await waitFor(() => expect(matchCountCalls).toBe(1)); + await screen.findByText('3 lexical matches'); + + // Leave Everything (destroying EverythingWorkspace) and come back. + const nav = screen.getByRole('navigation', { name: 'Primary' }); + await fireEvent.click(within(nav).getByRole('button', { name: 'Settings' })); + expect(screen.queryByRole('main', { name: 'Everything' })).toBeNull(); + await fireEvent.click(within(nav).getByRole('button', { name: 'Everything' })); + + await screen.findByText('Synthetic subject 1'); + await vi.advanceTimersByTimeAsync(100); + + // The cached count must be reused, not refetched, after the round-trip. + expect(await screen.findByText('3 lexical matches')).toBeDefined(); + expect(matchCountCalls).toBe(1); + } finally { + rendered.unmount(); + state.destroy(); + vi.useRealTimers(); + } + }); + + it('refetches a persisted group inspector detail after the analytical cache rebuilds', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let cacheRevision = 'cache-1'; + let count = 12; + let label = 'Group Seven v1'; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path.endsWith('/groups')) { + const row = { key: '7', label, count, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' }; + return Response.json({ rows: [row], total_count: 1, cache_revision: cacheRevision, search_provenance: {} }); + } + return Response.json(exploreResponse({ cache_revision: cacheRevision })); + }); + const state = new ExploreState(window); + state.commitNavigation({ groupingChain: ['source'] }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await screen.findByText('Group Seven v1'); + + await fireEvent.click(screen.getByRole('button', { name: 'Drill into Group Seven v1' })); + expect(await screen.findByRole('complementary', { name: 'Inspect Group Seven v1' })).toBeDefined(); + expect(await screen.findByText('12 items')).toBeDefined(); + + // Leave Everything via a workspace switch (destroying EverythingWorkspace, + // but not the session that persists the loaded detail — commitWorkspace + // resets selectedRow to null, so a normal round-trip through the + // Everything tab would already wipe the cached detail; use Back instead, + // which restores the exact prior committed state — including the same + // selected group — while still tearing down and recreating the + // component) and simulate an analytical cache rebuild — same key and + // predicate, but the underlying data changed — while away. + const nav = screen.getByRole('navigation', { name: 'Primary' }); + await fireEvent.click(within(nav).getByRole('button', { name: 'Settings' })); + expect(screen.queryByRole('main', { name: 'Everything' })).toBeNull(); + cacheRevision = 'cache-2'; + count = 99; + label = 'Group Seven v2'; + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + + // Must reflect the rebuilt data, not the detail persisted from before + // the rebuild under the same group key and predicate. + expect(await screen.findByRole('complementary', { name: 'Inspect Group Seven v2' })).toBeDefined(); + expect(await screen.findByText('99 items')).toBeDefined(); + expect(screen.queryByText('12 items')).toBeNull(); + rendered.unmount(); + state.destroy(); + }); + + // This coverage previously drove AppShell into the legacy People branch + // (workspace: 'people') to exercise debouncedSearchPatch's integration + // mechanics. normalize() now eagerly rewrites 'people' to 'relationships' + // on every navigation, so that branch is unreachable through ExploreState. + // The same debounce/flush/cancel mechanics are still live and reachable + // through the Files workspace's filename-query input, so these three + // tests exercise them there instead. + + it('aborts an in-flight exact-count request on visible churn and on destroy', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const countSignals: AbortSignal[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (new URL(request.url).pathname.endsWith('/match-counts')) { + countSignals.push(request.signal); + return new Promise<Response>(() => undefined); + } + return Response.json(exploreResponse({ + rows: Array.from({ length: 100 }, (_, index) => ({ + ...entry(index + 1), kind: 'conversation', message_type: 'sms' + })), + total_count: 100, + search_provenance: { lexical_index_revision: 'fts-1' } + })); + }); + const state = new ExploreState(window); + state.replaceTransient({ query: 'alpha', searchMode: 'full_text' }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + try { + const grid = await screen.findByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + await vi.advanceTimersByTimeAsync(60); + await waitFor(() => expect(countSignals).toHaveLength(1)); + + Object.defineProperty(grid, 'scrollTop', { configurable: true, value: 180, writable: true }); + await fireEvent.scroll(grid); + expect(countSignals[0]!.aborted).toBe(true); + await vi.advanceTimersByTimeAsync(60); + await waitFor(() => expect(countSignals).toHaveLength(2)); + + rendered.unmount(); + expect(countSignals[1]!.aborted).toBe(true); + } finally { + state.destroy(); + vi.useRealTimers(); + } + }); + + + it('uses one analytical context for grouped, timeline, and files presentations', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const path = new URL(request.url).pathname; + if (path.endsWith('/files/7')) return Response.json({ + id: 7, message_id: 1, conversation_id: 11, filename: 'analysis.pdf', + mime_type: 'application/pdf', size_bytes: 2048, + content_state: 'missing_blob', content_available: false + }); + if (path.endsWith('/explore/files')) return Response.json({ + files: [{ + id: 7, key: 'message:1:file:7', entry_key: 'message:1', message_id: 1, conversation_id: 11, + occurred_at: '2026-07-18T12:00:00Z', source_id: 1, + source_identifier: 'archive@example.com', title: 'Synthetic subject 1', + filename: 'analysis.pdf', mime_type: 'application/pdf', size: 2048 + }], + total_count: 1, cache_revision: 'cache-1', search_provenance: {} + }); + return Response.json(exploreResponse({ rows: [entry(1)], total_count: 1 })); + }); + const state = new ExploreState(window); + state.commitNavigation({ groupingChain: ['source'] }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + await waitFor(() => expect(requests).toHaveLength(1)); + expect(new URL(requests[0]!.url).pathname).toBe('/api/v1/explore/groups'); + + state.commitNavigation({ groupingChain: [], presentation: 'timeline' }); + expect(await screen.findByRole('region', { name: 'Canonical activity timeline' })).toBeDefined(); + expect(await screen.findByText('Synthetic subject 1')).toBeDefined(); + expect(new URL(requests.at(-1)!.url).pathname).toBe('/api/v1/explore'); + + state.commitNavigation({ presentation: 'files' }); + expect(await screen.findByRole('grid', { name: 'Files in current context' })).toBeDefined(); + expect(await screen.findByText('analysis.pdf')).toBeDefined(); + const filesRequest = requests.at(-1)!; + expect(new URL(filesRequest.url).pathname).toBe('/api/v1/explore/files'); + await expect(filesRequest.clone().json()).resolves.toMatchObject({ + predicate: { presentation: 'files' } + }); + state.replaceTransient({ + activeRow: 'message:1:file:7', + scrollAnchor: { key: 'message:1:file:7', offset: 4 } + }); + const filesGrid = screen.getByRole('grid', { name: 'Files in current context' }); + filesGrid.focus(); + await fireEvent.keyDown(filesGrid, { key: 'Enter' }); + await waitFor(() => expect(state.current.selectedRow).toBe('attachment:7')); + expect(await screen.findByRole('dialog', { name: 'View analysis.pdf' })).toBeDefined(); + expect(state.current.selectedRow).toBe('attachment:7'); + expect(parseExploreURLState(window.location.search).selectedRow).toBe('attachment:7'); + await fireEvent.click(screen.getByRole('button', { name: 'Close file viewer' })); + await waitFor(() => expect(screen.queryByRole('dialog', { name: 'View analysis.pdf' })).toBeNull()); + expect(state.current.selectedRow).toBeNull(); + expect(document.activeElement).toBe(filesGrid); + + await screen.findByText('analysis.pdf'); + const restoredFilesGrid = screen.getByRole('grid', { name: 'Files in current context' }); + await waitFor(() => expect(restoredFilesGrid.getAttribute('aria-activedescendant')).toContain('message-3a-1')); + restoredFilesGrid.focus(); + await fireEvent.keyDown(restoredFilesGrid, { key: 'Enter' }); + await waitFor(() => expect(state.current.selectedRow).toBe('attachment:7')); + expect(await screen.findByRole('dialog', { name: 'View analysis.pdf' })).toBeDefined(); + await fireEvent.click(screen.getByRole('button', { name: 'Open containing item' })); + expect(state.current.presentation).toBe('table'); + expect(state.current.selectedRow).toBe('message:1'); + expect(state.current.activeRow).toBeNull(); + expect(state.current.scrollAnchor).toBeNull(); + rendered.unmount(); + state.destroy(); + }); + + + it('renders dense group rows and drills a supported dimension into entry filters', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (new URL(request.url).pathname.endsWith('/groups')) { + return Response.json({ + rows: [{ key: '7', label: 'Example source', count: 12, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' }], + total_count: 1, + cache_revision: 'cache-1', + search_provenance: {} + }); + } + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceTransient({ groupingChain: ['source'] }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + await screen.findByText('Example source'); + await fireEvent.click(screen.getByRole('button', { name: 'Drill into Example source' })); + await waitFor(() => expect(requests).toHaveLength(3)); + + const entryRequest = requests.find((request) => new URL(request.url).pathname === '/api/v1/explore'); + const detailRequest = requests.find((request) => { + const path = new URL(request.url).pathname; + return path.endsWith('/groups') && request !== requests[0]; + }); + expect(entryRequest).toBeDefined(); + expect(detailRequest).toBeDefined(); + await expect(entryRequest!.clone().json()).resolves.toMatchObject({ + filters: [{ dimension: 'source', values: ['7'] }] + }); + await expect(detailRequest!.clone().json()).resolves.toMatchObject({ + filters: [{ dimension: 'source', values: ['7'] }], + grouping: ['source'], + limit: 1 + }); + expect(state.current.groupingChain).toEqual([]); + rendered.unmount(); + state.destroy(); + }); + + + it('aborts superseded and destroyed requests without destroying injected state', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const signals: AbortSignal[] = []; + const fetchFn = vi.fn<typeof fetch>((input) => { + const request = input instanceof Request ? input : new Request(input); + signals.push(request.signal); + return new Promise<Response>(() => undefined); + }); + const state = new ExploreState(window); + const destroy = vi.spyOn(state, 'destroy'); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await waitFor(() => expect(signals).toHaveLength(1)); + + state.commitSearch('next generation', 'full_text'); + await waitFor(() => expect(signals).toHaveLength(2)); + expect(signals[0]!.aborted).toBe(true); + + rendered.unmount(); + expect(signals[1]!.aborted).toBe(true); + expect(destroy).not.toHaveBeenCalled(); + state.destroy(); + }); + + + it('invalidates a late-settling noncompliant request before destroy aborts it', async () => { + const initialState = { + schemaVersion: 1, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: 'message:1200', selectedRow: null, inspectorPinned: true, + conversationAnchor: null, scrollAnchor: { key: 'message:1190', offset: 5 } + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(initialState))}`); + let resolveRequest: ((response: Response) => void) | undefined; + const fetchFn = vi.fn<typeof fetch>(() => new Promise<Response>((resolve) => { resolveRequest = resolve; })); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await waitFor(() => expect(fetchFn).toHaveBeenCalledOnce()); + + rendered.unmount(); + resolveRequest?.(Response.json(exploreResponse({ + rows: [entry(1)], total_count: 1200, next_cursor: 'page:500' + }))); + await new Promise((resolve) => setTimeout(resolve, 20)); + + expect(fetchFn).toHaveBeenCalledOnce(); + state.destroy(); + }); + + + it('loads cursor pages from grid interaction and deduplicates stable server keys', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const body = await request.clone().json(); + return Response.json( + body.cursor + ? exploreResponse({ rows: [entry(2), entry(3)], total_count: 3 }) + : exploreResponse({ rows: [entry(1), entry(2)], total_count: 3, next_cursor: 'page-2' }) + ); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + await screen.findByText('Synthetic subject 2'); + + Object.defineProperty(grid, 'scrollTop', { configurable: true, value: 36, writable: true }); + await fireEvent.scroll(grid); + + await screen.findByText('Synthetic subject 3'); + expect(requests).toHaveLength(2); + await expect(requests[1]!.clone().json()).resolves.toMatchObject({ cursor: 'page-2' }); + expect(screen.getAllByText('Synthetic subject 2')).toHaveLength(1); + rendered.unmount(); + state.destroy(); + }); + + + it('loads through cursor pages to restore a concrete selected row from a direct URL', async () => { + const initialState = { + schemaVersion: 1, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: null, selectedRow: 'message:1200', inspectorPinned: true, + conversationAnchor: null, scrollAnchor: null + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(initialState))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const body = await request.clone().json(); + const offset = body.cursor ? Number(String(body.cursor).slice('page:'.length)) : 0; + return Response.json(exploreResponse({ + rows: Array.from({ length: 500 }, (_, index) => entry(offset + index + 1)), + total_count: 1500, + ...(offset + 500 < 1500 ? { next_cursor: `page:${offset + 500}` } : {}) + })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect(await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeDefined(); + expect(requests).toHaveLength(3); + expect(state.current.selectedRow).toBe('message:1200'); + rendered.unmount(); + state.destroy(); + }); + + + it('restores every required durable identity while retaining page-one focus geometry', async () => { + const initialState = { + schemaVersion: 1, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: 'message:1', selectedRow: 'message:1200', inspectorPinned: true, + conversationAnchor: null, scrollAnchor: { key: 'message:1', offset: 7 } + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(initialState))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const body = await request.clone().json(); + const offset = body.cursor ? Number(String(body.cursor).slice('page:'.length)) : 0; + return Response.json(exploreResponse({ + rows: Array.from({ length: 500 }, (_, index) => entry(offset + index + 1)), + total_count: 1500, + ...(offset + 500 < 1500 ? { next_cursor: `page:${offset + 500}` } : {}) + })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect(await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeDefined(); + const grid = screen.getByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + await waitFor(() => expect(grid.getAttribute('aria-activedescendant')).toBe('everything-row-message-3a-1')); + await waitFor(() => expect(grid.scrollTop).toBe(7)); + expect(requests).toHaveLength(3); + expect(state.current.activeRow).toBe('message:1'); + expect(state.current.scrollAnchor).toEqual({ key: 'message:1', offset: 7 }); + expect(state.current.selectedRow).toBe('message:1200'); + rendered.unmount(); + state.destroy(); + }); + + + it('exhausts finite pages once when a distinct selected row is missing', async () => { + const initialState = { + schemaVersion: 1, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: 'message:1', selectedRow: 'message:missing', inspectorPinned: true, + conversationAnchor: null, scrollAnchor: { key: 'message:1', offset: 0 } + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(initialState))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const body = await request.clone().json(); + return Response.json(body.cursor + ? exploreResponse({ rows: [entry(2)], total_count: 2 }) + : exploreResponse({ rows: [entry(1)], total_count: 2, next_cursor: 'page:1' })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect((await screen.findByRole('alert')).textContent).toContain('selected entry is no longer available'); + await new Promise((resolve) => setTimeout(resolve, 20)); + expect(requests).toHaveLength(2); + expect(state.current.activeRow).toBe('message:1'); + expect(state.current.selectedRow).toBe('message:missing'); + rendered.unmount(); + state.destroy(); + }); + + + it('restores a later selected row with earlier focus geometry through Back and Forward', async () => { + const initialState = { + schemaVersion: 1, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: 'message:1', selectedRow: 'message:1200', inspectorPinned: true, + conversationAnchor: null, scrollAnchor: { key: 'message:1', offset: 4 } + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(initialState))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const body = await request.clone().json(); + const offset = body.cursor ? Number(String(body.cursor).slice('page:'.length)) : 0; + return Response.json(exploreResponse({ + rows: Array.from({ length: 500 }, (_, index) => entry(offset + index + 1)), + total_count: 1500, + ...(offset + 500 < 1500 ? { next_cursor: `page:${offset + 500}` } : {}) + })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1200' }); + await fireEvent.click(screen.getByRole('button', { name: 'Settings' })); + expect(screen.queryByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeNull(); + + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + expect(await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeDefined(); + const grid = screen.getByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + await waitFor(() => expect(requests).toHaveLength(6)); + await waitFor(() => expect(grid.getAttribute('aria-activedescendant')).toBe('everything-row-message-3a-1')); + await waitFor(() => expect(grid.scrollTop).toBe(4)); + + window.history.forward(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + await waitFor(() => expect(screen.getByRole('button', { name: 'Settings' }).getAttribute('aria-current')).toBe('page')); + expect(screen.queryByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeNull(); + rendered.unmount(); + state.destroy(); + }); + + + it('rehydrates an exact aggregate selection after the grouped rows are absent', async () => { + const initialState = { + schemaVersion: 1, workspace: 'everything', query: 'alpha', searchMode: 'full_text', + filters: [{ dimension: 'domain', values: ['example.com'] }], groupingChain: [], + presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: null, selectedRow: 'group:participant:42', inspectorPinned: true, + conversationAnchor: null, scrollAnchor: null + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(initialState))}`); + const groupRequests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (new URL(request.url).pathname.endsWith('/groups')) { + groupRequests.push(request); + return Response.json({ + rows: [{ key: '42', label: 'Alice Example', count: 3, estimated_bytes: 512, latest_at: '2026-07-18T12:00:00Z' }], + total_count: 1, cache_revision: 'cache-1', search_provenance: { lexical_index_revision: 'fts-1' } + }); + } + if (new URL(request.url).pathname.endsWith('/files')) { + return Response.json({ files: [], total_count: 0, cache_revision: 'cache-1', search_provenance: {} }); + } + return Response.json(exploreResponse({ search_provenance: { lexical_index_revision: 'fts-1' } })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect(await screen.findByRole('complementary', { name: 'Inspect Alice Example' })).toBeDefined(); + expect(screen.getByRole('region', { name: 'Group details' }).textContent).toContain('3 items'); + expect(groupRequests).toHaveLength(1); + await expect(groupRequests[0]!.clone().json()).resolves.toEqual({ + filters: [ + { dimension: 'domain', values: ['example.com'] }, + { dimension: 'participant', values: ['42'] } + ], + query: 'alpha', + search_mode: 'full_text', + grouping: ['participant'], + limit: 1, + presentation: 'table' + }); + rendered.unmount(); + state.destroy(); + }); + + + it('keeps missing concrete and aggregate selections visible as truthful inspector states', async () => { + const state = new ExploreState(window); + state.commitNavigation({ selectedRow: 'message:missing' }); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (new URL(request.url).pathname.endsWith('/groups')) return Response.json({ + rows: [], total_count: 0, cache_revision: 'cache-1', search_provenance: {} + }); + return Response.json(exploreResponse()); + }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect((await screen.findByRole('alert')).textContent).toContain('selected entry is no longer available'); + expect(state.current.selectedRow).toBe('message:missing'); + + state.commitNavigation({ selectedRow: 'group:domain:missing.example' }); + await waitFor(() => { + expect(screen.getByRole('alert').textContent).toContain('selected group is no longer available'); + }); + expect(state.current.selectedRow).toBe('group:domain:missing.example'); + rendered.unmount(); + state.destroy(); + }); + + + it('preserves cache-unavailable truth while restoring aggregate details', async () => { + const state = new ExploreState(window); + state.commitNavigation({ selectedRow: 'group:domain:example.com' }); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (new URL(request.url).pathname.endsWith('/groups')) return Response.json({ + error: 'cache_unavailable', + message: 'The analytical cache is absent.', + readiness: 'absent', + recovery_action: 'msgvault build-cache' + }, { status: 503 }); + return Response.json(exploreResponse()); + }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + const alert = await screen.findByRole('alert'); + expect(alert.textContent).toContain('The analytical cache is absent.'); + expect(alert.textContent).toContain('msgvault build-cache'); + expect(state.current.selectedRow).toBe('group:domain:example.com'); + rendered.unmount(); + state.destroy(); + }); + + + it('aborts superseded aggregate detail restoration and ignores a late stale response', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const pending: Array<{ + request: Request; + resolve: (response: Response) => void; + }> = []; + const fetchFn = vi.fn<typeof fetch>((input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path.endsWith('/groups')) { + return new Promise<Response>((resolve) => pending.push({ request, resolve })); + } + if (path.endsWith('/files')) { + return Promise.resolve(Response.json({ + files: [], total_count: 0, cache_revision: 'cache-1', search_provenance: {} + })); + } + return Promise.resolve(Response.json(exploreResponse())); + }); + const state = new ExploreState(window); + state.commitNavigation({ selectedRow: 'group:domain:first.example' }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await waitFor(() => expect(pending).toHaveLength(1)); + + state.commitNavigation({ selectedRow: 'group:domain:second.example' }); + await waitFor(() => expect(pending).toHaveLength(2)); + expect(pending[0]!.request.signal.aborted).toBe(true); + pending[1]!.resolve(Response.json({ + rows: [{ key: 'second.example', label: 'Second domain', count: 2 }], + total_count: 1, cache_revision: 'cache-1', search_provenance: {} + })); + expect(await screen.findByRole('complementary', { name: 'Inspect Second domain' })).toBeDefined(); + + pending[0]!.resolve(Response.json({ + rows: [{ key: 'first.example', label: 'Stale first domain', count: 9 }], + total_count: 1, cache_revision: 'cache-1', search_provenance: {} + })); + await new Promise((resolve) => setTimeout(resolve, 20)); + expect(screen.queryByText('Stale first domain')).toBeNull(); + expect(state.current.selectedRow).toBe('group:domain:second.example'); + rendered.unmount(); + state.destroy(); + }); + + + it('rejects a cursor page whose committed cache revision changed', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const body = await request.clone().json(); + return Response.json( + body.cursor + ? exploreResponse({ rows: [entry(2)], total_count: 2, cache_revision: 'cache-2' }) + : exploreResponse({ rows: [entry(1)], total_count: 2, next_cursor: 'page-2' }) + ); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + await screen.findByText('Synthetic subject 1'); + + grid.focus(); + await fireEvent.keyDown(grid, { key: 'End' }); + + expect((await screen.findByRole('alert')).textContent).toMatch(/results changed/i); + expect(screen.queryByText('Synthetic subject 2')).toBeNull(); + expect(fetchFn).toHaveBeenCalledTimes(2); + rendered.unmount(); + state.destroy(); + }); + + + it('stops End cursor draining after a network error without automatic retry', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const body = await request.clone().json(); + if (body.cursor) throw new Error('Network disconnected.'); + return Response.json(exploreResponse({ rows: [entry(1)], total_count: 2, next_cursor: 'page-2' })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 1'); + + grid.focus(); + await fireEvent.keyDown(grid, { key: 'End' }); + + expect((await screen.findByRole('alert')).textContent).toContain('Network disconnected.'); + expect(fetchFn).toHaveBeenCalledTimes(2); + rendered.unmount(); + state.destroy(); + }); + + + it('preserves a deep durable key when automatic restoration fails mid-page', async () => { + const deepState = { + schemaVersion: 1, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: 'message:5000', selectedRow: null, inspectorPinned: true, + conversationAnchor: null, scrollAnchor: { key: 'message:4990', offset: 3 } + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(deepState))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const body = await request.clone().json(); + if (body.cursor) throw new Error('Restoration network failed.'); + return Response.json(exploreResponse({ rows: [entry(1)], total_count: 5000, next_cursor: 'page-2' })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect((await screen.findByRole('alert')).textContent).toContain('Restoration network failed.'); + await new Promise((resolve) => setTimeout(resolve, 20)); + expect(state.current.activeRow).toBe('message:5000'); + expect(state.current.scrollAnchor).toEqual({ key: 'message:4990', offset: 3 }); + rendered.unmount(); + state.destroy(); + }); + + + it('retries a failed deep restoration without replacing its durable focus', async () => { + const deepState = { + schemaVersion: 1, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: 'message:1200', selectedRow: null, inspectorPinned: true, + conversationAnchor: null, scrollAnchor: { key: 'message:1190', offset: 3 } + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(deepState))}`); + let failedOnce = false; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const body = await request.clone().json(); + const offset = body.cursor ? Number(String(body.cursor).slice('page:'.length)) : 0; + if (offset === 500 && !failedOnce) { + failedOnce = true; + throw new Error('Restoration network failed.'); + } + return Response.json(exploreResponse({ + rows: Array.from({ length: 500 }, (_, index) => entry(offset + index + 1)), + total_count: 1500, + ...(offset + 500 < 1500 ? { next_cursor: `page:${offset + 500}` } : {}) + })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect((await screen.findByRole('alert')).textContent).toContain('Restoration network failed.'); + expect(state.current.activeRow).toBe('message:1200'); + expect(state.current.scrollAnchor).toEqual({ key: 'message:1190', offset: 3 }); + await fireEvent.click(screen.getByRole('button', { name: 'Retry restoration' })); + + const grid = screen.getByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + await waitFor(() => expect(grid.getAttribute('aria-activedescendant')).toBe('everything-row-message-3a-1200')); + await waitFor(() => expect(grid.scrollTop).toBe(1189 * 36 + 3)); + expect(state.current.activeRow).toBe('message:1200'); + expect(state.current.scrollAnchor).toEqual({ key: 'message:1190', offset: 3 }); + expect(fetchFn).toHaveBeenCalledTimes(5); + rendered.unmount(); + state.destroy(); + }); + + + it('cancels an in-flight restoration when a newer committed navigation wins', async () => { + const deepState = { + schemaVersion: 1, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: 'message:1200', selectedRow: null, inspectorPinned: true, + conversationAnchor: null, scrollAnchor: { key: 'message:1190', offset: 3 } + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(deepState))}`); + let resolveSecond: ((response: Response) => void) | undefined; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const body = await request.clone().json(); + if (body.cursor) return new Promise<Response>((resolve) => { resolveSecond = resolve; }); + return Response.json(exploreResponse({ rows: [entry(1)], total_count: 1200, next_cursor: 'page:500' })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await waitFor(() => expect(fetchFn).toHaveBeenCalledTimes(2)); + + state.commitNavigation({ selectedRow: 'message:1' }); + expect(state.peekRestorationEpoch()).toBeUndefined(); + resolveSecond?.(Response.json(exploreResponse({ rows: [entry(2)], total_count: 1200 }))); + + const grid = screen.getByRole('grid', { name: 'Everything results' }); + await waitFor(() => expect(grid.getAttribute('aria-activedescendant')).toBe('everything-row-message-3a-1')); + expect(fetchFn).toHaveBeenCalledTimes(2); + rendered.unmount(); + state.destroy(); + }); + + + it('stops End cursor draining at a page-level cache-unavailable response', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const body = await request.clone().json(); + if (body.cursor) return Response.json({ + error: 'cache_unavailable', message: 'Cache publication changed.', readiness: 'building', + recovery_action: 'msgvault build-cache' + }, { status: 503 }); + return Response.json(exploreResponse({ rows: [entry(1)], total_count: 2, next_cursor: 'page-2' })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 1'); + + grid.focus(); + await fireEvent.keyDown(grid, { key: 'End' }); + + expect((await screen.findByRole('alert')).textContent).toContain('Cache publication changed.'); + expect(fetchFn).toHaveBeenCalledTimes(2); + rendered.unmount(); + state.destroy(); + }); + + + it('stops End cursor draining after a repeated cursor without retrying forever', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const body = await request.clone().json(); + return Response.json(body.cursor + ? exploreResponse({ rows: [entry(2)], total_count: 3, next_cursor: 'page-2' }) + : exploreResponse({ rows: [entry(1)], total_count: 3, next_cursor: 'page-2' })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 1'); + + grid.focus(); + await fireEvent.keyDown(grid, { key: 'End' }); + + expect((await screen.findByRole('alert')).textContent).toMatch(/cursor|progress/i); + expect(requests).toHaveLength(2); + rendered.unmount(); + state.destroy(); + }); + + + it('stops End when an advancing cursor page adds no new stable keys', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const body = await request.clone().json(); + return Response.json(body.cursor + ? exploreResponse({ rows: [entry(1)], total_count: 3, next_cursor: 'page-3' }) + : exploreResponse({ rows: [entry(1)], total_count: 3, next_cursor: 'page-2' })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 1'); + + grid.focus(); + await fireEvent.keyDown(grid, { key: 'End' }); + + expect((await screen.findByRole('alert')).textContent).toMatch(/no row progress/i); + expect(requests).toHaveLength(2); + rendered.unmount(); + state.destroy(); + }); + + + it('caps End at LOAD_THROUGH_END_MAX_PAGES pages per press', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let explorePostCount = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path !== '/api/v1/explore') return Response.json(exploreResponse()); + explorePostCount += 1; + const page = explorePostCount; + return Response.json(exploreResponse({ + rows: [entry(page)], total_count: 10_000, next_cursor: `cursor-${page}` + })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 1'); + + grid.focus(); + await fireEvent.keyDown(grid, { key: 'End' }); + + await waitFor(() => expect(explorePostCount).toBe(1 + LOAD_THROUGH_END_MAX_PAGES)); + expect(await screen.findByText(/press End again to continue/)).toBeDefined(); + await new Promise((resolve) => setTimeout(resolve, 20)); + expect(explorePostCount).toBe(1 + LOAD_THROUGH_END_MAX_PAGES); + rendered.unmount(); + state.destroy(); + }); + + + it('clears the End pause notice once a later press reaches the true end', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let explorePostCount = 0; + const totalPages = 1 + LOAD_THROUGH_END_MAX_PAGES + 2; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path !== '/api/v1/explore') return Response.json(exploreResponse()); + explorePostCount += 1; + const page = explorePostCount; + return Response.json(exploreResponse({ + rows: [entry(page)], total_count: 10_000, + ...(page < totalPages ? { next_cursor: `cursor-${page}` } : {}) + })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 1'); + + grid.focus(); + await fireEvent.keyDown(grid, { key: 'End' }); + await waitFor(() => expect(explorePostCount).toBe(1 + LOAD_THROUGH_END_MAX_PAGES)); + expect(await screen.findByText(/press End again to continue/)).toBeDefined(); + + await fireEvent.keyDown(grid, { key: 'End' }); + await waitFor(() => expect(explorePostCount).toBe(totalPages)); + await waitFor(() => expect( + screen.getByRole('status', { name: 'Sort status' }).textContent + ).toBe('Newest first is the canonical Everything order.')); + rendered.unmount(); + state.destroy(); + }); + + + it('does not restore a predicate-A key into local predicate B but Back restores A', async () => { + const initialState = { + schemaVersion: 1, workspace: 'everything', query: 'alpha', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: 'message:1200', selectedRow: null, inspectorPinned: true, + conversationAnchor: null, scrollAnchor: { key: 'message:1190', offset: 5 } + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(initialState))}`); + const alphaRequests: Request[] = []; + const betaRequests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const body = await request.clone().json(); + const target = body.query === 'beta' ? betaRequests : alphaRequests; + target.push(request); + const offset = body.cursor ? Number(String(body.cursor).slice('page:'.length)) : 0; + const rows = Array.from({ length: 500 }, (_, index) => body.query === 'beta' + ? { ...entry(offset + index + 1), key: `beta:${offset + index + 1}` } + : entry(offset + index + 1)); + return Response.json(exploreResponse({ + rows, + total_count: 1500, + search_provenance: { lexical_index_revision: `fts-${body.query}` }, + ...(offset + 500 < 1500 ? { next_cursor: `page:${offset + 500}` } : {}) + })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await waitFor(() => expect(alphaRequests).toHaveLength(3)); + expect(state.current.activeRow).toBe('message:1200'); + + const search = screen.getByRole('searchbox', { name: 'Search everything' }); + await fireEvent.input(search, { target: { value: 'beta' } }); + await waitFor(() => expect(betaRequests.length).toBeGreaterThan(0)); + await new Promise((resolve) => setTimeout(resolve, 20)); + + expect(betaRequests).toHaveLength(1); + expect(state.current.activeRow).not.toBe('message:1200'); + expect(state.current.scrollAnchor).toBeNull(); + await fireEvent.click(screen.getByRole('button', { name: /^Search$/ })); + + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + await waitFor(() => expect(alphaRequests).toHaveLength(6)); + expect(state.current.activeRow).toBe('message:1200'); + expect(state.current.scrollAnchor).toEqual({ key: 'message:1190', offset: 5 }); + rendered.unmount(); + state.destroy(); + }); + + + it('joins an in-flight cursor request before End continues draining', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const requests: Request[] = []; + let resolveSecond: ((response: Response) => void) | undefined; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const body = await request.clone().json(); + if (!body.cursor) return Response.json(exploreResponse({ rows: [entry(1)], total_count: 3, next_cursor: 'page-2' })); + if (body.cursor === 'page-2') return new Promise<Response>((resolve) => { resolveSecond = resolve; }); + return Response.json(exploreResponse({ rows: [entry(3)], total_count: 3 })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }) as HTMLDivElement; + await screen.findByText('Synthetic subject 1'); + await fireEvent.scroll(grid); + await waitFor(() => expect(requests).toHaveLength(2)); + + grid.focus(); + const end = fireEvent.keyDown(grid, { key: 'End' }); + resolveSecond?.(Response.json(exploreResponse({ rows: [entry(2)], total_count: 3, next_cursor: 'page-3' }))); + await end; + + await screen.findByText('Synthetic subject 3'); + expect(requests).toHaveLength(3); + rendered.unmount(); + state.destroy(); + }); + + + it('accepts search provenance with canonical field order across cursor pages', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const body = await request.clone().json(); + return Response.json(body.cursor + ? exploreResponse({ rows: [entry(2)], total_count: 2, search_provenance: { vector_generation: 4, lexical_index_revision: 'fts-1' } }) + : exploreResponse({ rows: [entry(1)], total_count: 2, next_cursor: 'page-2', search_provenance: { lexical_index_revision: 'fts-1', vector_generation: 4 } })); + }); + const state = new ExploreState(window); + state.replaceTransient({ query: 'alpha', searchMode: 'hybrid' }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 1'); + await fireEvent.scroll(grid); + + await screen.findByText('Synthetic subject 2'); + expect(screen.queryByRole('alert')).toBeNull(); + rendered.unmount(); + state.destroy(); + }); + + + it('renders grouped cache failure as a named retry panel instead of empty groups', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let calls = 0; + const fetchFn = vi.fn<typeof fetch>(async () => { + calls += 1; + if (calls === 1) return Response.json({ + error: 'cache_unavailable', message: 'Cache missing.', readiness: 'absent', + recovery_action: 'msgvault build-cache' + }, { status: 503 }); + return Response.json({ rows: [], total_count: 0, cache_revision: 'cache-1', search_provenance: {} }); + }); + const state = new ExploreState(window); + state.replaceTransient({ groupingChain: ['source'] }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect((await screen.findByRole('alert')).textContent).toContain('Cache missing.'); + expect(screen.queryByText('No groups match this view.')).toBeNull(); + await fireEvent.click(screen.getByRole('button', { name: 'Retry cache check' })); + await waitFor(() => expect(calls).toBe(2)); + rendered.unmount(); + state.destroy(); + }); + + + it('clears stale group rows while a changed grouping request is loading', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let resolveNext: ((response: Response) => void) | undefined; + let calls = 0; + const fetchFn = vi.fn<typeof fetch>(async () => { + calls += 1; + if (calls === 1) return Response.json({ + rows: [{ key: '7', label: 'Old source group', count: 2, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' }], + total_count: 1, cache_revision: 'cache-1', search_provenance: {} + }); + return new Promise<Response>((resolve) => { resolveNext = resolve; }); + }); + const state = new ExploreState(window); + state.replaceTransient({ groupingChain: ['source'] }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await screen.findByText('Old source group'); + + state.commitNavigation({ groupingChain: ['month'] }); + await waitFor(() => expect(calls).toBe(2)); + + expect(screen.queryByText('Old source group')).toBeNull(); + expect(screen.getByText('Loading grouped results…')).toBeDefined(); + resolveNext?.(Response.json({ rows: [], total_count: 0, cache_revision: 'cache-1', search_provenance: {} })); + rendered.unmount(); + state.destroy(); + }); + + + it('restores the durable grouped active key and scroll anchor after drill Back', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const groupRows = Array.from({ length: 30 }, (_, index) => ({ + key: String(index + 1), label: `Source ${index + 1}`, count: index + 1, + estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' + })); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + return new URL(request.url).pathname.endsWith('/groups') + ? Response.json({ rows: groupRows, total_count: 30, cache_revision: 'cache-1', search_provenance: {} }) + : Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.commitNavigation({ groupingChain: ['source'] }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything grouped by source' }) as HTMLDivElement; + await screen.findByText('Source 2'); + Object.defineProperty(grid, 'scrollTop', { configurable: true, value: 36, writable: true }); + await fireEvent.scroll(grid); + await waitFor(() => expect(state.current).toMatchObject({ + activeRow: 'group:source:2', scrollAnchor: { key: 'group:source:2', offset: 0 } + })); + await fireEvent.click(screen.getByRole('button', { name: 'Drill into Source 2' })); + + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + const restored = await screen.findByRole('grid', { name: 'Everything grouped by source' }); + await waitFor(() => expect(restored.getAttribute('aria-activedescendant')).toBe('everything-group-3Asource-3A2')); + expect(state.current.activeRow).toBe('group:source:2'); + expect(state.current.scrollAnchor).toEqual({ key: 'group:source:2', offset: 0 }); + rendered.unmount(); + state.destroy(); + }); + + + it('restores an inspected drilled group across Back and Forward after grouped rows are cleared', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (!path.endsWith('/groups')) return Response.json(exploreResponse()); + const body = await request.clone().json() as { filters?: Array<{ dimension: string; values: string[] }> }; + const restoringDetail = body.filters?.some((filter) => + filter.dimension === 'source' && filter.values.includes('7')); + return Response.json({ + rows: [{ + key: '7', + label: restoringDetail ? 'Restored source detail' : 'Example source group', + count: 12, + estimated_bytes: 42, + latest_at: '2026-07-18T12:00:00Z' + }], + total_count: 1, + cache_revision: 'cache-1', + search_provenance: {} + }); + }); + const state = new ExploreState(window); + state.commitNavigation({ groupingChain: ['source'] }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await screen.findByText('Example source group'); + + await fireEvent.click(screen.getByRole('button', { name: 'Drill into Example source group' })); + expect(await screen.findByRole('complementary', { name: 'Inspect Restored source detail' })).toBeDefined(); + expect(screen.queryByRole('grid', { name: 'Everything grouped by source' })).toBeNull(); + expect(state.current.selectedRow).toBe('group:source:7'); + + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + expect(await screen.findByRole('grid', { name: 'Everything grouped by source' })).toBeDefined(); + expect(state.current.selectedRow).toBeNull(); + + window.history.forward(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + expect(await screen.findByRole('complementary', { name: 'Inspect Restored source detail' })).toBeDefined(); + expect(state.current.selectedRow).toBe('group:source:7'); + rendered.unmount(); + state.destroy(); + }); + + + it('reapplies a retained group anchor across same-count result generations', async () => { + const initialState = { + schemaVersion: 1, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: ['source'], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: 'group:source:20', selectedRow: null, inspectorPinned: true, + conversationAnchor: null, scrollAnchor: { key: 'group:source:15', offset: 4 } + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(initialState))}`); + let generation = 0; + const fetchFn = vi.fn<typeof fetch>(async () => { + generation += 1; + return Response.json({ + rows: Array.from({ length: 30 }, (_, index) => ({ + key: String(index + 1), label: `Generation ${generation} Source ${index + 1}`, + count: index + 1, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' + })), + total_count: 30, cache_revision: 'cache-1', search_provenance: {} + }); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything grouped by source' }) as HTMLDivElement; + await screen.findByText('Generation 1 Source 20'); + await waitFor(() => expect(grid.scrollTop).toBe(14 * 36 + 4)); + expect(grid.getAttribute('aria-activedescendant')).toBe('everything-group-3Asource-3A20'); + + grid.scrollTop = 0; + state.replaceTransient({ filters: [{ dimension: 'source', values: ['20'] }] }); + await screen.findByText('Generation 2 Source 20'); + + await waitFor(() => expect(grid.scrollTop).toBe(14 * 36 + 4)); + expect(grid.getAttribute('aria-activedescendant')).toBe('everything-group-3Asource-3A20'); + expect(state.current.activeRow).toBe('group:source:20'); + expect(state.current.scrollAnchor).toEqual({ key: 'group:source:15', offset: 4 }); + rendered.unmount(); + state.destroy(); + }); + + + it('retries grouped deep restoration with exact focus and scroll state', async () => { + const initialState = { + schemaVersion: 1, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: ['source'], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: 'group:source:1200', selectedRow: null, inspectorPinned: true, + conversationAnchor: null, scrollAnchor: { key: 'group:source:1190', offset: 3 } + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(initialState))}`); + let failedOnce = false; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const body = await request.clone().json(); + const offset = body.cursor ? Number(String(body.cursor).slice('page:'.length)) : 0; + if (offset === 500 && !failedOnce) { + failedOnce = true; + throw new Error('Grouped restoration interrupted.'); + } + return Response.json({ + rows: Array.from({ length: 500 }, (_, index) => ({ + key: String(offset + index + 1), label: `Source ${offset + index + 1}`, + count: offset + index + 1, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' + })), + total_count: 1500, + cache_revision: 'cache-1', + search_provenance: {}, + ...(offset + 500 < 1500 ? { next_cursor: `page:${offset + 500}` } : {}) + }); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + expect((await screen.findByRole('alert')).textContent).toContain('Grouped restoration interrupted.'); + expect(state.current.activeRow).toBe('group:source:1200'); + expect(state.current.scrollAnchor).toEqual({ key: 'group:source:1190', offset: 3 }); + await fireEvent.click(screen.getByRole('button', { name: 'Retry restoration' })); + + const grid = screen.getByRole('grid', { name: 'Everything grouped by source' }) as HTMLDivElement; + await waitFor(() => expect(grid.getAttribute('aria-activedescendant')).toBe('everything-group-3Asource-3A1200')); + await waitFor(() => expect(grid.scrollTop).toBe(1189 * 36 + 3)); + expect(state.current.activeRow).toBe('group:source:1200'); + expect(state.current.scrollAnchor).toEqual({ key: 'group:source:1190', offset: 3 }); + expect(fetchFn).toHaveBeenCalledTimes(5); + rendered.unmount(); + state.destroy(); + }); + + it.each([ + ['participant', '42', [{ dimension: 'participant', values: ['42'] }]], + ['domain', 'example.com', [{ dimension: 'domain', values: ['example.com'] }]], + ['message_type', 'email', [{ dimension: 'message_type', values: ['email'] }]], + ['year', '2026', [ + { dimension: 'after', values: ['2026-01-01T00:00:00Z'] }, + { dimension: 'before', values: ['2027-01-01T00:00:00Z'] } + ]], + ['month', '2026-07', [ + { dimension: 'after', values: ['2026-07-01T00:00:00Z'] }, + { dimension: 'before', values: ['2026-08-01T00:00:00Z'] } + ]], + ['month', '2026-12', [ + { dimension: 'after', values: ['2026-12-01T00:00:00Z'] }, + { dimension: 'before', values: ['2027-01-01T00:00:00Z'] } + ]] + ] as const)('drills %s groups into a changed URL predicate', async (dimension, key, expectedFilters) => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (new URL(request.url).pathname.endsWith('/groups')) return Response.json({ + rows: [{ key, label: key, count: 2, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' }], + total_count: 1, cache_revision: 'cache-1', search_provenance: {} + }); + return Response.json(exploreResponse()); + }); + const state = new ExploreState(window); + state.replaceTransient({ + groupingChain: [dimension], + activeRow: `group:${dimension}:${key}`, + scrollAnchor: { key: `group:${dimension}:${key}`, offset: 4 } + }); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + await fireEvent.click(await screen.findByRole('button', { name: `Drill into ${key}` })); + + expect(state.current.filters).toEqual(expectedFilters); + expect(parseExploreURLState(window.location.search).filters).toEqual(expectedFilters); + expect(state.current.groupingChain).toEqual([]); + expect(state.current.scrollAnchor).toBeNull(); + rendered.unmount(); + state.destroy(); + }); + + + it('removes all-matching promotion while a newer predicate generation is loading', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let resolveNext: ((response: Response) => void) | undefined; + let calls = 0; + const fetchFn = vi.fn<typeof fetch>(async () => { + calls += 1; + if (calls === 1) { + return Response.json(exploreResponse({ rows: [entry(1), entry(2)], total_count: 2 })); + } + return new Promise<Response>((resolve) => { resolveNext = resolve; }); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 2'); + grid.focus(); + await fireEvent.keyDown(grid, { key: 'A' }); + expect(screen.getByRole('button', { name: 'Select all 2 matching items' })).toBeDefined(); + + state.commitSearch('new predicate', 'full_text'); + await waitFor(() => expect(calls).toBe(2)); + expect(screen.queryByRole('button', { name: 'Select all 2 matching items' })).toBeNull(); + + resolveNext?.(Response.json(exploreResponse({ + rows: [entry(3)], + total_count: 1, + search_provenance: { lexical_index_revision: 'fts-2' } + }))); + rendered.unmount(); + state.destroy(); + }); + + + it('opens, resizes, and closes the pinned inspector with URL and focus restoration', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async () => Response.json(exploreResponse({ + rows: [entry(1), entry(2)], total_count: 2 + }))); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 1'); + grid.focus(); + + await fireEvent.keyDown(grid, { key: 'Enter' }); + expect(await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1' })).toBeDefined(); + expect(state.current.selectedRow).toBe('message:1'); + expect(state.current.inspectorPinned).toBe(true); + expect(screen.queryByRole('dialog', { name: 'Inspect Synthetic subject 1' })).toBeNull(); + expect(document.querySelector('.kit-detail-drawer-overlay')).toBeNull(); + + await fireEvent.keyDown(screen.getByRole('button', { name: 'Resize inspector' }), { key: 'ArrowLeft' }); + expect(state.current.inspectorWidth).toBe(404); + expect(parseExploreURLState(window.location.search).inspectorWidth).toBe(404); + await fireEvent.click(screen.getByRole('button', { name: 'Close inspector' })); + + await waitFor(() => expect(document.activeElement).toBe(grid)); + expect(state.current.selectedRow).toBeNull(); + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + expect(await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1' })).toBeDefined(); + expect(state.current.inspectorWidth).toBe(404); + rendered.unmount(); + state.destroy(); + }); + + + it('replaces peer anchors so one Back restores the complete inspector URL and focus', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const row = { ...entry(1), anchor_message_id: 1, conversation_id: 7 }; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path.startsWith('/api/v1/conversations/')) { + const anchor = Number(new URL(request.url).searchParams.get('anchor')); + return Response.json({ + id: 7, + anchor_id: anchor, + messages: [1, 2].map((id) => ({ + id, conversation_id: 7, subject: `${row.title} ${id}`, message_type: 'email', + from: 'alice@example.com', to: ['bob@example.com'], sent_at: row.occurred_at, + snippet: row.preview, labels: [], has_attachments: false, size_bytes: 10, + body: `Archived body ${id}`, body_html: `<p>Archived body ${id}</p>`, attachments: [] + })), + has_before: false, has_after: false, total: 2 + }); + } + return Response.json(exploreResponse({ rows: [row], total_count: 1 })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText(row.title); + grid.focus(); + await fireEvent.keyDown(grid, { key: 'Enter' }); + const priorSearch = window.location.search; + + await fireEvent.click(await screen.findByRole('button', { name: 'View conversation' })); + + expect(state.current.conversationAnchor).toBe('1'); + expect(await screen.findByRole('heading', { name: 'Conversation' })).toBeDefined(); + await fireEvent.click(await screen.findByRole('button', { name: /Open message 2/ })); + await waitFor(() => expect(state.current.conversationAnchor).toBe('2')); + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + await waitFor(() => expect(state.current.conversationAnchor).toBeNull()); + expect(window.location.search).toBe(priorSearch); + const viewConversation = screen.getByRole('button', { name: 'View conversation' }); + expect(viewConversation).toBeDefined(); + await waitFor(() => expect(document.activeElement).toBe(viewConversation)); + await fireEvent.keyDown(viewConversation, { key: 'Escape' }); + await waitFor(() => expect(state.current.selectedRow).toBeNull()); + await waitFor(() => expect(document.activeElement).toBe(grid)); + rendered.unmount(); + state.destroy(); + }); + + + it('suspends application row shortcuts in explicit inspector content focus', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const fetchFn = vi.fn<typeof fetch>(async () => Response.json(exploreResponse({ + rows: [entry(1), entry(2)], total_count: 2 + }))); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + const grid = await screen.findByRole('grid', { name: 'Everything results' }); + await screen.findByText('Synthetic subject 2'); + grid.focus(); + await fireEvent.keyDown(grid, { key: 'Enter' }); + await fireEvent.click(await screen.findByRole('button', { name: 'Focus inspector content' })); + const content = screen.getByRole('region', { name: 'Inspector content' }); + expect(appShortcuts.activeScope()).toBe('everything-inspector-content'); + + const editor = document.createElement('textarea'); + content.append(editor); + editor.focus(); + await waitFor(() => expect(appShortcuts.activeScope()).toBe('everything-editable')); + const modified = new KeyboardEvent('keydown', { + key: 'k', ctrlKey: true, bubbles: true, cancelable: true + }); + editor.dispatchEvent(modified); + expect(modified.defaultPrevented).toBe(false); + editor.remove(); + await waitFor(() => expect(appShortcuts.activeScope()).toBe('everything-inspector-content')); + content.focus(); + + await fireEvent.keyDown(content, { key: 'j' }); + expect(state.current.activeRow).toBe('message:1'); + expect(state.current.selectedRow).toBe('message:1'); + await fireEvent.keyDown(content, { key: 'Escape' }); + expect(screen.getByRole('complementary', { name: 'Inspect Synthetic subject 1' })).toBeDefined(); + expect(appShortcuts.activeScope()).toBe('root'); + rendered.unmount(); + state.destroy(); + }); + + it('advances the main load, inspector detail, and coverage effects exactly once each for a single filter commit', async () => { + const initialState = { + schemaVersion: 1, workspace: 'everything', query: '', searchMode: 'hybrid', filters: [], + groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: null, selectedRow: 'group:source:7', inspectorPinned: true, + conversationAnchor: null, scrollAnchor: null + }; + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify(initialState))}`); + const counts = { explore: 0, groups: 0, coverage: 0 }; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = new URL(request.url).pathname; + if (path.endsWith('/explore/groups')) { + counts.groups += 1; + return Response.json({ + rows: [{ key: '7', label: 'Example source', count: 12, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' }], + total_count: 1, cache_revision: 'cache-1', search_provenance: {} + }); + } + if (path.endsWith('/search/coverage')) { + counts.coverage += 1; + return Response.json({ + status: 'ready', eligible_count: 10, embedded_count: 10, percentage: 100, + cache_revision: 'cache-1', actions: [] + }); + } + counts.explore += 1; + return Response.json(exploreResponse({ rows: [entry(1)], total_count: 1 })); + }); + const state = new ExploreState(window); + const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); + + await screen.findByText('Synthetic subject 1'); + await screen.findByText('Example source'); + await waitFor(() => expect(counts).toEqual({ explore: 1, groups: 1, coverage: 1 })); + + state.commitNavigation({ filters: [{ dimension: 'source', values: ['99'] }] }); + await waitFor(() => expect(counts.explore).toBe(2)); + await waitFor(() => expect(counts).toEqual({ explore: 2, groups: 2, coverage: 2 })); + await new Promise((resolve) => setTimeout(resolve, 20)); + expect(counts).toEqual({ explore: 2, groups: 2, coverage: 2 }); + + rendered.unmount(); + state.destroy(); + }); +}); diff --git a/web/src/lib/components/shell/KeyboardHelp.svelte b/web/src/lib/components/shell/KeyboardHelp.svelte new file mode 100644 index 000000000..a8690fc97 --- /dev/null +++ b/web/src/lib/components/shell/KeyboardHelp.svelte @@ -0,0 +1,112 @@ +<script lang="ts"> + import { KbdBadge, Modal, SearchInput } from '@kenn-io/kit-ui'; + + import type { AppCommand } from '../../commands/registry'; + + let { commands, onclose }: { commands: AppCommand[]; onclose: () => void } = $props(); + let query = $state(''); + const shortcuts = $derived(commands.filter((command) => command.keys.length > 0)); + const filtered = $derived.by(() => { + const needle = query.trim().toLowerCase(); + return needle + ? shortcuts.filter((command) => `${command.label} ${command.keywords}`.toLowerCase().includes(needle)) + : shortcuts; + }); + const sections = $derived([...new Set(filtered.map(({ section }) => section))]); +</script> + +<Modal title="Keyboard shortcuts" ariaLabel="Keyboard shortcuts" width="min(680px, calc(100vw - 32px))" {onclose}> + <div class="keyboard-help-search"> + <SearchInput + value={query} + ariaLabel="Search keyboard shortcuts" + placeholder="Search shortcuts" + block + autofocus + oninput={(value) => { query = value; }} + /> + </div> + <div class="keyboard-help-list" aria-live="polite"> + {#if filtered.length === 0} + <p>No matching shortcuts.</p> + {/if} + {#each sections as section (section)} + <section aria-labelledby={`shortcut-${section.toLowerCase()}`}> + <h2 id={`shortcut-${section.toLowerCase()}`}>{section}</h2> + <dl> + {#each filtered.filter((command) => command.section === section) as command (command.id)} + <div> + <dt>{command.label}</dt> + <dd> + {#if command.combos.length === 1 && command.keys.length > 1} + <KbdBadge keys={[...command.keys]} /> + {:else} + {#each command.keys as key, index (key)} + {#if index > 0}<span aria-hidden="true">or</span>{/if} + <KbdBadge keys={[key]} /> + {/each} + {/if} + {#if command.destructive}<span class="review-note">opens review</span>{/if} + </dd> + </div> + {/each} + </dl> + </section> + {/each} + </div> +</Modal> + +<style> + .keyboard-help-search { + position: sticky; + top: 0; + z-index: 1; + padding-bottom: var(--space-4); + background: var(--bg-surface); + } + + .keyboard-help-list { + display: grid; + gap: var(--space-5); + } + + h2 { + margin: 0 0 var(--space-2); + color: var(--text-muted); + font-size: var(--font-size-xs); + letter-spacing: 0.08em; + text-transform: uppercase; + } + + dl { + display: grid; + margin: 0; + } + + dl div { + display: grid; + min-height: var(--row-height); + grid-template-columns: minmax(0, 1fr) auto; + align-items: center; + gap: var(--space-4); + border-top: 1px solid var(--border-muted); + } + + dt { + color: var(--text-primary); + font-size: var(--font-size-sm); + } + + dd { + display: flex; + align-items: center; + gap: var(--space-2); + margin: 0; + color: var(--text-muted); + font-size: var(--font-size-xs); + } + + .review-note { + color: var(--status-destructive-ink); + } +</style> diff --git a/web/src/lib/components/shell/KeyboardHelp.test.ts b/web/src/lib/components/shell/KeyboardHelp.test.ts new file mode 100644 index 000000000..5ff6b1095 --- /dev/null +++ b/web/src/lib/components/shell/KeyboardHelp.test.ts @@ -0,0 +1,63 @@ +import { fireEvent, render, screen } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { + COMMAND_DEFINITIONS, + createCommandRegistry, + type CommandHandlers, + type CommandID +} from '../../commands/registry'; +import KeyboardHelp from './KeyboardHelp.svelte'; + +describe('keyboard command registry', () => { + it('covers every documented keyboard command with a handler', () => { + const handlers = handlersFor(COMMAND_DEFINITIONS.map(({ id }) => id)); + const registry = createCommandRegistry(handlers); + + expect(registry.map(({ id }) => id)).toEqual(COMMAND_DEFINITIONS.map(({ id }) => id)); + expect(registry.find(({ id }) => id === 'select-visible')?.keys).toContain('A'); + expect(registry.find(({ id }) => id === 'clear-selection')?.keys).toContain('x'); + expect(registry.some(({ keys }) => keys.includes('Tab'))).toBe(false); + expect(registry.filter(({ destructive }) => destructive).every(({ review }) => review)).toBe(true); + }); + + it('fails closed when a documented command lacks a handler', () => { + const ids = COMMAND_DEFINITIONS.map(({ id }) => id).filter((id) => id !== 'clear-selection'); + expect(() => createCommandRegistry(handlersFor(ids))).toThrow(/clear-selection.*handler/i); + }); + + it('renders and searches the registry, including selection commands', async () => { + const onclose = vi.fn(); + render(KeyboardHelp, { + commands: createCommandRegistry(handlersFor(COMMAND_DEFINITIONS.map(({ id }) => id))), + onclose + }); + + expect(screen.getByRole('dialog', { name: 'Keyboard shortcuts' })).toBeDefined(); + expect(screen.getByText('Select all visible rows')).toBeDefined(); + expect(screen.getByText('Clear selection')).toBeDefined(); + await fireEvent.input(screen.getByRole('searchbox', { name: 'Search keyboard shortcuts' }), { + target: { value: 'selection' } + }); + expect(screen.getByText('Select all visible rows')).toBeDefined(); + expect(screen.queryByText('Open filters')).toBeNull(); + await fireEvent.click(screen.getByRole('button', { name: 'Close' })); + expect(onclose).toHaveBeenCalledOnce(); + }); + + it('renders a modified shortcut as one chord instead of key alternatives', () => { + render(KeyboardHelp, { + commands: createCommandRegistry(handlersFor(COMMAND_DEFINITIONS.map(({ id }) => id))), + onclose: vi.fn() + }); + + const command = screen.getByText('Open command palette').closest('div'); + expect(command).not.toBeNull(); + expect(command?.querySelector('[aria-label="Mod K"]')).not.toBeNull(); + expect(command?.textContent).not.toContain('or'); + }); +}); + +function handlersFor(ids: CommandID[]): CommandHandlers { + return Object.fromEntries(ids.map((id) => [id, vi.fn()])) as unknown as CommandHandlers; +} diff --git a/web/src/lib/components/sources/SourcesWorkspace.svelte b/web/src/lib/components/sources/SourcesWorkspace.svelte new file mode 100644 index 000000000..ce57fddf6 --- /dev/null +++ b/web/src/lib/components/sources/SourcesWorkspace.svelte @@ -0,0 +1,288 @@ +<script lang="ts"> + import { Button, Spinner } from '@kenn-io/kit-ui'; + import { onDestroy, onMount } from 'svelte'; + + import type { APIClient } from '../../api/client'; + import type { components } from '../../api/generated/schema'; + + type SyncRun = components['schemas']['SyncRunStatus']; + type Source = components['schemas']['SourceStatus']; + + const MIN_POLL_MS = 500; + const MAX_POLL_MS = 8_000; + // A terminal result older than 24 hours is explicitly called stale. + const STALE_LAST_RESULT_MS = 24 * 60 * 60 * 1_000; + + let { + client, maxAwaitingPolls = 6, now = () => new Date() + }: { client: APIClient; maxAwaitingPolls?: number; now?: () => Date } = $props(); + let sources = $state<Source[]>([]); + let loading = $state(true); + let statusError = $state(''); + let triggerError = $state(''); + let triggering = $state<string>(); + let controller: AbortController | undefined; + let timer: ReturnType<typeof setTimeout> | undefined; + let generation = 0; + let pollDelay = MIN_POLL_MS; + let progress = new Map<number, number>(); + let awaitingSourceID = $state<number>(); + let awaitingBaselineRunID: number | undefined; + let awaitingAttempts = 0; + let awaitingState = $state<'idle' | 'awaiting' | 'not_observed'>('idle'); + let disposed = false; + + onMount(() => { + const visibilityChanged = (): void => { + if (document.hidden) { + stopPolling(); + } else { + pollDelay = MIN_POLL_MS; + void load(); + } + }; + document.addEventListener('visibilitychange', visibilityChanged); + void load(); + return () => document.removeEventListener('visibilitychange', visibilityChanged); + }); + + onDestroy(() => { + disposed = true; + stopPolling(); + }); + + function stopPolling(clearAwaiting = true): void { + generation += 1; + controller?.abort(); + controller = undefined; + if (timer !== undefined) clearTimeout(timer); + timer = undefined; + if (clearAwaiting) { + awaitingSourceID = undefined; + awaitingBaselineRunID = undefined; + awaitingAttempts = 0; + awaitingState = 'idle'; + } + } + + // allowIdle bypasses the active-sync gate below for error-path retries: + // a status-load failure must be able to reschedule itself even when no + // source is actively syncing and no accepted run is being awaited, or an + // initial/inactive-source failure would be unrecoverable until remount. + function schedulePoll(delay: number, allowIdle = false): void { + if (disposed || document.hidden) return; + if (!allowIdle && !sources.some((source) => source.active_sync) && awaitingSourceID === undefined) return; + if (timer !== undefined) clearTimeout(timer); + timer = setTimeout(() => { + timer = undefined; + void load(); + }, delay); + } + + async function load(): Promise<void> { + if (disposed || document.hidden) return; + const requestGeneration = ++generation; + controller?.abort(); + const requestController = new AbortController(); + controller = requestController; + try { + const { data, error: responseError } = await client.GET('/api/v1/sources/status', { + signal: requestController.signal + }); + if (requestGeneration !== generation || disposed) return; + if (!data) throw new Error(messageFor(responseError, 'Unable to load source status.')); + const next = data.sources ?? []; + let nextDelay: number | undefined; + let advanced = false; + const nextProgress = new Map<number, number>(); + for (const source of next) { + if (!source.active_sync) continue; + const processed = source.active_sync.messages_processed; + nextProgress.set(source.id, processed); + if (processed > (progress.get(source.id) ?? -1)) advanced = true; + } + progress = nextProgress; + const awaitedSource = awaitingSourceID === undefined + ? undefined + : next.find((source) => source.id === awaitingSourceID); + const acceptedRunObserved = Boolean(awaitedSource?.active_sync) + || (awaitedSource?.latest_sync != null && awaitedSource.latest_sync.id !== awaitingBaselineRunID); + if (acceptedRunObserved) { + awaitingSourceID = undefined; + awaitingBaselineRunID = undefined; + awaitingAttempts = 0; + awaitingState = 'idle'; + pollDelay = MIN_POLL_MS; + } else if (awaitingSourceID !== undefined) { + awaitingAttempts += 1; + if (awaitingAttempts >= maxAwaitingPolls) { + awaitingSourceID = undefined; + awaitingState = 'not_observed'; + } else { + awaitingState = 'awaiting'; + nextDelay = pollDelay; + pollDelay = Math.min(MAX_POLL_MS, pollDelay * 2); + } + } + if (next.some((source) => source.active_sync)) { + pollDelay = advanced ? MIN_POLL_MS : Math.min(MAX_POLL_MS, pollDelay * 2); + nextDelay = pollDelay; + } + sources = next; + statusError = ''; + if (nextDelay !== undefined) schedulePoll(nextDelay); + } catch (cause) { + if (requestController.signal.aborted || requestGeneration !== generation || disposed) return; + statusError = cause instanceof Error ? cause.message : 'Unable to load source status.'; + if (awaitingSourceID !== undefined) { + awaitingAttempts += 1; + if (awaitingAttempts >= maxAwaitingPolls) { + awaitingSourceID = undefined; + awaitingState = 'not_observed'; + } else { + awaitingState = 'awaiting'; + const nextDelay = pollDelay; + pollDelay = Math.min(MAX_POLL_MS, pollDelay * 2); + schedulePoll(nextDelay, true); + } + } else { + const nextDelay = pollDelay; + pollDelay = Math.min(MAX_POLL_MS, pollDelay * 2); + schedulePoll(nextDelay, true); + } + } finally { + if (requestGeneration === generation) { + controller = undefined; + loading = false; + } + } + } + + async function syncNow(source: Source): Promise<void> { + if (!source.can_sync || triggering) return; + triggering = source.identifier; + triggerError = ''; + try { + const { data, error: responseError, response } = await client.POST('/api/v1/sync/{account}', { + params: { path: { account: source.identifier } } + }); + if (response.status !== 202 || !data) { + throw new Error(messageFor(responseError, `Unable to start sync for ${source.identifier}.`)); + } + stopPolling(); + pollDelay = MIN_POLL_MS; + awaitingSourceID = source.id; + awaitingBaselineRunID = source.latest_sync?.id; + awaitingAttempts = 0; + awaitingState = 'awaiting'; + await load(); + } catch (cause) { + triggerError = cause instanceof Error ? cause.message : `Unable to start sync for ${source.identifier}.`; + schedulePoll(MIN_POLL_MS); + } finally { + triggering = undefined; + } + } + + function label(source: Source): string { + return source.display_name || source.identifier; + } + + function statusLabel(run: SyncRun | null): string { + if (!run) return 'Never'; + if (run.status === 'completed') return 'Completed'; + if (run.status === 'failed') return 'Failed'; + return run.status.replaceAll('_', ' '); + } + + function staleLastResult(source: Source): boolean { + const resultAt = source.latest_sync?.completed_at ?? source.latest_sync?.started_at; + if (!resultAt) return false; + const timestamp = Date.parse(resultAt); + return Number.isFinite(timestamp) && now().getTime() - timestamp > STALE_LAST_RESULT_MS; + } + + function messageFor(value: unknown, fallback: string): string { + return typeof value === 'object' && value !== null && 'message' in value && typeof value.message === 'string' + ? value.message : fallback; + } +</script> + +<main class="sources" aria-label="Sources"> + <header><div><p>Archive workspace</p><h1>Sources</h1></div><span>Status and incremental sync</span></header> + {#if statusError}<p class="notice notice--error" role="alert">{statusError}</p>{/if} + {#if triggerError}<p class="notice notice--error" role="alert">{triggerError}</p>{/if} + {#if awaitingState === 'awaiting'}<p class="notice" role="status">Awaiting accepted sync run…</p> + {:else if awaitingState === 'not_observed'}<p class="notice notice--error" role="status">sync_start_not_observed</p>{/if} + {#if loading}<p role="status">Loading source status…</p> + {:else if sources.length === 0}<p class="notice" role="status">No archived sources are available.</p> + {:else} + <section class="source-list" aria-label="Source status list"> + {#each sources as source (source.id)} + <article> + <div class="identity"> + <h2>{label(source)}</h2> + <span>{source.source_type} · {source.identifier}</span> + <span>Updated {source.updated_at}</span> + {#if source.scheduled} + <span>Scheduled · {source.schedule ?? 'schedule unavailable'}</span> + {#if source.next_sync_at}<span>Next {source.next_sync_at}</span>{/if} + {:else}<span>Not scheduled</span>{/if} + {#if source.scheduler_last_error}<span class="error-copy">Scheduler: {source.scheduler_last_error}</span>{/if} + </div> + <div class="run-state"> + {#if source.active_sync} + <span class="working"><Spinner size={12} label={`Syncing ${label(source)}`} /> Syncing</span> + <strong>{source.active_sync.messages_processed.toLocaleString()} processed</strong> + <span>{source.active_sync.messages_added.toLocaleString()} added · {source.active_sync.errors_count.toLocaleString()} errors</span> + {:else} + <span>Latest result</span> + {#if source.latest_sync} + <strong>{statusLabel(source.latest_sync)}</strong> + {#if staleLastResult(source)}<span class="reason">stale_last_result</span>{/if} + {#if source.latest_sync.error_message}<span class="error-copy">{source.latest_sync.error_message}</span>{/if} + {#if source.latest_sync.item_errors?.length} + <details open> + <summary>{source.latest_sync.item_errors.length} item {source.latest_sync.item_errors.length === 1 ? 'error' : 'errors'}</summary> + {#each source.latest_sync.item_errors as item (`${item.source_message_id}:${item.phase}:${item.created_at}`)} + <span class="error-copy">{item.error_message}</span> + {/each} + </details> + {/if} + {:else}<strong>No prior sync result</strong>{/if} + {/if} + </div> + <div class="last-success"> + <span>Last successful sync</span> + <strong>{source.last_successful_sync?.completed_at ?? 'No successful sync result'}</strong> + </div> + <div class="action"> + {#if source.can_sync} + <Button size="sm" tone="info" surface="soft" label={`Sync now ${label(source)}`} disabled={Boolean(triggering) || awaitingSourceID === source.id} onclick={() => void syncNow(source)} /> + {:else} + <span class="reason">{source.sync_unavailable_reason ?? 'sync_unavailable'}</span> + {/if} + </div> + </article> + {/each} + </section> + {/if} +</main> + +<style> + .sources { display: flex; min-height: 0; flex: 1; flex-direction: column; gap: var(--space-4); padding: var(--space-5) var(--space-6); } + header, article { display: flex; align-items: center; justify-content: space-between; gap: var(--space-4); } + header p, h1, h2 { margin: 0; } + header p { color: var(--accent-amber); font-size: var(--font-size-2xs); font-weight: 800; letter-spacing: .1em; text-transform: uppercase; } + header span, article span { color: var(--text-muted); font-size: var(--font-size-xs); } + .source-list { display: grid; border-top: 1px solid var(--border-muted); } + article { min-height: 72px; padding: var(--space-3); border-bottom: 1px solid var(--border-muted); } + .identity, .run-state, .last-success, details { display: grid; min-width: 0; gap: var(--space-1); } + .identity { flex: 1; } + .run-state, .last-success { width: min(22vw, 240px); } + .working { display: flex; align-items: center; gap: var(--space-2); color: var(--accent-teal); } + .reason, .error-copy { color: var(--text-danger); } + .notice { padding: var(--space-3); border-left: 3px solid var(--accent-amber); background: var(--bg-subtle); } + .notice--error { border-color: var(--accent-red); color: var(--text-danger); } + @media (max-width: 760px) { article { align-items: stretch; flex-direction: column; } .run-state, .last-success { width: auto; } } +</style> diff --git a/web/src/lib/components/sources/SourcesWorkspace.test.ts b/web/src/lib/components/sources/SourcesWorkspace.test.ts new file mode 100644 index 000000000..e21ecf6f3 --- /dev/null +++ b/web/src/lib/components/sources/SourcesWorkspace.test.ts @@ -0,0 +1,255 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import SourcesWorkspace from './SourcesWorkspace.svelte'; + +function source(overrides: Record<string, unknown> = {}) { + return { + id: 1, source_type: 'gmail', identifier: 'archive@example.com', display_name: 'Archive', + last_sync_at: '2026-07-19T10:00:00Z', updated_at: '2026-07-19T10:00:00Z', + active_sync: null, latest_sync: null, last_successful_sync: null, + can_sync: true, scheduled: false, next_sync_at: null, ...overrides + }; +} + +function run(status: string, processed: number, overrides: Record<string, unknown> = {}) { + return { + id: 9, source_id: 1, started_at: '2026-07-19T10:00:00Z', completed_at: null, + status, messages_processed: processed, messages_added: processed, + messages_updated: 0, errors_count: 0, error_message: null, + cursor_before: null, cursor_after: null, ...overrides + }; +} + +afterEach(() => vi.useRealTimers()); + +describe('SourcesWorkspace', () => { + it('shows status and only exposes Sync now from server capability truth', async () => { + const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ sources: [ + source(), + source({ id: 2, source_type: 'imap', identifier: 'readonly@example.com', display_name: null, + can_sync: false, sync_unavailable_reason: 'sync_not_configured' }) + ] })); + render(SourcesWorkspace, { client: createAPIClient(fetchFn) }); + + expect(await screen.findByRole('heading', { name: 'Archive' })).toBeDefined(); + expect(screen.getAllByText('Last successful sync')).toHaveLength(2); + expect(screen.getByRole('button', { name: 'Sync now Archive' })).toBeDefined(); + expect(screen.queryByRole('button', { name: 'Sync now readonly@example.com' })).toBeNull(); + expect(screen.getByText('sync_not_configured')).toBeDefined(); + expect(screen.getAllByText('Not scheduled')).toHaveLength(2); + expect(screen.getAllByText('No prior sync result')).toHaveLength(2); + expect(screen.getAllByText(/Updated 2026-07-19T10:00:00Z/)).toHaveLength(2); + }); + + it('shows authoritative schedule, fresh result, and bounded item error details', async () => { + const latest = run('completed', 10, { + completed_at: '2026-07-19T11:30:00Z', errors_count: 1, + item_errors: [{ + source_message_id: 'm-1', phase: 'ingest', error_kind: 'mime_error', + error_message: 'Malformed MIME header', created_at: '2026-07-19T11:30:00Z' + }] + }); + const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ sources: [source({ + scheduled: true, schedule: '0 */6 * * *', next_sync_at: '2026-07-19T18:00:00Z', latest_sync: latest + })] })); + render(SourcesWorkspace, { + client: createAPIClient(fetchFn), now: () => new Date('2026-07-19T12:00:00Z') + }); + + expect(await screen.findByText('Scheduled · 0 */6 * * *')).toBeDefined(); + expect(screen.getByText('Next 2026-07-19T18:00:00Z')).toBeDefined(); + expect(screen.queryByText('stale_last_result')).toBeNull(); + expect(screen.getByText('1 item error')).toBeDefined(); + expect(screen.getByText('Malformed MIME header')).toBeDefined(); + }); + + it('names a terminal result older than the documented threshold as stale', async () => { + const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ sources: [source({ + latest_sync: run('completed', 10, { completed_at: '2026-07-17T11:00:00Z' }) + })] })); + render(SourcesWorkspace, { + client: createAPIClient(fetchFn), now: () => new Date('2026-07-19T12:00:00Z') + }); + + expect(await screen.findByText('stale_last_result')).toBeDefined(); + }); + + it('keeps polling through idle status races until the accepted run appears and completes', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + let statusReads = 0; + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (request.method === 'POST') return Response.json({ status: 'accepted', message: 'started' }, { status: 202 }); + statusReads += 1; + if (statusReads === 1) return Response.json({ sources: [source()] }); + if (statusReads <= 3) return Response.json({ sources: [source()] }); + if (statusReads === 4) return Response.json({ sources: [source({ can_sync: false, sync_unavailable_reason: 'sync_already_running', active_sync: run('running', 5) })] }); + return Response.json({ sources: [source({ latest_sync: run('completed', 8, { completed_at: '2026-07-19T10:01:00Z' }) })] }); + }); + const rendered = render(SourcesWorkspace, { client: createAPIClient(fetchFn) }); + + await fireEvent.click(await screen.findByRole('button', { name: 'Sync now Archive' })); + expect(requests[1]!.method).toBe('POST'); + expect(new URL(requests[1]!.url).pathname).toBe('/api/v1/sync/archive%40example.com'); + + await vi.advanceTimersByTimeAsync(500); + expect(screen.getByText('Awaiting accepted sync run…')).toBeDefined(); + await vi.advanceTimersByTimeAsync(1_000); + await waitFor(() => expect(screen.getByText('5 processed')).toBeDefined()); + await vi.advanceTimersByTimeAsync(500); + await waitFor(() => expect(screen.getByText('Completed')).toBeDefined()); + + expect(requests.every((request) => request.headers.get('Accept') !== 'text/event-stream')).toBe(true); + expect(requests.every((request) => ['/api/v1/sources/status', '/api/v1/sync/archive%40example.com'].includes(new URL(request.url).pathname))).toBe(true); + rendered.unmount(); + }); + + it('observes an accepted sync that finishes between status polls', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + let statusReads = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (request.method === 'POST') { + return Response.json({ status: 'accepted', message: 'started' }, { status: 202 }); + } + statusReads += 1; + if (statusReads === 1) { + return Response.json({ sources: [source({ + latest_sync: run('completed', 4, { id: 9, completed_at: '2026-07-19T10:00:00Z' }) + })] }); + } + return Response.json({ sources: [source({ + latest_sync: run('completed', 7, { id: 10, completed_at: '2026-07-19T10:01:00Z' }) + })] }); + }); + const rendered = render(SourcesWorkspace, { client: createAPIClient(fetchFn) }); + + await fireEvent.click(await screen.findByRole('button', { name: 'Sync now Archive' })); + await waitFor(() => expect(statusReads).toBe(2)); + + expect(screen.getByText('Completed')).toBeDefined(); + await waitFor(() => expect(screen.queryByText('Awaiting accepted sync run…')).toBeNull()); + expect(screen.queryByText('sync_start_not_observed')).toBeNull(); + rendered.unmount(); + }); + + it('stops awaiting an accepted run at the configured cap with a named state', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + let reads = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (request.method === 'POST') return Response.json({ status: 'accepted', message: 'started' }, { status: 202 }); + reads += 1; + return Response.json({ sources: [source()] }); + }); + const rendered = render(SourcesWorkspace, { + client: createAPIClient(fetchFn), maxAwaitingPolls: 3 + }); + + await fireEvent.click(await screen.findByRole('button', { name: 'Sync now Archive' })); + await vi.advanceTimersByTimeAsync(500); + await vi.advanceTimersByTimeAsync(1_000); + + expect(await screen.findByText('sync_start_not_observed')).toBeDefined(); + expect(reads).toBe(4); + await vi.advanceTimersByTimeAsync(8_000); + expect(reads).toBe(4); + rendered.unmount(); + }); + + it('names conflicting runs', async () => { + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (request.method === 'POST') { + return Response.json({ error: 'sync_error', message: 'sync already running for archive@example.com' }, { status: 409 }); + } + return Response.json({ sources: [source()] }); + }); + const rendered = render(SourcesWorkspace, { client: createAPIClient(fetchFn) }); + + await fireEvent.click(await screen.findByRole('button', { name: 'Sync now Archive' })); + expect((await screen.findByRole('alert')).textContent).toContain('already running'); + rendered.unmount(); + }); + + it('retries an idle status failure without an active sync or an awaited accepted run', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + let reads = 0; + const fetchFn = vi.fn<typeof fetch>(async () => { + reads += 1; + if (reads === 1) throw new Error('status endpoint unreachable'); + return Response.json({ sources: [source()] }); + }); + const rendered = render(SourcesWorkspace, { client: createAPIClient(fetchFn) }); + + expect((await screen.findByRole('alert')).textContent).toContain('status endpoint unreachable'); + expect(reads).toBe(1); + + // No source is syncing and no accepted run is awaited, so this retry + // relies on the error path bypassing the active-sync gate. + await vi.advanceTimersByTimeAsync(500); + await waitFor(() => expect(reads).toBe(2)); + expect(await screen.findByRole('heading', { name: 'Archive' })).toBeDefined(); + expect(screen.queryByRole('alert')).toBeNull(); + rendered.unmount(); + }); + + it("continues polling source A when source B's trigger is rejected", async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + let statusReads = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (request.method === 'POST') { + return Response.json({ error: 'sync_error', message: 'sync already running elsewhere' }, { status: 409 }); + } + statusReads += 1; + return Response.json({ sources: [ + source({ + id: 1, identifier: 'active@example.com', display_name: 'Active', can_sync: false, + sync_unavailable_reason: 'sync_already_running', active_sync: run('running', statusReads) + }), + source({ id: 2, identifier: 'ready@example.com', display_name: 'Ready' }) + ] }); + }); + const rendered = render(SourcesWorkspace, { client: createAPIClient(fetchFn) }); + + await screen.findByText('1 processed'); + await fireEvent.click(screen.getByRole('button', { name: 'Sync now Ready' })); + expect((await screen.findByRole('alert')).textContent).toContain('already running elsewhere'); + await vi.advanceTimersByTimeAsync(500); + + await waitFor(() => expect(screen.getByText('2 processed')).toBeDefined()); + expect(screen.getByRole('alert').textContent).toContain('already running elsewhere'); + expect(statusReads).toBe(2); + rendered.unmount(); + }); + + it('aborts a stale status poll when unmounted', async () => { + vi.useFakeTimers({ shouldAdvanceTime: true }); + const signals: AbortSignal[] = []; + let statusReads = 0; + const fetchFn = vi.fn<typeof fetch>((input) => { + const request = input instanceof Request ? input : new Request(input); + statusReads += 1; + signals.push(request.signal); + if (statusReads === 1) { + return Promise.resolve(Response.json({ sources: [source({ + can_sync: false, sync_unavailable_reason: 'sync_already_running', active_sync: run('running', 5) + })] })); + } + return new Promise<Response>(() => undefined); + }); + const rendered = render(SourcesWorkspace, { client: createAPIClient(fetchFn) }); + await screen.findByText('5 processed'); + await vi.advanceTimersByTimeAsync(500); + await waitFor(() => expect(signals).toHaveLength(2)); + + rendered.unmount(); + + expect(signals[1]!.aborted).toBe(true); + }); +}); diff --git a/web/src/lib/components/tasks/CreateTaskDialog.svelte b/web/src/lib/components/tasks/CreateTaskDialog.svelte new file mode 100644 index 000000000..b37fdcc81 --- /dev/null +++ b/web/src/lib/components/tasks/CreateTaskDialog.svelte @@ -0,0 +1,131 @@ +<script lang="ts"> + import { Button, Modal } from '@kenn-io/kit-ui'; + import { untrack } from 'svelte'; + + import type { APIClient } from '../../api/client'; + + let { client, messageId, project, defaultTitle, archiveUID, conversationId, sourceType, sourceIdentifier, + sourceMessageId, subject, from, sentAt, + oncreated = () => undefined, onclose = () => undefined }: { + client: APIClient; messageId: number; project: string; defaultTitle: string; + archiveUID: string; conversationId: number; sourceType: string; sourceIdentifier: string; + sourceMessageId: string; subject: string; from: string; sentAt: string; + oncreated?: () => void; onclose?: () => void; + } = $props(); + + let title = $state(untrack(() => defaultTitle)); + let description = $state(''); + let priority = $state(''); + let labels = $state(''); + let pending = $state(false); + let error = $state(''); + let browserRequestID = globalThis.crypto.randomUUID(); + let addedAt = $state(new Date().toISOString()); + let lastAttemptFingerprint = $state<string>(); + const payloadFingerprint = $derived(JSON.stringify(currentPayload())); + + $effect(() => { + const fingerprint = payloadFingerprint; + if (lastAttemptFingerprint === undefined || lastAttemptFingerprint === fingerprint) return; + rotateRetryIdentity(); + lastAttemptFingerprint = undefined; + }); + + async function create(): Promise<void> { + if (!title.trim() || pending) return; + const payload = currentPayload(); + const fingerprint = JSON.stringify(payload); + lastAttemptFingerprint = fingerprint; + pending = true; error = ''; + try { + const { data, error: responseError } = await client.POST('/api/v1/messages/{id}/tasks', { + params: { path: { id: messageId }, header: { 'X-Request-Id': browserRequestID } }, + body: { + ...payload, + added_at: addedAt + } + }); + if (!data) throw new Error(messageFor(responseError)); + oncreated(); + } catch (cause) { + error = cause instanceof Error ? cause.message : 'Unable to create task.'; + } finally { pending = false; } + } + + function rotateRetryIdentity(): void { + browserRequestID = globalThis.crypto.randomUUID(); + const now = new Date().toISOString(); + addedAt = now > addedAt ? now : new Date(Date.parse(addedAt) + 1).toISOString(); + } + + function parsedLabels(): string[] { + return labels.split(',').map((label) => label.trim()).filter(Boolean); + } + + function currentPayload(): { + title: string; + description?: string; + priority?: string; + labels?: string[]; + } { + const normalizedLabels = parsedLabels(); + return { + title: title.trim(), + ...(description.trim() ? { description: description.trim() } : {}), + ...(priority ? { priority } : {}), + ...(normalizedLabels.length ? { labels: normalizedLabels } : {}) + }; + } + + function messageFor(value: unknown): string { + return typeof value === 'object' && value !== null && 'message' in value && typeof value.message === 'string' + ? value.message : 'Unable to create task.'; + } +</script> + +<Modal title="Create task" onclose={onclose}> + <form onsubmit={(event) => { event.preventDefault(); void create(); }}> + <p class="project"><span>Project</span><strong>{project}</strong></p> + <label>Task title<input aria-label="Task title" bind:value={title} autocomplete="off" /></label> + <label>Description<textarea aria-label="Description" bind:value={description}></textarea></label> + <label>Priority<select aria-label="Priority" bind:value={priority}> + <option value="">Default</option><option value="low">Low</option><option value="normal">Normal</option> + <option value="high">High</option><option value="urgent">Urgent</option> + </select></label> + <label>Labels<input aria-label="Labels" bind:value={labels} placeholder="mail, follow-up" autocomplete="off" /></label> + <details> + <summary>Metadata leaving the archive</summary> + <dl> + <div><dt>Archive UID</dt><dd>{archiveUID}</dd></div> + <div><dt>Message ID</dt><dd>{messageId}</dd></div> + <div><dt>Conversation ID</dt><dd>{conversationId}</dd></div> + <div><dt>Source type</dt><dd>{sourceType}</dd></div> + <div><dt>Source identifier</dt><dd>{sourceIdentifier}</dd></div> + <div><dt>Source message ID</dt><dd>{sourceMessageId}</dd></div> + <div><dt>Subject snapshot</dt><dd>{subject}</dd></div> + <div><dt>Sender snapshot</dt><dd>{from}</dd></div> + <div><dt>Sent at</dt><dd>{sentAt}</dd></div> + <div><dt>Link added at</dt><dd>{addedAt}</dd></div> + </dl> + <p>Bodies and attachments are never sent.</p> + </details> + {#if error}<p role="alert">{error}</p>{/if} + <div class="actions"> + <Button surface="soft" label="Cancel" onclick={onclose} /> + <Button type="submit" tone="info" surface="solid" label="Create task" disabled={pending || !title.trim()} /> + </div> + </form> +</Modal> + +<style> + form { display: grid; gap: var(--space-4); min-width: min(28rem, 80vw); } + label { display: grid; gap: var(--space-1); color: var(--text-muted); font-size: var(--font-size-xs); } + input, textarea, select { padding: var(--space-2); border: 1px solid var(--border-default); border-radius: var(--radius-sm); background: var(--bg-canvas); color: var(--text-primary); } + textarea { min-height: 6rem; resize: vertical; } + .project { display: flex; justify-content: space-between; margin: 0; } + .project span, details { color: var(--text-muted); font-size: var(--font-size-xs); } + dl { display: grid; gap: var(--space-1); } + dl div { display: grid; grid-template-columns: 8rem minmax(0, 1fr); gap: var(--space-2); } + dt { color: var(--text-muted); } dd { margin: 0; overflow-wrap: anywhere; color: var(--text-primary); } + .actions { display: flex; justify-content: flex-end; gap: var(--space-2); } +</style> diff --git a/web/src/lib/components/tasks/CreateTaskDialog.test.ts b/web/src/lib/components/tasks/CreateTaskDialog.test.ts new file mode 100644 index 000000000..9d2c05c76 --- /dev/null +++ b/web/src/lib/components/tasks/CreateTaskDialog.test.ts @@ -0,0 +1,93 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import CreateTaskDialog from './CreateTaskDialog.svelte'; + +describe('CreateTaskDialog', () => { + it('fixes the project, sends task fields, and discloses each outbound metadata value', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + requests.push(input instanceof Request ? input : new Request(input)); + return Response.json({ task: { id: 'task-1', project: 'project', title: 'Edited', revision: 'r1' } }, { status: 201 }); + }); + const oncreated = vi.fn(); + render(CreateTaskDialog, { + client: createAPIClient(fetchFn), messageId: 42, project: 'project', defaultTitle: 'Synthetic subject', + archiveUID: 'archive-a', conversationId: 7, sourceType: 'gmail', sourceIdentifier: 'archive@example.com', + sourceMessageId: 'source-42', subject: 'Synthetic subject', from: 'sender@example.com', sentAt: '2026-07-18T12:00:00Z', + oncreated, onclose: vi.fn() + }); + + expect(screen.getByText('project')).toBeDefined(); + for (const value of ['archive-a', '42', '7', 'gmail', 'archive@example.com', 'source-42', 'Synthetic subject', 'sender@example.com', '2026-07-18T12:00:00Z']) { + expect(screen.getByText(value)).toBeDefined(); + } + expect(screen.getByText(/Bodies and attachments are never sent/i)).toBeDefined(); + await fireEvent.input(screen.getByLabelText('Task title'), { target: { value: 'Edited' } }); + await fireEvent.input(screen.getByLabelText('Description'), { target: { value: 'Notes' } }); + await fireEvent.change(screen.getByLabelText('Priority'), { target: { value: 'high' } }); + await fireEvent.input(screen.getByLabelText('Labels'), { target: { value: 'mail, follow-up' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Create task' })); + await waitFor(() => expect(oncreated).toHaveBeenCalledOnce()); + await expect(requests[0]!.clone().json()).resolves.toMatchObject({ + title: 'Edited', description: 'Notes', priority: 'high', labels: ['mail', 'follow-up'] + }); + const body = await requests[0]!.clone().json() as { added_at?: string }; + expect(body.added_at).toMatch(/^\d{4}-\d{2}-\d{2}T/); + expect(requests[0]!.headers.get('X-Request-Id')).toBeTruthy(); + }); + + it('keeps the request ID and added_at stable across a failed browser retry', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (requests.length === 1) return Response.json({ message: 'Unavailable' }, { status: 503 }); + return Response.json({ task: { id: 'task-1', project: 'project', title: 'Synthetic', revision: 'r1' } }, { status: 201 }); + }); + render(CreateTaskDialog, { + client: createAPIClient(fetchFn), messageId: 42, project: 'project', defaultTitle: 'Synthetic subject', + archiveUID: 'archive-a', conversationId: 7, sourceType: 'gmail', sourceIdentifier: 'archive@example.com', + sourceMessageId: 'source-42', subject: 'Synthetic subject', from: 'sender@example.com', sentAt: '2026-07-18T12:00:00Z' + }); + + await fireEvent.click(screen.getByRole('button', { name: 'Create task' })); + await screen.findByRole('alert'); + await fireEvent.click(screen.getByRole('button', { name: 'Create task' })); + await waitFor(() => expect(requests).toHaveLength(2)); + expect(requests[0]!.headers.get('X-Request-Id')).toBe(requests[1]!.headers.get('X-Request-Id')); + await expect(requests[0]!.clone().text()).resolves.toBe(await requests[1]!.clone().text()); + }); + + it.each([ + ['title', async () => fireEvent.input(screen.getByLabelText('Task title'), { target: { value: 'Changed title' } })], + ['description', async () => fireEvent.input(screen.getByLabelText('Description'), { target: { value: 'Changed notes' } })], + ['priority', async () => fireEvent.change(screen.getByLabelText('Priority'), { target: { value: 'high' } })], + ['labels', async () => fireEvent.input(screen.getByLabelText('Labels'), { target: { value: 'changed' } })] + ])('rotates request identity before sending an edited %s retry', async (_field, edit) => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (requests.length === 1) return Response.json({ message: 'Unavailable' }, { status: 503 }); + return Response.json({ task: { id: 'task-1', project: 'project', title: 'Synthetic', revision: 'r1' } }, { status: 201 }); + }); + render(CreateTaskDialog, { + client: createAPIClient(fetchFn), messageId: 42, project: 'project', defaultTitle: 'Synthetic subject', + archiveUID: 'archive-a', conversationId: 7, sourceType: 'gmail', sourceIdentifier: 'archive@example.com', + sourceMessageId: 'source-42', subject: 'Synthetic subject', from: 'sender@example.com', sentAt: '2026-07-18T12:00:00Z' + }); + + await fireEvent.click(screen.getByRole('button', { name: 'Create task' })); + await screen.findByRole('alert'); + const first = await requests[0]!.clone().json() as { added_at: string }; + await edit(); + await waitFor(() => expect(screen.queryByText(first.added_at)).toBeNull()); + await fireEvent.click(screen.getByRole('button', { name: 'Create task' })); + await waitFor(() => expect(requests).toHaveLength(2)); + expect(requests[0]!.headers.get('X-Request-Id')).not.toBe(requests[1]!.headers.get('X-Request-Id')); + const second = await requests[1]!.clone().json() as { added_at: string }; + expect(second.added_at).not.toBe(first.added_at); + }); +}); diff --git a/web/src/lib/components/tasks/TaskLinks.svelte b/web/src/lib/components/tasks/TaskLinks.svelte new file mode 100644 index 000000000..94831809a --- /dev/null +++ b/web/src/lib/components/tasks/TaskLinks.svelte @@ -0,0 +1,219 @@ +<script lang="ts"> + import { Button } from '@kenn-io/kit-ui'; + + import type { APIClient } from '../../api/client'; + import type { components } from '../../api/generated/schema'; + import CreateTaskDialog from './CreateTaskDialog.svelte'; + + type Lookup = components['schemas']['TaskLinkLookupResponse']; + type OutboundMetadata = components['schemas']['TaskLinkOutboundMetadata']; + type Task = components['schemas']['TaskSummary']; + + let { client, messageId, title, sourceType, sourceIdentifier, onsettings = () => undefined }: { + client: APIClient; messageId: number; title: string; sourceType: string; sourceIdentifier: string; + onsettings?: () => void; + } = $props(); + + let loading = $state(true); + let integrationState = $state('loading'); + let integrationMessage = $state(''); + let indexState = $state('loading'); + let complete = $state(false); + let reason = $state(''); + let lastScan = $state(''); + let tasks = $state<Task[]>([]); + let project = $state(''); + let searchQuery = $state(''); + let searchResults = $state<Task[]>([]); + let searching = $state(false); + let mutating = $state(false); + let error = $state(''); + let creating = $state(false); + let outboundMetadata = $state<OutboundMetadata | null>(null); + let generation = 0; + let activeController: AbortController | null = null; + let existingLinkRetry: { taskID: string; requestID: string; addedAt: string } | undefined; + + $effect(() => { + const selectedMessageID = messageId; + const selectedClient = client; + void selectedClient; + void loadFor(selectedMessageID); + return () => activeController?.abort(); + }); + + function resetForSelection(): void { + loading = true; integrationState = 'loading'; integrationMessage = ''; + indexState = 'loading'; complete = false; reason = ''; lastScan = ''; + tasks = []; project = ''; searchQuery = ''; searchResults = []; searching = false; + mutating = false; error = ''; creating = false; outboundMetadata = null; + existingLinkRetry = undefined; + } + + async function loadFor(selectedMessageID: number): Promise<void> { + const currentGeneration = ++generation; + activeController?.abort(); + const controller = new AbortController(); + activeController = controller; + resetForSelection(); + try { + const status = await client.GET('/api/v1/integrations/tasks/status', { signal: controller.signal }); + if (currentGeneration !== generation) return; + project = status.data?.project ?? ''; + integrationState = status.data?.state ?? 'unavailable'; + integrationMessage = status.data?.message ?? 'Task integration status is unavailable.'; + + // The reverse-index response is independently authoritative and may + // contain a safe last-good snapshot even when the live integration is down. + const { data, error: responseError } = await client.GET('/api/v1/messages/{id}/tasks', { + params: { path: { id: selectedMessageID } }, signal: controller.signal + }); + if (currentGeneration !== generation) return; + if (!data) throw new Error(messageFor(responseError, 'Unable to load linked tasks.')); + apply(data); + } catch (cause) { + if (currentGeneration !== generation || controller.signal.aborted) return; + indexState = 'unavailable'; complete = false; + error = cause instanceof Error ? cause.message : 'Task integration is unavailable.'; + } finally { + if (currentGeneration === generation) loading = false; + } + } + + function apply(data: Lookup): void { + indexState = data.state; complete = data.complete; reason = data.reason ?? ''; + lastScan = data.last_scan ?? ''; tasks = data.tasks ?? []; + outboundMetadata = data.outbound_metadata; + } + + async function search(): Promise<void> { + const query = searchQuery.trim(); + if (!query || searching || !mutationReady) return; + const currentGeneration = generation; + searching = true; error = ''; searchResults = []; + try { + const { data, error: responseError } = await client.GET('/api/v1/integrations/tasks/search', { + params: { query: { q: query } }, signal: activeController?.signal + }); + if (currentGeneration !== generation) return; + if (!data) throw new Error(messageFor(responseError, 'Unable to search tasks.')); + searchResults = data.tasks ?? []; + } catch (cause) { + if (currentGeneration !== generation || activeController?.signal.aborted) return; + error = cause instanceof Error ? cause.message : 'Unable to search tasks.'; + } finally { + if (currentGeneration === generation) searching = false; + } + } + + async function linkExisting(taskID: string): Promise<void> { + if (!taskID || mutating || !mutationReady) return; + const selectedMessageID = messageId; + const currentGeneration = generation; + const retry = existingLinkRetry?.taskID === taskID + ? existingLinkRetry + : { taskID, requestID: globalThis.crypto.randomUUID(), addedAt: new Date().toISOString() }; + existingLinkRetry = retry; + mutating = true; error = ''; + try { + const { data, error: responseError } = await client.POST('/api/v1/messages/{id}/tasks', { + params: { path: { id: selectedMessageID }, header: { 'X-Request-Id': retry.requestID } }, + body: { task_id: taskID, added_at: retry.addedAt }, signal: activeController?.signal + }); + if (currentGeneration !== generation) return; + if (!data) throw new Error(messageFor(responseError, 'Unable to link task.')); + existingLinkRetry = undefined; + searchQuery = ''; searchResults = []; + await loadFor(selectedMessageID); + } catch (cause) { + if (currentGeneration !== generation || activeController?.signal.aborted) return; + error = cause instanceof Error ? cause.message : 'Unable to link task.'; + } finally { + if (currentGeneration === generation) mutating = false; + } + } + + async function unlink(taskID: string): Promise<void> { + if (!taskID || mutating || !mutationReady) return; + const selectedMessageID = messageId; + const currentGeneration = generation; + mutating = true; error = ''; + try { + const { data, error: responseError } = await client.DELETE('/api/v1/messages/{id}/tasks/{task_id}', { + params: { path: { id: selectedMessageID, task_id: taskID } }, signal: activeController?.signal + }); + if (currentGeneration !== generation) return; + if (!data) throw new Error(messageFor(responseError, 'Unable to unlink task.')); + await loadFor(selectedMessageID); + } catch (cause) { + if (currentGeneration !== generation || activeController?.signal.aborted) return; + error = cause instanceof Error ? cause.message : 'Unable to unlink task.'; + } finally { + if (currentGeneration === generation) mutating = false; + } + } + + function messageFor(value: unknown, fallback: string): string { + return typeof value === 'object' && value !== null && 'message' in value && typeof value.message === 'string' + ? value.message : fallback; + } + + const mutationReady = $derived(integrationState === 'ready' && indexState === 'ready'); + const degraded = $derived(!mutationReady); + const sessionOnlyIndex = $derived(mutationReady && reason === 'cache_persistence_unsupported'); + const stateMessage = $derived(integrationState !== 'ready' && integrationState !== 'loading' + ? `Task integration ${integrationState}: ${integrationMessage || 'No status detail was provided.'}` + : reason === 'cache_persistence_unsupported' ? 'Linked-task cache persistence is unavailable on this platform; current-session links may be shown.' + : indexState === 'authentication_required' ? 'Authentication is required. Cached task titles are hidden until access is restored.' + : indexState === 'incompatible' ? 'The configured task service is incompatible.' + : indexState === 'partial' ? `The reverse index is partial${lastScan ? ` (last scanned ${new Date(lastScan).toLocaleString()})` : ''}; an empty result is not authoritative.` + : indexState === 'stale' ? `The linked-task index is stale${lastScan ? ` (last scanned ${new Date(lastScan).toLocaleString()})` : ''}.` + : indexState === 'unavailable' ? 'The task service is unavailable; safe cached links may be shown.' + : indexState === 'disabled' ? 'Task integration is disabled.' + : indexState === 'not_found' ? 'The configured task service was not found.' + : indexState === 'wrong_project' ? 'The configured task service has the wrong project or it is unavailable.' + : reason ? `Task index: ${reason}.` : 'Task integration status is unavailable.'); +</script> + +<section class="task-links" aria-label="Linked tasks"> + <header><h2>Tasks</h2>{#if mutationReady}<Button size="sm" surface="soft" label="Create task" onclick={() => { creating = true; }} />{/if}</header> + {#if loading}<p role="status">Loading linked tasks…</p> + {:else} + {#if degraded}<div class="degraded" role="alert"><p>{stateMessage} Mutations are disabled.</p><Button size="sm" surface="soft" label="Open Settings" onclick={onsettings} /></div>{/if} + {#if sessionOnlyIndex}<p role="status">Linked-task index updates are available for this session but are not persisted to disk on this platform.</p>{/if} + {#if tasks.length > 0} + <ul>{#each tasks as task (task.id)}<li><span>{task.title}</span><Button size="sm" surface="outline" label={`Unlink ${task.title}`} disabled={mutating || !mutationReady} onclick={() => void unlink(task.id)} /></li>{/each}</ul> + {:else if complete}<p>No linked tasks.</p>{/if} + {#if mutationReady} + <form onsubmit={(event) => { event.preventDefault(); void search(); }}> + <label>Search tasks<input aria-label="Search tasks" bind:value={searchQuery} autocomplete="off" /></label> + <Button type="submit" size="sm" surface="soft" label="Search" disabled={searching || !searchQuery.trim()} /> + </form> + {#if searchResults.length > 0} + <ul aria-label="Task search results">{#each searchResults as task (task.id)}<li><span>{task.title}</span><Button size="sm" surface="soft" label={`Link ${task.title}`} disabled={mutating} onclick={() => void linkExisting(task.id)} /></li>{/each}</ul> + {/if} + {/if} + {#if error}<p role="alert">{error}</p>{/if} + {/if} +</section> + +{#if creating && outboundMetadata} + <CreateTaskDialog {client} {messageId} {project} defaultTitle={title} + archiveUID={outboundMetadata.archive_uid} conversationId={outboundMetadata.conversation_id} + sourceType={outboundMetadata.source_type} sourceIdentifier={outboundMetadata.source_identifier} + sourceMessageId={outboundMetadata.source_message_id} subject={outboundMetadata.subject} + from={outboundMetadata.from} sentAt={outboundMetadata.sent_at} + onclose={() => { creating = false; }} oncreated={() => { creating = false; void loadFor(messageId); }} /> +{/if} + +<style> + .task-links { display: grid; gap: var(--space-3); padding: 0 var(--space-5) var(--space-5); } + header, form, li { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); } + h2, p, ul { margin: 0; } + h2 { font-size: var(--font-size-sm); } + .degraded { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); } + ul { display: grid; gap: var(--space-2); padding: 0; list-style: none; } + label { display: grid; flex: 1; gap: var(--space-1); color: var(--text-muted); font-size: var(--font-size-xs); } + input { min-width: 0; padding: var(--space-2); border: 1px solid var(--border-default); border-radius: var(--radius-sm); background: var(--bg-canvas); color: var(--text-primary); } + [role='alert'] { color: var(--text-muted); font-size: var(--font-size-xs); } +</style> diff --git a/web/src/lib/components/tasks/TaskLinks.test.ts b/web/src/lib/components/tasks/TaskLinks.test.ts new file mode 100644 index 000000000..2a771dedf --- /dev/null +++ b/web/src/lib/components/tasks/TaskLinks.test.ts @@ -0,0 +1,198 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../../api/client'; +import TaskLinks from './TaskLinks.svelte'; + +describe('TaskLinks', () => { + it('shows loading, linked tasks, and honest partial authority', async () => { + let resolve!: (response: Response) => void; + const fetchFn = vi.fn<typeof fetch>((input) => { + const request = input instanceof Request ? input : new Request(input); + if (request.url.endsWith('/integrations/tasks/status')) return Promise.resolve(Response.json({ state: 'ready', project: 'project', message: 'Ready' })); + return new Promise((done) => { resolve = done; }); + }); + render(TaskLinks, { client: createAPIClient(fetchFn), messageId: 42, title: 'Synthetic', sourceType: 'gmail', sourceIdentifier: 'archive@example.com' }); + expect(screen.getByRole('status').textContent).toContain('Loading linked tasks'); + await waitFor(() => expect(resolve).toBeTypeOf('function')); + resolve(Response.json({ state: 'partial', complete: false, last_scan: '2026-07-19T01:00:00Z', remote_revision: 'remote-1', reason: 'safety_limit', tasks: [{ id: 'task-1', title: 'Follow up', revision: 'r1' }] })); + expect(await screen.findByText('Follow up')).toBeDefined(); + expect(screen.getByRole('alert').textContent).toContain('partial'); + expect(screen.getByRole('alert').textContent).toContain('2026'); + }); + + it.each([ + ['authentication_required', 'Authentication is required'], + ['incompatible', 'incompatible'], + ['unavailable', 'unavailable'], + ['stale', 'stale'], + ['partial', 'partial'], + ['disabled', 'disabled'], + ['not_found', 'not found'], + ['wrong_project', 'wrong project'] + ])('renders the %s state without claiming there are no links', async (state, copy) => { + render(TaskLinks, { client: createAPIClient(vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + return request.url.endsWith('/integrations/tasks/status') + ? Response.json({ state: 'ready', project: 'project', message: 'Ready' }) + : Response.json({ state, complete: false, reason: state, last_scan: '2026-07-19T01:00:00Z', tasks: [] }); + })), messageId: 42, title: 'Synthetic', sourceType: 'gmail', sourceIdentifier: 'archive@example.com' }); + expect((await screen.findByRole('alert')).textContent?.toLowerCase()).toContain(copy.toLowerCase()); + expect(screen.queryByText('No linked tasks.')).toBeNull(); + }); + + it('loads last-good cached links even when integration status is unavailable', async () => { + render(TaskLinks, { client: createAPIClient(vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + return request.url.endsWith('/integrations/tasks/status') + ? Response.json({ state: 'unavailable', project: 'project', message: 'Unavailable' }) + : Response.json({ state: 'unavailable', complete: false, reason: 'daemon_unavailable', tasks: [{ id: 'task-1', title: 'Cached task' }], outbound_metadata: { + archive_uid: 'archive-a', message_id: 42, conversation_id: 7, source_type: 'gmail', source_identifier: 'archive@example.com', source_message_id: 'source-42', subject: 'Synthetic', from: 'sender@example.com', sent_at: '2026-07-18T12:00:00Z' + } }); + })), messageId: 42, title: 'Synthetic', sourceType: 'gmail', sourceIdentifier: 'archive@example.com' }); + expect(await screen.findByText('Cached task')).toBeDefined(); + expect(screen.getByRole('alert').textContent).toContain('unavailable'); + }); + + it.each(['disabled', 'not_found', 'authentication_required', 'unreachable', 'incompatible', 'wrong_project'])( + 'keeps cached tasks visible but disables mutations when integration status is %s', + async (integrationState) => { + const requests: Request[] = []; + const onsettings = vi.fn(); + render(TaskLinks, { client: createAPIClient(vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return request.url.endsWith('/integrations/tasks/status') + ? Response.json({ state: integrationState, project: 'project', message: `Integration reason: ${integrationState}` }) + : Response.json({ state: 'ready', complete: true, tasks: [{ id: 'task-1', title: 'Cached task' }], outbound_metadata: {} }); + })), messageId: 42, title: 'Synthetic', sourceType: 'gmail', sourceIdentifier: 'archive@example.com', onsettings }); + + expect(await screen.findByText('Cached task')).toBeDefined(); + const unlink = screen.getByRole('button', { name: 'Unlink Cached task' }); + expect((unlink as HTMLButtonElement).disabled).toBe(true); + expect(screen.getByRole('alert').textContent).toContain(`Integration reason: ${integrationState}`); + await fireEvent.click(unlink); + expect(requests.some((request) => request.method === 'DELETE')).toBe(false); + await fireEvent.click(screen.getByRole('button', { name: 'Open Settings' })); + expect(onsettings).toHaveBeenCalledOnce(); + } + ); + + it('explains unsupported disk persistence while keeping safe current-session mutations ready', async () => { + render(TaskLinks, { client: createAPIClient(vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + return request.url.endsWith('/integrations/tasks/status') + ? Response.json({ state: 'ready', project: 'project', message: 'Ready' }) + : Response.json({ + state: 'ready', complete: true, reason: 'cache_persistence_unsupported', + tasks: [{ id: 'task-1', title: 'Current-session task' }], outbound_metadata: {} + }); + })), messageId: 42, title: 'Synthetic', sourceType: 'gmail', sourceIdentifier: 'archive@example.com' }); + + expect(await screen.findByText('Current-session task')).toBeDefined(); + expect(screen.getByRole('status').textContent).toContain('not persisted to disk'); + expect((screen.getByRole('button', { name: 'Unlink Current-session task' }) as HTMLButtonElement).disabled).toBe(false); + expect(screen.getByRole('button', { name: 'Create task' })).toBeDefined(); + }); + + it('ignores a late response for the previously selected message', async () => { + const responses = new Map<number, (response: Response) => void>(); + const fetchFn = vi.fn<typeof fetch>((input) => { + const request = input instanceof Request ? input : new Request(input); + if (request.url.endsWith('/integrations/tasks/status')) return Promise.resolve(Response.json({ state: 'ready', project: 'project', message: 'Ready' })); + const id = Number(request.url.match(/messages\/(\d+)\/tasks/)?.[1]); + return new Promise((resolve) => responses.set(id, resolve)); + }); + const view = render(TaskLinks, { client: createAPIClient(fetchFn), messageId: 42, title: 'Message A', sourceType: 'gmail', sourceIdentifier: 'archive@example.com' }); + await waitFor(() => expect(responses.has(42)).toBe(true)); + await view.rerender({ client: createAPIClient(fetchFn), messageId: 43, title: 'Message B', sourceType: 'gmail', sourceIdentifier: 'archive@example.com' }); + expect(screen.queryByText('Task A')).toBeNull(); + await waitFor(() => expect(responses.has(43)).toBe(true)); + responses.get(43)!(Response.json({ state: 'ready', complete: true, tasks: [{ id: 'task-b', title: 'Task B' }], outbound_metadata: {} })); + expect(await screen.findByText('Task B')).toBeDefined(); + responses.get(42)!(Response.json({ state: 'ready', complete: true, tasks: [{ id: 'task-a', title: 'Task A' }], outbound_metadata: {} })); + await Promise.resolve(); + expect(screen.queryByText('Task A')).toBeNull(); + expect(screen.getByText('Task B')).toBeDefined(); + }); + + it('does not let an unlink settlement mutate a newly selected message', async () => { + let resolveDelete!: (response: Response) => void; + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>((input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (request.url.endsWith('/integrations/tasks/status')) return Promise.resolve(Response.json({ state: 'ready', project: 'project', message: 'Ready' })); + if (request.method === 'DELETE') return new Promise((resolve) => { resolveDelete = resolve; }); + const id = Number(request.url.match(/messages\/(\d+)\/tasks/)?.[1]); + return Promise.resolve(Response.json({ state: 'ready', complete: true, tasks: [{ id: `task-${id}`, title: `Task ${id}` }], outbound_metadata: {} })); + }); + const client = createAPIClient(fetchFn); + const view = render(TaskLinks, { client, messageId: 42, title: 'Message A', sourceType: 'gmail', sourceIdentifier: 'archive@example.com' }); + await fireEvent.click(await screen.findByRole('button', { name: 'Unlink Task 42' })); + await waitFor(() => expect(resolveDelete).toBeTypeOf('function')); + await view.rerender({ client, messageId: 43, title: 'Message B', sourceType: 'gmail', sourceIdentifier: 'archive@example.com' }); + expect(screen.queryByText('Task 42')).toBeNull(); + expect(await screen.findByText('Task 43')).toBeDefined(); + resolveDelete(Response.json({ task: { id: 'task-42', project: 'project', title: 'Task 42', revision: 'r2' } })); + await Promise.resolve(); + expect(screen.getByText('Task 43')).toBeDefined(); + expect(screen.queryByText('Task 42')).toBeNull(); + expect(requests.filter((request) => request.method === 'GET' && request.url.includes('/messages/42/tasks'))).toHaveLength(1); + }); + + it('searches the configured project and links a selected result through the typed route', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); requests.push(request); + if (request.url.endsWith('/integrations/tasks/status')) return Response.json({ state: 'ready', project: 'project', message: 'Ready' }); + if (request.url.includes('/integrations/tasks/search')) return Response.json({ tasks: [{ id: 'task-1', title: 'Synthetic result', revision: 'r1' }] }); + if (request.method === 'POST') return Response.json({ task: { id: 'task-1', project: 'project', title: 'Linked', revision: 'r1' } }, { status: 201 }); + return Response.json({ state: 'ready', complete: true, last_scan: '2026-07-19T01:00:00Z', tasks: [], outbound_metadata: { + archive_uid: 'archive-a', message_id: 42, conversation_id: 7, source_type: 'gmail', source_identifier: 'archive@example.com', source_message_id: 'source-42', subject: 'Synthetic', from: 'sender@example.com', sent_at: '2026-07-18T12:00:00Z' + } }); + }); + render(TaskLinks, { client: createAPIClient(fetchFn), messageId: 42, title: 'Synthetic', sourceType: 'gmail', sourceIdentifier: 'archive@example.com' }); + await screen.findByText('No linked tasks.'); + await fireEvent.input(screen.getByLabelText('Search tasks'), { target: { value: 'Synthetic' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Search' })); + await fireEvent.click(await screen.findByRole('button', { name: 'Link Synthetic result' })); + await waitFor(() => expect(requests.some((request) => request.method === 'POST')).toBe(true)); + const post = requests.find((request) => request.method === 'POST')!; + await expect(post.clone().json()).resolves.toMatchObject({ task_id: 'task-1' }); + expect(post.headers.get('X-Request-Id')).toBeTruthy(); + const body = await post.clone().json() as { added_at?: string }; + expect(body.added_at).toMatch(/^\d{4}-\d{2}-\d{2}T/); + }); + + it('reuses one request identity and timestamp when retrying the same existing-task link', async () => { + const posts: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + if (request.url.endsWith('/integrations/tasks/status')) return Response.json({ state: 'ready', project: 'project', message: 'Ready' }); + if (request.url.includes('/integrations/tasks/search')) return Response.json({ tasks: [{ id: 'task-1', title: 'Synthetic result', revision: 'r1' }] }); + if (request.method === 'POST') { + posts.push(request); + if (posts.length === 1) return Response.json({ message: 'Unavailable' }, { status: 503 }); + return Response.json({ task: { id: 'task-1', project: 'project', title: 'Linked', revision: 'r2' } }, { status: 201 }); + } + return Response.json({ state: 'ready', complete: true, tasks: [], outbound_metadata: {} }); + }); + render(TaskLinks, { + client: createAPIClient(fetchFn), messageId: 42, title: 'Synthetic', + sourceType: 'gmail', sourceIdentifier: 'archive@example.com' + }); + + await screen.findByText('No linked tasks.'); + await fireEvent.input(screen.getByLabelText('Search tasks'), { target: { value: 'Synthetic' } }); + await fireEvent.click(screen.getByRole('button', { name: 'Search' })); + const link = await screen.findByRole('button', { name: 'Link Synthetic result' }); + await fireEvent.click(link); + await screen.findByRole('alert'); + await fireEvent.click(link); + await waitFor(() => expect(posts).toHaveLength(2)); + + expect(posts[0]!.headers.get('X-Request-Id')).toBe(posts[1]!.headers.get('X-Request-Id')); + await expect(posts[0]!.clone().text()).resolves.toBe(await posts[1]!.clone().text()); + }); +}); diff --git a/web/src/lib/content/frame-document.test.ts b/web/src/lib/content/frame-document.test.ts new file mode 100644 index 000000000..bc2c1996e --- /dev/null +++ b/web/src/lib/content/frame-document.test.ts @@ -0,0 +1,81 @@ +import { createHash } from 'node:crypto'; +import { describe, expect, it } from 'vitest'; + +import { archivedContentBridge, buildFrameDocument } from './frame-document'; + +describe('buildFrameDocument', () => { + it('denies network and authorizes only the exact bridge script hash', async () => { + const document = await buildFrameDocument({ + html: '<p>Archived content</p>', + nonce: 'frame-nonce', + targetOrigin: 'https://archive.example', + }); + const digest = createHash('sha256').update(archivedContentBridge('https://archive.example')).digest('base64'); + + expect(document).toContain("default-src 'none'"); + expect(document).toContain("connect-src 'none'"); + expect(document).toContain("media-src 'none'"); + expect(document).toContain(`script-src 'sha256-${digest}'`); + expect(document).not.toContain("'unsafe-eval'"); + expect(document).not.toContain("script-src 'self'"); + expect(document).toContain('data-bridge-nonce="frame-nonce"'); + expect(document).toContain('const o="https://archive.example"'); + expect(document).not.toContain("postMessage({channel:c,nonce:n,type:'key',key:e.key},'*')"); + }); + + it('allows only non-network data images unless remote consent is explicit', async () => { + const blocked = await buildFrameDocument({ + html: '<p>Archived content</p>', nonce: 'n', targetOrigin: 'https://archive.example' + }); + expect(blocked).toContain("img-src data:"); + expect(blocked).not.toContain('img-src data: https://archive.example'); + expect(blocked).not.toContain('images.example'); + + const consented = await buildFrameDocument({ + html: '<img src="https://images.example/chart.png">', + nonce: 'n', + targetOrigin: 'https://archive.example', + remoteImages: ['https://images.example/chart.png'] + }); + expect(consented).toContain('https://images.example/chart.png'); + }); + + it('rejects CSP-delimiter, credentialed, and non-HTTP remote sources', async () => { + const document = await buildFrameDocument({ + html: '<p>Archived content</p>', + nonce: 'n', + targetOrigin: 'https://archive.example', + remoteImages: [ + 'https://images.example/good.png?token=synthetic', + 'https://images.example/a.png; img-src https://collector.example', + 'https://user:pass@images.example/credential.png', + 'javascript:alert(1)' + ] + }); + + expect(document).toContain('https://images.example/good.png?token=synthetic'); + expect(document).not.toContain('collector.example'); + expect(document).not.toContain('user:pass'); + expect(document).not.toContain('javascript:'); + }); + + it('escapes shell-generated nonce and archived closing tags', async () => { + const document = await buildFrameDocument({ + html: '<p title="</body><script>bad()</script>">Words</p>', + nonce: '" onload=bad()', + targetOrigin: 'https://archive.example', + }); + + expect(document).toContain('data-bridge-nonce="&quot; onload=bad()"'); + expect(document.match(/<script>/g)).toHaveLength(1); + }); + + it('rejects non-shell and injection-shaped target origins', async () => { + await expect(buildFrameDocument({ + html: '<p>Words</p>', nonce: 'n', targetOrigin: 'null' + })).rejects.toThrow('valid HTTP origin'); + await expect(buildFrameDocument({ + html: '<p>Words</p>', nonce: 'n', targetOrigin: 'https://archive.example/path' + })).rejects.toThrow('exact origin'); + }); +}); diff --git a/web/src/lib/content/frame-document.ts b/web/src/lib/content/frame-document.ts new file mode 100644 index 000000000..32eb3cb87 --- /dev/null +++ b/web/src/lib/content/frame-document.ts @@ -0,0 +1,96 @@ +export const ARCHIVED_CONTENT_CHANNEL = 'msgvault-archived-content'; +export const MAX_ARCHIVED_SCROLL_DELTA = 10_000; + +// This generated script is the only executable content admitted by the frame +// CSP. Its sole dynamic value is a validated, JSON-serialized shell origin. +export function archivedContentBridge(targetOrigin: string): string { + return `(()=>{const n=document.documentElement.dataset.bridgeNonce;const c='${ARCHIVED_CONTENT_CHANNEL}';const o=${JSON.stringify(targetOrigin)};const keys=new Set(['Escape','PageUp','PageDown','Home','End','ArrowUp','ArrowDown']);addEventListener('keydown',e=>{if(keys.has(e.key))parent.postMessage({channel:c,nonce:n,type:'key',key:e.key},o)});addEventListener('wheel',e=>{if(Number.isFinite(e.deltaY)&&Math.abs(e.deltaY)<=${MAX_ARCHIVED_SCROLL_DELTA})parent.postMessage({channel:c,nonce:n,type:'scroll',deltaY:e.deltaY},o)},{passive:true})})()`; +} + +export interface FrameDocumentOptions { + html: string; + nonce: string; + targetOrigin: string; + remoteImages?: string[]; +} + +function escapeAttribute(value: string): string { + return value + .replaceAll('&', '&') + .replaceAll('"', '"') + .replaceAll('<', '<') + .replaceAll('>', '>'); +} + +function encodedBody(html: string): string { + // Sanitization is the primary boundary. This is a small defense against a + // caller accidentally passing a closing script/body sequence directly. + return html + .replace(/<\/script/gi, '</script') + .replace(/<script/gi, '<script') + .replace(/<\/body/gi, '</body') + .replace(/<\/html/gi, '</html'); +} + +function validImageSource(value: string): string | undefined { + try { + const url = new URL(value); + if (url.protocol !== 'https:' && url.protocol !== 'http:') return undefined; + if (url.username || url.password || /[;\u0000-\u001f\u007f]/.test(value)) return undefined; + url.hash = ''; + return url.toString(); + } catch { + return undefined; + } +} + +async function sha256Base64(value: string): Promise<string> { + const bytes = new TextEncoder().encode(value); + const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', bytes)); + let binary = ''; + for (const byte of digest) binary += String.fromCharCode(byte); + return btoa(binary); +} + +function exactHTTPOrigin(value: string): string { + let parsed: URL; + try { + parsed = new URL(value); + } catch { + throw new Error('Archived content bridge requires a valid HTTP origin'); + } + if ((parsed.protocol !== 'http:' && parsed.protocol !== 'https:') || parsed.origin !== value) { + throw new Error('Archived content bridge requires an exact origin'); + } + return parsed.origin; +} + +export async function buildFrameDocument(options: FrameDocumentOptions): Promise<string> { + const bridge = archivedContentBridge(exactHTTPOrigin(options.targetOrigin)); + const scriptHash = await sha256Base64(bridge); + const remoteSources = [...new Set((options.remoteImages ?? []) + .map(validImageSource) + .filter((source): source is string => source !== undefined))]; + const imageSources = ['data:', ...remoteSources].map(escapeAttribute).join(' '); + const csp = [ + "default-src 'none'", + "connect-src 'none'", + `img-src ${imageSources}`, + "media-src 'none'", + "font-src data:", + "style-src 'unsafe-inline'", + `script-src 'sha256-${scriptHash}'`, + "object-src 'none'", + "frame-src 'none'", + "base-uri 'none'", + "form-action 'none'" + ].join('; '); + + return '<!doctype html>' + + `<html data-bridge-nonce="${escapeAttribute(options.nonce)}"><head>` + + '<meta charset="utf-8">' + + `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(csp)}">` + + '<meta name="referrer" content="no-referrer">' + + '<style>html{color-scheme:light dark}body{margin:0;padding:12px;overflow-wrap:anywhere;font:14px/1.5 system-ui,sans-serif}img{max-width:100%;height:auto}[data-archived-remote-image]{display:inline-block;padding:8px;border:1px dashed currentColor}</style>' + + `</head><body>${encodedBody(options.html)}<script>${bridge}</script></body></html>`; +} diff --git a/web/src/lib/content/sanitize.test.ts b/web/src/lib/content/sanitize.test.ts new file mode 100644 index 000000000..df03d760f --- /dev/null +++ b/web/src/lib/content/sanitize.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, it } from 'vitest'; + +import { sanitizeArchivedHTML } from './sanitize'; + +describe('sanitizeArchivedHTML', () => { + it('removes active content, network-capable elements, and focus authority', () => { + const result = sanitizeArchivedHTML(` + <script>parent.postMessage({type:'close'}, '*')</script> + <form action="https://collector.example/submit"><input autofocus name="secret"><button accesskey="x">Esc</button></form> + <svg><a xlink:href="https://collector.example/svg"><text>SVG link</text></a></svg> + <video poster="https://collector.example/poster"><source src="https://collector.example/movie"></video> + <a href="https://collector.example/click" onclick="steal()" tabindex="0">External</a> + <div role="button" contenteditable="true" onkeydown="steal()">Press Esc</div> + <p style="background-image:url(https://collector.example/css)">Safe words</p> + <style>@import 'https://collector.example/import';</style> + `, { messageId: 42 }); + + expect(result.html).toContain('Safe words'); + expect(result.html).toContain('Press Esc'); + expect(result.html).not.toMatch(/<(?:script|form|input|button|svg|video|source)\b|style=/i); + expect(result.html).not.toMatch(/https?:|collector\.example|onclick|onkeydown|autofocus|accesskey|tabindex|contenteditable|role=/i); + expect(result.remoteImages).toEqual([]); + }); + + it('removes every sender style without trying to parse hostile CSS', () => { + const result = sanitizeArchivedHTML(` + <p style="background:UrL(https://collector.example/mixed)">Mixed</p> + <p style="background:u/**/rl(https://collector.example/comment)">Comment</p> + <p style="background:\\75\\72\\6c(https://collector.example/escaped)">Escaped</p> + <p style="background:url(https://collector.example/entity)">Encoded</p> + <p style="@\\69mport 'https://collector.example/import'">Import</p> + <p style="color: red">Benign looking</p> + `, { messageId: 42 }); + + expect(result.html).toContain('Benign looking'); + expect(result.html).not.toMatch(/style=|collector\.example|url\s*\(/i); + }); + + it('extracts CID images into URL-free shell descriptors', () => { + const result = sanitizeArchivedHTML( + '<img src="cid:logo/part@example.com" alt="Archived logo">', + { messageId: 42 } + ); + + expect(result.html).toContain('Inline image loading: Archived logo'); + expect(result.html).not.toMatch(/cid:|\/api\/v1\/messages|logo\/part@example\.com/); + expect(result.inlineImages).toEqual([{ cid: 'logo/part@example.com', alt: 'Archived logo' }]); + expect(result.remoteImages).toEqual([]); + }); + + it('replaces remote images with URL-free consent placeholders', () => { + const result = sanitizeArchivedHTML(` + <img src="https://images.example/pixel.png?secret=1" alt="Chart"> + <img src="//images.example/second.png" alt="Second"> + `, { messageId: 42 }); + + expect(result.remoteImages).toEqual([ + 'https://images.example/pixel.png?secret=1', + 'https://images.example/second.png' + ]); + expect(result.html).toContain('Remote image blocked: Chart'); + expect(result.html).toContain('Remote image blocked: Second'); + expect(result.html).not.toMatch(/images\.example|secret=1/); + }); + + it('allows only explicitly consented remote image URLs', () => { + const result = sanitizeArchivedHTML( + '<img src="https://images.example/chart.png" alt="Chart"><p style="background:url(https://tracker.example/bg)">Text</p>', + { messageId: 42, allowRemoteImages: true } + ); + + expect(result.html).toContain('src="https://images.example/chart.png"'); + expect(result.html).not.toContain('tracker.example'); + expect(result.remoteImages).toEqual(['https://images.example/chart.png']); + }); + + it('handles malformed HTML without restoring stripped content', () => { + const result = sanitizeArchivedHTML( + '<div><p>Readable<script><img src=https://tracker.example/pixel></div><img src="javascript:alert(1)">', + { messageId: 7 } + ); + + expect(result.html).toContain('Readable'); + expect(result.html).not.toMatch(/script|javascript:|tracker\.example/i); + }); +}); diff --git a/web/src/lib/content/sanitize.ts b/web/src/lib/content/sanitize.ts new file mode 100644 index 000000000..05bd3d5ba --- /dev/null +++ b/web/src/lib/content/sanitize.ts @@ -0,0 +1,130 @@ +import DOMPurify from 'dompurify'; + +export interface ArchivedHTMLSanitizeOptions { + messageId: number; + allowRemoteImages?: boolean; +} + +export interface SanitizedArchivedHTML { + html: string; + remoteImages: string[]; + inlineImages: ArchivedInlineImage[]; +} + +export interface ArchivedInlineImage { + cid: string; + alt: string; +} + +const EMAIL_TAGS = [ + 'a', 'abbr', 'address', 'article', 'b', 'blockquote', 'br', 'caption', 'center', 'cite', + 'code', 'col', 'colgroup', 'dd', 'del', 'details', 'div', 'dl', 'dt', 'em', 'figcaption', + 'figure', 'font', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hr', 'i', + 'img', 'ins', 'kbd', 'li', 'main', 'mark', 'ol', 'p', 'pre', 'q', 's', 'samp', + 'section', 'small', 'span', 'strike', 'strong', 'sub', 'summary', 'sup', 'table', + 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var' +]; + +const EMAIL_ATTRIBUTES = [ + 'abbr', 'align', 'alt', 'border', 'cellpadding', 'cellspacing', 'class', 'colspan', + 'dir', 'height', 'href', 'id', 'lang', 'rowspan', 'src', 'title', 'valign', 'width' +]; + +const SAFE_DATA_IMAGE = /^data:image\/(?:gif|jpe?g|png|webp);base64,[a-z0-9+/=\s]+$/i; + +function remoteImageURL(value: string): string | undefined { + try { + if (/[;\u0000-\u001f\u007f]/.test(value)) return undefined; + const url = /^https?:\/\//i.test(value) + ? new URL(value) + : value.startsWith('//') ? new URL(`https:${value}`) : undefined; + if (!url || (url.protocol !== 'https:' && url.protocol !== 'http:')) return undefined; + if (url.username || url.password) return undefined; + url.hash = ''; + return url.toString(); + } catch { + return undefined; + } + return undefined; +} + +function blockedImage(document: Document, alt: string, index: number): HTMLElement { + const placeholder = document.createElement('span'); + placeholder.setAttribute('data-archived-remote-image', String(index)); + placeholder.textContent = `Remote image blocked${alt ? `: ${alt}` : ''}`; + return placeholder; +} + +function inlineImagePlaceholder(document: Document, alt: string, index: number): HTMLElement { + const placeholder = document.createElement('span'); + placeholder.setAttribute('data-archived-inline-image', String(index)); + placeholder.textContent = `Inline image loading${alt ? `: ${alt}` : ''}`; + return placeholder; +} + +/** + * Sanitizes sender-controlled archived HTML before it is put into srcdoc. + * Remote URLs are returned to the shell separately and never retained in the + * archived DOM until the user explicitly opts in. + */ +export function sanitizeArchivedHTML( + input: string, + options: ArchivedHTMLSanitizeOptions +): SanitizedArchivedHTML { + const source = document.createElement('template'); + source.innerHTML = input; + for (const container of source.content.querySelectorAll('svg, math')) { + container.replaceWith(document.createTextNode(container.textContent ?? '')); + } + const sanitized = DOMPurify.sanitize(source.innerHTML, { + ALLOWED_TAGS: EMAIL_TAGS, + ALLOWED_ATTR: EMAIL_ATTRIBUTES, + ALLOW_DATA_ATTR: false, + ALLOW_ARIA_ATTR: false, + FORBID_CONTENTS: ['script', 'style', 'template', 'noscript'], + SANITIZE_NAMED_PROPS: true + }); + const template = document.createElement('template'); + template.innerHTML = sanitized; + const remoteImages: string[] = []; + const inlineImages: ArchivedInlineImage[] = []; + + for (const element of template.content.querySelectorAll<HTMLElement>('*')) { + for (const attribute of [...element.attributes]) { + const name = attribute.name.toLowerCase(); + if (name.startsWith('on') || [ + 'accesskey', 'autofocus', 'contenteditable', 'draggable', 'form', 'formaction', + 'name', 'nonce', 'ping', 'popover', 'srcdoc', 'srcset', 'tabindex', 'target' + ].includes(name)) { + element.removeAttribute(attribute.name); + } + } + element.removeAttribute('style'); + + if (element instanceof HTMLAnchorElement) { + const href = element.getAttribute('href')?.trim() ?? ''; + if (!href.startsWith('#')) element.removeAttribute('href'); + } + if (!(element instanceof HTMLImageElement)) continue; + + const source = element.getAttribute('src')?.trim() ?? ''; + if (/^cid:/i.test(source)) { + const cid = source.slice(source.indexOf(':') + 1); + const alt = element.getAttribute('alt') ?? ''; + const index = inlineImages.push({ cid, alt }) - 1; + element.replaceWith(inlineImagePlaceholder(document, alt, index)); + continue; + } + if (SAFE_DATA_IMAGE.test(source)) continue; + const remote = remoteImageURL(source); + if (remote) { + const index = remoteImages.push(remote) - 1; + if (options.allowRemoteImages) element.setAttribute('src', remote); + else element.replaceWith(blockedImage(document, element.getAttribute('alt') ?? '', index)); + continue; + } + element.removeAttribute('src'); + } + + return { html: template.innerHTML, remoteImages, inlineImages }; +} diff --git a/web/src/lib/explore/api.test.ts b/web/src/lib/explore/api.test.ts new file mode 100644 index 000000000..16745deb1 --- /dev/null +++ b/web/src/lib/explore/api.test.ts @@ -0,0 +1,220 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../api/client'; +import type { FileMetadata, FileSearchRow } from './models'; +import { createExploreAPI } from './api'; + +type RequiredNonNullableString<T, K extends keyof T> = + {} extends Pick<T, K> ? false : null extends T[K] ? false : T[K] extends string ? true : false; +const generatedFileStringContract: [ + RequiredNonNullableString<FileMetadata, 'filename'>, + RequiredNonNullableString<FileMetadata, 'mime_type'>, + RequiredNonNullableString<FileSearchRow, 'filename'>, + RequiredNonNullableString<FileSearchRow, 'mime_type'> +] = [true, true, true, true]; + +describe('ExploreAPI routing', () => { + it('keeps public TypeScript file metadata strings required and non-nullable', () => { + expect(generatedFileStringContract).toEqual([true, true, true, true]); + }); + it('loads filtered semantic coverage and exact visible lexical counts', async () => { + const paths: string[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + paths.push(new URL(request.url).pathname); + if (new URL(request.url).pathname.endsWith('/coverage')) { + return Response.json({ + status: 'incomplete', eligible_count: 2, embedded_count: 1, percentage: 50, + vector_generation: 7, cache_revision: 'cache-2', actions: [] + }); + } + return Response.json({ + counts: [{ row_key: 'conversation:1', count: 3 }], cache_revision: 'cache-2', + lexical_index_revision: 'fts-2', canonical_query_hash: 'query-2' + }); + }); + const api = createExploreAPI(createAPIClient(fetchFn)); + + const coverage = await api.coverage([{ dimension: 'source', values: ['1'] }]); + const counts = await api.matchCounts( + { query: 'alpha', search_mode: 'hybrid', filters: [], presentation: 'table' }, + ['conversation:1'] + ); + + expect(coverage).toMatchObject({ status: 'incomplete', eligible_count: 2, embedded_count: 1 }); + expect(counts.counts).toEqual({ 'conversation:1': 3 }); + expect(counts.canonicalQueryHash).toBe('query-2'); + expect(paths).toEqual(['/api/v1/search/coverage', '/api/v1/explore/match-counts']); + }); + + it('passes cursors and cancellation through the session-aware entry request', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return Response.json({ + rows: [], + total_count: 50_000, + cache_revision: 'cache-1', + search_provenance: {}, + next_cursor: 'page-2' + }); + }); + const controller = new AbortController(); + const api = createExploreAPI(createAPIClient(fetchFn)); + + await api.explore({ filters: [], presentation: 'table', limit: 500, cursor: 'page-1' }, controller.signal); + + expect(requests).toHaveLength(1); + expect(new URL(requests[0]!.url).pathname).toBe('/api/v1/explore'); + controller.abort(); + expect(requests[0]!.signal.aborted).toBe(true); + await expect(requests[0]!.json()).resolves.toMatchObject({ cursor: 'page-1', limit: 500 }); + }); + + it('routes grouped predicates to the groups endpoint with one active dimension', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return Response.json({ + rows: [{ key: '7', label: 'Example source', count: 12, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' }], + total_count: 1, + cache_revision: 'cache-1', + search_provenance: {} + }); + }); + const api = createExploreAPI(createAPIClient(fetchFn)); + + const loaded = await api.groups( + { + filters: [{ dimension: 'source', values: ['2'] }], + grouping: ['source', 'year'], + presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + limit: 500, + cursor: 'groups-2' + }, + 'source' + ); + + expect(loaded.status).toBe('ready'); + expect(new URL(requests[0]!.url).pathname).toBe('/api/v1/explore/groups'); + await expect(requests[0]!.json()).resolves.toEqual({ + filters: [{ dimension: 'source', values: ['2'] }], + grouping: ['source'], + presentation: 'table', + limit: 500, + cursor: 'groups-2' + }); + }); + + it('routes grouped Files exclusively through the file population with file constraints', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return Response.json({ + rows: [{ key: '7', label: 'Example source', count: 2, estimated_bytes: 300, latest_at: '2026-07-18T12:00:00Z' }], + total_count: 1, cache_revision: 'cache-files', search_provenance: {} + }); + }); + const api = createExploreAPI(createAPIClient(fetchFn)); + + const loaded = await api.fileGroups( + { filters: [{ dimension: 'source', values: ['7'] }], presentation: 'table', limit: 500 }, + 'invoice', ['pdf'], 'participant' + ); + + expect(loaded.status).toBe('ready'); + expect(requests).toHaveLength(1); + expect(new URL(requests[0]!.url).pathname).toBe('/api/v1/files/groups'); + await expect(requests[0]!.json()).resolves.toEqual({ + predicate: { filters: [{ dimension: 'source', values: ['7'] }], presentation: 'table', limit: 500 }, + filename_query: 'invoice', mime_families: ['pdf'], grouping: ['participant'], limit: 500 + }); + }); + + it('loads one bounded attachment-fact page with cancellation and canonical context', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return Response.json({ + files: [{ + key: 'message:4:file:8', entry_key: 'message:4', message_id: 4, + occurred_at: '2026-07-18T12:00:00Z', source_id: 1, + source_identifier: 'archive@example.com', title: 'Synthetic entry', + filename: 'notes.txt', size: 42 + }], + total_count: 230, + cache_revision: 'cache-2', + search_provenance: { lexical_index_revision: 'fts-2' }, + next_cursor: 'files-2' + }); + }); + const controller = new AbortController(); + const api = createExploreAPI(createAPIClient(fetchFn)); + + const loaded = await api.files({ + query: 'pasta', search_mode: 'full_text', + filters: [{ dimension: 'participant', values: ['42'] }], + presentation: 'files', limit: 500, cursor: 'files-1' + }, controller.signal); + + expect(loaded.status).toBe('ready'); + expect(loaded).toMatchObject({ + result: { totalCount: 230, nextCursor: 'files-2', files: [{ filename: 'notes.txt' }] } + }); + expect(new URL(requests[0]!.url).pathname).toBe('/api/v1/explore/files'); + await expect(requests[0]!.clone().json()).resolves.toEqual({ + predicate: { + query: 'pasta', search_mode: 'full_text', + filters: [{ dimension: 'participant', values: ['42'] }], + presentation: 'files', limit: 500 + }, + cursor: 'files-1', limit: 100 + }); + controller.abort(); + expect(requests[0]!.signal.aborted).toBe(true); + }); + + it('uses a non-interactive full rebuild and accepts only a completed NDJSON stream', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + return new Response([ + JSON.stringify({ type: 'stdout', data: 'Building generation 2\n' }), + JSON.stringify({ type: 'complete' }) + ].join('\n') + '\n', { headers: { 'Content-Type': 'application/x-ndjson' } }); + }); + const api = createExploreAPI(createAPIClient(fetchFn)); + + await api.runCoverageAction('build_index', 'stale'); + + expect(requests).toHaveLength(1); + await expect(requests[0]!.json()).resolves.toEqual({ + args: ['embeddings', 'build', '--full-rebuild', '--yes'] + }); + }); + + it.each([ + ['error event', [ + { type: 'stdout', data: 'started\n' }, + { type: 'error', error: 'embedding endpoint failed' } + ]], + ['failed completion', [ + { type: 'complete', error: 'generation activation failed' } + ]] + ])('rejects a successful HTTP response containing a streamed %s', async (_name, events) => { + const fetchFn = vi.fn<typeof fetch>(async () => new Response( + events.map((event) => JSON.stringify(event)).join('\n') + '\n', + { headers: { 'Content-Type': 'application/x-ndjson' } } + )); + const api = createExploreAPI(createAPIClient(fetchFn)); + + await expect(api.runCoverageAction('build_index', 'incomplete')) + .rejects.toThrow(/failed|activation/i); + }); +}); diff --git a/web/src/lib/explore/api.ts b/web/src/lib/explore/api.ts new file mode 100644 index 000000000..9b7de683d --- /dev/null +++ b/web/src/lib/explore/api.ts @@ -0,0 +1,215 @@ +import type { APIClient } from '../api/client'; +import type { + ExploreCacheUnavailable, + ExploreFilter, + ExploreFilesLoadResult, + ExploreFilesResponse, + ExploreGroupDimension, + ExploreGroupLoadResult, + ExploreGroupsResponse, + ExploreLoadResult, + ExplorePredicate, + ExploreResponse, + ExploreResult, + FileGroupsResponse, + FileMIMEFamily +} from './models'; +import { parseSearchCoverage, type SearchCoverageAction, type SearchCoverageStatus, type SearchCoverageValue } from '../search/modes'; + +export interface VisibleLexicalCounts { + counts: Record<string, number>; + cacheRevision: string; + lexicalRevision: string; + canonicalQueryHash: string; +} + +function isCacheUnavailable(value: unknown): value is ExploreCacheUnavailable { + if (typeof value !== 'object' || value === null) return false; + const candidate = value as Partial<ExploreCacheUnavailable>; + return ( + typeof candidate.error === 'string' && + typeof candidate.message === 'string' && + typeof candidate.readiness === 'string' && + typeof candidate.recovery_action === 'string' + ); +} + +function normalize(data: ExploreResponse): ExploreResult { + return { + rows: data.rows, + totalCount: data.total_count, + cacheRevision: data.cache_revision, + searchProvenance: data.search_provenance, + candidateSnapshotId: data.candidate_snapshot_id, + candidatePoolSaturated: data.candidate_pool_saturated ?? false, + nextCursor: data.next_cursor + }; +} + +function normalizeGroups(data: ExploreGroupsResponse | FileGroupsResponse): Extract<ExploreGroupLoadResult, { status: 'ready' }>['result'] { + return { + rows: data.rows, + totalCount: data.total_count, + cacheRevision: data.cache_revision, + searchProvenance: data.search_provenance, + candidateSnapshotId: data.candidate_snapshot_id, + nextCursor: data.next_cursor + }; +} + +function normalizeFiles(data: ExploreFilesResponse): Extract<ExploreFilesLoadResult, { status: 'ready' }>['result'] { + return { + files: data.files, + totalCount: data.total_count, + cacheRevision: data.cache_revision, + searchProvenance: data.search_provenance, + candidateSnapshotId: data.candidate_snapshot_id, + nextCursor: data.next_cursor + }; +} + +function messageFor(error: unknown, status: number): string { + return typeof error === 'object' && error !== null && 'message' in error + ? String(error.message) + : `Exploration request failed (${status})`; +} + +export interface ExploreAPI { + explore(predicate: ExplorePredicate, signal?: AbortSignal): Promise<ExploreLoadResult>; + groups( + predicate: ExplorePredicate, + dimension: ExploreGroupDimension, + signal?: AbortSignal + ): Promise<ExploreGroupLoadResult>; + fileGroups( + predicate: ExplorePredicate, + filenameQuery: string, + mimeFamilies: FileMIMEFamily[], + dimension: ExploreGroupDimension, + signal?: AbortSignal + ): Promise<ExploreGroupLoadResult>; + files(predicate: ExplorePredicate, signal?: AbortSignal): Promise<ExploreFilesLoadResult>; + coverage(filters: ExploreFilter[], signal?: AbortSignal): Promise<SearchCoverageValue>; + matchCounts(predicate: ExplorePredicate, rowKeys: string[], signal?: AbortSignal): Promise<VisibleLexicalCounts>; + runCoverageAction( + action: SearchCoverageAction, + status: SearchCoverageStatus, + signal?: AbortSignal + ): Promise<void>; +} + +function requireCompletedCLIRun(stream: string): void { + let completed = false; + for (const line of stream.split('\n')) { + if (!line.trim()) continue; + let event: { type?: unknown; error?: unknown }; + try { + event = JSON.parse(line) as { type?: unknown; error?: unknown }; + } catch { + throw new Error('Semantic index action returned an invalid event stream'); + } + if (event.type === 'error' || event.type === 'failed' || + (event.type === 'complete' && typeof event.error === 'string' && event.error)) { + throw new Error(typeof event.error === 'string' && event.error + ? event.error + : 'Semantic index action failed'); + } + if (event.type === 'complete') completed = true; + } + if (!completed) throw new Error('Semantic index action did not complete'); +} + +export function createExploreAPI(client: APIClient): ExploreAPI { + return { + async explore(predicate, signal) { + const { data, error, response } = await client.POST('/api/v1/explore', { + body: predicate, + signal + }); + if (data) return { status: 'ready', result: normalize(data) }; + if (response.status === 503 && isCacheUnavailable(error)) { + return { status: 'unavailable', unavailable: error }; + } + throw new Error(messageFor(error, response.status)); + }, + async groups(predicate, dimension, signal) { + const body = { + ...(predicate.cursor ? { cursor: predicate.cursor } : {}), + ...(predicate.filters ? { filters: predicate.filters } : {}), + ...(predicate.query ? { query: predicate.query, search_mode: predicate.search_mode } : {}), + grouping: [dimension], + limit: predicate.limit, + presentation: 'table' as const + }; + const { data, error, response } = await client.POST('/api/v1/explore/groups', { + body, + signal + }); + if (data) return { status: 'ready', result: normalizeGroups(data) }; + if (response.status === 503 && isCacheUnavailable(error)) { + return { status: 'unavailable', unavailable: error }; + } + throw new Error(messageFor(error, response.status)); + }, + async fileGroups(predicate, filenameQuery, mimeFamilies, dimension, signal) { + const { cursor, ...context } = predicate; + const { data, error, response } = await client.POST('/api/v1/files/groups', { + body: { + predicate: context, + ...(filenameQuery ? { filename_query: filenameQuery } : {}), + ...(mimeFamilies.length ? { mime_families: mimeFamilies } : {}), + grouping: [dimension], + limit: predicate.limit, + ...(cursor ? { cursor } : {}) + }, + signal + }); + if (data) return { status: 'ready', result: normalizeGroups(data) }; + if (response.status === 503 && isCacheUnavailable(error)) { + return { status: 'unavailable', unavailable: error }; + } + throw new Error(messageFor(error, response.status)); + }, + async files(predicate, signal) { + const { cursor, ...context } = predicate; + const { data, error, response } = await client.POST('/api/v1/explore/files', { + body: { predicate: context, limit: 100, ...(cursor ? { cursor } : {}) }, + signal + }); + if (data) return { status: 'ready', result: normalizeFiles(data) }; + if (response.status === 503 && isCacheUnavailable(error)) { + return { status: 'unavailable', unavailable: error }; + } + throw new Error(messageFor(error, response.status)); + }, + async coverage(filters, signal) { + const { data, error, response } = await client.POST('/api/v1/search/coverage', { + body: { filters }, signal + }); + const coverage = parseSearchCoverage(data); + if (coverage) return coverage; + if (data) throw new Error('Semantic coverage response is incompatible with this browser'); + throw new Error(messageFor(error, response.status)); + }, + async matchCounts(predicate, rowKeys, signal) { + const { data, error, response } = await client.POST('/api/v1/explore/match-counts', { + body: { predicate, row_keys: rowKeys }, signal + }); + if (!data) throw new Error(messageFor(error, response.status)); + return { + counts: Object.fromEntries(data.counts.map((entry) => [entry.row_key, entry.count])), + cacheRevision: data.cache_revision, + lexicalRevision: data.lexical_index_revision, + canonicalQueryHash: data.canonical_query_hash + }; + }, + async runCoverageAction(action, _status, signal) { + if (action === 'retry') return; + const { data, error, response } = await client.POST('/api/v1/cli/run', { + body: { args: ['embeddings', 'build', '--full-rebuild', '--yes'] }, signal, parseAs: 'text' + }); + if (!response.ok) throw new Error(messageFor(error, response.status)); + requireCompletedCLIRun(data ?? ''); + } + }; +} diff --git a/web/src/lib/explore/attachment-authority.test.ts b/web/src/lib/explore/attachment-authority.test.ts new file mode 100644 index 000000000..caea43be2 --- /dev/null +++ b/web/src/lib/explore/attachment-authority.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from 'vitest'; + +import { attachmentSelection, parseAttachmentSelection } from './attachment-authority'; + +describe('attachment viewer authority', () => { + it('uses a distinct namespaced selection and round-trips positive safe IDs', () => { + expect(attachmentSelection(42)).toBe('attachment:42'); + expect(parseAttachmentSelection('attachment:42')).toBe(42); + }); + + it.each([ + null, + 'message:42', + '42', + 'attachment:0', + 'attachment:-1', + 'attachment:1.5', + 'attachment:9007199254740992', + 'attachment:7:message:1' + ])('rejects ambiguous or invalid authority %s', (value) => { + expect(parseAttachmentSelection(value)).toBeUndefined(); + }); +}); diff --git a/web/src/lib/explore/attachment-authority.ts b/web/src/lib/explore/attachment-authority.ts new file mode 100644 index 000000000..c8f1fe706 --- /dev/null +++ b/web/src/lib/explore/attachment-authority.ts @@ -0,0 +1,14 @@ +const ATTACHMENT_SELECTION_PREFIX = 'attachment:'; + +export function attachmentSelection(id: number): string { + if (!Number.isSafeInteger(id) || id < 1) throw new Error('Attachment authority requires a positive safe integer ID.'); + return `${ATTACHMENT_SELECTION_PREFIX}${id}`; +} + +export function parseAttachmentSelection(value: string | null): number | undefined { + if (!value?.startsWith(ATTACHMENT_SELECTION_PREFIX)) return undefined; + const encoded = value.slice(ATTACHMENT_SELECTION_PREFIX.length); + if (!/^[1-9][0-9]*$/.test(encoded)) return undefined; + const id = Number(encoded); + return Number.isSafeInteger(id) ? id : undefined; +} diff --git a/web/src/lib/explore/authority.ts b/web/src/lib/explore/authority.ts new file mode 100644 index 000000000..55086bfff --- /dev/null +++ b/web/src/lib/explore/authority.ts @@ -0,0 +1,15 @@ +import { canonicalFingerprint } from './selection'; + +interface AnalyticalAuthorityWire { + cache_revision: string; + search_provenance?: unknown; + candidate_snapshot_id?: string; +} + +export function analyticalAuthority(value: AnalyticalAuthorityWire): string { + return canonicalFingerprint({ + revision: value.cache_revision, + provenance: value.search_provenance ?? {}, + snapshot: value.candidate_snapshot_id ?? '' + }); +} diff --git a/web/src/lib/explore/group-context.test.ts b/web/src/lib/explore/group-context.test.ts new file mode 100644 index 000000000..087baa675 --- /dev/null +++ b/web/src/lib/explore/group-context.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest'; + +import { filtersForGroup, parseGroupSelection } from './group-context'; + +describe('group context', () => { + it.each([ + ['source', '7'], + ['participant', '42'], + ['domain', 'example.com'], + ['message_type', 'email'] + ] as const)('constrains %s details with the canonical scalar filter', (dimension, key) => { + expect(filtersForGroup( + [{ dimension: 'deletion', values: ['active'] }, { dimension, values: ['old'] }], + dimension, + key + )).toEqual([ + { dimension: 'deletion', values: ['active'] }, + { dimension, values: [key] } + ]); + }); + + it('constrains year and month details with exact half-open UTC intervals', () => { + expect(filtersForGroup( + [{ dimension: 'domain', values: ['example.com'] }, { dimension: 'after', values: ['old'] }], + 'year', + '2026' + )).toEqual([ + { dimension: 'domain', values: ['example.com'] }, + { dimension: 'after', values: ['2026-01-01T00:00:00Z'] }, + { dimension: 'before', values: ['2027-01-01T00:00:00Z'] } + ]); + expect(filtersForGroup([], 'month', '2026-12')).toEqual([ + { dimension: 'after', values: ['2026-12-01T00:00:00Z'] }, + { dimension: 'before', values: ['2027-01-01T00:00:00Z'] } + ]); + }); + + it('intersects temporal groups with an existing partial interval', () => { + expect(filtersForGroup([ + { dimension: 'after', values: ['2026-06-15T00:00:00Z'] }, + { dimension: 'before', values: ['2026-06-20T00:00:00Z'] } + ], 'month', '2026-06')).toEqual([ + { dimension: 'after', values: ['2026-06-15T00:00:00Z'] }, + { dimension: 'before', values: ['2026-06-20T00:00:00Z'] } + ]); + expect(filtersForGroup([ + { dimension: 'domain', values: ['example.com'] }, + { dimension: 'after', values: ['2026-07-01T00:00:00Z'] } + ], 'month', '2026-06')).toBeUndefined(); + }); + + it('rejects malformed temporal keys and parses only requestable group selections', () => { + expect(filtersForGroup([], 'year', '20x6')).toBeUndefined(); + expect(filtersForGroup([], 'month', '2026-13')).toBeUndefined(); + expect(parseGroupSelection('group:domain:example.com')).toEqual({ dimension: 'domain', key: 'example.com' }); + expect(parseGroupSelection('group:kind:email')).toBeUndefined(); + expect(parseGroupSelection('message:1')).toBeUndefined(); + }); +}); diff --git a/web/src/lib/explore/group-context.ts b/web/src/lib/explore/group-context.ts new file mode 100644 index 000000000..8fba12400 --- /dev/null +++ b/web/src/lib/explore/group-context.ts @@ -0,0 +1,76 @@ +import { isGroupingDimension } from '../grouping/catalog'; +import type { ExploreFilter, ExploreGroupDimension } from './models'; + +export interface GroupSelectionKey { + dimension: ExploreGroupDimension; + key: string; +} + +export function parseGroupSelection(value: string | null | undefined): GroupSelectionKey | undefined { + if (!value?.startsWith('group:')) return undefined; + const separator = value.indexOf(':', 'group:'.length); + if (separator < 0) return undefined; + const dimension = value.slice('group:'.length, separator); + const key = value.slice(separator + 1); + return isGroupingDimension(dimension) && key ? { dimension, key } : undefined; +} + +export function filtersForGroup( + filters: readonly ExploreFilter[], + dimension: ExploreGroupDimension, + key: string +): ExploreFilter[] | undefined { + if ( + dimension === 'source' || + dimension === 'participant' || + dimension === 'domain' || + dimension === 'message_type' + ) { + return [ + ...filters.filter((filter) => filter.dimension !== dimension), + { dimension, values: [key] } + ]; + } + if (dimension === 'year' && /^\d{4}$/.test(key)) { + const year = Number(key); + return replaceTimeFilters(filters, `${key}-01-01T00:00:00Z`, `${year + 1}-01-01T00:00:00Z`); + } + if (dimension === 'month' && /^\d{4}-(0[1-9]|1[0-2])$/.test(key)) { + const [year, month] = key.split('-').map(Number) as [number, number]; + const nextYear = month === 12 ? year + 1 : year; + const nextMonth = month === 12 ? 1 : month + 1; + return replaceTimeFilters( + filters, + `${key}-01T00:00:00Z`, + `${nextYear}-${String(nextMonth).padStart(2, '0')}-01T00:00:00Z` + ); + } + return undefined; +} + +function replaceTimeFilters( + filters: readonly ExploreFilter[], + after: string, + before: string +): ExploreFilter[] | undefined { + const existingAfter = filters.find((filter) => filter.dimension === 'after')?.values[0]; + const existingBefore = filters.find((filter) => filter.dimension === 'before')?.values[0]; + const lower = laterBound(after, existingAfter); + const upper = earlierBound(before, existingBefore); + if (Date.parse(lower) >= Date.parse(upper)) return undefined; + return [ + ...filters.filter((filter) => filter.dimension !== 'after' && filter.dimension !== 'before'), + { dimension: 'after', values: [lower] }, + { dimension: 'before', values: [upper] } + ]; +} + +function laterBound(groupBound: string, existing: string | undefined): string { + return existing && Number.isFinite(Date.parse(existing)) && Date.parse(existing) > Date.parse(groupBound) + ? existing : groupBound; +} + +function earlierBound(groupBound: string, existing: string | undefined): string { + return existing && Number.isFinite(Date.parse(existing)) && Date.parse(existing) < Date.parse(groupBound) + ? existing : groupBound; +} diff --git a/web/src/lib/explore/loader.svelte.test.ts b/web/src/lib/explore/loader.svelte.test.ts new file mode 100644 index 000000000..1acf6f2f8 --- /dev/null +++ b/web/src/lib/explore/loader.svelte.test.ts @@ -0,0 +1,230 @@ +import { flushSync } from 'svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../api/client'; +import { ExploreLoader, type ExploreLoaderCallbacks } from './loader.svelte'; +import { LOAD_THROUGH_END_MAX_PAGES } from './paging'; +import { ExploreState } from './state.svelte'; + +function exploreResponse(overrides: Record<string, unknown> = {}) { + return { + rows: [], + total_count: 0, + cache_revision: 'cache-1', + search_provenance: {}, + ...overrides + }; +} + +function entry(index: number) { + return { + key: `message:${index}`, + kind: 'message', + message_type: 'email', + conversation_type: 'email', + title: `Synthetic subject ${index}`, + preview: `Synthetic excerpt ${index}`, + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + source_type: 'synthetic', + participant_labels: ['Example Person'], + participant_ids: [1], + attachment_count: 0, + attachment_size: 0, + has_attachments: false, + deleted_from_source: false, + message_count: 1, + match: {} + }; +} + +function callbacks(overrides: Partial<ExploreLoaderCallbacks> = {}): ExploreLoaderCallbacks { + return { + isEnabled: () => true, + onPredicateChange: () => undefined, + onPagingNotice: () => undefined, + onRestorationFocus: () => undefined, + ...overrides + }; +} + +/** Constructs an ExploreLoader inside a standalone effect root (the loader + * relies on `$effect` internally, which requires an active reactive root + * outside of a component). Returns the loader plus a teardown that disposes + * both the root and the loader. */ +function setup( + fetchFn: typeof fetch, + state: ExploreState, + callbackOverrides: Partial<ExploreLoaderCallbacks> = {} +): { loader: ExploreLoader; cleanup: () => void } { + const client = createAPIClient(fetchFn); + let loader!: ExploreLoader; + const disposeRoot = $effect.root(() => { + loader = new ExploreLoader(client, state, callbacks(callbackOverrides)); + }); + flushSync(); + return { + loader, + cleanup: () => { + loader.destroy(); + disposeRoot(); + } + }; +} + +describe('ExploreLoader', () => { + it('issues exactly one fetch for one filter commit', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let exploreCount = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path !== '/api/v1/explore') return Response.json(exploreResponse()); + exploreCount += 1; + return Response.json(exploreResponse({ rows: [entry(1)] })); + }); + const state = new ExploreState(window); + const { loader, cleanup } = setup(fetchFn, state); + await vi.waitFor(() => expect(loader.rows).toHaveLength(1)); + expect(exploreCount).toBe(1); + + state.commitNavigation({ filters: [{ dimension: 'source', values: ['1'] }] }); + flushSync(); + await vi.waitFor(() => expect(exploreCount).toBe(2)); + + cleanup(); + state.destroy(); + }); + + it('does not refetch when an equal-content filters array replaces the current one by reference', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let exploreCount = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path !== '/api/v1/explore') return Response.json(exploreResponse()); + exploreCount += 1; + return Response.json(exploreResponse({ rows: [entry(1)] })); + }); + const state = new ExploreState(window); + const { cleanup } = setup(fetchFn, state); + await vi.waitFor(() => expect(exploreCount).toBe(1)); + + state.replaceTransient({ activeRow: 'message:1' }); + flushSync(); + await new Promise((resolve) => setTimeout(resolve, 10)); + expect(exploreCount).toBe(1); + + cleanup(); + state.destroy(); + }); + + it('caps loadThroughEnd at LOAD_THROUGH_END_MAX_PAGES and reports a paging notice', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let explorePostCount = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path !== '/api/v1/explore') return Response.json(exploreResponse()); + explorePostCount += 1; + const page = explorePostCount; + return Response.json(exploreResponse({ + rows: [entry(page)], total_count: 10_000, next_cursor: `cursor-${page}` + })); + }); + const state = new ExploreState(window); + const notices: (string | null)[] = []; + const { loader, cleanup } = setup(fetchFn, state, { onPagingNotice: (message) => notices.push(message) }); + await vi.waitFor(() => expect(loader.rows).toHaveLength(1)); + + await loader.loadThroughEnd(); + + expect(explorePostCount).toBe(1 + LOAD_THROUGH_END_MAX_PAGES); + expect(notices).toEqual([expect.stringContaining('press End again to continue')]); + + cleanup(); + state.destroy(); + }); + + it('clears the paging notice once a later loadThroughEnd reaches the true end', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let explorePostCount = 0; + const totalPages = 1 + LOAD_THROUGH_END_MAX_PAGES + 2; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path !== '/api/v1/explore') return Response.json(exploreResponse()); + explorePostCount += 1; + const page = explorePostCount; + return Response.json(exploreResponse({ + rows: [entry(page)], total_count: 10_000, + ...(page < totalPages ? { next_cursor: `cursor-${page}` } : {}) + })); + }); + const state = new ExploreState(window); + const notices: (string | null)[] = []; + const { loader, cleanup } = setup(fetchFn, state, { onPagingNotice: (message) => notices.push(message) }); + await vi.waitFor(() => expect(loader.rows).toHaveLength(1)); + + await loader.loadThroughEnd(); + expect(notices.at(-1)).toMatch(/press End again to continue/); + + await loader.loadThroughEnd(); + expect(explorePostCount).toBe(totalPages); + expect(notices.at(-1)).toBeNull(); + + cleanup(); + state.destroy(); + }); + + it('walks cursor pages during restoration until the durable selected row becomes visible', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let explorePostCount = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path !== '/api/v1/explore') return Response.json(exploreResponse()); + explorePostCount += 1; + const page = explorePostCount; + return Response.json(exploreResponse({ + rows: [entry(page)], total_count: 10_000, + ...(page < 4 ? { next_cursor: `cursor-${page}` } : {}) + })); + }); + const state = new ExploreState(window); + state.replaceTransient({ selectedRow: 'message:3' }); + const restorationFocusCalls: number[] = []; + const { loader, cleanup } = setup(fetchFn, state, { + onRestorationFocus: () => restorationFocusCalls.push(1) + }); + + await vi.waitFor(() => expect(loader.rows.some((row) => row.key === 'message:3')).toBe(true)); + expect(explorePostCount).toBe(3); + await vi.waitFor(() => expect(state.peekRestorationEpoch()).toBeUndefined()); + expect(restorationFocusCalls).toEqual([]); + + cleanup(); + state.destroy(); + }); + + it('exhausts finite pages once when a distinct selected row is missing', async () => { + window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + let explorePostCount = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const path = new URL(input instanceof Request ? input.url : String(input)).pathname; + if (path !== '/api/v1/explore') return Response.json(exploreResponse()); + explorePostCount += 1; + const page = explorePostCount; + return Response.json(exploreResponse({ + rows: [entry(page)], total_count: 10_000, + ...(page < 3 ? { next_cursor: `cursor-${page}` } : {}) + })); + }); + const state = new ExploreState(window); + state.replaceTransient({ selectedRow: 'message:missing' }); + const { loader, cleanup } = setup(fetchFn, state); + + await vi.waitFor(() => expect(state.peekRestorationEpoch()).toBeUndefined()); + expect(explorePostCount).toBe(3); + expect(loader.nextCursor).toBeUndefined(); + + cleanup(); + state.destroy(); + }); +}); diff --git a/web/src/lib/explore/loader.svelte.ts b/web/src/lib/explore/loader.svelte.ts new file mode 100644 index 000000000..c8d7ceb04 --- /dev/null +++ b/web/src/lib/explore/loader.svelte.ts @@ -0,0 +1,439 @@ +import { tick, untrack } from 'svelte'; + +import type { APIClient } from '../api/client'; +import type { + EntryRow, + ExploreCacheUnavailable, + ExploreFileFact, + ExploreFilesResult, + ExploreGroupDimension, + ExploreGroupResult, + ExploreGroupRow, + ExploreResult, + FileMIMEFamily +} from './models'; +import { createExploreAPI, type ExploreAPI } from './api'; +import { parseAttachmentSelection } from './attachment-authority'; +import { parseGroupSelection } from './group-context'; +import { LOAD_THROUGH_END_MAX_PAGES } from './paging'; +import { canonicalFingerprint, predicateFingerprint } from './selection'; +import type { ExploreState } from './state.svelte'; + +export type PageLoadOutcome = + | { status: 'advanced' } + | { status: 'exhausted' } + | { status: 'failed' | 'unavailable' | 'aborted' | 'stale' }; + +/** Cross-cutting seams the loader cannot own itself: `sortNotice` is shared + * with the Files-shell grouped view and the reverse-sort-attempt message + * (AppShell), `selection` is a shell-wide object, and grid focus restoration + * depends on AppShell's `currentGrid()` DOM query (shared across workspaces). + * The loader calls these at the exact points the original AppShell effect + * wrote to that shared state, preserving imperative last-write-wins order. */ +export interface ExploreLoaderCallbacks { + isEnabled: () => boolean; + onPredicateChange: () => void; + onPagingNotice: (message: string | null) => void; + onRestorationFocus: () => void; +} + +const REPEATED_CURSOR_NOTICE = 'Pagination stopped because the server repeated a cursor without progress.'; +const NO_PROGRESS_NOTICE = 'Pagination stopped because the next page made no row progress.'; +const REVISION_CHANGED_NOTICE = 'Results changed while loading another page. Reload this view.'; +export const END_PAUSE_NOTICE = + 'End paused loading to keep the table responsive; press End again to continue or refine the filters.'; + +function samePageAuthority( + next: Pick<ExploreResult, 'cacheRevision' | 'searchProvenance' | 'candidateSnapshotId'>, + first: Pick<ExploreResult, 'cacheRevision' | 'searchProvenance' | 'candidateSnapshotId'> +): boolean { + return next.cacheRevision === first.cacheRevision && + canonicalFingerprint(next.searchProvenance) === canonicalFingerprint(first.searchProvenance) && + next.candidateSnapshotId === first.candidateSnapshotId; +} + +/** + * Owns the Everything workspace's row/group/file loading: the main + * per-predicate load, cursor paging (including the End-key drain-to-end), + * and deep-link restoration walk that pages forward until a durable + * selection/scroll/active key becomes visible again. + * + * The same load path also serves the Files-shell grouped view (AppShell + * gates on `workspace === 'files' && groupingChain.length > 0` in addition + * to `workspace === 'everything'`), so this class is shared by both call + * sites rather than owned exclusively by EverythingWorkspace. + */ +export class ExploreLoader { + rows = $state<EntryRow[]>([]); + groupRows = $state<ExploreGroupRow[]>([]); + fileFacts = $state<ExploreFileFact[]>([]); + resultGeneration = $state(0); + result = $state<ExploreResult>(); + unavailable = $state<ExploreCacheUnavailable>(); + loading = $state(false); + loadingMore = $state(false); + error = $state(''); + nextCursor = $state<string>(); + resultFingerprint = $state(''); + restoring = $state(false); + + private readonly state: ExploreState; + private readonly api: ExploreAPI; + private readonly callbacks: ExploreLoaderCallbacks; + private requestGeneration = 0; + private requestController: AbortController | undefined; + private pagePredicate: ReturnType<ExploreState['predicate']> | undefined; + private pageGrouping: ExploreGroupDimension | undefined; + private pageGroupsFiles = false; + private pageKind: 'entries' | 'groups' | 'files' = 'entries'; + private pageFileFilenameQuery = ''; + private pageFileMIMEFamilies: FileMIMEFamily[] = []; + private pageAuthority: Pick<ExploreResult, 'cacheRevision' | 'searchProvenance' | 'candidateSnapshotId'> | undefined; + private pageRequest: Promise<PageLoadOutcome> | undefined; + private seenCursors = new Set<string>(); + private restorationCycleCompleted = false; + private selectionFingerprint = ''; + #retryRevision = $state(0); + + constructor(client: APIClient, state: ExploreState, callbacks: ExploreLoaderCallbacks) { + this.state = state; + this.api = createExploreAPI(client); + this.callbacks = callbacks; + + // Declared as a constructor-local (rather than a class field) because a + // $derived field initializer referencing `this.state` would run before + // the constructor body above assigns it; TS's field-initializer-order + // check flags that even though $derived itself is evaluated lazily. + const requestFingerprint = $derived(predicateFingerprint(this.state.predicate())); + + $effect(() => { + if (this.state.peekRestorationEpoch() === undefined) this.restoring = false; + }); + + $effect(() => this.runLoad(requestFingerprint)); + } + + private runLoad(requestFingerprint: string): void { + const workspace = this.state.current.workspace; + const restorationEpoch = this.state.restorationEpoch; + void this.#retryRevision; + const predicate = untrack(() => this.state.predicate()); + const fingerprint = requestFingerprint; + if (fingerprint !== this.selectionFingerprint) { + this.callbacks.onPredicateChange(); + this.selectionFingerprint = fingerprint; + } + const filesGrouping = workspace === 'files' && this.state.current.groupingChain.length > 0; + const fileFilenameQuery = this.state.current.fileFilenameQuery; + const fileMIMEFamilies = this.state.current.fileMIMEFamilies; + if (!this.callbacks.isEnabled() || (workspace !== 'everything' && !filesGrouping)) { + this.requestGeneration += 1; + this.requestController?.abort(); + this.loading = false; + // Session bootstrap temporarily disables the shell before its first + // request. Preserve that initial URL restoration until authority is + // available. Enabled workspaces without their own analytical grid have + // no row-loading phase, so acknowledge their history epoch here. + if (this.callbacks.isEnabled() && workspace !== 'files') { + const epoch = untrack(() => this.state.peekRestorationEpoch()); + if (epoch !== undefined) this.state.acknowledgeRestoration(epoch); + this.restoring = false; + this.restorationCycleCompleted = true; + } + return; + } + this.requestController?.abort(); + this.requestController = new AbortController(); + const controller = this.requestController; + const generation = ++this.requestGeneration; + this.resultGeneration = generation; + this.nextCursor = undefined; + this.pageAuthority = undefined; + this.pagePredicate = predicate; + this.pageGrouping = undefined; + this.pageGroupsFiles = filesGrouping; + const presentation = this.state.current.presentation; + const grouping = this.state.current.groupingChain; + this.pageKind = grouping.length > 0 ? 'groups' : presentation === 'files' ? 'files' : 'entries'; + this.pageFileFilenameQuery = fileFilenameQuery; + this.pageFileMIMEFamilies = fileMIMEFamilies; + this.pageRequest = undefined; + this.seenCursors = new Set<string>(); + const restoreEpoch = untrack(() => this.state.peekRestorationEpoch()); + const restoreTargets = restoreEpoch === restorationEpoch ? untrack(() => this.restorationKeys()) : []; + this.restoring = restoreTargets.length > 0; + this.loadingMore = false; + this.loading = true; + this.error = ''; + this.unavailable = undefined; + if (this.pageKind === 'groups') this.groupRows = []; + if (this.pageKind === 'files') this.fileFacts = []; + const request = grouping.length > 0 + ? (filesGrouping + ? this.api.fileGroups( + predicate, fileFilenameQuery, fileMIMEFamilies, + (this.pageGrouping = grouping[0]!), controller.signal + ) + : this.api.groups(predicate, (this.pageGrouping = grouping[0]!), controller.signal)) + : presentation === 'files' + ? this.api.files({ ...predicate, grouping: undefined }, controller.signal) + : this.api.explore({ ...predicate, grouping: undefined, presentation: 'table' }, controller.signal); + void request + .then((loaded) => { + if (generation !== this.requestGeneration) return; + if (loaded.status === 'unavailable') { + this.rows = []; + this.groupRows = []; + this.fileFacts = []; + this.result = undefined; + this.unavailable = loaded.unavailable; + return; + } + if (this.pageKind === 'entries') { + const entryResult = loaded.result as ExploreResult; + this.result = entryResult; + this.rows = entryResult.rows; + this.groupRows = []; + this.fileFacts = []; + this.resultFingerprint = fingerprint; + this.pageAuthority = entryResult; + } else if (this.pageKind === 'groups') { + const groupResult = loaded.result as ExploreGroupResult; + this.result = { + rows: [], + totalCount: groupResult.totalCount, + cacheRevision: groupResult.cacheRevision, + searchProvenance: groupResult.searchProvenance, + candidateSnapshotId: groupResult.candidateSnapshotId, + candidatePoolSaturated: false, + nextCursor: groupResult.nextCursor + }; + this.rows = []; + this.groupRows = groupResult.rows; + this.fileFacts = []; + this.resultFingerprint = ''; + this.pageAuthority = groupResult; + } else { + const filesResult = loaded.result as ExploreFilesResult; + this.result = { + rows: [], totalCount: filesResult.totalCount, + cacheRevision: filesResult.cacheRevision, + searchProvenance: filesResult.searchProvenance, + candidateSnapshotId: filesResult.candidateSnapshotId, + candidatePoolSaturated: false, + nextCursor: filesResult.nextCursor + }; + this.rows = []; + this.groupRows = []; + this.fileFacts = filesResult.files; + this.resultFingerprint = fingerprint; + this.pageAuthority = filesResult; + } + this.nextCursor = loaded.result.nextCursor; + }) + .catch((cause: unknown) => { + if (generation !== this.requestGeneration) return; + if (cause instanceof DOMException && cause.name === 'AbortError') return; + this.rows = []; + this.groupRows = []; + this.result = undefined; + this.error = cause instanceof Error ? cause.message : 'Could not load Everything.'; + }) + .finally(() => { + if (generation === this.requestGeneration) { + this.loading = false; + void this.restoreDeepState(generation, restoreEpoch, restoreTargets); + } + }); + } + + private failPaging(message: string): PageLoadOutcome { + this.error = message; + this.nextCursor = undefined; + return { status: 'failed' }; + } + + private async performLoadMore(): Promise<PageLoadOutcome> { + if (!this.nextCursor || !this.pagePredicate || !this.requestController || !this.pageAuthority) { + return { status: 'exhausted' }; + } + const generation = this.requestGeneration; + const cursor = this.nextCursor; + if (this.seenCursors.has(cursor)) { + return this.failPaging(REPEATED_CURSOR_NOTICE); + } + const first = this.pageAuthority; + const previousCount = this.pageKind === 'groups' + ? this.groupRows.length + : this.pageKind === 'files' ? this.fileFacts.length : this.rows.length; + this.loadingMore = true; + this.error = ''; + try { + const predicate = { ...this.pagePredicate, cursor }; + const loaded = this.pageKind === 'groups' && this.pageGrouping + ? (this.pageGroupsFiles + ? await this.api.fileGroups( + predicate, this.pageFileFilenameQuery, this.pageFileMIMEFamilies, + this.pageGrouping, this.requestController.signal + ) + : await this.api.groups(predicate, this.pageGrouping, this.requestController.signal)) + : this.pageKind === 'files' + ? await this.api.files({ ...predicate, grouping: undefined }, this.requestController.signal) + : await this.api.explore({ ...predicate, grouping: undefined, presentation: 'table' }, this.requestController.signal); + if (generation !== this.requestGeneration) return { status: 'stale' }; + if (loaded.status === 'unavailable') { + this.unavailable = loaded.unavailable; + this.nextCursor = undefined; + return { status: 'unavailable' }; + } + if (!samePageAuthority(loaded.result, first)) { + return this.failPaging(REVISION_CHANGED_NOTICE); + } + this.seenCursors.add(cursor); + const followingCursor = loaded.result.nextCursor; + if (this.pageKind === 'entries') { + const entryResult = loaded.result as ExploreResult; + const merged = new Map(this.rows.map((row) => [row.key, row])); + for (const row of entryResult.rows) merged.set(row.key, row); + this.rows = [...merged.values()]; + this.nextCursor = followingCursor; + this.result = { ...entryResult, rows: this.rows, nextCursor: followingCursor }; + } else if (this.pageKind === 'groups') { + const groupResult = loaded.result as ExploreGroupResult; + const merged = new Map(this.groupRows.map((row) => [row.key, row])); + for (const row of groupResult.rows) merged.set(row.key, row); + this.groupRows = [...merged.values()]; + this.nextCursor = followingCursor; + } else { + const filesResult = loaded.result as ExploreFilesResult; + const merged = new Map(this.fileFacts.map((file) => [file.key, file])); + for (const file of filesResult.files) merged.set(file.key, file); + this.fileFacts = [...merged.values()]; + this.nextCursor = followingCursor; + this.result = this.result ? { ...this.result, nextCursor: followingCursor } : this.result; + } + const currentCount = this.pageKind === 'groups' + ? this.groupRows.length + : this.pageKind === 'files' ? this.fileFacts.length : this.rows.length; + if (followingCursor && (followingCursor === cursor || this.seenCursors.has(followingCursor))) { + return this.failPaging(REPEATED_CURSOR_NOTICE); + } + if (followingCursor && currentCount === previousCount) { + return this.failPaging(NO_PROGRESS_NOTICE); + } + return followingCursor ? { status: 'advanced' } : { status: 'exhausted' }; + } catch (cause: unknown) { + if (generation !== this.requestGeneration) return { status: 'stale' }; + if (cause instanceof DOMException && cause.name === 'AbortError') return { status: 'aborted' }; + this.error = cause instanceof Error ? cause.message : 'Could not load more Everything results.'; + return { status: 'failed' }; + } finally { + if (generation === this.requestGeneration) this.loadingMore = false; + } + } + + /** Dedupes concurrent callers (e.g. a keyboard repeat and a scroll-edge + * trigger firing in the same tick) onto one in-flight page request. */ + loadMore = (): Promise<PageLoadOutcome> => { + if (this.pageRequest) return this.pageRequest; + const pending = this.performLoadMore(); + this.pageRequest = pending; + void pending.finally(() => { + if (this.pageRequest === pending) this.pageRequest = undefined; + }); + return pending; + }; + + /** Drains cursor pages until exhausted, capped at + * `LOAD_THROUGH_END_MAX_PAGES` per press so a huge view cannot spiral into + * thousands of round-trips; pressing End again continues from the cursor. */ + loadThroughEnd = async (): Promise<void> => { + let pages = 0; + while (this.nextCursor && !this.requestController?.signal.aborted) { + if (pages >= LOAD_THROUGH_END_MAX_PAGES) { + this.callbacks.onPagingNotice(END_PAUSE_NOTICE); + return; + } + const outcome = await this.loadMore(); + if (outcome.status !== 'advanced') break; + pages += 1; + } + if (!this.nextCursor) this.callbacks.onPagingNotice(null); + }; + + private restorationKeys(): string[] { + const current = this.state.current; + const selectedAttachmentID = parseAttachmentSelection(current.selectedRow); + const selected = selectedAttachmentID === undefined ? current.selectedRow : null; + return [...new Set([ + current.activeRow, + current.scrollAnchor?.key, + this.pageKind !== 'files' && selected && !parseGroupSelection(selected) ? selected : undefined + ].filter((key): key is string => Boolean(key)))]; + } + + private hasRestorationKey(key: string): boolean { + if (this.pageKind === 'groups' && this.pageGrouping) { + return this.groupRows.some((row) => `group:${this.pageGrouping}:${row.key}` === key); + } + if (this.pageKind === 'files') return this.fileFacts.some((file) => file.key === key); + return this.rows.some((row) => row.key === key); + } + + private async restoreDeepState( + generation: number, + epoch: number | undefined, + keys: string[] + ): Promise<void> { + if (epoch === undefined || keys.length === 0) { + if (generation === this.requestGeneration) { + const shouldRestoreFocus = epoch !== undefined && this.restorationCycleCompleted && + this.state.current.selectedRow === null; + if (epoch !== undefined) this.state.acknowledgeRestoration(epoch); + this.restoring = false; + this.restorationCycleCompleted = true; + if (shouldRestoreFocus) { + await tick(); + this.callbacks.onRestorationFocus(); + } + } + return; + } + while ( + generation === this.requestGeneration && + this.state.peekRestorationEpoch() === epoch && + this.nextCursor && + keys.some((key) => !this.hasRestorationKey(key)) + ) { + const outcome = await this.loadMore(); + if (outcome.status === 'advanced') continue; + if (outcome.status !== 'exhausted') return; + break; + } + if ( + generation === this.requestGeneration && + this.state.peekRestorationEpoch() === epoch && + (keys.every((key) => this.hasRestorationKey(key)) || + (this.pageAuthority !== undefined && !this.nextCursor && !this.error && !this.unavailable)) + ) { + await tick(); + if (generation === this.requestGeneration && this.state.peekRestorationEpoch() === epoch) { + this.state.acknowledgeRestoration(epoch); + this.restoring = false; + this.restorationCycleCompleted = true; + if (this.state.current.selectedRow === null) this.callbacks.onRestorationFocus(); + } + } + } + + /** Forces a fresh load of the current predicate (e.g. a named retry + * action after a failed request), without changing any URL state. */ + retry = (): void => { + this.#retryRevision += 1; + }; + + destroy = (): void => { + this.requestGeneration += 1; + this.requestController?.abort(); + }; +} diff --git a/web/src/lib/explore/models.ts b/web/src/lib/explore/models.ts new file mode 100644 index 000000000..66e95ce8e --- /dev/null +++ b/web/src/lib/explore/models.ts @@ -0,0 +1,168 @@ +import type { components } from '../api/generated/schema'; + +export type EntryRow = components['schemas']['EntryRow']; +export type ExploreCacheUnavailable = components['schemas']['ExploreCacheUnavailableResponse']; +export type ExploreFilter = components['schemas']['ExploreFilter']; +export type ExploreFileFact = components['schemas']['ExploreFileFact']; +export type ExploreFilesResponse = components['schemas']['ExploreFilesHTTPResponse']; +export type FileMetadata = components['schemas']['FileMetadataResponse']; +export type FileSearchRequest = components['schemas']['FileSearchHTTPRequest']; +export type FileSearchResponse = components['schemas']['FileSearchHTTPResponse']; +export type FileSearchRow = components['schemas']['FileSearchRow']; +export interface FileViewerTarget { + id: FileSearchRow['id']; + key?: FileSearchRow['key']; + entry_key?: FileSearchRow['entry_key']; + message_id?: FileSearchRow['message_id']; + conversation_id?: FileSearchRow['conversation_id']; + filename?: FileSearchRow['filename']; + mime_type?: FileSearchRow['mime_type']; + size_bytes?: FileSearchRow['size_bytes']; +} +export type FileGroupsResponse = components['schemas']['FileGroupsHTTPResponse']; +export type FileSearchSort = { + field: 'occurred_at' | 'filename' | 'size'; + direction: 'asc' | 'desc'; +}; +export type FileMIMEFamily = 'image' | 'pdf' | 'audio' | 'video' | 'text' | 'document' | 'archive' | 'other'; +export type ExploreGroupDimension = components['schemas']['ExploreGroupDimension']; +export type ExploreGroupRow = components['schemas']['ExploreGroupRow']; +export type ExploreGroupsResponse = components['schemas']['ExploreGroupsHTTPResponse']; +export type ExplorePredicate = components['schemas']['ExploreHTTPRequest']; +export type ExploreResponse = components['schemas']['ExploreHTTPResponse']; +export type ExploreSearchMode = NonNullable<ExplorePredicate['search_mode']>; +export type ExploreSort = components['schemas']['ExploreSort']; +export type SearchProvenance = components['schemas']['SearchProvenance']; +export type PersonSummary = components['schemas']['PersonSummary']; +export type PersonIdentifier = components['schemas']['PersonIdentifier']; +export type PersonCluster = components['schemas']['PersonCluster']; +export type PersonClusterEdge = components['schemas']['PersonClusterEdge']; +export type DomainSummary = components['schemas']['DomainSummary']; +export type PersonContextSummaryResponse = components['schemas']['PersonContextSummaryHTTPResponse']; +export type DomainContextSummaryResponse = components['schemas']['DomainContextSummaryHTTPResponse']; +export type IdentitySearchSort = components['schemas']['IdentitySearchSort']; + +export type ExploreWorkspace = + | 'everything' + | 'files' + | 'relationships' + | 'saved_views' + | 'sources' + | 'deletions' + | 'settings'; +export type RelationshipFacet = 'people' | 'domains'; +export type ExploreColumn = + | 'kind' + | 'people' + | 'title' + | 'excerpt' + | 'time' + | 'attachments' + | 'size'; + +export const DEFAULT_EXPLORE_COLUMNS: ExploreColumn[] = [ + 'kind', + 'people', + 'title', + 'excerpt', + 'time', + 'attachments' +]; + +export interface ExploreScrollAnchor { + key: string; + offset: number; +} + +/** Browser-restorable exploration context. Bulk selection is session-only. */ +export interface ExploreURLState { + schemaVersion: number; + workspace: ExploreWorkspace; + query: string; + searchMode: ExploreSearchMode; + filters: ExploreFilter[]; + groupingChain: ExploreGroupDimension[]; + presentation: 'table' | 'timeline' | 'files'; + sort: ExploreSort[]; + fileSort?: FileSearchSort; + fileFilenameQuery: string; + fileMIMEFamilies: FileMIMEFamily[]; + identityQuery?: string; + identitySort?: IdentitySearchSort; + analysisTarget?: string | null; + selectedIdentifier?: string | null; + relationshipFacet: RelationshipFacet; + relationshipTarget: string | null; + relationshipShowAll: boolean; + relationshipFiles: boolean; + columns: ExploreColumn[]; + columnWidths: Partial<Record<ExploreColumn, number>>; + activeRow: string | null; + selectedRow: string | null; + inspectorPinned: boolean; + inspectorWidth: number; + conversationAnchor: string | null; + scrollAnchor: ExploreScrollAnchor | null; + selection?: never; + [futureField: string]: unknown; +} + +export interface ExplicitExploreSelection { + mode: 'explicit'; + rowKeys: string[]; +} + +export interface AllMatchingExploreSelection { + mode: 'all_matching'; + predicate: ExplorePredicate; + exclusions: string[]; + cacheRevision: string; + searchProvenance: SearchProvenance; + candidateSnapshotId?: string; + /** Session-only identity proving which exact predicate produced this selection. */ + predicateFingerprint: string; + /** Session-only monotonically increasing query generation. */ + resultGeneration: number; +} + +export type ExploreSelection = ExplicitExploreSelection | AllMatchingExploreSelection; + +export interface ExploreResult { + rows: EntryRow[]; + totalCount?: number; + cacheRevision: string; + searchProvenance: SearchProvenance; + candidateSnapshotId?: string; + candidatePoolSaturated: boolean; + nextCursor?: string; +} + +export interface ExploreGroupResult { + rows: ExploreGroupRow[]; + totalCount: number; + cacheRevision: string; + searchProvenance: SearchProvenance; + candidateSnapshotId?: string; + nextCursor?: string; +} + +export interface ExploreFilesResult { + files: ExploreFileFact[]; + totalCount: number; + cacheRevision: string; + searchProvenance: SearchProvenance; + candidateSnapshotId?: string; + nextCursor?: string; +} + +export type ExploreLoadResult = + | { status: 'ready'; result: ExploreResult } + | { status: 'unavailable'; unavailable: ExploreCacheUnavailable }; + +export type ExploreGroupLoadResult = + | { status: 'ready'; result: ExploreGroupResult } + | { status: 'unavailable'; unavailable: ExploreCacheUnavailable }; + +export type ExploreFilesLoadResult = + | { status: 'ready'; result: ExploreFilesResult } + | { status: 'unavailable'; unavailable: ExploreCacheUnavailable }; diff --git a/web/src/lib/explore/paging.ts b/web/src/lib/explore/paging.ts new file mode 100644 index 000000000..39ee69a10 --- /dev/null +++ b/web/src/lib/explore/paging.ts @@ -0,0 +1,4 @@ +// Each walk-to-end invocation loads at most this many pages so `End` on a +// multi-million-row view cannot spiral into thousands of sequential +// round-trips. The cursor is preserved: pressing End again continues. +export const LOAD_THROUGH_END_MAX_PAGES = 20; diff --git a/web/src/lib/explore/selection.ts b/web/src/lib/explore/selection.ts new file mode 100644 index 000000000..22e430409 --- /dev/null +++ b/web/src/lib/explore/selection.ts @@ -0,0 +1,67 @@ +import type { + AllMatchingExploreSelection, + ExplorePredicate, + ExploreResult, + SearchProvenance +} from './models'; + +function canonical(value: unknown): unknown { + if (Array.isArray(value)) return value.map(canonical); + if (typeof value !== 'object' || value === null) return value; + return Object.fromEntries( + Object.entries(value) + .filter(([, item]) => item !== undefined) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, item]) => [key, canonical(item)]) + ); +} + +export function canonicalFingerprint(value: unknown): string { + return JSON.stringify(canonical(value)); +} + +export function predicateFingerprint(predicate: ExplorePredicate): string { + const { cursor: _cursor, candidate_snapshot_id: _snapshot, ...base } = predicate; + return canonicalFingerprint(base); +} + +export function hasValidSearchAuthority( + predicate: ExplorePredicate, + provenance: SearchProvenance, + candidateSnapshotId?: string +): boolean { + const lexical = provenance.lexical_index_revision; + const vector = provenance.vector_generation; + if (!predicate.query) return !lexical && vector === undefined && !candidateSnapshotId; + if (predicate.search_mode === 'full_text') { + return Boolean(lexical) && vector === undefined && !candidateSnapshotId; + } + if (predicate.search_mode === 'semantic') { + return !lexical && vector !== undefined && Boolean(candidateSnapshotId); + } + if (predicate.search_mode === 'hybrid') { + return Boolean(lexical) && vector !== undefined && Boolean(candidateSnapshotId); + } + return false; +} + +export function createAllMatchingSelection( + predicate: ExplorePredicate, + result: ExploreResult, + resultGeneration: number +): AllMatchingExploreSelection | undefined { + if (result.candidatePoolSaturated) return undefined; + if (!hasValidSearchAuthority(predicate, result.searchProvenance, result.candidateSnapshotId)) { + return undefined; + } + return { + mode: 'all_matching', + predicate, + exclusions: [], + cacheRevision: result.cacheRevision, + searchProvenance: result.searchProvenance, + ...(result.candidateSnapshotId ? { candidateSnapshotId: result.candidateSnapshotId } : {}), + predicateFingerprint: predicateFingerprint(predicate), + resultGeneration + }; +} diff --git a/web/src/lib/explore/state.svelte.ts b/web/src/lib/explore/state.svelte.ts new file mode 100644 index 000000000..7bb76325b --- /dev/null +++ b/web/src/lib/explore/state.svelte.ts @@ -0,0 +1,601 @@ +import { SvelteSet } from 'svelte/reactivity'; + +import type { + AllMatchingExploreSelection, + ExploreColumn, + ExploreFilter, + ExploreGroupDimension, + ExplorePredicate, + ExploreScrollAnchor, + ExploreSearchMode, + ExploreSelection, + ExploreSort, + FileSearchSort, + FileMIMEFamily, + ExploreURLState, + ExploreWorkspace, + RelationshipFacet +} from './models'; +import { DEFAULT_EXPLORE_COLUMNS } from './models'; +import { isGroupingDimension, validateGroupingChain } from '../grouping/catalog'; +import { hasValidSearchAuthority, predicateFingerprint } from './selection'; +import { parseAttachmentSelection } from './attachment-authority'; +import { + availableSearchModeStorage, + explicitSearchModeFromURL, + rememberSearchMode, + resolveInitialSearchMode, + type SearchModeStorage +} from '../search/modes'; + +const STATE_PARAMETER = 'explore'; +const FILTER_DIMENSIONS = new Set([ + 'source', + 'participant', + 'domain', + 'message_type', + 'after', + 'before', + 'deletion' +]); +const COLUMNS = new Set(['kind', 'people', 'title', 'excerpt', 'time', 'attachments', 'size']); +const TRANSIENT_HISTORY_FIELDS = [ + 'columns', + 'columnWidths', + 'inspectorWidth', + 'activeRow', + 'scrollAnchor' +] as const satisfies ReadonlyArray<keyof ExploreURLState>; +const RESTORATION_INVALIDATING_FIELDS = new Set<keyof ExploreURLState>([ + 'workspace', + 'query', + 'searchMode', + 'filters', + 'groupingChain', + 'presentation', + 'sort', + 'fileSort', + 'fileFilenameQuery', + 'fileMIMEFamilies', + 'identityQuery', + 'identitySort', + 'analysisTarget', + 'selectedIdentifier', + 'relationshipFacet', + 'relationshipTarget' +]); +const FILE_MIME_FAMILIES = new Set<FileMIMEFamily>([ + 'image', 'pdf', 'audio', 'video', 'text', 'document', 'archive', 'other' +]); + +export const defaultExploreURLState: ExploreURLState = { + schemaVersion: 2, + workspace: 'relationships', + query: '', + searchMode: 'full_text', + filters: [], + groupingChain: [], + presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + fileSort: { field: 'occurred_at', direction: 'desc' }, + fileFilenameQuery: '', + fileMIMEFamilies: [], + identityQuery: '', + identitySort: { field: 'activity_count', direction: 'desc' }, + analysisTarget: null, + selectedIdentifier: null, + relationshipFacet: 'people', + relationshipTarget: null, + relationshipShowAll: false, + relationshipFiles: false, + columns: [...DEFAULT_EXPLORE_COLUMNS], + columnWidths: {}, + activeRow: null, + selectedRow: null, + inspectorPinned: true, + inspectorWidth: 380, + conversationAnchor: null, + scrollAnchor: null +}; + +interface ExploreWindow { + location: Pick<Location, 'href' | 'pathname' | 'search' | 'hash'>; + history: Pick<History, 'pushState' | 'replaceState'>; + addEventListener(type: 'popstate', listener: () => void): void; + removeEventListener(type: 'popstate', listener: () => void): void; +} + +function isRecord(value: unknown): value is Record<string, unknown> { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function freshDefaults(): ExploreURLState { + return { + ...defaultExploreURLState, + filters: defaultExploreURLState.filters.map((filter) => ({ + ...filter, + values: [...filter.values] + })), + groupingChain: [...defaultExploreURLState.groupingChain], + sort: defaultExploreURLState.sort.map((sort) => ({ ...sort })), + fileSort: defaultExploreURLState.fileSort ? { ...defaultExploreURLState.fileSort } : undefined, + fileMIMEFamilies: [...defaultExploreURLState.fileMIMEFamilies], + columns: [...defaultExploreURLState.columns], + columnWidths: { ...defaultExploreURLState.columnWidths } + }; +} + +function isFilter(value: unknown): value is ExploreFilter { + return ( + isRecord(value) && + typeof value.dimension === 'string' && + FILTER_DIMENSIONS.has(value.dimension) && + Array.isArray(value.values) && + value.values.every((item) => typeof item === 'string') + ); +} + +function filters(value: unknown): ExploreFilter[] { + return Array.isArray(value) && value.every(isFilter) + ? value.map((filter) => ({ ...filter, values: [...filter.values] })) + : []; +} + +function groups(value: unknown): ExploreGroupDimension[] { + return validateGroupingChain(value); +} + +function columns(value: unknown): ExploreColumn[] { + return Array.isArray(value) && value.every((item) => COLUMNS.has(String(item))) + ? ([...value] as ExploreColumn[]) + : [...DEFAULT_EXPLORE_COLUMNS]; +} + +function sorts(value: unknown): ExploreSort[] { + return Array.isArray(value) && + value.every( + (item) => + isRecord(item) && item.field === 'occurred_at' && item.direction === 'desc' + ) + ? (value.map((item) => ({ ...item })) as ExploreSort[]) + : defaultExploreURLState.sort.map((sort) => ({ ...sort })); +} + +function fileSort(value: unknown): FileSearchSort { + return isRecord(value) && + (value.field === 'occurred_at' || value.field === 'filename' || value.field === 'size') && + (value.direction === 'asc' || value.direction === 'desc') + ? { field: value.field, direction: value.direction } + : { field: 'occurred_at', direction: 'desc' }; +} + +function fileMIMEFamilies(value: unknown): FileMIMEFamily[] { + return Array.isArray(value) && value.every((item) => + typeof item === 'string' && FILE_MIME_FAMILIES.has(item as FileMIMEFamily)) + ? [...new Set(value)] as FileMIMEFamily[] + : []; +} + +function widths(value: unknown): Partial<Record<ExploreColumn, number>> { + if (!isRecord(value)) return {}; + const result: Partial<Record<ExploreColumn, number>> = {}; + for (const [key, width] of Object.entries(value)) { + if (COLUMNS.has(key) && typeof width === 'number' && Number.isFinite(width) && width > 0) { + result[key as ExploreColumn] = width; + } + } + return result; +} + +function scrollAnchor(value: unknown): ExploreScrollAnchor | null { + if (value === null) return null; + return isRecord(value) && typeof value.key === 'string' && typeof value.offset === 'number' + ? { key: value.key, offset: value.offset } + : null; +} + +function selectedRow(value: unknown): string | null { + if (value === null) return null; + if (typeof value !== 'string') return null; + if (!value.startsWith('attachment:')) return value; + return parseAttachmentSelection(value) === undefined ? null : value; +} + +function relationshipTargetValue(value: unknown): string | null { + return typeof value === 'string' && + (/^cluster:\d+$/.test(value) || /^domain:\S+$/.test(value)) + ? value + : null; +} + +function legacyRelationshipTarget( + analysisTarget: string | null, + facet: RelationshipFacet +): string | null { + if (analysisTarget === null) return null; + if (facet === 'people' && analysisTarget.startsWith('person:')) { + return relationshipTargetValue(`cluster:${analysisTarget.slice('person:'.length)}`); + } + if (facet === 'domains' && analysisTarget.startsWith('domain:')) { + return relationshipTargetValue(analysisTarget); + } + return null; +} + +function normalize(value: unknown): ExploreURLState { + if (!isRecord(value)) return freshDefaults(); + const { selection: _selection, bulkSelection: _bulkSelection, ...knownAndFuture } = value; + const searchMode = + value.searchMode === 'full_text' || + value.searchMode === 'semantic' || + value.searchMode === 'hybrid' + ? value.searchMode + : defaultExploreURLState.searchMode; + const presentation = + value.presentation === 'table' || + value.presentation === 'timeline' || + value.presentation === 'files' + ? value.presentation + : defaultExploreURLState.presentation; + const legacyFacet: RelationshipFacet | undefined = + value.workspace === 'people' ? 'people' : value.workspace === 'domains' ? 'domains' : undefined; + const workspace = value.workspace === 'everything' || value.workspace === 'settings' || + value.workspace === 'files' || value.workspace === 'relationships' || + value.workspace === 'saved_views' || value.workspace === 'sources' || + value.workspace === 'deletions' + ? value.workspace + : 'relationships'; + const analysisTarget = typeof value.analysisTarget === 'string' && + (/^person:[1-9][0-9]*$/.test(value.analysisTarget) || /^domain:[a-z0-9.-]+$/.test(value.analysisTarget)) + ? value.analysisTarget : null; + const relationshipFacet: RelationshipFacet = legacyFacet ?? + (value.relationshipFacet === 'people' || value.relationshipFacet === 'domains' + ? value.relationshipFacet + : 'people'); + const relationshipTarget = legacyFacet + ? legacyRelationshipTarget(analysisTarget, legacyFacet) + : relationshipTargetValue(value.relationshipTarget); + + return { + ...knownAndFuture, + schemaVersion: value.schemaVersion === 1 + ? defaultExploreURLState.schemaVersion + : typeof value.schemaVersion === 'number' && Number.isSafeInteger(value.schemaVersion) + ? value.schemaVersion + : defaultExploreURLState.schemaVersion, + workspace, + query: typeof value.query === 'string' ? value.query : '', + searchMode, + filters: filters(value.filters), + groupingChain: groups(value.groupingChain), + presentation, + sort: sorts(value.sort), + fileSort: fileSort(value.fileSort), + fileFilenameQuery: value.schemaVersion === 2 && typeof value.fileFilenameQuery === 'string' + ? value.fileFilenameQuery + : '', + fileMIMEFamilies: value.schemaVersion === 2 ? fileMIMEFamilies(value.fileMIMEFamilies) : [], + identityQuery: typeof value.identityQuery === 'string' ? value.identityQuery : '', + identitySort: isRecord(value.identitySort) && + (value.identitySort.field === 'activity_count' || value.identitySort.field === 'latest_at' || value.identitySort.field === 'display_label') && + (value.identitySort.direction === 'asc' || value.identitySort.direction === 'desc') + ? { field: value.identitySort.field, direction: value.identitySort.direction } + : { field: 'activity_count', direction: 'desc' }, + analysisTarget, + selectedIdentifier: typeof value.selectedIdentifier === 'string' ? value.selectedIdentifier : null, + relationshipFacet, + relationshipTarget, + relationshipShowAll: value.relationshipShowAll === true, + relationshipFiles: value.relationshipFiles === true, + columns: columns(value.columns), + columnWidths: widths(value.columnWidths), + activeRow: + typeof value.activeRow === 'string' || value.activeRow === null + ? value.activeRow + : null, + selectedRow: selectedRow(value.selectedRow), + inspectorPinned: true, + inspectorWidth: + typeof value.inspectorWidth === 'number' && Number.isFinite(value.inspectorWidth) && + value.inspectorWidth >= 320 && value.inspectorWidth <= 720 + ? value.inspectorWidth + : defaultExploreURLState.inspectorWidth, + conversationAnchor: + typeof value.conversationAnchor === 'string' || value.conversationAnchor === null + ? value.conversationAnchor + : null, + scrollAnchor: scrollAnchor(value.scrollAnchor) + } as ExploreURLState; +} + +export function serializeExploreURLState(state: ExploreURLState, baseSearch = ''): string { + const parameters = new URLSearchParams(baseSearch.startsWith('?') ? baseSearch.slice(1) : baseSearch); + parameters.set(STATE_PARAMETER, JSON.stringify(normalize(state))); + return `?${parameters.toString()}`; +} + +export function parseExploreURLState(search: string): ExploreURLState { + const parameters = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search); + const encoded = parameters.get(STATE_PARAMETER); + if (encoded === null) return freshDefaults(); + try { + return normalize(JSON.parse(encoded)); + } catch { + return freshDefaults(); + } +} + +export class ExploreState { + current = $state<ExploreURLState>(freshDefaults()); + restorationEpoch = $state(1); + private readonly browser: ExploreWindow; + private readonly preferenceStorage: SearchModeStorage | null; + private committed: ExploreURLState; + private pendingRestorationEpoch = $state<number | undefined>(1); + private pendingSearchPriorFocus?: Pick<ExploreURLState, 'activeRow' | 'scrollAnchor'>; + private readonly handlePopState = (): void => { + this.current = this.readURLState(); + this.committed = normalize(this.current); + this.pendingSearchPriorFocus = undefined; + this.restorationEpoch += 1; + this.pendingRestorationEpoch = this.restorationEpoch; + }; + + constructor( + browser: ExploreWindow = window, + preferenceStorage: SearchModeStorage | null = browser === globalThis.window + ? availableSearchModeStorage() + : null + ) { + this.browser = browser; + this.preferenceStorage = preferenceStorage; + this.current = this.readURLState(); + this.committed = normalize(this.current); + browser.addEventListener('popstate', this.handlePopState); + } + + replaceTransient(patch: Partial<ExploreURLState>): void { + this.navigate(patch, 'replace'); + } + + replaceCommittedNavigation(patch: Partial<ExploreURLState>): void { + this.navigate(patch, 'replace'); + this.committed = normalize(this.current); + this.pendingSearchPriorFocus = undefined; + } + + replaceCommittedDraft(patch: Partial<ExploreURLState>): void { + const priorCommitted = this.committed; + this.navigate(patch, 'replace'); + this.committed = normalize({ ...priorCommitted, ...patch }); + } + + peekRestorationEpoch(): number | undefined { + return this.pendingRestorationEpoch; + } + + acknowledgeRestoration(epoch: number): void { + if (this.pendingRestorationEpoch === epoch) this.pendingRestorationEpoch = undefined; + } + + replaceSearchDraft(query: string, searchMode: ExploreSearchMode): void { + rememberSearchMode(searchMode, this.preferenceStorage); + this.pendingSearchPriorFocus ??= { + activeRow: this.current.activeRow, + scrollAnchor: this.current.scrollAnchor + ? { ...this.current.scrollAnchor } + : null + }; + this.navigate({ query, searchMode, activeRow: null, scrollAnchor: null }, 'replace'); + } + + commitSearch(query: string, searchMode: ExploreSearchMode): void { + rememberSearchMode(searchMode, this.preferenceStorage); + this.navigate({ query, searchMode, selectedRow: null, conversationAnchor: null, activeRow: null, scrollAnchor: null }, 'push'); + } + + commitWorkspace(workspace: ExploreWorkspace): void { + this.navigate({ + workspace, + analysisTarget: null, + selectedIdentifier: null, + activeRow: null, + selectedRow: null, + conversationAnchor: null, + scrollAnchor: null + }, 'push'); + } + + commitNavigation(patch: Partial<ExploreURLState>): void { + const selectionChanged = 'selectedRow' in patch && patch.selectedRow !== this.current.selectedRow; + this.navigate(selectionChanged && !('conversationAnchor' in patch) + ? { ...patch, conversationAnchor: null } + : patch, 'push'); + } + + commitRestorableNavigation(patch: Partial<ExploreURLState>): void { + this.navigate(patch, 'push'); + this.restorationEpoch += 1; + this.pendingRestorationEpoch = this.restorationEpoch; + } + + replaceCommittedRestorableNavigation(patch: Partial<ExploreURLState>): void { + this.navigate(patch, 'replace'); + this.committed = normalize(this.current); + this.pendingSearchPriorFocus = undefined; + this.restorationEpoch += 1; + this.pendingRestorationEpoch = this.restorationEpoch; + } + + commitGrouping(dimension: ExploreGroupDimension): void { + if (!isGroupingDimension(dimension) || this.current.groupingChain.includes(dimension)) return; + this.navigate({ + groupingChain: [...this.current.groupingChain, dimension], + activeRow: null, + scrollAnchor: null + }, 'push'); + } + + commitUngroup(): void { + if (this.current.groupingChain.length === 0) return; + this.navigate({ + groupingChain: this.current.groupingChain.slice(0, -1), + activeRow: null, + scrollAnchor: null + }, 'push'); + } + + predicate(): ExplorePredicate { + const query = this.current.query.trim(); + return { + ...(query ? { query, search_mode: this.current.searchMode } : {}), + filters: this.current.filters, + grouping: this.current.groupingChain, + presentation: this.current.presentation, + sort: this.current.sort, + limit: 500 + }; + } + + destroy(): void { + this.browser.removeEventListener('popstate', this.handlePopState); + } + + private readURLState(): ExploreURLState { + const parsed = parseExploreURLState(this.browser.location.search); + parsed.searchMode = resolveInitialSearchMode( + explicitSearchModeFromURL(this.browser.location.search), + this.preferenceStorage + ); + return parsed; + } + + private navigate( + patch: Partial<ExploreURLState>, + mode: 'push' | 'replace' + ): void { + if ( + mode === 'push' || + Object.keys(patch).some((key) => RESTORATION_INVALIDATING_FIELDS.has(key as keyof ExploreURLState)) + ) { + this.pendingRestorationEpoch = undefined; + } + const baseSearch = this.browser.location.search; + if (mode === 'push') { + const priorFocus = this.pendingSearchPriorFocus; + const transient = Object.fromEntries( + TRANSIENT_HISTORY_FIELDS + .filter((key) => !priorFocus || (key !== 'activeRow' && key !== 'scrollAnchor')) + .map((key) => [key, this.current[key]]) + ) as Partial<ExploreURLState>; + const priorEntry = normalize({ ...this.committed, ...transient, ...priorFocus }); + const committedURL = `${this.browser.location.pathname}${serializeExploreURLState(priorEntry, baseSearch)}${this.browser.location.hash}`; + this.browser.history.replaceState(null, '', committedURL); + } + const next = normalize({ ...this.current, ...patch }); + // Preserve per-field reactivity: transient scroll/column changes must not + // invalidate consumers that only read the canonical server predicate. + for (const key of Object.keys(patch)) { + if (key in next) this.current[key] = next[key]; + } + const url = `${this.browser.location.pathname}${serializeExploreURLState(this.current, baseSearch)}${this.browser.location.hash}`; + if (mode === 'push') { + this.browser.history.pushState(null, '', url); + this.committed = normalize(this.current); + this.pendingSearchPriorFocus = undefined; + } else this.browser.history.replaceState(null, '', url); + } +} + +export class ExploreSelectionState { + mode = $state<'explicit' | 'all_matching'>('explicit'); + readonly explicitKeys = new SvelteSet<string>(); + readonly exclusions = new SvelteSet<string>(); + private allMatching?: Omit<AllMatchingExploreSelection, 'exclusions'>; + private rangeAnchorKey: string | undefined; + + get count(): number { + return this.mode === 'explicit' ? this.explicitKeys.size : 0; + } + + isSelected(key: string): boolean { + return this.mode === 'explicit' ? this.explicitKeys.has(key) : !this.exclusions.has(key); + } + + selectedKeys(keys: string[]): string[] { + return keys.filter((key) => this.isSelected(key)); + } + + toggle(key: string, index: number, orderedKeys: string[], range = false): void { + if (this.mode === 'all_matching') { + if (this.exclusions.has(key)) this.exclusions.delete(key); + else this.exclusions.add(key); + return; + } + const rangeAnchor = this.rangeAnchorKey ? orderedKeys.indexOf(this.rangeAnchorKey) : -1; + if (range && rangeAnchor >= 0) { + const start = Math.min(rangeAnchor, index); + const end = Math.max(rangeAnchor, index); + for (let cursor = start; cursor <= end; cursor += 1) { + const next = orderedKeys[cursor]; + if (next !== undefined) this.explicitKeys.add(next); + } + return; + } + if (this.explicitKeys.has(key)) this.explicitKeys.delete(key); + else this.explicitKeys.add(key); + this.rangeAnchorKey = key; + } + + selectVisible(keys: string[]): void { + if (this.mode === 'all_matching') { + for (const key of keys) this.exclusions.delete(key); + return; + } + for (const key of keys) this.explicitKeys.add(key); + if (keys.length > 0) this.rangeAnchorKey = keys[0]; + } + + selectAllMatching(selection: AllMatchingExploreSelection): void { + if (selection.predicateFingerprint !== predicateFingerprint(selection.predicate)) { + throw new Error('All-matching selection predicate fingerprint does not match'); + } + if (selection.resultGeneration < 1) { + throw new Error('All-matching selection requires a result generation'); + } + if ( + (selection.predicate.search_mode === 'semantic' || + selection.predicate.search_mode === 'hybrid') && + !selection.candidateSnapshotId + ) { + throw new Error('Semantic all-matching selection requires a server candidate snapshot'); + } + if (!hasValidSearchAuthority( + selection.predicate, + selection.searchProvenance, + selection.candidateSnapshotId + )) { + throw new Error('All-matching selection search provenance does not match its mode'); + } + this.clear(); + this.mode = 'all_matching'; + const { exclusions, ...pinned } = selection; + this.allMatching = pinned; + for (const key of exclusions) this.exclusions.add(key); + } + + clear(): void { + this.mode = 'explicit'; + this.explicitKeys.clear(); + this.exclusions.clear(); + this.allMatching = undefined; + this.rangeAnchorKey = undefined; + } + + snapshot(): ExploreSelection { + if (this.mode === 'all_matching' && this.allMatching) { + return { ...this.allMatching, exclusions: [...this.exclusions] }; + } + return { mode: 'explicit', rowKeys: [...this.explicitKeys] }; + } +} diff --git a/web/src/lib/explore/state.test.ts b/web/src/lib/explore/state.test.ts new file mode 100644 index 000000000..de09da189 --- /dev/null +++ b/web/src/lib/explore/state.test.ts @@ -0,0 +1,786 @@ +import { describe, expect, it } from 'vitest'; + +import type { + ExplorePredicate, + ExploreSelection, + ExploreURLState, + SearchProvenance +} from './models'; +import { + ExploreSelectionState, + ExploreState, + defaultExploreURLState, + parseExploreURLState, + serializeExploreURLState +} from './state.svelte'; +import { createAllMatchingSelection, predicateFingerprint } from './selection'; +import { SEARCH_MODE_PREFERENCE_KEY } from '../search/modes'; + +describe('Explore URL state', () => { + it('round-trips every primary management workspace through the URL', () => { + for (const workspace of ['saved_views', 'sources', 'deletions'] as const) { + const parsed = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + workspace + })); + expect(parsed.workspace).toBe(workspace); + } + }); + + it('round-trips every durable field in the versioned envelope', () => { + const state: ExploreURLState = { + schemaVersion: 2, + workspace: 'files', + query: 'from:alice quarterly plan', + searchMode: 'hybrid', + filters: [ + { dimension: 'message_type', values: ['email'] }, + { dimension: 'after', values: ['2025-01-01'] } + ], + groupingChain: ['participant', 'year'], + presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + fileSort: { field: 'filename', direction: 'asc' }, + fileFilenameQuery: 'invoice', + fileMIMEFamilies: ['pdf', 'image'], + identityQuery: 'Shared Name', + identitySort: { field: 'display_label', direction: 'asc' }, + analysisTarget: 'person:42', + selectedIdentifier: 'email:alice@example.com', + relationshipFacet: 'domains', + relationshipTarget: 'domain:example.com', + relationshipShowAll: true, + relationshipFiles: true, + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments', 'size'], + columnWidths: { people: 240, title: 360 }, + selectedRow: 'message:42', + inspectorPinned: true, + inspectorWidth: 456, + conversationAnchor: 'message:37', + scrollAnchor: { key: 'message:31', offset: 12 }, + activeRow: 'message:33' + }; + + expect(parseExploreURLState(serializeExploreURLState(state))).toEqual(state); + }); + + it('normalizes legacy unpinned inspector states to pinned', () => { + const parsed = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + inspectorPinned: false + })); + expect(parsed.inspectorPinned).toBe(true); + }); + + it('restores legacy People and Domains targets as relationships without treating display labels as identity', () => { + for (const [workspace, target] of [['people', 'person:42'], ['domains', 'domain:example.com']] as const) { + const restored = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + workspace, + identityQuery: 'Shared Name', + analysisTarget: target, + selectedIdentifier: workspace === 'people' ? 'phone:+15550100001' : null + } as unknown as ExploreURLState)); + + expect(restored.workspace).toBe('relationships'); + expect(restored.relationshipFacet).toBe(workspace); + expect(restored.relationshipTarget).toBe(workspace === 'people' ? 'cluster:42' : target); + expect(restored.identityQuery).toBe('Shared Name'); + expect(restored.analysisTarget).toBe(target); + expect(restored.selectedIdentifier).toBe(workspace === 'people' ? 'phone:+15550100001' : null); + } + }); + + it('migrates supported version-one URLs before Files controls are edited', () => { + const legacy = new URLSearchParams({ + explore: JSON.stringify({ ...defaultExploreURLState, schemaVersion: 1, workspace: 'files' }) + }); + const restored = parseExploreURLState(`?${legacy}`); + expect(restored.schemaVersion).toBe(2); + expect(serializeExploreURLState({ ...restored, fileFilenameQuery: 'invoice', fileMIMEFamilies: ['pdf'] })) + .toContain('explore='); + expect(parseExploreURLState(serializeExploreURLState({ + ...restored, fileFilenameQuery: 'invoice', fileMIMEFamilies: ['pdf'] + }))).toMatchObject({ schemaVersion: 2, fileFilenameQuery: 'invoice', fileMIMEFamilies: ['pdf'] }); + }); + + it('preserves unknown future fields while dropping session selection', () => { + const future = { + ...defaultExploreURLState, + schemaVersion: 2, + futureLayout: { density: 'compact' }, + selection: { mode: 'explicit', rowKeys: ['message:42'] } + } as unknown as ExploreURLState; + + const restored = parseExploreURLState(serializeExploreURLState(future)); + + expect(restored).toMatchObject({ schemaVersion: 2, futureLayout: { density: 'compact' } }); + expect(restored).not.toHaveProperty('selection'); + }); + + it('preserves unrelated URL parameters while replacing the exploration envelope', () => { + const serialized = serializeExploreURLState( + { ...defaultExploreURLState, query: 'quarterly' }, + '?feature=preview&source=desktop&explore=obsolete' + ); + const parameters = new URLSearchParams(serialized); + + expect(parameters.get('feature')).toBe('preview'); + expect(parameters.get('source')).toBe('desktop'); + expect(parseExploreURLState(serialized).query).toBe('quarterly'); + }); + + it('uses independent defaults for absent and malformed state', () => { + const absent = parseExploreURLState(''); + const malformed = parseExploreURLState('?explore=%7Bbad-json'); + absent.columns.push('size'); + absent.columnWidths.people = 100; + + expect(malformed).toEqual(defaultExploreURLState); + expect(absent.columns).not.toBe(defaultExploreURLState.columns); + expect(absent.columnWidths).not.toBe(defaultExploreURLState.columnWidths); + }); + + it('rejects malformed file-specific filters without changing canonical search', () => { + const restored = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + query: 'canonical terms', + fileFilenameQuery: 42, + fileMIMEFamilies: ['pdf', 'bogus'] + } as unknown as ExploreURLState)); + + expect(restored.query).toBe('canonical terms'); + expect(restored.fileFilenameQuery).toBe(''); + expect(restored.fileMIMEFamilies).toEqual([]); + }); + + it('drops malformed namespaced attachment authority instead of treating it as an entry key', () => { + const restored = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + selectedRow: 'attachment:0' + })); + + expect(restored.selectedRow).toBeNull(); + }); +}); + +describe('relationships workspace state', () => { + it('normalizes legacy people/domains workspaces to relationships facets', () => { + const fromPeople = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + workspace: 'people', + analysisTarget: 'person:12' + } as unknown as ExploreURLState)); + expect(fromPeople.workspace).toBe('relationships'); + expect(fromPeople.relationshipFacet).toBe('people'); + expect(fromPeople.relationshipTarget).toBe('cluster:12'); + + const fromDomains = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + workspace: 'domains', + analysisTarget: 'domain:example.com' + } as unknown as ExploreURLState)); + expect(fromDomains.workspace).toBe('relationships'); + expect(fromDomains.relationshipFacet).toBe('domains'); + expect(fromDomains.relationshipTarget).toBe('domain:example.com'); + + const withoutTarget = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + workspace: 'people', + analysisTarget: null + } as unknown as ExploreURLState)); + expect(withoutTarget.workspace).toBe('relationships'); + expect(withoutTarget.relationshipFacet).toBe('people'); + expect(withoutTarget.relationshipTarget).toBeNull(); + }); + + it('defaults workspace to relationships', () => { + expect(defaultExploreURLState.workspace).toBe('relationships'); + const restored = parseExploreURLState(`?explore=${encodeURIComponent('{}')}`); + expect(restored.workspace).toBe('relationships'); + expect(restored.relationshipFacet).toBe('people'); + expect(restored.relationshipTarget).toBeNull(); + expect(restored.relationshipShowAll).toBe(false); + expect(restored.relationshipFiles).toBe(false); + }); + + it('round-trips relationship fields through serialize/parse', () => { + const state: ExploreURLState = { + ...defaultExploreURLState, + workspace: 'relationships', + relationshipFacet: 'domains', + relationshipTarget: 'domain:example.com', + relationshipShowAll: true, + relationshipFiles: true + }; + + expect(parseExploreURLState(serializeExploreURLState(state))).toMatchObject({ + workspace: 'relationships', + relationshipFacet: 'domains', + relationshipTarget: 'domain:example.com', + relationshipShowAll: true, + relationshipFiles: true + }); + }); + + it('rejects invalid facet/target shapes', () => { + const restored = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + relationshipFacet: 'x', + relationshipTarget: 'garbage' + } as unknown as ExploreURLState)); + + expect(restored.relationshipFacet).toBe('people'); + expect(restored.relationshipTarget).toBeNull(); + + const validCluster = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + relationshipTarget: 'cluster:12' + } as unknown as ExploreURLState)); + expect(validCluster.relationshipTarget).toBe('cluster:12'); + + const invalidCluster = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + relationshipTarget: 'cluster:abc' + } as unknown as ExploreURLState)); + expect(invalidCluster.relationshipTarget).toBeNull(); + + const validDomain = parseExploreURLState(serializeExploreURLState({ + ...defaultExploreURLState, + relationshipTarget: 'domain:example.com' + } as unknown as ExploreURLState)); + expect(validDomain.relationshipTarget).toBe('domain:example.com'); + }); +}); + +describe('ExploreState history ownership', () => { + it('uses remembered mode only when the URL has no explicit mode and remembers user changes', () => { + const values = new Map([[SEARCH_MODE_PREFERENCE_KEY, 'semantic']]); + const storage = { + getItem: (key: string) => values.get(key) ?? null, + setItem: (key: string, value: string) => values.set(key, value) + }; + window.history.replaceState(null, '', '/'); + const remembered = new ExploreState(window, storage); + expect(remembered.current.searchMode).toBe('semantic'); + remembered.replaceSearchDraft('', 'hybrid'); + expect(values.get(SEARCH_MODE_PREFERENCE_KEY)).toBe('hybrid'); + remembered.destroy(); + + window.history.replaceState(null, '', serializeExploreURLState({ + ...defaultExploreURLState, searchMode: 'full_text' + })); + const explicit = new ExploreState(window, storage); + expect(explicit.current.searchMode).toBe('full_text'); + explicit.destroy(); + }); + + it('advances restoration epochs only for initial URL ownership and popstate', () => { + window.history.replaceState(null, '', '/'); + const state = new ExploreState(window); + const initialEpoch = state.restorationEpoch; + expect(state.peekRestorationEpoch()).toBe(initialEpoch); + + state.replaceTransient({ columns: ['kind', 'title'] }); + expect(state.peekRestorationEpoch()).toBe(initialEpoch); + state.replaceTransient({ query: 'draft' }); + expect(state.peekRestorationEpoch()).toBeUndefined(); + state.commitSearch('committed', 'full_text'); + expect(state.restorationEpoch).toBe(initialEpoch); + + window.dispatchEvent(new PopStateEvent('popstate')); + expect(state.restorationEpoch).toBe(initialEpoch + 1); + expect(state.peekRestorationEpoch()).toBe(initialEpoch + 1); + state.acknowledgeRestoration(initialEpoch); + expect(state.peekRestorationEpoch()).toBe(initialEpoch + 1); + state.acknowledgeRestoration(initialEpoch + 1); + expect(state.peekRestorationEpoch()).toBeUndefined(); + state.destroy(); + }); + + it('can explicitly arm restoration for programmatic cross-workspace navigation', () => { + window.history.replaceState(null, '', '/'); + const state = new ExploreState(window); + state.acknowledgeRestoration(state.restorationEpoch); + + state.commitRestorableNavigation({ workspace: 'everything', selectedRow: 'message:901' }); + + expect(state.current.workspace).toBe('everything'); + expect(state.current.selectedRow).toBe('message:901'); + expect(state.peekRestorationEpoch()).toBe(state.restorationEpoch); + state.destroy(); + }); + + it('omits the search pair from the server predicate until a query exists', () => { + window.history.replaceState(null, '', '/'); + const state = new ExploreState(window); + + expect(state.predicate()).not.toHaveProperty('query'); + expect(state.predicate()).not.toHaveProperty('search_mode'); + state.replaceTransient({ query: 'roadmap' }); + expect(state.predicate()).toMatchObject({ query: 'roadmap', search_mode: 'full_text' }); + state.destroy(); + }); + + it('keeps transient typing and resizing in the current entry while committing navigation state', () => { + window.history.replaceState(null, '', '/?'); + const state = new ExploreState(window); + + state.replaceTransient({ query: 'quarter', columnWidths: { title: 420 } }); + state.commitSearch('quarter', 'semantic'); + state.commitNavigation({ groupingChain: ['source'], selectedRow: 'message:2' }); + + expect(state.current.searchMode).toBe('semantic'); + expect(state.current.groupingChain).toEqual(['source']); + expect(state.current.selectedRow).toBe('message:2'); + expect(parseExploreURLState(window.location.search)).toMatchObject({ + query: 'quarter', + searchMode: 'semantic', + groupingChain: ['source'], + selectedRow: 'message:2' + }); + state.destroy(); + }); + + it('replaces committed navigation without creating another history entry', async () => { + window.history.replaceState(null, '', '/?feature=preview'); + const state = new ExploreState(window); + state.commitNavigation({ selectedRow: 'message:1', conversationAnchor: '1' }); + const preReplaceLength = window.history.length; + + const replace = (state as ExploreState & { + replaceCommittedNavigation?: (patch: Partial<ExploreURLState>) => void + }).replaceCommittedNavigation; + if (!replace) { + state.destroy(); + throw new Error('replaceCommittedNavigation is missing'); + } + replace.call(state, { conversationAnchor: '2' }); + + expect(window.history.length).toBe(preReplaceLength); + expect(state.current.conversationAnchor).toBe('2'); + expect(parseExploreURLState(window.location.search).conversationAnchor).toBe('2'); + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + expect(state.current.conversationAnchor).toBeNull(); + state.destroy(); + }); + + it('commits a filename draft by replacement so the next push retains a byte-identical prior entry', async () => { + window.history.replaceState(null, '', '/?feature=files'); + const state = new ExploreState(window); + state.commitWorkspace('files'); + const beforeTypingLength = window.history.length; + const replace = (state as ExploreState & { + replaceCommittedDraft?: (patch: Partial<ExploreURLState>) => void + }).replaceCommittedDraft; + if (!replace) { + state.destroy(); + throw new Error('replaceCommittedDraft is missing'); + } + + replace.call(state, { + fileFilenameQuery: 'inv', activeRow: null, selectedRow: null, scrollAnchor: null + }); + replace.call(state, { + fileFilenameQuery: 'invoice', activeRow: null, selectedRow: null, scrollAnchor: null + }); + const filteredURL = `${window.location.pathname}${window.location.search}${window.location.hash}`; + expect(window.history.length).toBe(beforeTypingLength); + state.commitNavigation({ selectedRow: 'file:9' }); + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + + expect(`${window.location.pathname}${window.location.search}${window.location.hash}`).toBe(filteredURL); + expect(state.current.fileFilenameQuery).toBe('invoice'); + expect(state.current.selectedRow).toBeNull(); + state.destroy(); + }); + + it('preserves search prior-focus restoration across committed filename draft replacement', async () => { + window.history.replaceState(null, '', '/?feature=files'); + const state = new ExploreState(window); + state.commitWorkspace('files'); + state.replaceCommittedNavigation({ query: 'committed', searchMode: 'full_text' }); + state.replaceTransient({ activeRow: 'file:4' }); + state.replaceSearchDraft('draft', 'full_text'); + state.replaceCommittedDraft({ fileFilenameQuery: 'invoice' }); + state.commitSearch('draft', 'full_text'); + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + + expect(state.current.fileFilenameQuery).toBe('invoice'); + expect(state.current.activeRow).toBe('file:4'); + expect(state.current.query).toBe('committed'); + state.destroy(); + }); + + it('clears conversation authority when selection or search context changes', () => { + window.history.replaceState(null, '', '/'); + const state = new ExploreState(window); + state.commitNavigation({ selectedRow: 'message:1', conversationAnchor: '1' }); + state.commitNavigation({ selectedRow: 'message:2' }); + expect(state.current.conversationAnchor).toBeNull(); + state.commitNavigation({ conversationAnchor: '2' }); + state.commitNavigation({ selectedRow: null }); + expect(state.current.conversationAnchor).toBeNull(); + state.commitNavigation({ selectedRow: 'message:3', conversationAnchor: '3' }); + state.commitSearch('new context', 'full_text'); + expect(state.current.conversationAnchor).toBeNull(); + state.destroy(); + }); + + it('keeps ordered nested groups distinct from filters and semantic search', () => { + window.history.replaceState(null, '', '/'); + const state = new ExploreState(window); + + state.commitGrouping('participant'); + state.commitGrouping('year'); + state.commitSearch('pasta', 'semantic'); + state.commitNavigation({ filters: [{ dimension: 'domain', values: ['example.com'] }] }); + + expect(state.current).toMatchObject({ + query: 'pasta', + searchMode: 'semantic', + groupingChain: ['participant', 'year'], + filters: [{ dimension: 'domain', values: ['example.com'] }] + }); + expect(state.predicate()).toMatchObject({ + query: 'pasta', + search_mode: 'semantic', + grouping: ['participant', 'year'], + filters: [{ dimension: 'domain', values: ['example.com'] }] + }); + expect(parseExploreURLState(window.location.search)).toMatchObject({ + groupingChain: ['participant', 'year'], + filters: [{ dimension: 'domain', values: ['example.com'] }] + }); + state.destroy(); + }); + + it('appends domain to type and source to time in catalog order without duplicates', () => { + window.history.replaceState(null, '', '/'); + const state = new ExploreState(window); + + state.commitGrouping('domain'); + state.commitGrouping('message_type'); + state.commitGrouping('domain'); + expect(state.current.groupingChain).toEqual(['domain', 'message_type']); + + state.commitGrouping('kind'); + expect(state.current.groupingChain).toEqual(['domain', 'message_type']); + + state.commitNavigation({ groupingChain: [] }); + state.commitGrouping('source'); + state.commitGrouping('month'); + expect(state.current.groupingChain).toEqual(['source', 'month']); + state.destroy(); + }); + + it('uses a history entry for ungroup so browser Back restores the nested chain', async () => { + window.history.replaceState(null, '', '/'); + const state = new ExploreState(window); + state.commitNavigation({ groupingChain: ['participant', 'year'] }); + + state.commitUngroup(); + expect(state.current.groupingChain).toEqual(['participant']); + + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + expect(state.current.groupingChain).toEqual(['participant', 'year']); + state.destroy(); + }); + + it('restores row, scroll, inspector, grouping, and mode on popstate', () => { + const state = new ExploreState(window); + const restored: ExploreURLState = { + ...defaultExploreURLState, + selectedRow: 'conversation:9', + scrollAnchor: { key: 'conversation:7', offset: 8 }, + inspectorPinned: true, + inspectorWidth: 512, + groupingChain: ['source', 'month'], + searchMode: 'hybrid', + activeRow: 'conversation:8' + }; + window.history.replaceState(null, '', serializeExploreURLState(restored)); + + window.dispatchEvent(new PopStateEvent('popstate')); + + expect(state.current).toMatchObject(restored); + state.destroy(); + }); + + it('restores the previous committed search after transient typing is committed', async () => { + window.history.replaceState(null, '', '/?feature=preview'); + const state = new ExploreState(window); + state.commitSearch('alpha', 'full_text'); + state.replaceTransient({ query: 'beta draft' }); + state.commitSearch('beta', 'semantic'); + + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + + expect(state.current.query).toBe('alpha'); + expect(state.current.searchMode).toBe('full_text'); + expect(new URLSearchParams(window.location.search).get('feature')).toBe('preview'); + state.destroy(); + }); + + it('merges the latest transient layout and focus into the prior committed history entry', async () => { + window.history.replaceState(null, '', '/?feature=preview'); + const state = new ExploreState(window); + state.commitSearch('alpha', 'full_text'); + state.replaceTransient({ + columns: ['kind', 'title', 'time'], + columnWidths: { title: 432 }, + activeRow: 'message:4888', + scrollAnchor: { key: 'message:4880', offset: 17 } + }); + state.replaceSearchDraft('beta draft', 'semantic'); + expect(state.current.activeRow).toBeNull(); + expect(state.current.scrollAnchor).toBeNull(); + state.commitSearch('beta', 'semantic'); + + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + + expect(state.current).toMatchObject({ + query: 'alpha', + searchMode: 'full_text', + columns: ['kind', 'title', 'time'], + columnWidths: { title: 432 }, + activeRow: 'message:4888', + scrollAnchor: { key: 'message:4880', offset: 17 } + }); + state.destroy(); + }); + + it.each([ + ['opening a row', (state: ExploreState) => state.commitNavigation({ selectedRow: 'message:beta' })], + ['drilling a group', (state: ExploreState) => state.commitNavigation({ + filters: [{ dimension: 'source', values: ['7'] }], + groupingChain: [], + selectedRow: 'group:source:7' + })], + ['switching workspace', (state: ExploreState) => state.commitWorkspace('settings')] + ] as const)('applies a pending search-draft snapshot when %s is the next push', async (_name, push) => { + window.history.replaceState(null, '', '/?feature=preview'); + const state = new ExploreState(window); + state.commitSearch('alpha', 'full_text'); + state.replaceTransient({ + columns: ['kind', 'title', 'time'], + columnWidths: { title: 432 }, + activeRow: 'message:4888', + scrollAnchor: { key: 'message:4880', offset: 17 } + }); + state.replaceSearchDraft('beta draft', 'semantic'); + + push(state); + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + + expect(state.current).toMatchObject({ + workspace: 'relationships', + query: 'alpha', + searchMode: 'full_text', + columns: ['kind', 'title', 'time'], + columnWidths: { title: 432 }, + activeRow: 'message:4888', + scrollAnchor: { key: 'message:4880', offset: 17 } + }); + state.destroy(); + }); + + it('consumes the draft snapshot once while retaining each later entry focus', async () => { + window.history.replaceState(null, '', '/'); + const state = new ExploreState(window); + state.commitSearch('alpha', 'full_text'); + state.replaceTransient({ + columns: ['kind', 'title', 'time'], + activeRow: 'message:4888', + scrollAnchor: { key: 'message:4880', offset: 17 } + }); + state.replaceSearchDraft('beta draft', 'semantic'); + state.commitNavigation({ selectedRow: 'message:beta' }); + state.replaceTransient({ + activeRow: 'message:beta', + scrollAnchor: { key: 'message:beta', offset: 9 } + }); + state.commitWorkspace('settings'); + + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + expect(state.current).toMatchObject({ + query: 'beta draft', + activeRow: 'message:beta', + scrollAnchor: { key: 'message:beta', offset: 9 } + }); + + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + expect(state.current).toMatchObject({ + query: 'alpha', + activeRow: 'message:4888', + scrollAnchor: { key: 'message:4880', offset: 17 } + }); + state.destroy(); + }); + + it('keeps the original snapshot through draft replacement and cancels it on popstate', async () => { + window.history.replaceState(null, '', '/'); + const state = new ExploreState(window); + state.commitSearch('alpha', 'full_text'); + state.replaceTransient({ + activeRow: 'message:alpha', + scrollAnchor: { key: 'message:alpha', offset: 4 } + }); + state.replaceSearchDraft('beta draft', 'semantic'); + state.replaceTransient({ + activeRow: 'message:beta', + scrollAnchor: { key: 'message:beta', offset: 8 } + }); + state.replaceSearchDraft('gamma draft', 'hybrid'); + state.commitNavigation({ selectedRow: 'message:gamma' }); + + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + expect(state.current).toMatchObject({ + query: 'alpha', + activeRow: 'message:alpha', + scrollAnchor: { key: 'message:alpha', offset: 4 } + }); + + const replacement = { + ...defaultExploreURLState, + query: 'replacement', + activeRow: 'message:replacement', + scrollAnchor: { key: 'message:replacement', offset: 12 } + }; + window.history.replaceState(null, '', serializeExploreURLState(replacement)); + window.dispatchEvent(new PopStateEvent('popstate')); + state.commitNavigation({ selectedRow: 'message:next' }); + window.history.back(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + expect(state.current).toMatchObject(replacement); + state.destroy(); + }); +}); + +describe('all-matching result authority', () => { + const baseResult = { + rows: [], + totalCount: 8, + cacheRevision: 'cache-3', + searchProvenance: {}, + candidatePoolSaturated: false + }; + const authorityCases: Array<[ + ExplorePredicate, + SearchProvenance, + string | undefined, + boolean + ]> = [ + [{ filters: [], presentation: 'table' }, {}, undefined, true], + [{ query: 'alpha', search_mode: 'full_text' }, { lexical_index_revision: 'fts-2' }, undefined, true], + [{ query: 'alpha', search_mode: 'semantic' }, { vector_generation: 4 }, 'snapshot-2', true], + [{ query: 'alpha', search_mode: 'hybrid' }, { lexical_index_revision: 'fts-2', vector_generation: 4 }, 'snapshot-2', true], + [{ query: 'alpha', search_mode: 'full_text' }, {}, undefined, false], + [{ query: 'alpha', search_mode: 'semantic' }, { lexical_index_revision: 'fts-2' }, 'snapshot-2', false], + [{ query: 'alpha', search_mode: 'hybrid' }, { vector_generation: 4 }, 'snapshot-2', false] + ]; + + it.each(authorityCases)('validates mode-specific search provenance for %o', (predicate, provenance, snapshot, valid) => { + const selection = createAllMatchingSelection( + predicate, + { ...baseResult, searchProvenance: provenance, ...(snapshot ? { candidateSnapshotId: snapshot } : {}) }, + 7 + ); + + expect(Boolean(selection)).toBe(valid); + if (selection) { + expect(selection.predicateFingerprint).toBe(predicateFingerprint(predicate)); + expect(selection.resultGeneration).toBe(7); + } + }); + + it('does not mint exact all-matching authority from a capped candidate pool', () => { + expect(createAllMatchingSelection( + { query: 'alpha', search_mode: 'full_text' }, + { ...baseResult, candidatePoolSaturated: true, searchProvenance: { lexical_index_revision: 'fts-2' } }, + 7 + )).toBeUndefined(); + }); +}); + +describe('session-owned exploration selection', () => { + it('supports explicit toggle and inclusive shift range by stable row key', () => { + const selection = new ExploreSelectionState(); + const keys = ['message:1', 'message:2', 'message:3', 'message:4']; + + selection.toggle(keys[0]!, 0, keys); + selection.toggle(keys[3]!, 3, keys, true); + + expect(selection.selectedKeys(keys)).toEqual(keys); + }); + + it('anchors a visible selection range to its first stable key after reorder', () => { + const selection = new ExploreSelectionState(); + selection.selectVisible(['message:2', 'message:3']); + + selection.toggle('message:4', 2, ['message:1', 'message:2', 'message:4', 'message:3'], true); + + expect(selection.selectedKeys(['message:1', 'message:2', 'message:3', 'message:4'])) + .toEqual(['message:2', 'message:3', 'message:4']); + }); + + it('retains the complete all-matching predicate, exclusions, and revisions', () => { + const allMatching: ExploreSelection = { + mode: 'all_matching', + predicate: { + query: 'roadmap', + search_mode: 'hybrid', + filters: [{ dimension: 'source', values: ['7'] }], + grouping: ['participant'], + presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }] + }, + exclusions: [], + cacheRevision: 'cache-12', + searchProvenance: { lexical_index_revision: 'fts-4', vector_generation: 8 }, + candidateSnapshotId: 'snapshot-3', + predicateFingerprint: predicateFingerprint({ + query: 'roadmap', + search_mode: 'hybrid', + filters: [{ dimension: 'source', values: ['7'] }], + grouping: ['participant'], + presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }] + }), + resultGeneration: 3 + }; + const selection = new ExploreSelectionState(); + + selection.selectAllMatching(allMatching); + selection.toggle('message:9', 0, ['message:9']); + + expect(selection.snapshot()).toEqual({ ...allMatching, exclusions: ['message:9'] }); + expect(window.location.search).not.toContain('selection'); + }); + + it('requires a server candidate snapshot for semantic all-matching selection', () => { + const selection = new ExploreSelectionState(); + + expect(() => + selection.selectAllMatching({ + mode: 'all_matching', + predicate: { query: 'roadmap', search_mode: 'semantic' }, + exclusions: [], + cacheRevision: 'cache-12', + searchProvenance: { vector_generation: 8 }, + predicateFingerprint: predicateFingerprint({ query: 'roadmap', search_mode: 'semantic' }), + resultGeneration: 3 + }) + ).toThrow('candidate snapshot'); + }); +}); diff --git a/web/src/lib/grouping/catalog.test.ts b/web/src/lib/grouping/catalog.test.ts new file mode 100644 index 000000000..257e8f1b0 --- /dev/null +++ b/web/src/lib/grouping/catalog.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from 'vitest'; + +import { + GROUPING_CATALOG, + groupingByDimension, + groupingOptions, + isGroupingDimension, + validateGroupingChain +} from './catalog'; + +describe('universal grouping catalog', () => { + it('is the ordered source of truth for every canonical analytical group', () => { + expect(GROUPING_CATALOG.map((entry) => entry.concept)).toEqual([ + 'people', + 'identifiers', + 'domains', + 'time', + 'source', + 'modality', + 'labels', + 'attachment_facts', + 'conversation' + ]); + expect(groupingOptions().map((entry) => entry.value)).toEqual( + ['participant', 'domain', 'year', 'month', 'source', 'message_type'] + ); + expect(groupingByDimension('participant')).toMatchObject({ + label: 'People', + family: 'people' + }); + expect(groupingByDimension('year')).toMatchObject({ family: 'time' }); + }); + + it('keeps unsupported concepts visible but incapable of forming API requests', () => { + for (const concept of ['identifiers', 'labels', 'attachment_facts', 'conversation'] as const) { + const entry = GROUPING_CATALOG.find((candidate) => candidate.concept === concept); + expect(entry).toMatchObject({ requestable: false }); + expect(entry?.unavailableReason).toBeTruthy(); + expect(entry?.requestDimensions).toEqual([]); + } + expect(groupingOptions({ includeUnavailable: true })).toEqual(expect.arrayContaining([ + expect.objectContaining({ + value: 'unavailable:labels', + label: expect.stringContaining('Label grouping is not in the analytical API yet.'), + disabled: true + }), + expect.objectContaining({ value: 'unavailable:attachment_facts', disabled: true }) + ])); + expect(isGroupingDimension('kind')).toBe(false); + }); + + it('validates URL and Saved View grouping without accepting unknown dimensions', () => { + expect(isGroupingDimension('source')).toBe(true); + expect(isGroupingDimension('labels')).toBe(false); + expect(validateGroupingChain(['participant', 'year'])).toEqual(['participant', 'year']); + expect(validateGroupingChain(['domain', 'message_type'])).toEqual(['domain', 'message_type']); + expect(validateGroupingChain(['source', 'month'])).toEqual(['source', 'month']); + expect(validateGroupingChain(['source', 'kind'])).toEqual([]); + }); +}); diff --git a/web/src/lib/grouping/catalog.ts b/web/src/lib/grouping/catalog.ts new file mode 100644 index 000000000..a5dd086db --- /dev/null +++ b/web/src/lib/grouping/catalog.ts @@ -0,0 +1,161 @@ +import type { SelectDropdownOption } from '@kenn-io/kit-ui'; + +import type { ExploreGroupDimension } from '../explore/models'; + +export type GroupingConcept = + | 'people' + | 'identifiers' + | 'domains' + | 'time' + | 'source' + | 'modality' + | 'labels' + | 'attachment_facts' + | 'conversation'; + +export type GroupingFamily = GroupingConcept; + +export interface GroupingCatalogEntry { + concept: GroupingConcept; + label: string; + family: GroupingFamily; + keywords: string; + requestable: boolean; + requestDimensions: readonly ExploreGroupDimension[]; + unavailableReason?: string; +} + +/** + * One product-level grouping catalog for controls, commands, URL validation, + * and Saved Views. Unsupported concepts stay visible and explicit, but have no + * request dimension and therefore can never leak an invalid API value. + */ +export const GROUPING_CATALOG: readonly GroupingCatalogEntry[] = [ + { + concept: 'people', + label: 'People', + family: 'people', + keywords: 'person sender recipient email phone identity', + requestable: true, + requestDimensions: ['participant'] + }, + { + concept: 'identifiers', + label: 'Identifiers', + family: 'identifiers', + keywords: 'email phone address account identity', + requestable: false, + requestDimensions: [], + unavailableReason: 'Identifier-level grouping is not in the analytical API yet.' + }, + { + concept: 'domains', + label: 'Domains', + family: 'domains', + keywords: 'organization host email domain', + requestable: true, + requestDimensions: ['domain'] + }, + { + concept: 'time', + label: 'Time', + family: 'time', + keywords: 'year month date annual monthly', + requestable: true, + requestDimensions: ['year', 'month'] + }, + { + concept: 'source', + label: 'Source', + family: 'source', + keywords: 'account archive provider', + requestable: true, + requestDimensions: ['source'] + }, + { + concept: 'modality', + label: 'Modality', + family: 'modality', + keywords: 'type email chat text calendar meeting note', + requestable: true, + requestDimensions: ['message_type'] + }, + { + concept: 'labels', + label: 'Labels', + family: 'labels', + keywords: 'tag category mailbox', + requestable: false, + requestDimensions: [], + unavailableReason: 'Label grouping is not in the analytical API yet.' + }, + { + concept: 'attachment_facts', + label: 'Attachment facts', + family: 'attachment_facts', + keywords: 'file attachment size type extension', + requestable: false, + requestDimensions: [], + unavailableReason: 'Attachment-fact grouping is reserved for the Files workspace.' + }, + { + concept: 'conversation', + label: 'Conversation', + family: 'conversation', + keywords: 'thread chat logical entry', + requestable: false, + requestDimensions: [], + unavailableReason: 'Conversation grouping is not filterable in the analytical API yet.' + } +] as const; + +const requestableDimensions = new Set<ExploreGroupDimension>( + GROUPING_CATALOG.flatMap((entry) => entry.requestDimensions) +); + +export function isGroupingDimension(value: unknown): value is ExploreGroupDimension { + return typeof value === 'string' && requestableDimensions.has(value as ExploreGroupDimension); +} + +export function validateGroupingChain(value: unknown): ExploreGroupDimension[] { + return Array.isArray(value) && value.every(isGroupingDimension) + ? [...value] + : []; +} + +export function groupingByDimension( + dimension: ExploreGroupDimension +): GroupingCatalogEntry { + return GROUPING_CATALOG.find((entry) => entry.requestDimensions.includes(dimension))!; +} + +export function groupingDimensionLabel(dimension: ExploreGroupDimension): string { + const entry = groupingByDimension(dimension); + if (entry.concept !== 'time') return entry.label; + return dimension === 'year' ? 'Year' : 'Month'; +} + +export function groupingOptions( + options: { + excluded?: readonly ExploreGroupDimension[]; + includeUnavailable?: boolean; + } = {} +): SelectDropdownOption[] { + const excluded = new Set(options.excluded ?? []); + const result: SelectDropdownOption[] = []; + for (const entry of GROUPING_CATALOG) { + for (const dimension of entry.requestDimensions) { + if (!excluded.has(dimension)) { + result.push({ value: dimension, label: groupingDimensionLabel(dimension) }); + } + } + if (options.includeUnavailable && !entry.requestable) { + result.push({ + value: `unavailable:${entry.concept}`, + label: `${entry.label} — unavailable: ${entry.unavailableReason}`, + disabled: true + }); + } + } + return result; +} diff --git a/web/src/lib/relationships/controller.svelte.test.ts b/web/src/lib/relationships/controller.svelte.test.ts new file mode 100644 index 000000000..01ecaadd5 --- /dev/null +++ b/web/src/lib/relationships/controller.svelte.test.ts @@ -0,0 +1,635 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { createAPIClient } from '../api/client'; +import type { DomainSummary, PersonSummary } from '../explore/models'; +import { RelationshipsController } from './controller.svelte'; +import type { RelationshipRow, RelationshipTimelineRow } from './models'; + +const when = '2026-07-19T10:00:00Z'; + +function relationshipRow(id: number, label: string): RelationshipRow { + return { + canonical_id: id, + display_label: label, + last_at: when, + member_ids: [id], + score: 1, + signals: { + last_interaction_at: when, + meeting_count: 0, + meetings_together: 0, + modalities: 1, + received_from_them: 1, + sent_count: 1, + sent_to_them: 1 + } + }; +} + +function person(id: number): PersonSummary { + return { + id, + display_label: `Person ${id}`, + partial_label: false, + identifiers: [], + activity_count: 1, + file_count: 0, + source_counts: [], + first_at: when, + last_at: when, + cache_revision: 'cache-rel' + }; +} + +function domainSummary(domain: string): DomainSummary { + return { + domain, + activity_count: 3, + file_count: 1, + person_count: 2, + first_at: when, + last_at: when, + source_counts: [], + cache_revision: 'cache-rel' + }; +} + +function timelineRow(key: string): RelationshipTimelineRow { + return { + key, + kind: 'email', + occurred_at: when, + preview: 'preview', + source_id: 1, + title: key, + has_attachments: false, + message_count: 1 + }; +} + +function pathOf(request: Request): string { + return new URL(request.url, document.baseURI).pathname; +} + +describe('RelationshipsController.loadList', () => { + it('ranks via /api/v1/relationships when the people facet has an empty query, preserving server order', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (pathOf(request) === '/api/v1/relationships') { + return Response.json({ + rows: [relationshipRow(2, 'Bob'), relationshipRow(1, 'Alice')], + total_count: 2, + cache_revision: 'cache-rel', + identity_revision: 1 + }); + } + throw new Error(`unexpected path ${pathOf(request)}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + controller.showAll = true; + + await controller.loadList({ filters: [{ dimension: 'source', values: ['1'] }], presentation: 'table' }); + + expect(controller.listRows).toEqual([relationshipRow(2, 'Bob'), relationshipRow(1, 'Alice')]); + expect(controller.listLoading).toBe(false); + expect(controller.listError).toBeNull(); + await expect(requests[0]!.clone().json()).resolves.toEqual({ + show_all: true, + limit: 200, + filters: [{ dimension: 'source', values: ['1'] }] + }); + }); + + it('searches via /api/v1/people/search once the query is non-empty, finding any person ranked or not', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (pathOf(request) === '/api/v1/people/search') { + return Response.json({ rows: [person(11)], total_count: 1, cache_revision: 'cache-rel', search_provenance: {} }); + } + throw new Error(`unexpected path ${pathOf(request)}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + controller.query = 'ali'; + + await controller.loadList({ filters: [], presentation: 'table' }); + + expect(controller.listRows).toEqual([person(11)]); + await expect(requests[0]!.clone().json()).resolves.toMatchObject({ identity_query: 'ali' }); + }); + + it('always searches domains via /api/v1/domains/search, even with an empty query', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + if (pathOf(request) === '/api/v1/domains/search') { + return Response.json({ + rows: [domainSummary('example.com')], total_count: 1, cache_revision: 'cache-rel', search_provenance: {} + }); + } + throw new Error(`unexpected path ${pathOf(request)}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + controller.facet = 'domains'; + + await controller.loadList({ filters: [], presentation: 'table' }); + + expect(controller.listRows).toEqual([domainSummary('example.com')]); + expect(requests).toHaveLength(1); + await expect(requests[0]!.clone().json()).resolves.toMatchObject({ identity_query: '' }); + }); + + it('names cache unavailability as a degraded state instead of throwing', async () => { + const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ + error: 'analytical_cache_unavailable', message: 'The committed analytical cache is unavailable', + readiness: 'stale_schema', recovery_action: 'Rebuild the analytical cache' + }, { status: 503 })); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + + await expect(controller.loadList({ filters: [], presentation: 'table' })).resolves.toBeUndefined(); + + expect(controller.degraded).toBe('cache_unavailable'); + expect(controller.listError).toBeNull(); + expect(controller.listLoading).toBe(false); + }); +}); + +describe('RelationshipsController.openTarget', () => { + it("opens a cluster target's person header and cluster timeline, storing canonical_id + identity_revision", async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const path = pathOf(request); + if (path === '/api/v1/people/12' && request.method === 'GET') return Response.json(person(12)); + if (path === '/api/v1/relationships/12/timeline') { + return Response.json({ + canonical_id: 12, identity_revision: 3, rows: [timelineRow('t1')], total_count: 1, cache_revision: 'cache-rel' + }); + } + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'America/New_York'); + + await controller.openTarget('cluster:12', { + filters: [{ dimension: 'source', values: ['1'] }], presentation: 'table' + }); + + expect(controller.detail).toEqual(person(12)); + expect(controller.timelineRows).toEqual([timelineRow('t1')]); + expect(controller.canonicalID).toBe(12); + expect(controller.identityRevision).toBe(3); + expect(controller.timelineLoading).toBe(false); + const timelineRequest = requests.find((request) => pathOf(request) === '/api/v1/relationships/12/timeline'); + await expect(timelineRequest!.clone().json()).resolves.toMatchObject({ + timezone: 'America/New_York', filters: [{ dimension: 'source', values: ['1'] }], limit: 200 + }); + }); + + it('opens a domain target through the existing domain detail + domain timeline endpoints, not a cluster timeline', async () => { + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const path = pathOf(request); + if (path === '/api/v1/domains/example.com' && request.method === 'GET') return Response.json(domainSummary('example.com')); + if (path === '/api/v1/domains/example.com/timeline') { + return Response.json({ rows: [timelineRow('t1')], total_count: 1, cache_revision: 'cache-rel', search_provenance: {} }); + } + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + + await controller.openTarget('domain:example.com', { filters: [], presentation: 'table' }); + + expect(controller.detail).toEqual(domainSummary('example.com')); + expect(controller.timelineRows).toEqual([timelineRow('t1')]); + expect(controller.canonicalID).toBeNull(); + expect(controller.identityRevision).toBeNull(); + expect(requests.some((request) => pathOf(request).includes('/relationships/'))).toBe(false); + }); + + it('dispatches on the target prefix, not the facet, when the two disagree', async () => { + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = pathOf(request); + if (path === '/api/v1/domains/example.com' && request.method === 'GET') return Response.json(domainSummary('example.com')); + if (path === '/api/v1/domains/example.com/timeline') { + return Response.json({ rows: [], total_count: 0, cache_revision: 'cache-rel', search_provenance: {} }); + } + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + controller.facet = 'people'; + + await controller.openTarget('domain:example.com', { filters: [], presentation: 'table' }); + + expect(controller.detail).toEqual(domainSummary('example.com')); + }); + + it('discards a stale openTarget response that resolves after a newer openTarget call', async () => { + let resolveFirstPerson: ((response: Response) => void) | undefined; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = pathOf(request); + if (path === '/api/v1/people/1' && request.method === 'GET') { + return new Promise<Response>((resolve) => { resolveFirstPerson = resolve; }); + } + if (path === '/api/v1/relationships/1/timeline') { + return Response.json({ canonical_id: 1, identity_revision: 1, rows: [], total_count: 0, cache_revision: 'cache-rel' }); + } + if (path === '/api/v1/people/2' && request.method === 'GET') return Response.json(person(2)); + if (path === '/api/v1/relationships/2/timeline') { + return Response.json({ + canonical_id: 2, identity_revision: 5, rows: [timelineRow('t2')], total_count: 1, cache_revision: 'cache-rel' + }); + } + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + + const firstOpen = controller.openTarget('cluster:1', { filters: [], presentation: 'table' }); + await controller.openTarget('cluster:2', { filters: [], presentation: 'table' }); + + expect(controller.detail).toEqual(person(2)); + expect(controller.canonicalID).toBe(2); + + resolveFirstPerson?.(Response.json(person(1))); + await firstOpen; + + expect(controller.detail).toEqual(person(2)); + expect(controller.canonicalID).toBe(2); + expect(controller.timelineRows).toEqual([timelineRow('t2')]); + }); +}); + +describe('RelationshipsController.loadMoreTimeline', () => { + it('sends the stored cursor and restarts from page 1 exactly once on a 409 cursor_invalidated', async () => { + let timelineCalls = 0; + const requests: Request[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + requests.push(request); + const path = pathOf(request); + if (path === '/api/v1/people/1' && request.method === 'GET') return Response.json(person(1)); + if (path === '/api/v1/relationships/1/timeline') { + timelineCalls += 1; + const body = (await request.clone().json()) as { cursor?: string }; + if (body.cursor === undefined && timelineCalls === 1) { + return Response.json({ + canonical_id: 1, identity_revision: 1, rows: [timelineRow('t1')], total_count: 3, + cache_revision: 'cache-rel', next_cursor: 'page-2' + }); + } + if (body.cursor === 'page-2') { + return Response.json({ error: 'cursor_invalidated', message: 'The timeline context changed; restart pagination' }, { status: 409 }); + } + if (body.cursor === undefined && timelineCalls === 3) { + return Response.json({ + canonical_id: 1, identity_revision: 2, rows: [timelineRow('t1-restart')], total_count: 1, cache_revision: 'cache-rel-2' + }); + } + throw new Error(`unexpected timeline call ${timelineCalls} body ${JSON.stringify(body)}`); + } + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + await controller.openTarget('cluster:1', { filters: [], presentation: 'table' }); + expect(controller.timelineCursor).toBe('page-2'); + + await controller.loadMoreTimeline(); + + expect(timelineCalls).toBe(3); + expect(controller.timelineRows).toEqual([timelineRow('t1-restart')]); + expect(controller.timelineCursor).toBeNull(); + expect(controller.timelineLoadingMore).toBe(false); + expect(controller.identityRevision).toBe(2); + expect(controller.timelineRestartNotice).toBe('Timeline restarted: the archive changed.'); + }); + + it('clears a stale restart notice when a fresh openTarget navigates elsewhere', async () => { + let timelineCalls = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = pathOf(request); + if (path === '/api/v1/people/1' && request.method === 'GET') return Response.json(person(1)); + if (path === '/api/v1/people/2' && request.method === 'GET') return Response.json(person(2)); + if (path === '/api/v1/relationships/1/timeline') { + timelineCalls += 1; + const body = (await request.clone().json()) as { cursor?: string }; + if (body.cursor === undefined && timelineCalls === 1) { + return Response.json({ + canonical_id: 1, identity_revision: 1, rows: [timelineRow('t1')], total_count: 2, + cache_revision: 'cache-rel', next_cursor: 'page-2' + }); + } + if (body.cursor === 'page-2') { + return Response.json({ error: 'cursor_invalidated', message: 'restart pagination' }, { status: 409 }); + } + return Response.json({ + canonical_id: 1, identity_revision: 2, rows: [timelineRow('t1-restart')], total_count: 1, cache_revision: 'cache-rel-2' + }); + } + if (path === '/api/v1/relationships/2/timeline') { + return Response.json({ canonical_id: 2, identity_revision: 1, rows: [], total_count: 0, cache_revision: 'cache-rel' }); + } + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + await controller.openTarget('cluster:1', { filters: [], presentation: 'table' }); + await controller.loadMoreTimeline(); + expect(controller.timelineRestartNotice).toBe('Timeline restarted: the archive changed.'); + + await controller.openTarget('cluster:2', { filters: [], presentation: 'table' }); + expect(controller.timelineRestartNotice).toBeNull(); + }); + + it('resets timelineLoadingMore on every early return, so a failed page never wedges pagination', async () => { + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = pathOf(request); + if (path === '/api/v1/people/1' && request.method === 'GET') return Response.json(person(1)); + if (path === '/api/v1/relationships/1/timeline') { + const body = (await request.clone().json()) as { cursor?: string }; + if (body.cursor === undefined) { + return Response.json({ + canonical_id: 1, identity_revision: 1, rows: [timelineRow('t1')], total_count: 2, + cache_revision: 'cache-rel', next_cursor: 'page-2' + }); + } + return Response.json({ error: 'internal_error', message: 'boom' }, { status: 500 }); + } + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + await controller.openTarget('cluster:1', { filters: [], presentation: 'table' }); + expect(controller.timelineLoadingMore).toBe(false); + + // A second concurrent call while a page is in flight must return immediately without + // ever setting the flag itself (the guard at the top of loadMoreTimeline). + const first = controller.loadMoreTimeline(); + const second = controller.loadMoreTimeline(); + await Promise.all([first, second]); + + expect(controller.timelineLoadingMore).toBe(false); + expect(controller.timelineError).toBe('boom'); + expect(controller.timelineCursor).toBeNull(); + + // Pagination stopped (cursor cleared on failure): calling again is a guarded no-op, + // not a second in-flight request left stuck at loadingMore = true forever. + await controller.loadMoreTimeline(); + expect(controller.timelineLoadingMore).toBe(false); + }); +}); + +describe('RelationshipsController.linkParticipants / unlinkParticipants', () => { + it('maps 200 to ok with cacheState, and re-opens the target once identityRevision actually changed', async () => { + let personCalls = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = pathOf(request); + if (path === '/api/v1/people/1' && request.method === 'GET') { + personCalls += 1; + return Response.json(person(1)); + } + if (path === '/api/v1/relationships/1/timeline') { + return Response.json({ + canonical_id: 1, identity_revision: personCalls, rows: [], total_count: 0, cache_revision: 'cache-rel' + }); + } + if (path === '/api/v1/identity/links') return Response.json({ identity_revision: 2, cache_state: 'ready' }); + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + await controller.openTarget('cluster:1', { filters: [], presentation: 'table' }); + expect(controller.identityRevision).toBe(1); + + const outcome = await controller.linkParticipants(1, 2); + + expect(outcome).toEqual({ ok: true, identityRevision: 2, cacheState: 'ready' }); + expect(personCalls).toBe(2); + expect(controller.identityRevision).toBe(2); + }); + + it('does not re-open the target when identityRevision is unchanged', async () => { + let personCalls = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = pathOf(request); + if (path === '/api/v1/people/1' && request.method === 'GET') { + personCalls += 1; + return Response.json(person(1)); + } + if (path === '/api/v1/relationships/1/timeline') { + return Response.json({ canonical_id: 1, identity_revision: 1, rows: [], total_count: 0, cache_revision: 'cache-rel' }); + } + if (path === '/api/v1/identity/unlinks') return Response.json({ identity_revision: 1, cache_state: 'stale' }); + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + await controller.openTarget('cluster:1', { filters: [], presentation: 'table' }); + expect(personCalls).toBe(1); + + const outcome = await controller.unlinkParticipants(1, 3); + + expect(outcome).toEqual({ ok: true, identityRevision: 1, cacheState: 'stale' }); + expect(personCalls).toBe(1); + }); + + it('also reloads the ranked list after a successful link, using the last-loaded predicate', async () => { + let personCalls = 0; + let relationshipsCalls = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = pathOf(request); + if (path === '/api/v1/relationships') { + relationshipsCalls += 1; + return Response.json({ + rows: [relationshipRow(1, 'Alice')], total_count: 1, cache_revision: 'cache-rel', identity_revision: 1 + }); + } + if (path === '/api/v1/people/1' && request.method === 'GET') { + personCalls += 1; + return Response.json(person(1)); + } + if (path === '/api/v1/relationships/1/timeline') { + return Response.json({ + canonical_id: 1, identity_revision: personCalls, rows: [], total_count: 0, cache_revision: 'cache-rel' + }); + } + if (path === '/api/v1/identity/links') return Response.json({ identity_revision: 2, cache_state: 'ready' }); + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + await controller.loadList({ filters: [], presentation: 'table' }); + await controller.openTarget('cluster:1', { filters: [], presentation: 'table' }); + expect(relationshipsCalls).toBe(1); + + const outcome = await controller.linkParticipants(1, 2); + + expect(outcome).toEqual({ ok: true, identityRevision: 2, cacheState: 'ready' }); + expect(relationshipsCalls).toBe(2); + }); + + it('does not reload the list when none was ever loaded (no stray fetch)', async () => { + const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ identity_revision: 1, cache_state: 'ready' })); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + + const outcome = await controller.linkParticipants(1, 2); + + expect(outcome).toEqual({ ok: true, identityRevision: 1, cacheState: 'ready' }); + expect(fetchFn).toHaveBeenCalledTimes(1); + }); + + it('refreshes detail unconditionally when identityRevision was null after a failed timeline load', async () => { + let personCalls = 0; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = pathOf(request); + if (path === '/api/v1/people/1' && request.method === 'GET') { + personCalls += 1; + return Response.json(person(1)); + } + if (path === '/api/v1/relationships/1/timeline') { + return personCalls === 1 + ? Response.json({ error: 'internal_error', message: 'boom' }, { status: 500 }) + : Response.json({ + canonical_id: 1, identity_revision: 3, rows: [], total_count: 0, cache_revision: 'cache-rel' + }); + } + if (path === '/api/v1/identity/links') return Response.json({ identity_revision: 3, cache_state: 'ready' }); + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + await controller.openTarget('cluster:1', { filters: [], presentation: 'table' }); + expect(controller.identityRevision).toBeNull(); + expect(personCalls).toBe(1); + + const outcome = await controller.linkParticipants(1, 2); + + expect(outcome).toEqual({ ok: true, identityRevision: 3, cacheState: 'ready' }); + expect(personCalls).toBe(2); + expect(controller.identityRevision).toBe(3); + }); + + it('maps a 409 already_linked body to code already_linked', async () => { + const fetchFn = vi.fn<typeof fetch>(async () => Response.json( + { error: 'already_linked', message: 'these participants are already connected through other links' }, + { status: 409 } + )); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + + const outcome = await controller.linkParticipants(1, 2); + + expect(outcome).toEqual({ + ok: false, code: 'already_linked', message: 'these participants are already connected through other links' + }); + }); + + it('maps a 400 body to code invalid', async () => { + const fetchFn = vi.fn<typeof fetch>(async () => Response.json( + { error: 'invalid_participant_id', message: 'participant_a and participant_b must be distinct positive participant IDs' }, + { status: 400 } + )); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + + const outcome = await controller.unlinkParticipants(-1, 2); + + expect(outcome).toEqual({ + ok: false, code: 'invalid', message: 'participant_a and participant_b must be distinct positive participant IDs' + }); + }); + + it('maps any other failure (500, network error) to code error', async () => { + const fetchFn = vi.fn<typeof fetch>(async () => Response.json( + { error: 'internal_error', message: 'failed to update participant links' }, { status: 500 } + )); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + + const outcome = await controller.linkParticipants(1, 2); + + expect(outcome).toEqual({ ok: false, code: 'error', message: 'failed to update participant links' }); + }); +}); + +describe('RelationshipsController.clearTarget', () => { + it('resets target/detail/timeline state and discards a late in-flight detail response', async () => { + let resolveTimeline: ((response: Response) => void) | undefined; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const path = pathOf(request); + if (path === '/api/v1/people/1' && request.method === 'GET') return Response.json(person(1)); + if (path === '/api/v1/relationships/1/timeline') { + return new Promise<Response>((resolve) => { resolveTimeline = resolve; }); + } + throw new Error(`unexpected path ${path}`); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + const openPromise = controller.openTarget('cluster:1', { filters: [], presentation: 'table' }); + await Promise.resolve(); + + controller.clearTarget(); + + expect(controller.target).toBeNull(); + expect(controller.detail).toBeNull(); + expect(controller.canonicalID).toBeNull(); + expect(controller.identityRevision).toBeNull(); + + resolveTimeline?.(Response.json({ + canonical_id: 1, identity_revision: 1, rows: [timelineRow('t1')], total_count: 1, cache_revision: 'cache-rel' + })); + await openPromise; + + expect(controller.target).toBeNull(); + expect(controller.detail).toBeNull(); + expect(controller.timelineRows).toEqual([]); + }); +}); + +describe('RelationshipsController abort/destroy', () => { + it('destroy() aborts the in-flight list load and detail load AbortControllers', async () => { + const signals: AbortSignal[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + signals.push(request.signal); + return new Promise<Response>(() => { + // Never resolves; destroy() must abort it rather than leave it hanging forever. + }); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + + void controller.loadList({ filters: [], presentation: 'table' }); + void controller.openTarget('cluster:1', { filters: [], presentation: 'table' }); + await Promise.resolve(); + await Promise.resolve(); + + expect(signals.length).toBeGreaterThanOrEqual(2); + expect(signals.every((signal) => !signal.aborted)).toBe(true); + + controller.destroy(); + + expect(signals.every((signal) => signal.aborted)).toBe(true); + }); + + it('aborts the previous list load when a new one starts', async () => { + const signals: AbortSignal[] = []; + const fetchFn = vi.fn<typeof fetch>(async (input) => { + const request = input instanceof Request ? input : new Request(input); + signals.push(request.signal); + if (signals.length === 1) return new Promise<Response>(() => {}); + return Response.json({ rows: [], total_count: 0, cache_revision: 'cache-rel', identity_revision: 1 }); + }); + const controller = new RelationshipsController(createAPIClient(fetchFn), () => 'UTC'); + + void controller.loadList({ filters: [], presentation: 'table' }); + await Promise.resolve(); + await controller.loadList({ filters: [], presentation: 'table' }); + + expect(signals[0]!.aborted).toBe(true); + expect(signals[1]!.aborted).toBe(false); + }); +}); diff --git a/web/src/lib/relationships/controller.svelte.ts b/web/src/lib/relationships/controller.svelte.ts new file mode 100644 index 000000000..b11aa0876 --- /dev/null +++ b/web/src/lib/relationships/controller.svelte.ts @@ -0,0 +1,428 @@ +import type { APIClient } from '../api/client'; +import type { + DomainSummary, + ExploreCacheUnavailable, + ExploreFilter, + ExplorePredicate, + IdentitySearchSort, + PersonSummary +} from '../explore/models'; +import { predicateFingerprint } from '../explore/selection'; +import type { RelationshipFacet, RelationshipRow, RelationshipTimelineRow } from './models'; + +export type LinkOutcome = + | { ok: true; identityRevision: number; cacheState: 'ready' | 'stale' } + | { ok: false; code: 'already_linked' | 'invalid' | 'error'; message: string }; + +const RANKED_LIST_LIMIT = 200; +const SEARCH_LIST_LIMIT = 500; +const TIMELINE_PAGE_LIMIT = 200; +const DEFAULT_IDENTITY_SORT: IdentitySearchSort = { field: 'activity_count', direction: 'desc' }; +const REPEATED_CURSOR_MESSAGE = 'Pagination stopped because the server repeated a cursor without progress.'; +const TIMELINE_RESTART_MESSAGE = 'Timeline restarted: the archive changed.'; + +/** + * Data controller for the Relationships hub's left list (ranked/searched + * people or domains), center detail (cluster or domain header + timeline), + * and identity link/unlink mutations. + * + * Ports the proven mechanics from PeopleWorkspace.svelte/DomainWorkspace.svelte + * as plain class methods: a generation counter per independent load (list, + * detail) that discards stale async completions, an AbortController per load + * that `destroy()` sweeps, and a seen-cursors set that stops runaway + * pagination if the server ever repeats a cursor without progress. + * + * `openTarget` dispatches on the target's prefix (`cluster:` vs `domain:`), + * never on `facet` — a target string can legitimately disagree with the + * facet that produced it (e.g. restored from a URL), and the prefix is the + * only value that determines which endpoints to call. + */ +export class RelationshipsController { + facet = $state<RelationshipFacet>('people'); + query = $state(''); + showAll = $state(false); + listRows = $state<RelationshipRow[] | PersonSummary[] | DomainSummary[]>([]); + listLoading = $state(false); + listError = $state<string | null>(null); + degraded = $state<'cache_unavailable' | null>(null); + + target = $state<string | null>(null); + /** Fingerprint of the predicate `target` was last opened with — lets a + * caller (AppShell's URL-hydration effect) tell "this target is already + * open" apart from "this target is open, but for a stale predicate" (e.g. + * filters picked up from Everything/Files while the URL-carried target + * itself didn't change) without keeping its own shadow copy that could + * fall out of sync with a call made through a different path (the + * in-hub click handler calls openTarget directly). Set by openTarget, + * cleared by clearTarget. */ + lastPredicateFingerprint = $state<string | null>(null); + detail = $state<PersonSummary | DomainSummary | null>(null); + timelineRows = $state<RelationshipTimelineRow[]>([]); + timelineCursor = $state<string | null>(null); + timelineLoading = $state(false); + timelineLoadingMore = $state(false); + timelineError = $state<string | null>(null); + /** One-line notice set when a cursor_invalidated 409 silently restarted the + * timeline from page 1; cleared on the next openTarget navigation. */ + timelineRestartNotice = $state<string | null>(null); + canonicalID = $state<number | null>(null); + identityRevision = $state<number | null>(null); + + private readonly client: APIClient; + private readonly timezone: () => string; + private listAbort: AbortController | undefined; + private detailAbort: AbortController | undefined; + private listGeneration = 0; + private detailGeneration = 0; + private seenCursors = new Set<string>(); + private lastPredicate: ExplorePredicate | undefined; + private lastListPredicate: ExplorePredicate | undefined; + + constructor(client: APIClient, timezone: () => string) { + this.client = client; + this.timezone = timezone; + } + + async loadList(predicate: ExplorePredicate): Promise<void> { + this.listAbort?.abort(); + const controller = new AbortController(); + this.listAbort = controller; + const generation = ++this.listGeneration; + this.lastListPredicate = predicate; + this.listLoading = true; + this.listError = null; + this.degraded = null; + const context = contextPredicate(predicate); + const query = this.query.trim(); + try { + if (this.facet === 'domains') { + const response = await this.client.POST('/api/v1/domains/search', { + body: { predicate: context, identity_query: query, sort: DEFAULT_IDENTITY_SORT, limit: SEARCH_LIST_LIMIT }, + signal: controller.signal + }); + this.applyListResponse(generation, controller.signal, response); + } else if (query === '') { + const response = await this.client.POST('/api/v1/relationships', { + body: { filters: predicate.filters, show_all: this.showAll, limit: RANKED_LIST_LIMIT }, + signal: controller.signal + }); + this.applyListResponse(generation, controller.signal, response); + } else { + const response = await this.client.POST('/api/v1/people/search', { + body: { predicate: context, identity_query: query, sort: DEFAULT_IDENTITY_SORT, limit: SEARCH_LIST_LIMIT }, + signal: controller.signal + }); + this.applyListResponse(generation, controller.signal, response); + } + } catch (cause: unknown) { + if (generation === this.listGeneration && !controller.signal.aborted) this.listError = errorMessage(cause, 0); + } finally { + if (generation === this.listGeneration) this.listLoading = false; + } + } + + private applyListResponse( + generation: number, + signal: AbortSignal, + response: { data?: { rows?: unknown[] | null }; error?: unknown; response: Response } + ): void { + if (signal.aborted || generation !== this.listGeneration) return; + const { data, error, response: res } = response; + if (data) { + this.listRows = (data.rows ?? []) as RelationshipRow[] | PersonSummary[] | DomainSummary[]; + return; + } + if (res.status === 503 && isCacheUnavailable(error)) { + this.degraded = 'cache_unavailable'; + return; + } + this.listError = errorMessage(error, res.status); + } + + async openTarget(target: string, predicate: ExplorePredicate): Promise<void> { + this.detailAbort?.abort(); + const controller = new AbortController(); + this.detailAbort = controller; + const generation = ++this.detailGeneration; + this.target = target; + this.lastPredicate = predicate; + this.lastPredicateFingerprint = predicateFingerprint(predicate); + this.detail = null; + this.timelineRows = []; + this.timelineCursor = null; + this.timelineError = null; + this.timelineRestartNotice = null; + this.timelineLoadingMore = false; + this.canonicalID = null; + this.identityRevision = null; + this.seenCursors = new Set<string>(); + + const clusterID = parseClusterID(target); + const domainName = parseDomainName(target); + if (clusterID === undefined && domainName === undefined) { + this.timelineLoading = false; + return; + } + this.timelineLoading = true; + try { + if (clusterID !== undefined) await this.openCluster(clusterID, predicate, generation, controller.signal); + else if (domainName !== undefined) await this.openDomain(domainName, predicate, generation, controller.signal); + } finally { + if (generation === this.detailGeneration) this.timelineLoading = false; + } + } + + private async openCluster( + id: number, + predicate: ExplorePredicate, + generation: number, + signal: AbortSignal + ): Promise<void> { + try { + const [personResponse] = await Promise.all([ + this.client.GET('/api/v1/people/{id}', { params: { path: { id } }, signal }), + this.fetchClusterPage(id, predicate.filters ?? undefined, generation, undefined, signal) + ]); + if (signal.aborted || generation !== this.detailGeneration) return; + if (personResponse.data) this.detail = personResponse.data; + else this.timelineError ||= errorMessage(personResponse.error, personResponse.response.status); + } catch (cause: unknown) { + if (!signal.aborted && generation === this.detailGeneration) this.timelineError ||= errorMessage(cause, 0); + } + } + + private async openDomain( + domain: string, + predicate: ExplorePredicate, + generation: number, + signal: AbortSignal + ): Promise<void> { + try { + const [detailResponse] = await Promise.all([ + this.client.GET('/api/v1/domains/{domain}', { params: { path: { domain } }, signal }), + this.fetchDomainPage(domain, contextPredicate(predicate), generation, undefined, signal) + ]); + if (signal.aborted || generation !== this.detailGeneration) return; + if (detailResponse.data) this.detail = detailResponse.data; + else this.timelineError ||= errorMessage(detailResponse.error, detailResponse.response.status); + } catch (cause: unknown) { + if (!signal.aborted && generation === this.detailGeneration) this.timelineError ||= errorMessage(cause, 0); + } + } + + /** + * Drops the open target and its detail/timeline state (cluster or domain + * header, rows, canonical ID, revision) back to the hub's empty state. + * Called when the URL-carried target becomes null (Esc, Back/Forward) so + * the center pane never keeps showing a stale person/domain after the + * target it belonged to is gone. Aborts any in-flight detail fetch and + * bumps the generation counter so a late response cannot resurrect it. + */ + clearTarget(): void { + this.detailAbort?.abort(); + this.detailAbort = undefined; + ++this.detailGeneration; + this.target = null; + this.lastPredicate = undefined; + this.lastPredicateFingerprint = null; + this.detail = null; + this.timelineRows = []; + this.timelineCursor = null; + this.timelineLoading = false; + this.timelineLoadingMore = false; + this.timelineError = null; + this.timelineRestartNotice = null; + this.canonicalID = null; + this.identityRevision = null; + this.seenCursors = new Set<string>(); + } + + async loadMoreTimeline(): Promise<void> { + if (this.timelineLoadingMore || !this.timelineCursor || !this.target || !this.lastPredicate || !this.detailAbort) { + return; + } + const clusterID = parseClusterID(this.target); + const domainName = parseDomainName(this.target); + if (clusterID === undefined && domainName === undefined) return; + this.timelineLoadingMore = true; + const generation = this.detailGeneration; + const signal = this.detailAbort.signal; + const cursor = this.timelineCursor; + if (clusterID !== undefined) { + await this.fetchClusterPage(clusterID, this.lastPredicate.filters ?? undefined, generation, cursor, signal); + } else if (domainName !== undefined) { + await this.fetchDomainPage(domainName, contextPredicate(this.lastPredicate), generation, cursor, signal); + } + } + + private async fetchClusterPage( + id: number, + filters: ExploreFilter[] | undefined, + generation: number, + cursor: string | undefined, + signal: AbortSignal + ): Promise<void> { + try { + if (cursor && this.seenCursors.has(cursor)) { + this.applyTimelineFailure(generation, REPEATED_CURSOR_MESSAGE); + return; + } + const response = await this.client.POST('/api/v1/relationships/{id}/timeline', { + params: { path: { id } }, + body: { timezone: this.timezone(), filters, limit: TIMELINE_PAGE_LIMIT, ...(cursor ? { cursor } : {}) }, + signal + }); + if (signal.aborted || generation !== this.detailGeneration) return; + const { data, error, response: res } = response; + if (!data) { + if (cursor && res.status === 409 && isErrorCode(error, 'cursor_invalidated')) { + this.timelineRows = []; + this.timelineCursor = null; + this.seenCursors = new Set<string>(); + this.timelineRestartNotice = TIMELINE_RESTART_MESSAGE; + await this.fetchClusterPage(id, filters, generation, undefined, signal); + return; + } + this.applyTimelineFailure(generation, errorMessage(error, res.status)); + return; + } + this.timelineError = null; + if (cursor) this.seenCursors.add(cursor); + this.timelineRows = mergeTimelineRows(this.timelineRows, data.rows ?? []); + this.canonicalID = data.canonical_id; + this.identityRevision = data.identity_revision; + this.timelineCursor = data.next_cursor ?? null; + } catch (cause: unknown) { + if (!signal.aborted && generation === this.detailGeneration) this.timelineError = errorMessage(cause, 0); + } finally { + if (generation === this.detailGeneration) this.timelineLoadingMore = false; + } + } + + private async fetchDomainPage( + domain: string, + context: ExplorePredicate, + generation: number, + cursor: string | undefined, + signal: AbortSignal + ): Promise<void> { + try { + if (cursor && this.seenCursors.has(cursor)) { + this.applyTimelineFailure(generation, REPEATED_CURSOR_MESSAGE); + return; + } + const response = await this.client.POST('/api/v1/domains/{domain}/timeline', { + params: { path: { domain } }, + body: { ...context, limit: TIMELINE_PAGE_LIMIT, ...(cursor ? { cursor } : {}) }, + signal + }); + if (signal.aborted || generation !== this.detailGeneration) return; + const { data, error, response: res } = response; + if (!data) { + this.applyTimelineFailure(generation, errorMessage(error, res.status)); + return; + } + this.timelineError = null; + if (cursor) this.seenCursors.add(cursor); + this.timelineRows = mergeTimelineRows(this.timelineRows, data.rows); + this.timelineCursor = data.next_cursor ?? null; + } catch (cause: unknown) { + if (!signal.aborted && generation === this.detailGeneration) this.timelineError = errorMessage(cause, 0); + } finally { + if (generation === this.detailGeneration) this.timelineLoadingMore = false; + } + } + + private applyTimelineFailure(generation: number, message: string): void { + if (generation !== this.detailGeneration) return; + this.timelineError = message; + this.timelineCursor = null; + } + + async linkParticipants(a: number, b: number): Promise<LinkOutcome> { + try { + const response = await this.client.POST('/api/v1/identity/links', { body: { participant_a: a, participant_b: b } }); + return await this.applyLinkResponse(response); + } catch (cause: unknown) { + return { ok: false, code: 'error', message: errorMessage(cause, 0) }; + } + } + + async unlinkParticipants(a: number, b: number): Promise<LinkOutcome> { + try { + const response = await this.client.POST('/api/v1/identity/unlinks', { body: { participant_a: a, participant_b: b } }); + return await this.applyLinkResponse(response); + } catch (cause: unknown) { + return { ok: false, code: 'error', message: errorMessage(cause, 0) }; + } + } + + private async applyLinkResponse(response: { + data?: { identity_revision: number; cache_state: 'ready' | 'stale' }; + error?: unknown; + response: Response; + }): Promise<LinkOutcome> { + const { data, error, response: res } = response; + if (data) { + // A null identityRevision means the prior timeline load failed, so + // there is nothing to compare against — treat that as "changed" and + // refresh anyway rather than silently skipping the reopen. The ranked + // list reloads unconditionally: a link/unlink can move rows (merge or + // split a cluster) regardless of whether the currently open target's + // own revision moved. + const changed = this.identityRevision === null || this.identityRevision !== data.identity_revision; + const reloads: Promise<void>[] = []; + if (changed && this.target && this.lastPredicate) reloads.push(this.openTarget(this.target, this.lastPredicate)); + if (this.lastListPredicate) reloads.push(this.loadList(this.lastListPredicate)); + await Promise.all(reloads); + return { ok: true, identityRevision: data.identity_revision, cacheState: data.cache_state }; + } + const message = errorMessage(error, res.status); + if (res.status === 409 && isErrorCode(error, 'already_linked')) return { ok: false, code: 'already_linked', message }; + if (res.status === 400) return { ok: false, code: 'invalid', message }; + return { ok: false, code: 'error', message }; + } + + destroy(): void { + this.listAbort?.abort(); + this.detailAbort?.abort(); + } +} + +function contextPredicate(predicate: ExplorePredicate): ExplorePredicate { + const { cursor: _cursor, grouping: _grouping, candidate_snapshot_id: _snapshot, ...context } = predicate; + return { ...context, presentation: 'table' }; +} + +function mergeTimelineRows<Row extends { key: string }>(existing: Row[], incoming: Row[]): Row[] { + const merged = new Map(existing.map((row) => [row.key, row])); + for (const row of incoming) merged.set(row.key, row); + return [...merged.values()]; +} + +function parseClusterID(target: string | null): number | undefined { + const match = target ? /^cluster:([1-9][0-9]*)$/.exec(target) : null; + if (!match?.[1]) return undefined; + const id = Number(match[1]); + return Number.isSafeInteger(id) ? id : undefined; +} + +function parseDomainName(target: string | null): string | undefined { + return target?.startsWith('domain:') ? target.slice('domain:'.length) : undefined; +} + +function isCacheUnavailable(value: unknown): value is ExploreCacheUnavailable { + return typeof value === 'object' && value !== null && 'readiness' in value && 'recovery_action' in value; +} + +function isErrorCode(value: unknown, code: string): boolean { + return typeof value === 'object' && value !== null && 'error' in value && (value as { error?: unknown }).error === code; +} + +function errorMessage(value: unknown, status: number): string { + if (typeof value === 'object' && value !== null && 'message' in value) { + const message = (value as { message?: unknown }).message; + if (typeof message === 'string' && message) return message; + } + return status ? `Request failed (${status})` : 'Request failed'; +} diff --git a/web/src/lib/relationships/models.ts b/web/src/lib/relationships/models.ts new file mode 100644 index 000000000..20c859832 --- /dev/null +++ b/web/src/lib/relationships/models.ts @@ -0,0 +1,6 @@ +import type { components } from '../api/generated/schema'; + +export type RelationshipRow = components['schemas']['RelationshipRow']; +export type RelationshipSignals = components['schemas']['RelationshipSignals']; +export type RelationshipTimelineRow = components['schemas']['TimelineRow']; +export type RelationshipFacet = 'people' | 'domains'; diff --git a/web/src/lib/search/modes.test.ts b/web/src/lib/search/modes.test.ts new file mode 100644 index 000000000..7d006b3b9 --- /dev/null +++ b/web/src/lib/search/modes.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { + SEARCH_MODE_PREFERENCE_KEY, + VisibleLexicalCountCache, + loadRememberedSearchMode, + rememberSearchMode, + resolveInitialSearchMode +} from './modes'; +import { parseSearchCoverage } from './modes'; + +function memoryStorage(initial: Record<string, string> = {}) { + const values = new Map(Object.entries(initial)); + return { + getItem: vi.fn((key: string) => values.get(key) ?? null), + setItem: vi.fn((key: string, value: string) => values.set(key, value)) + }; +} + +describe('search mode preference', () => { + it('validates the generated search coverage contract at runtime', () => { + expect(parseSearchCoverage({ + status: 'ready', eligible_count: 2, embedded_count: 2, percentage: 100, + cache_revision: 'cache-1', actions: ['retry'] + })).toMatchObject({ status: 'ready', actions: ['retry'] }); + expect(parseSearchCoverage({ + status: 'future_status', eligible_count: 2, embedded_count: 2, percentage: 100, + cache_revision: 'cache-1', actions: [] + })).toBeUndefined(); + expect(parseSearchCoverage({ + status: 'ready', eligible_count: 2, embedded_count: 2, percentage: 100, + cache_revision: 'cache-1', actions: ['future_action'] + })).toBeUndefined(); + expect(parseSearchCoverage({ + status: 'ready', eligible_count: 1.5, embedded_count: 2, percentage: 100, + cache_revision: 'cache-1', actions: [] + })).toBeUndefined(); + expect(parseSearchCoverage({ + status: 'ready', eligible_count: 2, embedded_count: 2, percentage: 100, + cache_revision: 'cache-1', actions: [], vector_generation: 1.25 + })).toBeUndefined(); + expect(parseSearchCoverage({ + status: 'ready', eligible_count: 2, embedded_count: 2, percentage: 100, + cache_revision: 'cache-1', actions: [], vector_fingerprint: null + })).toBeUndefined(); + expect(parseSearchCoverage({ + status: 'ready', eligible_count: 2, embedded_count: 2, percentage: 100, + cache_revision: 'cache-1', actions: [], detail: 42 + })).toBeUndefined(); + expect(parseSearchCoverage({ + status: 'ready', eligible_count: 2, embedded_count: 2, percentage: 100, + cache_revision: 'cache-1', actions: [], future_server_field: true + })).toMatchObject({ future_server_field: true }); + }); + it('uses an explicit URL mode before the remembered default', () => { + const storage = memoryStorage({ [SEARCH_MODE_PREFERENCE_KEY]: 'semantic' }); + + expect(resolveInitialSearchMode('hybrid', storage)).toBe('hybrid'); + expect(resolveInitialSearchMode(undefined, storage)).toBe('semantic'); + }); + + it('falls back safely and persists explicit user choices', () => { + const storage = memoryStorage({ [SEARCH_MODE_PREFERENCE_KEY]: 'invalid' }); + + expect(loadRememberedSearchMode(storage)).toBe('full_text'); + rememberSearchMode('hybrid', storage); + expect(storage.setItem).toHaveBeenCalledWith(SEARCH_MODE_PREFERENCE_KEY, 'hybrid'); + }); + + it('tolerates browser storage being unavailable', () => { + const storage = { + getItem: vi.fn(() => { throw new DOMException('denied', 'SecurityError'); }), + setItem: vi.fn(() => { throw new DOMException('denied', 'SecurityError'); }) + }; + + expect(loadRememberedSearchMode(storage)).toBe('full_text'); + expect(() => rememberSearchMode('semantic', storage)).not.toThrow(); + }); +}); + +describe('VisibleLexicalCountCache', () => { + it('keys counts by canonical query, archive revision, authority, and row keys', () => { + const cache = new VisibleLexicalCountCache(4); + const first = cache.key({ + query: ' alpha beta ', cacheRevision: 'cache-1', lexicalRevision: 'fts-1', rowKeys: ['b', 'a'] + }); + const equivalent = cache.key({ + query: 'alpha beta', cacheRevision: 'cache-1', lexicalRevision: 'fts-1', rowKeys: ['a', 'b'] + }); + + expect(first).toBe(equivalent); + cache.set(first, { a: 1, b: 2 }); + expect(cache.get(equivalent)).toEqual({ a: 1, b: 2 }); + expect(cache.key({ query: 'alpha beta', cacheRevision: 'cache-2', lexicalRevision: 'fts-1', rowKeys: ['a', 'b'] })).not.toBe(first); + expect(cache.key({ query: 'alpha beta', cacheRevision: 'cache-1', lexicalRevision: 'fts-2', rowKeys: ['a', 'b'] })).not.toBe(first); + const firstPredicate = cache.key({ + query: 'alpha beta', cacheRevision: 'cache-1', lexicalRevision: 'fts-1', rowKeys: ['a', 'b'], + predicateFingerprint: 'domain=example.com' + } as Parameters<typeof cache.key>[0]); + const secondPredicate = cache.key({ + query: 'alpha beta', cacheRevision: 'cache-1', lexicalRevision: 'fts-1', rowKeys: ['a', 'b'], + predicateFingerprint: 'domain=example.org' + } as Parameters<typeof cache.key>[0]); + expect(firstPredicate).not.toBe(secondPredicate); + }); + + it('invalidates old revisions and remains bounded', () => { + const cache = new VisibleLexicalCountCache(2); + cache.set('one', { a: 1 }, 'cache-1'); + cache.set('two', { b: 2 }, 'cache-1'); + cache.set('three', { c: 3 }, 'cache-1'); + + expect(cache.size).toBe(2); + expect(cache.get('one')).toBeUndefined(); + cache.invalidateRevision('cache-2'); + expect(cache.size).toBe(0); + }); +}); diff --git a/web/src/lib/search/modes.ts b/web/src/lib/search/modes.ts new file mode 100644 index 000000000..743bbed74 --- /dev/null +++ b/web/src/lib/search/modes.ts @@ -0,0 +1,145 @@ +import type { ExploreSearchMode } from '../explore/models'; +import type { components } from '../api/generated/schema'; + +export type SearchCoverageValue = components['schemas']['SearchCoverageResponse']; +export type SearchCoverageStatus = SearchCoverageValue['status']; +export type SearchCoverageAction = SearchCoverageValue['actions'][number]; + +const SEARCH_COVERAGE_STATUSES = new Set<SearchCoverageStatus>([ + 'disabled', 'initializing', 'stale', 'incomplete', 'unavailable', 'ready' +]); +const SEARCH_COVERAGE_ACTIONS = new Set<SearchCoverageAction>(['retry', 'build_index']); + +export function parseSearchCoverage(value: unknown): SearchCoverageValue | undefined { + if (typeof value !== 'object' || value === null) return undefined; + const candidate = value as Partial<SearchCoverageValue>; + if (typeof candidate.status !== 'string' || + !SEARCH_COVERAGE_STATUSES.has(candidate.status as SearchCoverageStatus) || + typeof candidate.eligible_count !== 'number' || !Number.isInteger(candidate.eligible_count) || + typeof candidate.embedded_count !== 'number' || !Number.isInteger(candidate.embedded_count) || + typeof candidate.percentage !== 'number' || !Number.isFinite(candidate.percentage) || + typeof candidate.cache_revision !== 'string' || !Array.isArray(candidate.actions) || + !candidate.actions.every((action) => typeof action === 'string' && + SEARCH_COVERAGE_ACTIONS.has(action as SearchCoverageAction)) || + (candidate.vector_generation !== undefined && + (typeof candidate.vector_generation !== 'number' || !Number.isInteger(candidate.vector_generation))) || + (candidate.vector_fingerprint !== undefined && typeof candidate.vector_fingerprint !== 'string') || + (candidate.detail !== undefined && typeof candidate.detail !== 'string')) return undefined; + return candidate as SearchCoverageValue; +} + +export type SearchModeStorage = Pick<Storage, 'getItem' | 'setItem'>; + +export const SEARCH_MODE_PREFERENCE_KEY = 'msgvault-search-mode'; +export const SEARCH_MODES: readonly ExploreSearchMode[] = ['full_text', 'semantic', 'hybrid']; + +export function availableSearchModeStorage(): SearchModeStorage | null { + try { + return typeof localStorage === 'undefined' ? null : localStorage; + } catch { + return null; + } +} + +export function loadRememberedSearchMode(storage: SearchModeStorage | null): ExploreSearchMode { + try { + const value = storage?.getItem(SEARCH_MODE_PREFERENCE_KEY); + return SEARCH_MODES.includes(value as ExploreSearchMode) + ? value as ExploreSearchMode + : 'full_text'; + } catch { + return 'full_text'; + } +} + +export function rememberSearchMode(mode: ExploreSearchMode, storage: SearchModeStorage | null): void { + try { + storage?.setItem(SEARCH_MODE_PREFERENCE_KEY, mode); + } catch { + // Storage may be disabled. URL state remains authoritative for this view. + } +} + +export function resolveInitialSearchMode( + explicitURLMode: ExploreSearchMode | undefined, + storage: SearchModeStorage | null +): ExploreSearchMode { + return explicitURLMode ?? loadRememberedSearchMode(storage); +} + +export function explicitSearchModeFromURL(search: string, parameter = 'explore'): ExploreSearchMode | undefined { + const encoded = new URLSearchParams(search.startsWith('?') ? search.slice(1) : search).get(parameter); + if (encoded === null) return undefined; + try { + const value = JSON.parse(encoded) as { searchMode?: unknown }; + return SEARCH_MODES.includes(value.searchMode as ExploreSearchMode) + ? value.searchMode as ExploreSearchMode + : undefined; + } catch { + return undefined; + } +} + +interface LexicalCountKeyInput { + query: string; + canonicalQueryHash?: string; + cacheRevision: string; + lexicalRevision: string; + rowKeys: string[]; + predicateFingerprint?: string; +} + +interface LexicalCountEntry { + counts: Record<string, number>; + revision: string; +} + +function canonicalQuery(query: string): string { + const tokens = query.trim().match(/"(?:\\.|[^"\\])*"|\S+/g) ?? []; + return tokens.map((token) => token.replace(/\s+/g, ' ')).sort().join(' '); +} + +export class VisibleLexicalCountCache { + private readonly entries = new Map<string, LexicalCountEntry>(); + private activeRevision = ''; + + constructor(private readonly maximum = 128) {} + + get size(): number { return this.entries.size; } + + key(input: LexicalCountKeyInput): string { + return JSON.stringify([ + input.canonicalQueryHash ?? canonicalQuery(input.query), + input.cacheRevision, + input.lexicalRevision, + input.predicateFingerprint ?? '', + [...new Set(input.rowKeys)].sort() + ]); + } + + get(key: string): Record<string, number> | undefined { + const entry = this.entries.get(key); + if (!entry) return undefined; + this.entries.delete(key); + this.entries.set(key, entry); + return { ...entry.counts }; + } + + set(key: string, counts: Record<string, number>, revision = this.activeRevision): void { + if (this.entries.has(key)) this.entries.delete(key); + while (this.entries.size >= this.maximum) { + const oldest = this.entries.keys().next().value as string | undefined; + if (oldest === undefined) break; + this.entries.delete(oldest); + } + this.entries.set(key, { counts: { ...counts }, revision }); + } + + invalidateRevision(revision: string): void { + if (this.activeRevision === revision) return; + this.activeRevision = revision; + for (const [key, entry] of this.entries) { + if (entry.revision !== revision) this.entries.delete(key); + } + } +} diff --git a/web/src/lib/settings/catalog.test.ts b/web/src/lib/settings/catalog.test.ts new file mode 100644 index 000000000..82e7a7ef0 --- /dev/null +++ b/web/src/lib/settings/catalog.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from 'vitest'; + +import { groupSettings, isManagedSetting, settingsCatalog, type SettingState } from './catalog'; + +describe('settings catalog', () => { + it('contains the supported browser, server, archive, search, source and integration groups', () => { + expect(settingsCatalog['web.default_search_mode'].options).toEqual([ + 'full_text', + 'semantic', + 'hybrid' + ]); + expect(settingsCatalog['server.trusted_proxies'].group).toBe('server'); + expect(settingsCatalog['analytics.auto_build_cache'].group).toBe('archive'); + expect(settingsCatalog['vector.embeddings.endpoint'].testable).toBe(true); + expect(settingsCatalog['beeper.schedule'].group).toBe('sources'); + expect(settingsCatalog['integrations.tasks.api_key'].secret).toBe(true); + }); + + it('filters unknown keys and groups known settings in task order', () => { + const settings: SettingState[] = [ + setting('integrations.tasks.enabled', false), + setting('unsupported.private_value', 'hidden'), + setting('web.theme', 'dark'), + setting('server.bind_addr', '127.0.0.1') + ]; + + expect(isManagedSetting('web.theme')).toBe(true); + expect(isManagedSetting('unsupported.private_value')).toBe(false); + expect(groupSettings(settings).map((group) => group.id)).toEqual([ + 'browser', + 'server', + 'integrations' + ]); + expect(groupSettings(settings).flatMap((group) => group.settings.map((item) => item.key))).not.toContain( + 'unsupported.private_value' + ); + }); +}); + +function setting(key: string, value: unknown): SettingState { + return { + key, + group: 'browser', + kind: typeof value === 'boolean' ? 'boolean' : 'string', + value: typeof value === 'boolean' ? { boolean: value } : { string: String(value) }, + restart_required: true + }; +} diff --git a/web/src/lib/settings/catalog.ts b/web/src/lib/settings/catalog.ts new file mode 100644 index 000000000..efec5ed48 --- /dev/null +++ b/web/src/lib/settings/catalog.ts @@ -0,0 +1,120 @@ +import type { components } from '../api/generated/schema'; + +export type SettingState = components['schemas']['Setting']; +export type SettingKind = SettingState['kind']; + +export type SecretSettingState = components['schemas']['SecretSettingState']; + +export type SettingValue = components['schemas']['SettingValue']; +export type SettingsDocument = components['schemas']['SettingsResponse']; + +export interface CatalogEntry { + group: SettingGroupID; + label: string; + description: string; + secret?: boolean; + testable?: boolean; + options?: string[]; +} + +export type SettingGroupID = + | 'browser' + | 'server' + | 'archive' + | 'search' + | 'sources' + | 'integrations'; + +export const settingGroups: ReadonlyArray<{ id: SettingGroupID; label: string }> = [ + { id: 'browser', label: 'Browser experience' }, + { id: 'server', label: 'Server access' }, + { id: 'archive', label: 'Archive and cache' }, + { id: 'search', label: 'Search and vectors' }, + { id: 'sources', label: 'Source schedules' }, + { id: 'integrations', label: 'Optional integrations' } +]; + +export const settingsCatalog: Record<string, CatalogEntry> = { + 'web.default_search_mode': entry('browser', 'Default search mode', 'Mode used when a URL does not choose one.', { + options: ['full_text', 'semantic', 'hybrid'] + }), + 'web.theme': entry('browser', 'Theme', 'Browser color theme.', { + options: ['system', 'light', 'dark'] + }), + 'web.density': entry('browser', 'Density', 'Table and toolbar spacing.', { + options: ['compact', 'comfortable'] + }), + 'server.bind_addr': entry('server', 'Bind address', 'Address the daemon listens on.'), + 'server.api_port': entry('server', 'API port', 'Zero lets the daemon select an available port.'), + 'server.api_key': entry('server', 'Daemon API key', 'Key used by remote clients and browser login.', { + secret: true + }), + 'server.allow_insecure': entry('server', 'Allow insecure access', 'Permit unauthenticated non-loopback access.'), + 'server.trusted_proxies': entry('server', 'Trusted proxies', 'IP addresses or CIDR ranges allowed to supply forwarded HTTPS metadata.'), + 'analytics.engine': entry('archive', 'Analytics engine', 'Engine used for aggregate queries.', { + options: ['auto', 'sql', 'duckdb'] + }), + 'analytics.auto_build_cache': entry('archive', 'Build stale cache automatically', 'Build Parquet analytics before cached queries.'), + 'vector.enabled': entry('search', 'Semantic search', 'Enable the vector subsystem.'), + 'vector.backend': entry('search', 'Vector backend', 'Storage backend for embeddings.', { + options: ['sqlite-vec', 'pgvector'] + }), + 'vector.db_path': entry('search', 'Vector database path', 'Optional vector database path override.'), + 'vector.skip_extension_create': entry('search', 'Skip extension creation', 'Use a vector extension installed by an administrator.'), + 'vector.embeddings.endpoint': entry('search', 'Embedding endpoint', 'OpenAI-compatible embedding endpoint.', { + testable: true + }), + 'vector.embeddings.api_key_env': entry('search', 'Embedding key environment variable', 'Environment variable containing the endpoint key.'), + 'vector.embeddings.model': entry('search', 'Embedding model', 'Model identifier used to build an index generation.'), + 'vector.embeddings.dimension': entry('search', 'Embedding dimension', 'Vector dimension returned by the model.'), + 'vector.embeddings.batch_size': entry('search', 'Embedding batch size', 'Items sent per embedding request.'), + 'vector.embeddings.max_retries': entry('search', 'Embedding retries', 'Maximum transient request retries.'), + 'vector.embeddings.max_input_chars': entry('search', 'Maximum input characters', 'Per-chunk text limit.'), + 'vector.embeddings.eta_window': entry('search', 'ETA window', 'Recent samples used for ETA smoothing.'), + 'vector.embed.schedule.cron': entry('search', 'Embedding schedule', 'Cron schedule for background embedding.'), + 'vector.embed.schedule.run_after_sync': entry('search', 'Embed after sync', 'Run embedding after a successful source sync.'), + 'vector.embed.scope.message_types': entry('search', 'Embedded message types', 'Optional message-type scope.'), + 'vector.search.rrf_k': entry('search', 'RRF constant', 'Reciprocal rank fusion constant.'), + 'vector.search.k_per_signal': entry('search', 'Candidates per signal', 'Candidate count contributed by each hybrid signal.'), + 'vector.search.subject_boost': entry('search', 'Subject boost', 'Hybrid ranking weight for subject matches.'), + 'beeper.enabled': entry('sources', 'Desktop chat schedule enabled', 'Enable scheduled imports from the supported desktop chat source.'), + 'beeper.schedule': entry('sources', 'Desktop chat schedule', 'Cron schedule for the supported desktop chat source.'), + 'integrations.tasks.enabled': entry('integrations', 'Task integration', 'Enable the provider-neutral task integration.'), + 'integrations.tasks.endpoint': entry('integrations', 'Task endpoint', 'HTTPS, loopback, Unix socket, or local discovery endpoint.', { + testable: true + }), + 'integrations.tasks.api_key': entry('integrations', 'Task integration API key', 'Bearer key used only by the daemon.', { + secret: true + }), + 'integrations.tasks.default_project': entry('integrations', 'Default task project', 'Project used for task creation and lookup.') +}; + +export interface SettingsGroup { + id: SettingGroupID; + label: string; + settings: SettingState[]; +} + +export function isManagedSetting(key: string): boolean { + return Object.hasOwn(settingsCatalog, key); +} + +export function groupSettings(settings: SettingState[]): SettingsGroup[] { + return settingGroups + .map((group) => ({ + ...group, + settings: settings.filter( + (setting) => isManagedSetting(setting.key) && settingsCatalog[setting.key]?.group === group.id + ) + })) + .filter((group) => group.settings.length > 0); +} + +function entry( + group: SettingGroupID, + label: string, + description: string, + options: Partial<CatalogEntry> = {} +): CatalogEntry { + return { group, label, description, ...options }; +} diff --git a/web/src/lib/theme/preferences.svelte.ts b/web/src/lib/theme/preferences.svelte.ts new file mode 100644 index 000000000..ea9fb3b96 --- /dev/null +++ b/web/src/lib/theme/preferences.svelte.ts @@ -0,0 +1,196 @@ +export type ThemePreference = 'system' | 'light' | 'dark'; +export type DensityPreference = 'compact' | 'comfortable'; + +export interface AppearanceDefaults { + theme: ThemePreference; + density: DensityPreference; +} + +export interface AppearanceSnapshot extends AppearanceDefaults { + overridden: boolean; +} + +const STORAGE_KEY = 'msgvault.appearance.override'; +const ROW_GEOMETRY_READINESS_FRAMES = 300; + +export class RowGeometry { + height = $state<number | undefined>(); + #observer: MutationObserver | undefined; + #readinessFrame: number | undefined; + #readinessAttempts = 0; + + constructor() { + this.#read(); + if (typeof document !== 'undefined' && typeof MutationObserver !== 'undefined') { + this.#observer = new MutationObserver(() => this.#read()); + this.#observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-density'] }); + } + this.#probeReadiness(); + } + + destroy(): void { + this.#observer?.disconnect(); + this.#stopReadinessProbe(); + } + + #read(): void { + if (typeof document === 'undefined') return; + const parsed = Number.parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--row-height')); + if (Number.isFinite(parsed) && parsed > 0) { + this.height = parsed; + this.#stopReadinessProbe(); + } + } + + #probeReadiness(): void { + if ( + this.height !== undefined || this.#readinessFrame !== undefined || + this.#readinessAttempts >= ROW_GEOMETRY_READINESS_FRAMES || + typeof requestAnimationFrame !== 'function' + ) return; + this.#readinessFrame = requestAnimationFrame(() => { + this.#readinessFrame = undefined; + this.#readinessAttempts += 1; + this.#read(); + this.#probeReadiness(); + }); + } + + #stopReadinessProbe(): void { + if (this.#readinessFrame === undefined) return; + if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(this.#readinessFrame); + this.#readinessFrame = undefined; + } +} + +export class AppearancePreferences { + #defaults = $state<AppearanceDefaults>({ theme: 'system', density: 'compact' }); + #override = $state<Partial<AppearanceDefaults>>({}); + #media: MediaQueryList | undefined; + #mediaListener: ((event: MediaQueryListEvent) => void) | undefined; + + constructor(defaults: AppearanceDefaults) { + this.#defaults = validDefaults(defaults); + this.#override = readOverride(); + if (typeof matchMedia === 'function') { + this.#media = matchMedia('(prefers-color-scheme: dark)'); + this.#mediaListener = () => this.#apply(); + this.#media.addEventListener('change', this.#mediaListener); + } + this.#apply(); + } + + get defaults(): AppearanceDefaults { + return { ...this.#defaults }; + } + + get current(): AppearanceSnapshot { + return { + theme: this.#override.theme ?? this.#defaults.theme, + density: this.#override.density ?? this.#defaults.density, + overridden: this.#override.theme !== undefined || this.#override.density !== undefined + }; + } + + get temporary(): Readonly<Partial<AppearanceDefaults>> { + return { ...this.#override }; + } + + setDefaults(defaults: AppearanceDefaults): void { + this.#defaults = validDefaults(defaults); + this.#apply(); + } + + setTemporary(override: Partial<AppearanceDefaults>): void { + this.#override = { + ...this.#override, + ...(override.theme && isTheme(override.theme) ? { theme: override.theme } : {}), + ...(override.density && isDensity(override.density) ? { density: override.density } : {}) + }; + writeOverride(this.#override); + this.#apply(); + } + + clearTemporary(kind?: keyof AppearanceDefaults): void { + if (kind) { + const next = { ...this.#override }; + delete next[kind]; + this.#override = next; + } else this.#override = {}; + if (typeof sessionStorage !== 'undefined') sessionStorage.removeItem(STORAGE_KEY); + if (Object.keys(this.#override).length > 0) writeOverride(this.#override); + this.#apply(); + } + + destroy(): void { + if (this.#media && this.#mediaListener) this.#media.removeEventListener('change', this.#mediaListener); + } + + #apply(): void { + if (typeof document === 'undefined') return; + const current = this.current; + const dark = current.theme === 'dark' || (current.theme === 'system' && (this.#media?.matches ?? false)); + const resolvedTheme = dark ? 'dark' : 'light'; + document.documentElement.dataset.theme = resolvedTheme; + document.documentElement.dataset.density = current.density; + document.documentElement.classList.toggle('dark', dark); + } +} + +export function createAppearancePreferences(defaults: AppearanceDefaults): AppearancePreferences { + return new AppearancePreferences(defaults); +} + +export function rebaseVirtualScroll(scrollTop: number, previousHeight: number, nextHeight: number): number { + if (previousHeight <= 0 || nextHeight <= 0 || previousHeight === nextHeight) return scrollTop; + const anchorIndex = Math.floor(scrollTop / previousHeight); + const offset = Math.min(scrollTop - anchorIndex * previousHeight, nextHeight - 1); + return anchorIndex * nextHeight + offset; +} + +export function tableViewportHeight( + containerHeight: number, + stickyHeaderHeight: number, + windowHeight: number +): number { + const container = containerHeight > 0 ? containerHeight : 360; + const windowCap = windowHeight > 0 ? windowHeight : 360; + return Math.max(0, Math.min(container, windowCap) - Math.max(0, stickyHeaderHeight)); +} + +function validDefaults(defaults: AppearanceDefaults): AppearanceDefaults { + return { + theme: isTheme(defaults.theme) ? defaults.theme : 'system', + density: isDensity(defaults.density) ? defaults.density : 'compact' + }; +} + +function readOverride(): Partial<AppearanceDefaults> { + if (typeof sessionStorage === 'undefined') return {}; + try { + const parsed = JSON.parse(sessionStorage.getItem(STORAGE_KEY) ?? '{}') as Partial<AppearanceDefaults>; + const sanitized = { + ...(parsed.theme && isTheme(parsed.theme) ? { theme: parsed.theme } : {}), + ...(parsed.density && isDensity(parsed.density) ? { density: parsed.density } : {}) + }; + if (Object.keys(sanitized).length === 0) sessionStorage.removeItem(STORAGE_KEY); + else sessionStorage.setItem(STORAGE_KEY, JSON.stringify(sanitized)); + return sanitized; + } catch { + sessionStorage.removeItem(STORAGE_KEY); + return {}; + } +} + +function writeOverride(override: Partial<AppearanceDefaults>): void { + if (typeof sessionStorage === 'undefined') return; + sessionStorage.setItem(STORAGE_KEY, JSON.stringify(override)); +} + +function isTheme(value: string): value is ThemePreference { + return value === 'system' || value === 'light' || value === 'dark'; +} + +function isDensity(value: string): value is DensityPreference { + return value === 'compact' || value === 'comfortable'; +} diff --git a/web/src/lib/theme/preferences.test.ts b/web/src/lib/theme/preferences.test.ts new file mode 100644 index 000000000..a5b5e234f --- /dev/null +++ b/web/src/lib/theme/preferences.test.ts @@ -0,0 +1,178 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; + +import { + createAppearancePreferences, + rebaseVirtualScroll, + tableViewportHeight +} from './preferences.svelte'; + +describe('appearance preferences', () => { + afterEach(() => { + document.documentElement.className = ''; + document.documentElement.removeAttribute('data-theme'); + document.documentElement.removeAttribute('data-density'); + sessionStorage.clear(); + vi.unstubAllGlobals(); + }); + + it('applies daemon settings as authoritative defaults', () => { + const preferences = createAppearancePreferences({ theme: 'dark', density: 'comfortable' }); + + expect(preferences.current).toEqual({ theme: 'dark', density: 'comfortable', overridden: false }); + expect(document.documentElement.dataset.theme).toBe('dark'); + expect(document.documentElement.dataset.density).toBe('comfortable'); + expect(document.documentElement.classList.contains('dark')).toBe(true); + }); + + it('keeps temporary overrides session-scoped and leaves daemon defaults unchanged', () => { + const preferences = createAppearancePreferences({ theme: 'light', density: 'compact' }); + preferences.setTemporary({ theme: 'dark', density: 'comfortable' }); + + expect(preferences.defaults).toEqual({ theme: 'light', density: 'compact' }); + expect(preferences.current).toEqual({ theme: 'dark', density: 'comfortable', overridden: true }); + expect(sessionStorage.getItem('msgvault.appearance.override')).toBe(JSON.stringify({ + theme: 'dark', density: 'comfortable' + })); + expect(localStorage.getItem('msgvault.appearance.override')).toBeNull(); + + const restored = createAppearancePreferences({ theme: 'system', density: 'compact' }); + expect(restored.current).toEqual({ theme: 'dark', density: 'comfortable', overridden: true }); + restored.clearTemporary(); + expect(restored.current).toEqual({ theme: 'system', density: 'compact', overridden: false }); + }); + + it('tracks system dark mode while system is effective and stops after disposal', () => { + let listener: ((event: MediaQueryListEvent) => void) | undefined; + const media = { + matches: true, + addEventListener: vi.fn((_name: string, handler: (event: MediaQueryListEvent) => void) => { listener = handler; }), + removeEventListener: vi.fn() + } as unknown as MediaQueryList; + vi.stubGlobal('matchMedia', vi.fn(() => media)); + const preferences = createAppearancePreferences({ theme: 'system', density: 'compact' }); + expect(document.documentElement.classList.contains('dark')).toBe(true); + + Object.defineProperty(media, 'matches', { value: false }); + listener?.({ matches: false } as MediaQueryListEvent); + expect(document.documentElement.classList.contains('dark')).toBe(false); + + preferences.destroy(); + expect(media.removeEventListener).toHaveBeenCalledWith('change', expect.any(Function)); + }); + + it.each([ + [{ theme: 'sepia', density: 'compact' }, { density: 'compact' }], + [{ theme: 'dark', density: 'spacious' }, { theme: 'dark' }], + [{ theme: 'sepia', density: 'spacious' }, {}] + ])('removes invalid parsed session values %#', (stored, retained) => { + sessionStorage.setItem('msgvault.appearance.override', JSON.stringify(stored)); + + const preferences = createAppearancePreferences({ theme: 'system', density: 'comfortable' }); + + expect(preferences.temporary).toEqual(retained); + if (Object.keys(retained).length === 0) { + expect(sessionStorage.getItem('msgvault.appearance.override')).toBeNull(); + } else { + expect(sessionStorage.getItem('msgvault.appearance.override')).toBe(JSON.stringify(retained)); + } + }); + + it('derives row geometry from the computed CSS token', async () => { + document.documentElement.style.removeProperty('--row-height'); + const style = document.createElement('style'); + style.textContent = ` + :root[data-density="compact"] { --row-height: 36px; } + :root[data-density="comfortable"] { --row-height: 46px; } + `; + document.head.append(style); + document.documentElement.dataset.density = 'compact'; + const { RowGeometry } = await import('./preferences.svelte'); + const geometry = new RowGeometry(); + expect(geometry.height).toBe(36); + + document.documentElement.dataset.density = 'comfortable'; + await new Promise((resolve) => setTimeout(resolve)); + expect(geometry.height).toBe(46); + geometry.destroy(); + style.remove(); + }); + + it('has no geometry before CSS exists and becomes ready from a later computed token', async () => { + document.documentElement.style.removeProperty('--row-height'); + document.documentElement.removeAttribute('data-density'); + const frames = new Map<number, FrameRequestCallback>(); + let nextFrame = 1; + vi.stubGlobal('requestAnimationFrame', vi.fn((callback: FrameRequestCallback) => { + const frame = nextFrame++; + frames.set(frame, callback); + return frame; + })); + vi.stubGlobal('cancelAnimationFrame', vi.fn((frame: number) => frames.delete(frame))); + const { RowGeometry } = await import('./preferences.svelte'); + const geometry = new RowGeometry(); + expect(geometry.height).toBeUndefined(); + expect(frames.size).toBe(1); + + const style = document.createElement('style'); + style.textContent = ':root { --row-height: 37px; }'; + document.head.append(style); + const [frame, callback] = [...frames.entries()][0]!; + frames.delete(frame); + callback(performance.now()); + + expect(geometry.height).toBe(37); + expect(frames.size).toBe(0); + geometry.destroy(); + style.remove(); + }); + + it('stops probing when CSS readiness remains unavailable', async () => { + document.documentElement.style.removeProperty('--row-height'); + document.documentElement.removeAttribute('data-density'); + const frames = new Map<number, FrameRequestCallback>(); + let nextFrame = 1; + vi.stubGlobal('requestAnimationFrame', vi.fn((callback: FrameRequestCallback) => { + const frame = nextFrame++; + frames.set(frame, callback); + return frame; + })); + vi.stubGlobal('cancelAnimationFrame', vi.fn((frame: number) => frames.delete(frame))); + const { RowGeometry } = await import('./preferences.svelte'); + const geometry = new RowGeometry(); + + for (let attempt = 0; attempt < 300; attempt += 1) { + expect(frames.size).toBe(1); + const [frame, callback] = [...frames.entries()][0]!; + frames.delete(frame); + callback(performance.now()); + } + + expect(geometry.height).toBeUndefined(); + expect(frames.size).toBe(0); + geometry.destroy(); + }); + + it('cancels an unfinished CSS readiness probe on disposal', async () => { + document.documentElement.style.removeProperty('--row-height'); + document.documentElement.removeAttribute('data-density'); + vi.stubGlobal('requestAnimationFrame', vi.fn(() => 42)); + const cancelAnimationFrame = vi.fn(); + vi.stubGlobal('cancelAnimationFrame', cancelAnimationFrame); + const { RowGeometry } = await import('./preferences.svelte'); + const geometry = new RowGeometry(); + + geometry.destroy(); + + expect(cancelAnimationFrame).toHaveBeenCalledWith(42); + }); + + it('rebases a virtual scroll offset around the same visible row', () => { + expect(rebaseVirtualScroll(249 * 36 + 5, 36, 46)).toBe(249 * 46 + 5); + }); + + it('excludes the sticky table header from the virtualized row viewport', () => { + expect(tableViewportHeight(360, 30, 800)).toBe(330); + expect(tableViewportHeight(900, 34, 640)).toBe(606); + expect(tableViewportHeight(20, 30, 800)).toBe(0); + }); +}); diff --git a/web/src/lib/util/debounce.test.ts b/web/src/lib/util/debounce.test.ts new file mode 100644 index 000000000..0e7f0717f --- /dev/null +++ b/web/src/lib/util/debounce.test.ts @@ -0,0 +1,60 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +import { debounce } from './debounce'; + +describe('debounce', () => { + beforeEach(() => vi.useFakeTimers()); + afterEach(() => vi.useRealTimers()); + + it('fires once with the latest arguments after the delay', () => { + const fn = vi.fn(); + const debounced = debounce(fn, 250); + debounced('a'); + debounced('ab'); + debounced('abc'); + expect(fn).not.toHaveBeenCalled(); + vi.advanceTimersByTime(250); + expect(fn).toHaveBeenCalledTimes(1); + expect(fn).toHaveBeenCalledWith('abc'); + }); + + it('restarts the delay on each call', () => { + const fn = vi.fn(); + const debounced = debounce(fn, 250); + debounced('a'); + vi.advanceTimersByTime(200); + debounced('ab'); + vi.advanceTimersByTime(200); + expect(fn).not.toHaveBeenCalled(); + vi.advanceTimersByTime(50); + expect(fn).toHaveBeenCalledTimes(1); + expect(fn).toHaveBeenCalledWith('ab'); + }); + + it('cancel discards the pending call', () => { + const fn = vi.fn(); + const debounced = debounce(fn, 250); + debounced('a'); + debounced.cancel(); + vi.advanceTimersByTime(1000); + expect(fn).not.toHaveBeenCalled(); + }); + + it('flush runs the pending call immediately with its captured arguments', () => { + const fn = vi.fn(); + const debounced = debounce(fn, 250); + debounced('a'); + debounced.flush(); + expect(fn).toHaveBeenCalledTimes(1); + expect(fn).toHaveBeenCalledWith('a'); + vi.advanceTimersByTime(1000); + expect(fn).toHaveBeenCalledTimes(1); + }); + + it('flush is a no-op when nothing is pending', () => { + const fn = vi.fn(); + const debounced = debounce(fn, 250); + debounced.flush(); + expect(fn).not.toHaveBeenCalled(); + }); +}); diff --git a/web/src/lib/util/debounce.ts b/web/src/lib/util/debounce.ts new file mode 100644 index 000000000..385f0fc8e --- /dev/null +++ b/web/src/lib/util/debounce.ts @@ -0,0 +1,37 @@ +export interface Debounced<T extends unknown[]> { + (...args: T): void; + cancel(): void; + flush(): void; +} + +export function debounce<T extends unknown[]>( + fn: (...args: T) => void, + delayMs: number +): Debounced<T> { + let timer: ReturnType<typeof setTimeout> | undefined; + let pendingArgs: T | undefined; + const debounced = (...args: T): void => { + if (timer !== undefined) clearTimeout(timer); + pendingArgs = args; + timer = setTimeout(() => { + timer = undefined; + const args = pendingArgs; + pendingArgs = undefined; + if (args) fn(...args); + }, delayMs); + }; + debounced.cancel = (): void => { + if (timer !== undefined) clearTimeout(timer); + timer = undefined; + pendingArgs = undefined; + }; + debounced.flush = (): void => { + if (timer === undefined) return; + clearTimeout(timer); + timer = undefined; + const args = pendingArgs; + pendingArgs = undefined; + if (args) fn(...args); + }; + return debounced; +} diff --git a/web/src/main.ts b/web/src/main.ts new file mode 100644 index 000000000..0404ad60e --- /dev/null +++ b/web/src/main.ts @@ -0,0 +1,13 @@ +import './app.css'; +import { mount } from 'svelte'; +import App from './App.svelte'; + +const target = document.getElementById('app'); + +if (!target) { + throw new Error('Application mount point not found'); +} + +const app = mount(App, { target }); + +export default app; diff --git a/web/src/styles/density.css b/web/src/styles/density.css new file mode 100644 index 000000000..03e85997a --- /dev/null +++ b/web/src/styles/density.css @@ -0,0 +1,16 @@ +:root, +:root[data-density="compact"] { + --row-height: 36px; + --table-header-height: 34px; + --control-height: 32px; + --density-block-padding: 4px; + --density-inline-padding: 8px; +} + +:root[data-density="comfortable"] { + --row-height: 46px; + --table-header-height: 36px; + --control-height: 38px; + --density-block-padding: 8px; + --density-inline-padding: 12px; +} diff --git a/web/src/styles/theme-dark.css b/web/src/styles/theme-dark.css new file mode 100644 index 000000000..958389ce5 --- /dev/null +++ b/web/src/styles/theme-dark.css @@ -0,0 +1,40 @@ +:root[data-theme='dark'] { + --bg-primary: #101712; + --bg-surface: #18211b; + --bg-canvas: #18211b; + --bg-subtle: #202c24; + --surface-raised: #26352c; + --bg-surface-hover: #26352c; + --bg-inset: #0c140f; + --border-default: #77857a; + --border-muted: #77857a; + --border-strong: #a2afa5; + --control-bg: #18211b; + --control-border: #829087; + --text-primary: #f0f3ea; + --text-secondary: #c3cec3; + --text-muted: #a9b7ac; + --text-danger: #ffb0a7; + --active-ink: #6ee2ce; + --link-ink: #6ee2ce; + --focus-color: #6ee2ce; + --selected-bg: #17372f; + --selected-border: #79e8d4; + --artifact-ink: #f0a078; + --status-success-bg: #183222; + --status-success-ink: #a2e7b9; + --status-warning-bg: #382b13; + --status-warning-ink: #ffd27b; + --status-error-bg: #3b201e; + --status-error-ink: #ffb0a7; + --status-destructive-bg: #401c1b; + --status-destructive-ink: #ffaaa3; + --accent-blue: #6ee2ce; + --accent-teal: #6ee2ce; + --accent-green: #9aefb7; + --accent-amber: #ffd27b; + --accent-red: #ffaaa3; + --accent-purple: #d2afe9; + --status-waiting: #ffd27b; + --overlay-bg: rgb(0 0 0 / 62%); +} diff --git a/web/src/styles/theme-light.css b/web/src/styles/theme-light.css new file mode 100644 index 000000000..416e70569 --- /dev/null +++ b/web/src/styles/theme-light.css @@ -0,0 +1,40 @@ +:root:not([data-theme='dark']) { + --bg-primary: #f3efe4; + --bg-surface: #fffdf6; + --bg-canvas: #fffdf6; + --bg-subtle: #ece6d9; + --surface-raised: #fffdf6; + --bg-surface-hover: #e2ebe5; + --bg-inset: #e6e1d5; + --border-default: #737c75; + --border-muted: #737c75; + --border-strong: #56625b; + --control-bg: #fffdf6; + --control-border: #737c75; + --text-primary: #13231d; + --text-secondary: #35483f; + --text-muted: #52625b; + --text-danger: #7d211e; + --active-ink: #006b61; + --link-ink: #006b61; + --focus-color: #006b61; + --selected-bg: #d5e9e1; + --selected-border: #006b61; + --artifact-ink: #8b3f1d; + --status-success-bg: #e2f1e5; + --status-success-ink: #165b36; + --status-warning-bg: #fff0c5; + --status-warning-ink: #714600; + --status-error-bg: #f9e6e3; + --status-error-ink: #882d27; + --status-destructive-bg: #f6dfdc; + --status-destructive-ink: #7d211e; + --accent-blue: #006b61; + --accent-teal: #006b61; + --accent-green: #166534; + --accent-amber: #8a5100; + --accent-red: #9a2822; + --accent-purple: #72518c; + --status-waiting: #8a5100; + --overlay-bg: rgb(19 35 29 / 42%); +} diff --git a/web/src/styles/tokens.css b/web/src/styles/tokens.css new file mode 100644 index 000000000..241a57353 --- /dev/null +++ b/web/src/styles/tokens.css @@ -0,0 +1,52 @@ +:root { + --font-sans: 'Figtree Variable', Figtree, ui-sans-serif, system-ui, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + --font-reading: Charter, 'Bitstream Charter', Georgia, serif; + --focus-ring: 2px solid var(--focus-color); + --transition-fast: 0.12s; + color-scheme: light; +} + +:root[data-theme='dark'] { + color-scheme: dark; +} + +:where(body, button, input, select, textarea) { + font-family: var(--font-sans); +} + +:where(body, table, [role='grid'], [data-numeric]) { + font-variant-numeric: tabular-nums; +} + +:where(code, kbd, samp, [data-metadata]) { + font-family: var(--font-mono); +} + +:where(a, [data-link]) { + color: var(--link-ink); +} + +:where([data-artifact]) { + color: var(--artifact-ink); +} + +:where(:focus-visible) { + outline-color: var(--focus-color); +} + +::selection { + background: var(--selected-bg); + color: var(--text-primary); +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + } +} diff --git a/web/src/test/setup.ts b/web/src/test/setup.ts new file mode 100644 index 000000000..f2de748b1 --- /dev/null +++ b/web/src/test/setup.ts @@ -0,0 +1,83 @@ +import { cleanup } from '@testing-library/svelte'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { afterEach, beforeEach } from 'vitest'; + +const densityCSS = readFileSync(join(process.cwd(), 'src/styles/density.css'), 'utf8'); + +const compactRowHeight = /--row-height:\s*([^;]+);/.exec(densityCSS)?.[1]?.trim(); +if (!compactRowHeight) throw new Error('density.css must define --row-height'); + +function createMemoryStorage(): Storage { + const data = new Map<string, string>(); + return { + get length() { + return data.size; + }, + clear() { + data.clear(); + }, + getItem(key) { + return data.get(key) ?? null; + }, + key(index) { + return Array.from(data.keys())[index] ?? null; + }, + removeItem(key) { + data.delete(key); + }, + setItem(key, value) { + data.set(key, value); + } + }; +} + +// Node may expose a warning-producing localStorage getter when no backing file +// is configured. Unit tests need deterministic, isolated browser storage. +Object.defineProperty(globalThis, 'localStorage', { + configurable: true, + writable: true, + value: createMemoryStorage() +}); + +Object.defineProperty(globalThis, 'sessionStorage', { + configurable: true, + writable: true, + value: createMemoryStorage() +}); + +class TestResizeObserver implements ResizeObserver { + readonly root = null; + readonly rootMargin = '0px'; + readonly thresholds = []; + disconnect(): void {} + observe(): void {} + unobserve(): void {} + takeRecords(): ResizeObserverEntry[] { + return []; + } +} + +Object.defineProperty(globalThis, 'ResizeObserver', { + configurable: true, + writable: true, + value: TestResizeObserver +}); + +Object.defineProperty(Element.prototype, 'scrollIntoView', { + configurable: true, + writable: true, + value() {} +}); + +beforeEach(() => { + document.documentElement.dataset.density = 'compact'; + document.documentElement.style.setProperty('--row-height', compactRowHeight); +}); + +afterEach(() => { + cleanup(); + document.documentElement.style.removeProperty('--row-height'); + localStorage.clear(); + sessionStorage.clear(); +}); diff --git a/web/svelte.config.js b/web/svelte.config.js new file mode 100644 index 000000000..e77986d58 --- /dev/null +++ b/web/svelte.config.js @@ -0,0 +1,4 @@ +/** @type {import('@sveltejs/vite-plugin-svelte').SvelteConfig} */ +const config = {}; + +export default config; diff --git a/web/tests/archive-management.spec.ts b/web/tests/archive-management.spec.ts new file mode 100644 index 000000000..59fd70d20 --- /dev/null +++ b/web/tests/archive-management.spec.ts @@ -0,0 +1,100 @@ +import { expect, test } from '@playwright/test'; + +const row = { + key: 'source:1:message:m1', kind: 'message', message_type: 'email', conversation_type: 'email', + title: 'Reviewed message', preview: 'Exact selection', occurred_at: '2026-07-19T10:00:00Z', + source_id: 1, source_identifier: 'archive@example.com', source_type: 'gmail', + participant_labels: ['Archive Owner'], participant_ids: [1], attachment_count: 1, + attachment_size: 20, has_attachments: true, deleted_from_source: false, + message_count: 1, match: {} +}; + +function syncRun(status: string, processed: number) { + return { + id: 9, source_id: 1, started_at: '2026-07-19T10:00:00Z', completed_at: null, + status, messages_processed: processed, messages_added: processed, messages_updated: 0, + errors_count: 0, error_message: null, cursor_before: null, cursor_after: null + }; +} + +test('archive management workspaces preserve reviewed authority and daemon job boundaries', async ({ page }) => { + const preflights: unknown[] = []; + const sourceRequests: Array<{ method: string; accept: string | null }> = []; + let syncStarted = false; + let sourceReads = 0; + + await page.route('**/api/session', (route) => route.fulfill({ + json: { auth_mode: 'loopback', https: false, plain_http_warning: false } + })); + await page.route('**/api/v1/explore/preflight', (route) => { + preflights.push(route.request().postDataJSON()); + return route.fulfill({ json: { + count: 1, estimated_bytes: 20, cache_revision: 'cache-management', search_provenance: {}, + unavailable_actions: [{ action: 'open_in_source', reason: 'trusted_source_link_unavailable' }], + action_targets: [{ action: 'export', message_id: 1, filename: 'message-1.eml' }], + operation_token: 'operation-1', expires_at: '2026-07-19T10:05:00Z' + } }); + }); + await page.route('**/api/v1/explore', (route) => route.fulfill({ json: { + rows: [row], total_count: 1, cache_revision: 'cache-management', search_provenance: {} + } })); + await page.route('**/api/v1/deletions', (route) => route.fulfill({ json: { manifests: [] } })); + await page.route('**/api/v1/sources/status', (route) => { + sourceRequests.push({ method: route.request().method(), accept: route.request().headers().accept ?? null }); + sourceReads += 1; + const active = syncStarted && sourceReads === 2 ? syncRun('running', 5) : null; + return route.fulfill({ json: { sources: [{ + id: 1, source_type: 'gmail', identifier: 'archive@example.com', display_name: 'Archive', + last_sync_at: null, updated_at: '2026-07-19T10:00:00Z', active_sync: active, + latest_sync: active, last_successful_sync: null, can_sync: !active, + ...(active ? { sync_unavailable_reason: 'sync_already_running' } : {}) + }] } }); + }); + await page.route('**/api/v1/sync/archive%40example.com', (route) => { + sourceRequests.push({ method: route.request().method(), accept: route.request().headers().accept ?? null }); + syncStarted = true; + return route.fulfill({ status: 202, json: { status: 'accepted', message: 'started' } }); + }); + await page.route('**/api/v1/saved-views', (route) => route.fulfill({ json: { saved_views: [{ + id: 7, name: 'Invoices', description: 'Canonical saved view', schema_version: 1, revision: 2, + created_at: '2026-07-19T10:00:00Z', updated_at: '2026-07-19T10:00:00Z', + canonical_state: { query: 'invoice', search_mode: 'full_text', filters: [], grouping: [], + presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'title'], inspector_pinned: false } + }] } })); + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(grid.getByText('Reviewed message')).toBeVisible(); + await grid.focus(); + await page.keyboard.press('Space'); + await expect(page.getByRole('button', { name: 'Export selection' })).toBeVisible(); + await expect(page.getByText('Open in source: trusted_source_link_unavailable')).toBeVisible(); + await expect.poll(() => preflights[0]).toMatchObject({ selection: { + mode: 'explicit', row_keys: ['source:1:message:m1'], cache_revision: 'cache-management' + } }); + + await page.keyboard.press('d'); + await expect(page.getByRole('main', { name: 'Deletions' })).toBeVisible(); + await expect(page.getByRole('dialog', { name: 'Confirm selected deletion' })).toBeVisible(); + await expect(page.getByText(/creates a staged manifest; it does not execute deletion/i)).toBeVisible(); + await page.getByRole('button', { name: 'Cancel' }).click(); + + await page.getByRole('button', { name: 'Everything' }).click(); + await page.getByRole('button', { name: 'Select all 1 matching items' }).click(); + await page.keyboard.press('Shift+d'); + await expect(page.getByRole('dialog', { name: 'Confirm matching deletion' })).toBeVisible(); + await page.getByRole('button', { name: 'Cancel' }).click(); + + await page.getByRole('button', { name: 'Sources' }).click(); + await page.getByRole('button', { name: 'Sync now Archive' }).click(); + await expect(page.getByText('5 processed')).toBeVisible(); + await expect.poll(() => sourceRequests.some((request) => request.method === 'POST')).toBe(true); + expect(sourceRequests.every((request) => request.accept !== 'text/event-stream')).toBe(true); + + await page.getByRole('button', { name: 'Saved Views' }).click(); + await page.getByRole('button', { name: 'Open Invoices' }).click(); + await expect(page.getByRole('searchbox', { name: 'Search everything' })).toHaveValue('invoice'); + await expect(page.getByRole('grid', { name: 'Everything results' })).toBeFocused(); + await expect.poll(() => JSON.parse(new URL(page.url()).searchParams.get('explore') ?? '{}').query).toBe('invoice'); +}); diff --git a/web/tests/conversation-content-frame.spec.ts b/web/tests/conversation-content-frame.spec.ts new file mode 100644 index 000000000..c1b7f84f8 --- /dev/null +++ b/web/tests/conversation-content-frame.spec.ts @@ -0,0 +1,272 @@ +import { expect, test } from '@playwright/test'; + +const row = { + key: 'source:1:message:source-1', + kind: 'message', + message_type: 'email', + conversation_type: 'email_thread', + title: 'Archived security fixture', + preview: 'A synthetic archived message', + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + source_type: 'synthetic', + participant_labels: ['Example Person'], + participant_ids: [1], + attachment_count: 0, + attachment_size: 0, + has_attachments: false, + deleted_from_source: false, + message_count: 1, + anchor_message_id: 42, + conversation_id: 7, + match: {} +}; + +test('archived content has an opaque capability boundary and durable conversation history', async ({ page, baseURL }) => { + const networkRequests: string[] = []; + const unintendedRequests: string[] = []; + const inlineRequests: Array<{ url: string; cookie?: string }> = []; + const remoteReferrers: Array<string | undefined> = []; + let releaseInitialInline!: () => void; + const initialInlineGate = new Promise<void>((resolve) => { releaseInitialInline = resolve; }); + let delayInitialInline = true; + let releaseRemoteImage!: () => void; + const remoteImageGate = new Promise<void>((resolve) => { releaseRemoteImage = resolve; }); + if (!baseURL) throw new Error('Playwright baseURL is required'); + await page.context().addCookies([{ + name: 'msgvault_session', value: 'synthetic-session', url: new URL(baseURL).origin + }]); + await page.route('**/api/session', (route) => + route.fulfill({ json: { auth_mode: 'session', https: false, plain_http_warning: false } }) + ); + await page.route('**/api/v1/explore', (route) => route.fulfill({ json: { + rows: [row], total_count: 1, cache_revision: 'cache-reader', search_provenance: {} + } })); + await page.route('**/api/v1/conversations/7**', (route) => route.fulfill({ json: { + id: 7, + anchor_id: Number(new URL(route.request().url()).searchParams.get('anchor')), + messages: [42, 43].map((id) => ({ + id, + conversation_id: 7, + subject: `${row.title} ${id}`, + message_type: 'email', + from: 'alice@example.com', + to: ['bob@example.com'], + sent_at: row.occurred_at, + snippet: row.preview, + labels: [], + has_attachments: false, + size_bytes: 10, + body: 'Plain archived body', + body_html: [ + '<button autofocus accesskey="x">Close inspector</button>', + '<form action="https://collector.example/submit"><input name="secret"></form>', + '<svg><a xlink:href="https://collector.example/svg"><text>SVG text</text></a></svg>', + '<p style="background:u/**/rl(https://collector.example/comment-css)">Safe archived words</p>', + '<p style="background:\\75\\72\\6c(https://collector.example/escaped-css)">Escaped CSS words</p>', + '<img src="cid:logo@example.com" alt="Inline logo">', + '<img src="https://images.example/chart.png?token=synthetic" alt="Chart">' + ].join(''), + attachments: [] + })), + has_before: false, + has_after: false, + total: 2 + } })); + await page.route('**/api/v1/messages/*/inline?**', async (route) => { + inlineRequests.push({ + url: route.request().url(), + cookie: route.request().headers()['cookie'] + }); + if (new URL(route.request().url()).pathname.includes('/messages/43/')) { + await route.fulfill({ status: 404, contentType: 'application/json', body: '{"error":"missing"}' }); + return; + } + if (delayInitialInline) { + delayInitialInline = false; + await initialInlineGate; + } + await route.fulfill({ + contentType: 'image/png', + body: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64') + }); + }); + await page.route('https://collector.example/**', async (route) => { + unintendedRequests.push(route.request().url()); + await route.abort(); + }); + await page.route('https://images.example/**', async (route) => { + networkRequests.push(route.request().url()); + remoteReferrers.push(route.request().headers()['referer']); + await remoteImageGate; + await route.fulfill({ + contentType: 'image/png', + body: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64') + }); + }); + + const explore = encodeURIComponent(JSON.stringify({ workspace: 'everything' })); + await page.goto(`/?feature=reader-security&explore=${explore}`); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(grid.getByText(row.title)).toBeVisible(); + await grid.focus(); + await page.keyboard.press('Enter'); + const priorURL = page.url(); + await page.getByRole('button', { name: 'View conversation' }).click(); + await expect(page.getByRole('heading', { name: 'Conversation' })).toBeVisible(); + + const frame = page.locator('iframe[title="Message body"]'); + const enter = page.getByRole('button', { name: 'Enter archived content' }); + await expect(page.getByText('Preparing archived content…')).toBeVisible(); + await expect(enter).toBeDisabled(); + await expect(frame).toHaveCount(0); + releaseInitialInline(); + await expect(frame).toHaveCount(1); + await expect(enter).toBeEnabled(); + await expect(frame).toHaveAttribute('sandbox', 'allow-scripts'); + await expect(frame).not.toHaveAttribute('sandbox', /allow-same-origin/); + const frameHandle = await frame.elementHandle(); + const contentFrame = await frameHandle?.contentFrame(); + expect(contentFrame).not.toBeNull(); + expect(await contentFrame!.evaluate(() => origin)).toBe('null'); + await expect(frame.contentFrame().getByText('Safe archived words')).toBeVisible(); + await expect(frame.contentFrame().getByText('Escaped CSS words')).toBeVisible(); + await expect(frame.contentFrame().getByRole('button')).toHaveCount(0); + await expect(frame.contentFrame().locator('img[alt="Inline logo"]')).toHaveAttribute('src', /^data:image\/png;base64,/); + await expect.poll(() => inlineRequests.length).toBe(1); + expect(inlineRequests[0]?.url).toContain('cid=logo%40example.com'); + expect(inlineRequests[0]?.cookie).toContain('msgvault_session=synthetic-session'); + const initialCSP = await frame.contentFrame().locator('meta[http-equiv="Content-Security-Policy"]').getAttribute('content'); + expect(initialCSP).toContain('img-src data:'); + expect(initialCSP).not.toContain('127.0.0.1'); + expect(networkRequests).toEqual([]); + expect(unintendedRequests).toEqual([]); + const archivedDocument = await frame.getAttribute('srcdoc'); + const shellOrigin = new URL(page.url()).origin; + expect(archivedDocument).toContain(`const o=${JSON.stringify(shellOrigin)}`); + expect(archivedDocument).not.toContain("postMessage({channel:c,nonce:n,type:'key',key:e.key},'*')"); + + // Re-embedding the exact document below an opaque unexpected parent cannot + // receive bridge messages because targetOrigin remains pinned to the shell. + await page.evaluate((srcdoc) => { + const host = document.createElement('iframe'); + host.title = 'Unexpected archived content parent'; + host.setAttribute('sandbox', 'allow-scripts'); + host.srcdoc = '<body data-received="no"><script>addEventListener("message",()=>document.body.dataset.received="yes")<\/script><iframe title="Re-embedded archived content" sandbox="allow-scripts"></iframe></body>'; + document.body.append(host); + }, archivedDocument); + const outerFrame = await page.getByTitle('Unexpected archived content parent').contentFrame(); + await outerFrame.getByTitle('Re-embedded archived content').evaluate((element, srcdoc) => { + element.setAttribute('srcdoc', srcdoc ?? ''); + }, archivedDocument); + const unexpectedFrame = outerFrame.getByTitle('Re-embedded archived content').contentFrame(); + await unexpectedFrame.locator('body').evaluate((body) => { + body.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })); + }); + await expect.poll(() => outerFrame.locator('body').getAttribute('data-received')).toBe('no'); + + await enter.focus(); + await expect(frame).toHaveJSProperty('inert', true); + const archivedWords = frame.contentFrame().getByText('Safe archived words'); + const archivedWordsBox = await archivedWords.boundingBox(); + expect(archivedWordsBox).not.toBeNull(); + await page.mouse.click( + archivedWordsBox!.x + archivedWordsBox!.width / 2, + archivedWordsBox!.y + archivedWordsBox!.height / 2 + ); + await expect(enter).toBeFocused(); + const box = await frame.boundingBox(); + expect(box).not.toBeNull(); + await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2); + await page.mouse.wheel(0, 100); + await expect(enter).toBeFocused(); + + await enter.click(); + await expect(page.getByText('Archived content active')).toBeVisible(); + await expect(frame).toHaveJSProperty('inert', false); + const nonce = await frame.contentFrame().locator('html').getAttribute('data-bridge-nonce'); + expect(nonce).toBeTruthy(); + + // Wrong source, wrong nonce, and an extra field must not exit content mode. + await page.evaluate((frameNonce) => { + postMessage({ + channel: 'msgvault-archived-content', nonce: frameNonce, + type: 'key', key: 'Escape' + }, '*'); + }, nonce); + await contentFrame!.evaluate(() => parent.postMessage({ + channel: 'msgvault-archived-content', nonce: 'wrong', type: 'key', key: 'Escape' + }, '*')); + await contentFrame!.evaluate((frameNonce) => parent.postMessage({ + channel: 'msgvault-archived-content', nonce: frameNonce, + type: 'key', key: 'Escape', extra: true + }, '*'), nonce); + await expect(page.getByText('Archived content active')).toBeVisible(); + + const shellScroll = page.locator('.frame-scroll'); + const priorScroll = await shellScroll.evaluate((element) => element.scrollTop); + await contentFrame!.evaluate((frameNonce) => parent.postMessage({ + channel: 'msgvault-archived-content', nonce: frameNonce, + type: 'scroll', deltaY: 10_001 + }, '*'), nonce); + expect(await shellScroll.evaluate((element) => element.scrollTop)).toBe(priorScroll); + + // Consent while entered synchronously exits shell content scope and detaches + // the old frame before the newly-capable document can be entered. + await page.getByRole('button', { name: 'Load 1 remote image' }).click(); + await expect(page.getByText('Archived content active')).toHaveCount(0); + await expect(page.getByLabel('Archived content controls')).toBeFocused(); + await expect(enter).toBeDisabled(); + expect(await frameHandle!.evaluate((element) => element.isConnected)).toBe(false); + await expect.poll(() => networkRequests.length).toBe(1); + expect(networkRequests[0]).toBe('https://images.example/chart.png?token=synthetic'); + expect(remoteReferrers).toEqual([undefined]); + expect(unintendedRequests).toEqual([]); + releaseRemoteImage(); + await expect(enter).toBeEnabled(); + + await enter.click(); + await expect(page.getByText('Archived content active')).toBeVisible(); + const consentedHandle = await frame.elementHandle(); + const consentedContentFrame = await consentedHandle?.contentFrame(); + const consentedNonce = await frame.contentFrame().locator('html').getAttribute('data-bridge-nonce'); + expect(consentedNonce).toBeTruthy(); + + // The exact expected replacement-frame message exits and returns focus to the shell. + await consentedContentFrame!.evaluate((frameNonce) => parent.postMessage({ + channel: 'msgvault-archived-content', nonce: frameNonce, + type: 'key', key: 'Escape' + }, '*'), consentedNonce); + await expect(page.getByText('Archived content active')).toHaveCount(0); + await expect(page.getByRole('button', { name: 'Enter archived content' })).toBeFocused(); + + await page.evaluate(() => history.back()); + const restoredAfterBrowserBack = page.getByRole('button', { name: 'View conversation' }); + await expect(restoredAfterBrowserBack).toBeVisible(); + await expect(restoredAfterBrowserBack).toBeFocused(); + expect(page.url()).toBe(priorURL); + + const reopenAtPeer = async (): Promise<void> => { + await page.getByRole('button', { name: 'View conversation' }).click(); + await expect(page.getByRole('heading', { name: 'Conversation' })).toBeVisible(); + await page.getByRole('button', { name: /Open message 43/ }).click(); + await expect(page.getByRole('article', { name: 'Selected message 43' })).toBeVisible(); + await expect(frame.contentFrame().getByText('Inline image unavailable: Inline logo')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Enter archived content' })).toBeEnabled(); + }; + + await reopenAtPeer(); + await page.getByRole('button', { name: 'Back from conversation' }).click(); + const restoredAfterToolbarBack = page.getByRole('button', { name: 'View conversation' }); + await expect(restoredAfterToolbarBack).toBeVisible(); + await expect(restoredAfterToolbarBack).toBeFocused(); + expect(page.url()).toBe(priorURL); + + await reopenAtPeer(); + await page.keyboard.press('Escape'); + const restoredAfterEscape = page.getByRole('button', { name: 'View conversation' }); + await expect(restoredAfterEscape).toBeVisible(); + await expect(restoredAfterEscape).toBeFocused(); + expect(page.url()).toBe(priorURL); +}); diff --git a/web/tests/density-restoration.spec.ts b/web/tests/density-restoration.spec.ts new file mode 100644 index 000000000..40890f476 --- /dev/null +++ b/web/tests/density-restoration.spec.ts @@ -0,0 +1,141 @@ +import { expect, test } from '@playwright/test'; + +const total = 300; + +test.beforeEach(async ({ page }) => { + await page.route('**/api/session', (route) => route.fulfill({ + json: { auth_mode: 'loopback', https: false, plain_http_warning: false } + })); + await page.route('**/api/v1/settings', (route) => route.fulfill({ json: { + settings: [ + { key: 'web.theme', value: { string: 'light' } }, + { key: 'web.density', value: { string: 'compact' } } + ], pending_restart: false + } })); +}); + +test('Everything preserves a deep focused row across density changes', async ({ page }) => { + await page.route('**/api/v1/explore', (route) => route.fulfill({ json: { + rows: Array.from({ length: total }, (_, index) => entry(index + 1)), + total_count: total, cache_revision: 'density-everything', search_provenance: {} + } })); + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await grid.evaluate((element) => { + element.scrollTop = 249 * 36; + element.dispatchEvent(new Event('scroll')); + }); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-250/); + await expect(grid.getByText('Synthetic subject 250')).toBeVisible(); + + await page.getByLabel('Temporary density').selectOption('comfortable'); + + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-250/); + await expect(grid.getByText('Synthetic subject 250').locator('xpath=ancestor::*[@role="row"]')).toHaveCSS('height', '46px'); +}); + +test('Group preserves a deep focused row across density changes', async ({ page }) => { + await page.route('**/api/v1/explore/groups', (route) => route.fulfill({ json: { + rows: Array.from({ length: total }, (_, index) => ({ + key: String(index + 1), label: `Source ${index + 1}`, count: index + 1, + estimated_bytes: 10, latest_at: '2026-07-18T12:00:00Z' + })), + total_count: total, cache_revision: 'density-groups', search_provenance: {} + } })); + const state = exploreState({ + groupingChain: ['source'], activeRow: 'group:source:250', + scrollAnchor: { key: 'group:source:250', offset: 5 } + }); + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); + const grid = page.getByRole('grid', { name: 'Everything grouped by source' }); + await expect(grid).toHaveAttribute('aria-activedescendant', /source-3A250/); + await expect(grid.getByText('Source 250')).toBeVisible(); + + await page.getByLabel('Temporary density').selectOption('comfortable'); + + await expect(grid).toHaveAttribute('aria-activedescendant', /source-3A250/); + await expect(grid.getByText('Source 250').locator('xpath=ancestor::*[@role="row"]')).toHaveCSS('height', '46px'); +}); + +test('Files preserves a deep focused row across density changes', async ({ page }) => { + await page.route('**/api/v1/files/search', (route) => route.fulfill({ json: { + files: Array.from({ length: total }, (_, index) => file(index + 1)), + total_count: total, cache_revision: 'density-files', search_provenance: {} + } })); + const state = exploreState({ workspace: 'files', activeRow: 'file:250' }); + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); + const grid = page.getByRole('grid', { name: 'Files results' }); + await expect(grid).toHaveAttribute('aria-activedescendant', 'file-row-250'); + await expect(grid.getByText('file-250.pdf')).toBeVisible(); + + await page.getByLabel('Temporary density').selectOption('comfortable'); + + await expect(grid).toHaveAttribute('aria-activedescendant', 'file-row-250'); + await expect(grid.getByText('file-250.pdf').locator('xpath=ancestor::*[@role="row"]')).toHaveCSS('height', '46px'); +}); + +test('Files restores a deep focused row when row CSS becomes ready late', async ({ page }) => { + await page.addInitScript(() => { + const original = CSSStyleDeclaration.prototype.getPropertyValue; + let rowGeometryReady = false; + CSSStyleDeclaration.prototype.getPropertyValue = function (property: string): string { + if (!rowGeometryReady && property === '--row-height') return ''; + return original.call(this, property); + }; + Object.defineProperty(window, '__releaseRowGeometry', { + value: () => { rowGeometryReady = true; } + }); + }); + await page.route('**/api/v1/files/search', (route) => route.fulfill({ json: { + files: Array.from({ length: total }, (_, index) => file(index + 1)), + total_count: total, cache_revision: 'late-density-files', search_provenance: {} + } })); + const state = exploreState({ workspace: 'files', activeRow: 'file:250' }); + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); + const grid = page.getByRole('grid', { name: 'Files results' }); + await expect(grid.getByText('Preparing files layout…')).toBeVisible(); + await expect(grid).not.toHaveAttribute('aria-activedescendant', /.+/); + const density = await page.locator('html').getAttribute('data-density'); + + await page.evaluate(() => { + (window as typeof window & { __releaseRowGeometry: () => void }).__releaseRowGeometry(); + }); + + await expect(page.locator('html')).toHaveAttribute('data-density', density ?? 'compact'); + await expect(grid).toHaveAttribute('aria-activedescendant', 'file-row-250'); + await expect(grid.getByText('file-250.pdf')).toBeVisible(); + await expect(grid.getByText('file-250.pdf').locator('xpath=ancestor::*[@role="row"]')).toHaveCSS('height', '36px'); +}); + +function exploreState(overrides: Record<string, unknown>) { + return { + schemaVersion: 2, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], + fileSort: { field: 'occurred_at', direction: 'desc' }, fileFilenameQuery: '', fileMIMEFamilies: [], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: null, selectedRow: null, inspectorPinned: false, inspectorWidth: 380, + conversationAnchor: null, scrollAnchor: null, ...overrides + }; +} + +function entry(index: number) { + return { + key: `message:${index}`, kind: 'message', message_type: 'email', conversation_type: 'email', + title: `Synthetic subject ${index}`, preview: `Synthetic excerpt ${index}`, + occurred_at: '2026-07-18T12:00:00Z', source_id: 1, source_identifier: 'archive@example.com', + source_type: 'synthetic', participant_labels: ['Example Person'], participant_ids: [1], + attachment_count: 0, attachment_size: 0, has_attachments: false, deleted_from_source: false, + message_count: 1, match: {} + }; +} + +function file(index: number) { + return { + id: index, key: `file:${index}`, entry_key: `message:${index}`, message_id: index, + conversation_id: index, occurred_at: '2026-07-18T12:00:00Z', source_id: 1, + source_type: 'synthetic', source_identifier: 'archive@example.com', + containing_title: `Containing item ${index}`, filename: `file-${index}.pdf`, + mime_type: 'application/pdf', mime_family: 'pdf', size_bytes: index, + content_state: 'missing_blob', content_available: false + }; +} diff --git a/web/tests/e2e/accessibility.spec.ts b/web/tests/e2e/accessibility.spec.ts new file mode 100644 index 000000000..0c94418e1 --- /dev/null +++ b/web/tests/e2e/accessibility.spec.ts @@ -0,0 +1,139 @@ +import AxeBuilder from '@axe-core/playwright'; +import { expect, test, type Page } from '@playwright/test'; +import { installMixedArchive } from './fixtures/mixed-archive'; + +async function assertNoViolations(page: Page, label: string) { + const result = await new AxeBuilder({ page }).analyze(); + expect(result.violations, `${label}: ${result.violations.map((v) => `${v.id}: ${v.help}`).join('; ')}`) + .toEqual([]); +} + +for (const theme of ['light', 'dark'] as const) { + for (const density of ['compact', 'comfortable'] as const) { + test(`${theme}/${density} primary workspaces and representative states have no axe violations`, async ({ page }) => { + await installMixedArchive(page); + await page.goto('/'); + await page.getByLabel('Temporary theme').selectOption(theme); + await page.getByLabel('Temporary density').selectOption(density); + + // The Relationships hub is the default landing workspace; walk its + // three panes (list, timeline, reading pane) open one at a time so + // each incremental layout gets its own axe pass. + const hub = page.getByRole('main', { name: 'Relationships' }); + await expect(hub).toBeVisible(); + const relationshipList = page.getByRole('grid', { name: 'Relationship results' }); + await expect(relationshipList.getByText('Archive Person')).toBeVisible(); + await assertNoViolations(page, `Relationships list ${theme}/${density}`); + await relationshipList.getByText('Archive Person').click(); + await expect(page.getByRole('heading', { name: 'Archive Person' })).toBeVisible(); + const relationshipTimeline = page.getByRole('grid', { name: 'Relationship activity' }); + await expect(relationshipTimeline.locator('[data-row-key]').first()).toBeVisible(); + await assertNoViolations(page, `Relationships timeline ${theme}/${density}`); + await relationshipTimeline.focus(); + await page.keyboard.press('Enter'); + await expect(page.getByRole('complementary', { name: /Inspect/ })).toBeVisible(); + await assertNoViolations(page, `Relationships reading pane ${theme}/${density}`); + await page.keyboard.press('Escape'); + + await page.getByRole('button', { name: 'Everything', exact: true }).click(); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(grid.locator('[data-row-key]').first()).toBeVisible(); + await assertNoViolations(page, `Everything ${theme}/${density}`); + await grid.focus(); + await page.keyboard.press('Enter'); + await expect(page.getByRole('complementary', { name: /Inspect/ })).toBeVisible(); + await assertNoViolations(page, `inspector ${theme}/${density}`); + await page.keyboard.press('Escape'); + + await page.keyboard.press('Shift+/'); + const keyboardHelp = page.getByRole('dialog', { name: 'Keyboard shortcuts' }); + await expect(keyboardHelp).toBeVisible(); + await assertNoViolations(page, `modal ${theme}/${density}`); + await keyboardHelp.getByRole('button', { name: 'Close' }).click(); + + for (const workspace of ['Files', 'Saved Views', 'Sources', 'Deletions', 'Settings']) { + await page.getByRole('button', { name: workspace, exact: true }).click(); + await expect(page.getByRole('heading', { level: 1, name: workspace, exact: true })).toBeVisible(); + await assertNoViolations(page, `${workspace} ${theme}/${density}`); + if (workspace === 'Files') { + const files = page.getByRole('grid', { name: 'Files results' }); + await files.focus(); + await page.keyboard.press('Enter'); + const viewer = page.getByRole('dialog', { name: 'View synthetic.txt' }); + await expect(viewer).toBeVisible(); + await assertNoViolations(page, `viewer ${theme}/${density}`); + await viewer.getByRole('button', { name: 'Close file viewer' }).click(); + } + } + }); + } +} + +for (const theme of ['light', 'dark'] as const) { + for (const density of ['compact', 'comfortable'] as const) { + test(`${theme}/${density} login, loading, empty, error, and degraded states have no axe violations`, async ({ page }) => { + let sessionRequired = true; + let exploreMode: 'loading' | 'empty' | 'error' | 'degraded' = 'loading'; + await page.addInitScript(({ theme, density }) => { + sessionStorage.setItem('msgvault.appearance.override', JSON.stringify({ theme, density })); + }, { theme, density }); + await page.route('**/api/session', (route) => route.fulfill({ json: sessionRequired + ? { auth_mode: 'required', https: true, plain_http_warning: false } + : { auth_mode: 'loopback', https: false, plain_http_warning: false } })); + await page.route('**/api/v1/settings', (route) => route.fulfill({ json: { + settings: [ + { key: 'web.theme', value: { string: 'light' } }, + { key: 'web.density', value: { string: 'compact' } } + ], pending_restart: false + } })); + await page.route('**/api/v1/explore', async (route) => { + if (exploreMode === 'loading') return new Promise(() => {}); + if (exploreMode === 'error') return route.fulfill({ + status: 500, json: { error: 'internal_error', message: 'Synthetic request failure.' } + }); + if (exploreMode === 'degraded') return route.fulfill({ status: 503, json: { + error: 'analytical_cache_unavailable', message: 'Synthetic cache unavailable.', + readiness: 'absent', recovery_action: 'msgvault build-cache' + } }); + return route.fulfill({ json: { + rows: [], total_count: 0, cache_revision: 'empty', search_provenance: {} + } }); + }); + + // This test exercises Everything's own loading/empty/error/degraded + // states, not the Relationships hub, so it lands there explicitly + // rather than relying on whatever the default landing workspace is. + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + await expect(page.locator('html')).toHaveAttribute('data-theme', theme); + await expect(page.locator('html')).toHaveAttribute('data-density', density); + await expect(page.getByRole('main', { name: 'Authentication' })).toBeVisible(); + await expect(page.getByLabel('API key')).toBeVisible(); + await assertNoViolations(page, `login ${theme}/${density}`); + + sessionRequired = false; + await page.reload(); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(grid).toHaveAttribute('aria-busy', 'true'); + await expect(page.getByTestId('everything-skeleton').first()).toBeVisible(); + await assertNoViolations(page, `loading ${theme}/${density}`); + + exploreMode = 'empty'; + await page.reload(); + await expect(page.getByText('No items match this view.')).toBeVisible(); + await assertNoViolations(page, `empty ${theme}/${density}`); + + exploreMode = 'error'; + await page.reload(); + await expect(page.getByRole('alert')).toContainText('Synthetic request failure.'); + await assertNoViolations(page, `error ${theme}/${density}`); + + exploreMode = 'degraded'; + await page.reload(); + const degraded = page.getByRole('alert'); + await expect(degraded).toContainText('Analytical cache unavailable'); + await expect(degraded).toContainText('Synthetic cache unavailable.'); + await expect(degraded).toContainText('msgvault build-cache'); + await assertNoViolations(page, `degraded ${theme}/${density}`); + }); + } +} diff --git a/web/tests/e2e/fixtures/mixed-archive.ts b/web/tests/e2e/fixtures/mixed-archive.ts new file mode 100644 index 000000000..68fb1d8a9 --- /dev/null +++ b/web/tests/e2e/fixtures/mixed-archive.ts @@ -0,0 +1,147 @@ +import { execFileSync } from 'node:child_process'; +import { readFileSync, unlinkSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import type { Page, Route } from '@playwright/test'; +import type { components } from '../../../src/lib/api/generated/schema'; + +export const CHAT_CONVERSATION_COUNT = 100; +export const RAW_CHAT_MESSAGE_COUNT = 100_000; +const FIXTURE_TIME = '2026-01-03T12:00:00Z'; + +const archivePerson = { + id: 12, + display_label: 'Archive Person', + display_name: 'Archive Person', + partial_label: false, + identifiers: [{ + type: 'email', value: 'person@example.com', display_value: 'person@example.com', + is_primary: true, provenance: 'participant_identifiers' + }], + activity_count: 3, + file_count: 1, + source_counts: [{ source_type: 'synthetic', count: 3 }], + first_at: FIXTURE_TIME, + last_at: FIXTURE_TIME, + cache_revision: 'mixed-100k' +}; + +const archiveDomain = { + domain: 'example.com', activity_count: 3, person_count: 1, file_count: 1, + source_counts: [{ source_type: 'synthetic', count: 3 }], first_at: FIXTURE_TIME, + last_at: FIXTURE_TIME, cache_revision: 'mixed-100k' +}; + +type MixedArchiveFixture = { + rawChatMessageCount: number; + chatConversationCount: number; + logicalRows: components['schemas']['EntryRow'][]; + firstPage: components['schemas']['ExploreHTTPResponse']; +}; + +let fixturePromise: Promise<MixedArchiveFixture> | undefined; + +export function loadMixedArchive(): Promise<MixedArchiveFixture> { + fixturePromise ??= Promise.resolve().then(() => { + const fixturePath = join(tmpdir(), `msgvault-mixed-archive-${process.pid}.json`); + const repositoryRoot = dirname(fileURLToPath(new URL('../../../../package.json', import.meta.url))); + execFileSync('go', [ + 'test', '-tags', 'fts5 sqlite_vec', './internal/query', + '-run', '^TestWriteMixedArchiveBrowserFixture$', '-count=1' + ], { + cwd: repositoryRoot, + env: { ...process.env, MSGVAULT_MIXED_ARCHIVE_FIXTURE: fixturePath }, + stdio: ['ignore', 'pipe', 'pipe'] + }); + const fixture = JSON.parse(readFileSync(fixturePath, 'utf8')) as MixedArchiveFixture; + unlinkSync(fixturePath); + return fixture; + }); + return fixturePromise; +} + +export async function installMixedArchive(page: Page) { + const fixture = await loadMixedArchive(); + await page.route('**/api/session', sessionRoute); + await page.route('**/api/v1/settings', (route) => route.fulfill({ json: { + settings: [ + { key: 'web.theme', value: { string: 'light' } }, + { key: 'web.density', value: { string: 'compact' } } + ], pending_restart: false + } })); + await page.route('**/api/v1/explore', (route) => route.fulfill({ json: fixture.firstPage })); + await page.route('**/api/v1/explore/groups', (route) => route.fulfill({ json: { + rows: [{ key: 'synthetic_chat', label: 'Synthetic chat', count: CHAT_CONVERSATION_COUNT, + estimated_bytes: RAW_CHAT_MESSAGE_COUNT * 64, latest_at: '2026-01-02T12:00:00Z' }], + total_count: 1, cache_revision: 'mixed-100k', search_provenance: {} + } })); + await page.route('**/api/v1/explore/preflight', (route) => route.fulfill({ json: { + count: 1, estimated_bytes: 2048, cache_revision: 'mixed-100k', search_provenance: {}, + unavailable_actions: [], action_targets: [], operation_token: 'synthetic-operation', + expires_at: '2026-01-03T12:05:00Z' + } })); + await page.route('**/api/v1/relationships', (route) => route.fulfill({ json: { + rows: [{ + canonical_id: 12, display_label: 'Archive Person', last_at: FIXTURE_TIME, member_ids: [12], score: 1, + signals: { + last_interaction_at: FIXTURE_TIME, meeting_count: 0, meetings_together: 0, modalities: 1, + received_from_them: 1, sent_count: 2, sent_to_them: 1 + } + }], + total_count: 1, cache_revision: 'mixed-100k', identity_revision: 1 + } })); + await page.route('**/api/v1/relationships/12/timeline', (route) => route.fulfill({ json: { + canonical_id: 12, identity_revision: 1, cache_revision: 'mixed-100k', + rows: [fixture.logicalRows[0]], total_count: 1 + } })); + await page.route('**/api/v1/people/search', (route) => route.fulfill({ json: { + rows: [archivePerson], total_count: 1, cache_revision: 'mixed-100k', search_provenance: {} + } })); + await page.route('**/api/v1/people/12', (route) => route.fulfill({ json: archivePerson })); + await page.route('**/api/v1/people/12/summary', (route) => route.fulfill({ json: { + summary: archivePerson, cache_revision: 'mixed-100k', search_provenance: {} + } })); + await page.route('**/api/v1/people/12/timeline', (route) => route.fulfill({ json: { + rows: [fixture.logicalRows[0]], total_count: 1, + cache_revision: 'mixed-100k', search_provenance: {} + } })); + await page.route('**/api/v1/people/12/files/search', (route) => route.fulfill({ json: { + files: [], total_count: 0, cache_revision: 'mixed-100k', search_provenance: {} + } })); + await page.route('**/api/v1/domains/search', (route) => route.fulfill({ json: { + rows: [archiveDomain], total_count: 1, cache_revision: 'mixed-100k', search_provenance: {} + } })); + await page.route('**/api/v1/domains/example.com', (route) => route.fulfill({ json: archiveDomain })); + await page.route('**/api/v1/domains/example.com/summary', (route) => route.fulfill({ json: { + summary: archiveDomain, cache_revision: 'mixed-100k', search_provenance: {} + } })); + await page.route('**/api/v1/domains/example.com/timeline', (route) => route.fulfill({ json: { + rows: [fixture.logicalRows[0]], total_count: 1, + cache_revision: 'mixed-100k', search_provenance: {} + } })); + await page.route('**/api/v1/domains/example.com/files/search', (route) => route.fulfill({ json: { + files: [], total_count: 0, cache_revision: 'mixed-100k', search_provenance: {} + } })); + await page.route('**/api/v1/files/search', (route) => route.fulfill({ json: { + files: [{ id: 1, key: 'file:1', entry_key: 'message:100001', message_id: 100001, + conversation_id: 100001, occurred_at: '2026-01-03T12:00:00Z', source_id: 1, + source_type: 'synthetic', source_identifier: 'archive@example.com', + containing_title: 'Synthetic email', filename: 'synthetic.txt', mime_type: 'text/plain', + mime_family: 'text', size_bytes: 2048, content_state: 'unsupported', content_available: true }], + total_count: 1, cache_revision: 'mixed-100k', search_provenance: {} + } })); + await page.route('**/api/v1/files/1', (route) => route.fulfill({ json: { + id: 1, message_id: 100001, conversation_id: 100001, filename: 'synthetic.txt', + mime_type: 'text/plain', size_bytes: 2048, content_hash: 'a'.repeat(64), + content_state: 'local_content', content_available: true + } })); + await page.route('**/api/v1/saved-views', (route) => route.fulfill({ json: { saved_views: [] } })); + await page.route('**/api/v1/sources/status', (route) => route.fulfill({ json: { sources: [] } })); + await page.route('**/api/v1/deletions', (route) => route.fulfill({ json: { manifests: [] } })); + return fixture; +} + +function sessionRoute(route: Route) { + return route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }); +} diff --git a/web/tests/e2e/keyboard.spec.ts b/web/tests/e2e/keyboard.spec.ts new file mode 100644 index 000000000..fb826e343 --- /dev/null +++ b/web/tests/e2e/keyboard.spec.ts @@ -0,0 +1,208 @@ +import { expect, test, type Page } from '@playwright/test'; +import { loadMixedArchive } from './fixtures/mixed-archive'; + +async function tabTo(page: Page, accessibleName: string, limit = 120, key: 'Tab' | 'Shift+Tab' = 'Tab') { + const seen = new Set<string>(); + for (let index = 0; index < limit; index += 1) { + const name = await page.evaluate(() => { + const active = document.activeElement; + if (!active) return ''; + if (!active.matches('button, input, select, textarea, [role]')) return ''; + return active.getAttribute('aria-label') || active.textContent?.trim() || ''; + }); + seen.add(name); + if (name.includes(accessibleName)) return; + await page.keyboard.press(key); + } + throw new Error(`Keyboard focus did not reach ${accessibleName}; saw ${[...seen].join(' | ')}`); +} + +test('pointer-free archive journey preserves focus, announcements, and history', async ({ page }) => { + const fixture = await loadMixedArchive(); + const firstServedRow = fixture.firstPage.rows[0]!; + const terminalServedRow = fixture.firstPage.rows.at(-1)!; + let authenticated = false; + await page.route('**/api/session', (route) => route.fulfill({ json: authenticated + ? { auth_mode: 'session', csrf_token: 'csrf', https: true, plain_http_warning: false } + : { auth_mode: 'required', https: true, plain_http_warning: false } })); + await page.route('**/api/session/login', (route) => { + authenticated = true; + return route.fulfill({ json: { auth_mode: 'session', csrf_token: 'csrf', https: true, plain_http_warning: false } }); + }); + await page.route('**/api/v1/settings', (route) => route.fulfill({ json: { settings: [], pending_restart: false } })); + await page.route('**/api/v1/explore', (route) => route.fulfill({ json: fixture.firstPage })); + await page.route('**/api/v1/explore/groups', (route) => route.fulfill({ json: { + rows: [{ key: '2', label: 'Synthetic chat', count: 100, estimated_bytes: 6400000, + latest_at: '2026-01-02T12:00:00Z' }], total_count: 1, + cache_revision: 'keyboard-100k', search_provenance: {} + } })); + await page.route('**/api/v1/conversations/**', (route) => route.fulfill({ json: { + id: 1, anchor_id: 1, has_before: false, has_after: false, total: 1, + messages: [{ id: 1, conversation_id: 1, subject: 'Synthetic conversation', + message_type: 'chat', from: 'Synthetic Participant', to: [], sent_at: '2026-01-02T12:00:00Z', + snippet: 'Synthetic chat body', body: 'Synthetic chat body', body_html: '', attachments: [] }] + } })); + await page.route('**/api/v1/files/search', (route) => route.fulfill({ json: { + files: [{ id: 7, key: 'file:7', entry_key: 'message:100001', message_id: 100001, + conversation_id: 1, occurred_at: '2026-01-03T12:00:00Z', source_id: 1, + source_type: 'synthetic', source_identifier: 'archive@example.com', containing_title: 'Synthetic email', + filename: 'fixture.txt', mime_type: 'text/plain', mime_family: 'text', size_bytes: 20, + content_state: 'unsupported', content_available: true }], total_count: 1, + cache_revision: 'keyboard-100k', search_provenance: {} + } })); + await page.route('**/api/v1/explore/preflight', (route) => route.fulfill({ json: { + count: 1, estimated_bytes: 20, cache_revision: 'keyboard-100k', search_provenance: {}, + unavailable_actions: [], action_targets: [], operation_token: 'keyboard-operation', + expires_at: '2026-01-03T12:05:00Z' + } })); + await page.route('**/api/v1/deletions', (route) => route.fulfill({ json: + route.request().method() === 'POST' + ? { message_count: 1, account: 'archive@example.com', dry_run: true } + : { manifests: [] } + })); + await page.route('**/api/v1/relationships', (route) => route.fulfill({ json: { + rows: [ + { canonical_id: 1, display_label: 'Alpha Person', last_at: '2026-01-02T12:00:00Z', member_ids: [1], score: 2, + signals: { last_interaction_at: '2026-01-02T12:00:00Z', meeting_count: 0, meetings_together: 0, + modalities: 1, received_from_them: 1, sent_count: 2, sent_to_them: 1 } }, + { canonical_id: 2, display_label: 'Beta Person', last_at: '2026-01-02T12:00:00Z', member_ids: [2], score: 1, + signals: { last_interaction_at: '2026-01-02T12:00:00Z', meeting_count: 0, meetings_together: 0, + modalities: 1, received_from_them: 1, sent_count: 1, sent_to_them: 1 } } + ], total_count: 2, cache_revision: 'keyboard-100k', identity_revision: 1 + } })); + await page.route('**/api/v1/people/2', (route) => route.fulfill({ json: { + id: 2, display_label: 'Beta Person', partial_label: false, identifiers: [], + activity_count: 1, file_count: 0, source_counts: [], first_at: '2026-01-02T12:00:00Z', + last_at: '2026-01-02T12:00:00Z', cache_revision: 'keyboard-100k' + } })); + await page.route('**/api/v1/relationships/2/timeline', (route) => route.fulfill({ json: { + canonical_id: 2, identity_revision: 1, cache_revision: 'keyboard-100k', + rows: [{ + key: 'message:200', kind: 'chat', occurred_at: '2026-01-02T12:00:00Z', + preview: 'Synthetic chat body', source_id: 1, title: 'Synthetic conversation', + has_attachments: false, message_count: 1 + }], total_count: 1 + } })); + + await page.goto('/'); + await page.keyboard.press('Tab'); + await expect(page.getByLabel('API key')).toBeFocused(); + await page.keyboard.type('synthetic-key'); + await page.keyboard.press('Tab'); + await expect(page.getByRole('button', { name: 'Log in' })).toBeFocused(); + await page.keyboard.press('Enter'); + + // Relationships is the default landing workspace. j/k move the ranked + // list's selection, Enter opens a cluster into the timeline, a second + // Enter opens the reading pane, and Esc walks back one layer at a time. + await expect(page.getByRole('main', { name: 'Relationships' })).toBeVisible(); + await tabTo(page, 'Relationship results'); + const relationshipList = page.getByRole('grid', { name: 'Relationship results' }); + await expect(relationshipList).toBeFocused(); + await page.keyboard.press('j'); + await page.keyboard.press('Enter'); + await expect(page.getByRole('heading', { name: 'Beta Person' })).toBeVisible(); + + await tabTo(page, 'Relationship activity'); + const relationshipTimeline = page.getByRole('grid', { name: 'Relationship activity' }); + await expect(relationshipTimeline).toBeFocused(); + await page.keyboard.press('Enter'); + const relationshipReading = page.getByRole('complementary', { name: /Inspect Synthetic conversation/ }); + await expect(relationshipReading).toBeVisible(); + + await page.keyboard.press('Escape'); + await expect(relationshipReading).toBeHidden(); + await expect(relationshipTimeline).toBeFocused(); + await page.keyboard.press('Escape'); + await expect(relationshipList).toBeFocused(); + await expect.poll(() => + JSON.parse(new URL(page.url()).searchParams.get('explore') ?? '{}').relationshipTarget + ).toBeNull(); + await expect(page.getByRole('heading', { name: 'Beta Person' })).toBeHidden(); + + await tabTo(page, 'Everything'); + await page.keyboard.press('Enter'); + await expect(page.getByRole('main', { name: 'Everything' })).toBeVisible(); + await page.keyboard.press('/'); + const search = page.getByRole('searchbox', { name: 'Search everything' }); + await expect(search).toBeFocused(); + await page.keyboard.type('synthetic'); + await tabTo(page, 'Full text'); + await expect(page.getByRole('radio', { name: 'Full text' })).toBeFocused(); + await page.keyboard.press('ArrowRight'); + await expect(page.getByRole('radio', { name: 'Semantic' })).toHaveAttribute('aria-checked', 'true'); + await page.keyboard.press('Shift+Tab'); + await page.keyboard.press('Enter'); + await expect(page.locator('[aria-live="polite"]').filter({ hasText: `${fixture.logicalRows.length} items` }).first()).toBeVisible(); + + await tabTo(page, 'Everything results'); + await page.keyboard.press(process.platform === 'darwin' ? 'Meta+K' : 'Control+K'); + await page.keyboard.type('group by source'); + await page.keyboard.press('Enter'); + const grouped = page.getByRole('grid', { name: 'Everything grouped by source' }); + await expect(grouped).toBeFocused(); + await tabTo(page, 'Drill into Synthetic chat'); + await page.keyboard.press('Enter'); + await expect(page.getByRole('complementary', { name: /Inspect/ })).toBeVisible(); + await page.keyboard.press('Escape'); + + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(grid).toBeFocused(); + const renderedActiveRow = grid.locator(`[data-row-key="${firstServedRow.key}"]`); + await expect(grid).toHaveAttribute('aria-busy', 'false'); + await expect(renderedActiveRow).toBeVisible(); + await expect(grid).toHaveAttribute('aria-activedescendant', await renderedActiveRow.getAttribute('id') ?? ''); + await page.keyboard.press('End'); + await expect.poll(() => { + const encoded = new URL(page.url()).searchParams.get('explore'); + return JSON.parse(encoded ?? '{}').activeRow; + }).toBe(terminalServedRow.key); + await expect(grid.locator(`[data-row-key="${terminalServedRow.key}"]`)).toBeVisible(); + await page.keyboard.press('Enter'); + await expect(page.getByRole('complementary', { name: /Inspect/ })).toBeVisible(); + await tabTo(page, 'View conversation'); + await page.keyboard.press('Enter'); + await expect(page.getByRole('heading', { name: 'Conversation', exact: true })).toBeVisible(); + await page.keyboard.press('Escape'); + await expect(page.getByRole('heading', { name: 'Conversation', exact: true })).toBeHidden(); + await expect(page.getByRole('complementary', { name: /Inspect/ })).toBeVisible(); + await expect(grid).toHaveAttribute('aria-busy', 'false'); + await page.keyboard.press('Escape'); + await expect(grid).toBeFocused(); + + await tabTo(page, 'Files'); + await expect(page.getByRole('button', { name: 'Files', exact: true })).toBeFocused(); + await page.keyboard.press('Enter'); + await expect(page.getByRole('heading', { level: 1, name: 'Files' })).toBeVisible(); + const files = page.getByRole('grid', { name: 'Files results' }); + await tabTo(page, 'Files results'); + await expect(files).toBeFocused(); + await tabTo(page, 'Everything', 120, 'Shift+Tab'); + await page.keyboard.press('Enter'); + await tabTo(page, 'Everything results'); + await expect(grid).toBeFocused(); + await page.keyboard.press('Space'); + await expect(grid.locator('[aria-selected="true"]')).toHaveCount(1); + const everythingURL = page.url(); + const everythingActiveRow = await grid.getAttribute('aria-activedescendant'); + expect(everythingActiveRow).not.toBeNull(); + await page.keyboard.press('d'); + await expect(page.getByRole('main', { name: 'Deletions' })).toBeVisible(); + await expect(page.getByRole('dialog', { name: 'Confirm selected deletion' })).toBeVisible(); + await tabTo(page, 'Cancel'); + await page.keyboard.press('Enter'); + await tabTo(page, 'Dry run'); + await page.keyboard.press('Enter'); + await expect(page.getByRole('status').filter({ hasText: /Dry run/i })).toBeVisible(); + + const deletionsURL = page.url(); + await page.goBack(); + await expect(page).toHaveURL(everythingURL); + await expect(page.getByRole('main', { name: 'Everything' })).toBeVisible(); + await expect(grid).toBeFocused(); + await expect(grid).toHaveAttribute('aria-activedescendant', everythingActiveRow!); + await page.goForward(); + await expect(page).toHaveURL(deletionsURL); + await expect(page.getByRole('main', { name: 'Deletions' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Deletions', exact: true })).toBeFocused(); +}); diff --git a/web/tests/e2e/mixed-archive.spec.ts b/web/tests/e2e/mixed-archive.spec.ts new file mode 100644 index 000000000..4fbb10770 --- /dev/null +++ b/web/tests/e2e/mixed-archive.spec.ts @@ -0,0 +1,28 @@ +import { expect, test } from '@playwright/test'; +import { + CHAT_CONVERSATION_COUNT, + RAW_CHAT_MESSAGE_COUNT, + installMixedArchive +} from './fixtures/mixed-archive'; + +test('100k raw chat fragments reach Everything only as logical conversation rows', async ({ page }) => { + const fixture = await installMixedArchive(page); + expect(fixture.rawChatMessageCount).toBe(RAW_CHAT_MESSAGE_COUNT); + expect(fixture.chatConversationCount).toBe(CHAT_CONVERSATION_COUNT); + expect(fixture.logicalRows.filter((row) => row.kind === 'conversation')).toHaveLength(CHAT_CONVERSATION_COUNT); + for (const kind of ['email', 'event', 'meeting', 'item']) { + expect(fixture.logicalRows.some((row) => row.kind === kind)).toBe(true); + } + expect(fixture.firstPage.rows).toHaveLength(50); + expect(fixture.firstPage.total_count).toBe(fixture.logicalRows.length); + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(grid).toBeVisible(); + await expect(grid.locator('[data-row-key]').first()).toBeVisible(); + await expect(grid.getByText('Preview text for message 1 about various topics', { exact: true })).toHaveCount(0); + const renderedLogicalChats = await grid.locator('[data-row-key*="conversation:"]').count(); + expect(renderedLogicalChats).toBeGreaterThan(0); + expect(renderedLogicalChats).toBeLessThanOrEqual(50); + await expect(page.getByText(`${fixture.logicalRows.length} items`)).toBeVisible(); +}); diff --git a/web/tests/e2e/security.spec.ts b/web/tests/e2e/security.spec.ts new file mode 100644 index 000000000..157c84f43 --- /dev/null +++ b/web/tests/e2e/security.spec.ts @@ -0,0 +1,145 @@ +import { expect, test } from '@playwright/test'; + +const secureRow = { + key: 'source:1:message:secure', kind: 'message', message_type: 'email', conversation_type: 'email_thread', + title: 'Synthetic secure message', preview: 'Archived content', occurred_at: '2026-01-03T12:00:00Z', + source_id: 1, source_identifier: 'archive@example.com', source_type: 'synthetic', + participant_labels: ['Synthetic Person'], participant_ids: [1], attachment_count: 0, + attachment_size: 0, has_attachments: false, deleted_from_source: false, message_count: 1, + anchor_message_id: 42, conversation_id: 7, match: {} +}; + +test('sanitized archived HTML requires remote-image consent and rejects forged frame messages', async ({ page }) => { + const remoteRequests: string[] = []; + await page.route('**/api/session', (route) => route.fulfill({ json: { + auth_mode: 'session', csrf_token: 'csrf', https: true, plain_http_warning: false + } })); + await page.route('**/api/v1/explore', (route) => route.fulfill({ json: { + rows: [secureRow], total_count: 1, cache_revision: 'security', search_provenance: {} + } })); + await page.route('**/api/v1/conversations/7**', (route) => route.fulfill({ json: { + id: 7, anchor_id: 42, has_before: false, has_after: false, total: 1, + messages: [{ id: 42, conversation_id: 7, subject: secureRow.title, message_type: 'email', + from: 'alice@example.com', to: ['bob@example.com'], sent_at: secureRow.occurred_at, + snippet: secureRow.preview, body: 'Plain body', attachments: [], + body_html: '<script>parent.document.body.remove()</script><form action="https://collector.example/"><input autofocus></form><img src="https://images.example/remote.png" alt="Remote chart"><p>Safe body</p>' }] + } })); + await page.route('https://images.example/**', (route) => { + remoteRequests.push(route.request().url()); + return route.fulfill({ contentType: 'image/png', body: Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64') }); + }); + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await grid.focus(); + await page.keyboard.press('Enter'); + await page.getByRole('button', { name: 'View conversation' }).click(); + const frame = page.locator('iframe[title="Message body"]'); + await expect(frame).toHaveAttribute('sandbox', 'allow-scripts'); + const frameHandle = await frame.elementHandle(); + const contentFrame = await frameHandle?.contentFrame(); + expect(contentFrame).not.toBeNull(); + expect(await contentFrame!.evaluate(() => origin)).toBe('null'); + await expect(frame.contentFrame().locator('script')).toHaveCount(1); + await expect(frame.contentFrame().locator('form,input')).toHaveCount(0); + const csp = await frame.contentFrame().locator('meta[http-equiv="Content-Security-Policy"]').getAttribute('content'); + expect(csp).toContain("default-src 'none'"); + expect(csp).toContain("object-src 'none'"); + expect(remoteRequests).toEqual([]); + + const enter = page.getByRole('button', { name: 'Enter archived content' }); + await enter.click(); + const nonce = await frame.contentFrame().locator('html').getAttribute('data-bridge-nonce'); + await page.evaluate((frameNonce) => postMessage({ + channel: 'msgvault-archived-content', nonce: frameNonce, type: 'key', key: 'Escape' + }, '*'), nonce); + await contentFrame!.evaluate(() => parent.postMessage({ + channel: 'msgvault-archived-content', nonce: 'forged', type: 'key', key: 'Escape' + }, '*')); + await expect(page.getByText('Archived content active')).toBeVisible(); + + await page.getByRole('button', { name: 'Load 1 remote image' }).click(); + await expect.poll(() => remoteRequests).toHaveLength(1); + await expect(page.getByLabel('Archived content controls')).toBeFocused(); +}); + +test('activating a replacement API key rejects the old browser authority and requires login', async ({ page }) => { + let daemonRestarted = false; + let settingsPatch: { body: unknown; csrf: string | undefined; cookie: string | undefined } | undefined; + const authorizedAfterPatchCookies: Array<string | undefined> = []; + const rejectedCookies: Array<string | undefined> = []; + const restartBootstrapCookies: Array<string | undefined> = []; + const daemonKey = { + key: 'server.api_key', kind: 'secret', secret: { configured: true }, restart_required: true + }; + await page.route('**/api/session', (route) => { + if (daemonRestarted) { + restartBootstrapCookies.push(route.request().headers().cookie); + return route.fulfill({ + json: { auth_mode: 'required', https: true, plain_http_warning: false } + }); + } + return route.fulfill({ + headers: { 'Set-Cookie': 'msgvault_session=old-authority; Path=/; HttpOnly; SameSite=Strict' }, + json: { auth_mode: 'session', csrf_token: 'old-csrf', https: true, plain_http_warning: false } + }); + }); + await page.route('**/api/v1/settings', async (route) => { + if (route.request().method() === 'PATCH') { + settingsPatch = { + body: route.request().postDataJSON(), + csrf: route.request().headers()['x-csrf-token'], + cookie: route.request().headers().cookie + }; + return route.fulfill({ + headers: { ETag: '"settings-b"' }, + json: { settings: [daemonKey], pending_restart: true } + }); + } + return route.fulfill({ + headers: { ETag: '"settings-a"' }, + json: { settings: [daemonKey], pending_restart: false } + }); + }); + await page.route('**/api/v1/explore', (route) => { + if (daemonRestarted) { + rejectedCookies.push(route.request().headers().cookie); + return route.fulfill({ status: 401, json: { + error: 'unauthorized', message: 'The activated key invalidated the old session.' + } }); + } + if (settingsPatch) authorizedAfterPatchCookies.push(route.request().headers().cookie); + return route.fulfill({ json: { + rows: [], total_count: 0, cache_revision: 'before-activation', search_provenance: {} + } }); + }); + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + await expect(page.getByRole('main', { name: 'Everything' })).toBeVisible(); + await page.getByRole('button', { name: 'Settings', exact: true }).click(); + await page.getByLabel('New daemon API key').fill('replacement-key'); + await page.getByLabel('I understand the API key changes after restart').check(); + await page.getByRole('button', { name: 'Save settings' }).click(); + await expect(page.getByRole('status')).toContainText('pending restart'); + expect(settingsPatch).toEqual({ + body: { + updates: [{ key: 'server.api_key', secret: { action: 'set', value: 'replacement-key' } }], + confirm_api_key_restart: true + }, + csrf: 'old-csrf', + cookie: expect.stringContaining('msgvault_session=old-authority') + }); + + await page.getByRole('button', { name: 'Everything', exact: true }).click(); + await expect(page.getByRole('main', { name: 'Everything' })).toBeVisible(); + expect(authorizedAfterPatchCookies).toEqual([expect.stringContaining('msgvault_session=old-authority')]); + expect(rejectedCookies).toEqual([]); + + daemonRestarted = true; + await page.reload(); + await expect(page.getByRole('main', { name: 'Authentication' })).toBeVisible(); + await expect(page.getByLabel('API key')).toBeVisible(); + expect(restartBootstrapCookies).toEqual([expect.stringContaining('msgvault_session=old-authority')]); + expect(rejectedCookies).toEqual([]); +}); diff --git a/web/tests/everything-inspector.spec.ts b/web/tests/everything-inspector.spec.ts new file mode 100644 index 000000000..565446173 --- /dev/null +++ b/web/tests/everything-inspector.spec.ts @@ -0,0 +1,198 @@ +import { expect, test } from '@playwright/test'; + +function entry(index: number) { + return { + key: `message:${index}`, + kind: 'message', + message_type: 'email', + conversation_type: 'email', + title: `Synthetic subject ${index}`, + preview: `Synthetic excerpt ${index}`, + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + source_type: 'synthetic', + participant_labels: ['Example Person'], + participant_ids: [1], + attachment_count: 1, + attachment_size: 128, + has_attachments: true, + deleted_from_source: false, + message_count: 1, + match: {} + }; +} + +function exploreURLState(overrides: Record<string, unknown> = {}) { + return { + schemaVersion: 1, + workspace: 'everything', + query: '', + searchMode: 'full_text', + filters: [], + groupingChain: [], + presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], + columnWidths: {}, + activeRow: null, + selectedRow: null, + inspectorPinned: false, + conversationAnchor: null, + scrollAnchor: null, + ...overrides + }; +} + +test('pinned inspector width and visibility restore through browser history', async ({ page }) => { + await page.route('**/api/session', (route) => + route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) + ); + await page.route('**/api/v1/explore', (route) => route.fulfill({ + json: { + rows: [entry(1), entry(2)], + total_count: 2, + cache_revision: 'cache-inspector', + search_provenance: {} + } + })); + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(grid.getByText('Synthetic subject 1')).toBeVisible(); + await grid.focus(); + await page.keyboard.press('Enter'); + + const inspector = page.getByRole('complementary', { name: 'Inspect Synthetic subject 1' }); + await expect(inspector).toBeVisible(); + await expect(page.locator('.kit-detail-drawer-overlay')).toHaveCount(0); + const resize = page.getByRole('button', { name: 'Resize inspector' }); + await resize.press('ArrowLeft'); + await resize.press('ArrowLeft'); + await expect(inspector).toHaveCSS('width', '428px'); + + await page.getByRole('button', { name: 'Close inspector' }).click(); + await expect(inspector).toHaveCount(0); + await expect(grid).toBeFocused(); + + await page.goBack(); + const restored = page.getByRole('complementary', { name: 'Inspect Synthetic subject 1' }); + await expect(restored).toBeVisible(); + await expect(restored).toHaveCSS('width', '428px'); + + await page.goForward(); + await expect(restored).toHaveCount(0); +}); + +test('a direct multi-target inspector URL restores through refresh, Back, and Forward', async ({ page }) => { + const requests: Array<{ cursor?: string; limit?: number }> = []; + await page.route('**/api/session', (route) => + route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) + ); + await page.route('**/api/v1/explore', async (route) => { + const body = route.request().postDataJSON() as { cursor?: string; limit?: number }; + requests.push(body); + const offset = body.cursor ? Number(body.cursor.slice('page:'.length)) : 0; + await route.fulfill({ json: { + rows: Array.from({ length: 500 }, (_, index) => entry(offset + index + 1)), + total_count: 1500, + cache_revision: 'cache-direct', + search_provenance: {}, + ...(offset + 500 < 1500 ? { next_cursor: `page:${offset + 500}` } : {}) + } }); + }); + const state = exploreURLState({ + activeRow: 'message:1', + selectedRow: 'message:1200', + scrollAnchor: { key: 'message:1', offset: 5 } + }); + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(page.getByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeVisible(); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-1/); + expect(requests).toHaveLength(3); + expect(requests.every(({ limit }) => (limit ?? 0) <= 500)).toBe(true); + + await page.reload(); + await expect(page.getByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeVisible(); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-1/); + expect(requests).toHaveLength(6); + + await page.getByRole('button', { name: 'Settings' }).evaluate((button: HTMLButtonElement) => button.click()); + await page.goBack(); + await expect(page.getByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeVisible(); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-1/); + await expect.poll(() => requests.length).toBe(9); + await page.goForward(); + await expect(page.getByRole('button', { name: 'Settings', exact: true })).toHaveAttribute('aria-current', 'page'); + await expect(page.getByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toHaveCount(0); +}); + +test('a drilled aggregate restores after grouped rows clear and through Back and Forward', async ({ page }) => { + await page.route('**/api/session', (route) => + route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) + ); + await page.route('**/api/v1/explore/groups', async (route) => { + const body = route.request().postDataJSON() as { + filters?: Array<{ dimension: string; values: string[] }>; + }; + const detail = body.filters?.some((filter) => + filter.dimension === 'source' && filter.values.includes('7')); + await route.fulfill({ json: { + rows: [{ + key: '7', label: detail ? 'Restored source detail' : 'Example source group', + count: 12, estimated_bytes: 42, latest_at: '2026-07-18T12:00:00Z' + }], + total_count: 1, cache_revision: 'cache-groups', search_provenance: {} + } }); + }); + await page.route('**/api/v1/explore', (route) => route.fulfill({ json: { + rows: [], total_count: 0, cache_revision: 'cache-groups', search_provenance: {} + } })); + const state = exploreURLState({ groupingChain: ['source'] }); + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); + const grouped = page.getByRole('grid', { name: 'Everything grouped by source' }); + await expect(grouped.getByText('Example source group')).toBeVisible(); + + await page.getByRole('button', { name: 'Drill into Example source group' }).click(); + await expect(page.getByRole('complementary', { name: 'Inspect Restored source detail' })).toBeVisible(); + await expect(grouped).toHaveCount(0); + + await page.goBack(); + await expect(page.getByRole('grid', { name: 'Everything grouped by source' })).toBeVisible(); + await page.goForward(); + await expect(page.getByRole('complementary', { name: 'Inspect Restored source detail' })).toBeVisible(); +}); + +test('global command and Escape shortcuts stay suspended in editable controls', async ({ page }) => { + await page.route('**/api/session', (route) => + route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) + ); + await page.route('**/api/v1/explore', (route) => route.fulfill({ json: { + rows: [], total_count: 0, cache_revision: 'cache-editable', search_provenance: {} + } })); + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + await page.evaluate(() => { + const textarea = document.createElement('textarea'); + textarea.id = 'shortcut-textarea'; + const editable = document.createElement('div'); + editable.id = 'shortcut-editable'; + editable.contentEditable = 'true'; + editable.tabIndex = 0; + document.body.append(textarea, editable); + }); + + for (const locator of [ + page.getByRole('searchbox', { name: 'Search everything' }), + page.locator('#shortcut-textarea'), + page.locator('#shortcut-editable') + ]) { + await locator.focus(); + await page.keyboard.press('Control+K'); + await page.keyboard.press('Meta+K'); + await expect(page.getByRole('dialog', { name: 'Everything commands' })).toHaveCount(0); + await page.keyboard.press('Escape'); + await expect(locator).toBeFocused(); + } +}); diff --git a/web/tests/everything-virtualization.spec.ts b/web/tests/everything-virtualization.spec.ts new file mode 100644 index 000000000..08f008574 --- /dev/null +++ b/web/tests/everything-virtualization.spec.ts @@ -0,0 +1,259 @@ +import { expect, test } from '@playwright/test'; + +function syntheticRow(index: number) { + return { + key: `message:${index}`, + kind: 'message', + message_type: 'email', + conversation_type: 'email', + title: `Synthetic subject ${index}`, + preview: `Synthetic excerpt ${index}`, + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + source_type: 'synthetic', + participant_labels: ['Example Person'], + participant_ids: [1], + attachment_count: 0, + attachment_size: 0, + has_attachments: false, + deleted_from_source: false, + message_count: 1, + match: {} + }; +} + +test('50,000 rows keep a bounded keyed DOM and stable grid focus', async ({ page }) => { + const total = 50_000; + const pageSize = 500; + const requests: Array<{ cursor?: string; limit?: number }> = []; + + await page.route('**/api/session', (route) => + route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) + ); + await page.route('**/api/v1/explore', async (route) => { + const body = route.request().postDataJSON() as { cursor?: string; limit?: number }; + requests.push(body); + const offset = body.cursor ? Number(body.cursor.slice('page:'.length)) : 0; + const count = Math.min(pageSize, total - offset); + const rows = Array.from({ length: count }, (_, pageIndex) => { + const index = offset + pageIndex + 1; + return { + key: `message:${index}`, + kind: index % 2 === 0 ? 'message' : 'future_item', + message_type: index % 2 === 0 ? 'email' : 'future_type', + conversation_type: 'email', + title: `Synthetic subject ${index}`, + preview: `Synthetic excerpt ${index}`, + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + source_type: 'synthetic', + participant_labels: ['Example Person'], + participant_ids: [1], + attachment_count: 0, + attachment_size: 0, + has_attachments: false, + deleted_from_source: false, + message_count: 1, + match: {} + }; + }); + expect(rows.length).toBeLessThanOrEqual(pageSize); + await new Promise((resolve) => setTimeout(resolve, 5)); + await route.fulfill({ + json: { + rows, + total_count: total, + cache_revision: 'cache-e2e', + search_provenance: {}, + ...(offset + count < total ? { next_cursor: `page:${offset + count}` } : {}) + } + }); + }); + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + expect( + await page.evaluate(() => getComputedStyle(document.documentElement).getPropertyValue('--accent-blue').trim()) + ).toBe('#006b61'); + const mutedContrast = await page.evaluate(() => { + const style = getComputedStyle(document.documentElement); + const luminance = (color: string) => { + const channels = color.match(/[0-9a-f]{2}/gi)!.map((part) => Number.parseInt(part, 16) / 255); + const linear = channels.map((value) => value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4); + return 0.2126 * linear[0]! + 0.7152 * linear[1]! + 0.0722 * linear[2]!; + }; + const ratio = (left: string, right: string) => { + const values = [luminance(left), luminance(right)].sort((a, b) => b - a); + return (values[0]! + 0.05) / (values[1]! + 0.05); + }; + const muted = style.getPropertyValue('--text-muted').trim(); + return ['--bg-primary', '--bg-surface', '--bg-surface-hover', '--bg-inset'].map((token) => ({ + token, + ratio: ratio(muted, style.getPropertyValue(token).trim()) + })); + }); + for (const { token, ratio } of mutedContrast) expect(ratio, token).toBeGreaterThanOrEqual(4.5); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(grid).toBeVisible(); + await expect.poll(async () => grid.locator('[role="row"]').count()).toBeGreaterThan(1); + expect(await grid.locator('[role="row"]').count()).toBeLessThan(80); + expect(requests).toHaveLength(1); + expect(requests[0]?.limit).toBeLessThanOrEqual(pageSize); + + await grid.evaluate((element) => { + element.scrollTop = (500 - 10) * 36; + element.dispatchEvent(new Event('scroll')); + }); + await expect.poll(() => requests.length).toBe(2); + await expect(grid.getByText('Synthetic subject 501')).toBeVisible(); + await grid.focus(); + await page.keyboard.press('PageDown'); + await page.keyboard.press('PageDown'); + await expect.poll(async () => { + const active = await grid.getAttribute('aria-activedescendant'); + return Number(active?.match(/message-3a-(\d+)/)?.[1] ?? 0); + }).toBeGreaterThan(500); + + await page.keyboard.press('End'); + await expect(grid).toHaveAttribute('aria-busy', 'true'); + await expect(page.getByText(/^Loading more…/)).toBeVisible(); + await expect(grid).toBeFocused(); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-50000/, { timeout: 15_000 }); + await expect(grid.getByText('Synthetic subject 50000')).toBeVisible(); + expect(requests).toHaveLength(100); + expect(requests.every(({ limit }) => (limit ?? 0) <= pageSize)).toBe(true); + expect(await grid.locator('[role="row"]').count()).toBeLessThan(80); + const activeId = await grid.getAttribute('aria-activedescendant'); + expect(await page.locator(`#${activeId}`).count()).toBe(1); +}); + +test('committed searches survive transient typing across real back and forward traversal', async ({ page }) => { + await page.route('**/api/session', (route) => + route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) + ); + await page.route('**/api/v1/explore', (route) => + route.fulfill({ + json: { + rows: [], + total_count: 0, + cache_revision: 'cache-history', + search_provenance: route.request().postDataJSON().query + ? { lexical_index_revision: 'fts-history' } + : {} + } + }) + ); + + const explore = encodeURIComponent(JSON.stringify({ workspace: 'everything' })); + await page.goto(`/?feature=preview&explore=${explore}`); + const search = page.getByRole('searchbox', { name: 'Search everything' }); + await search.fill('alpha'); + await page.getByRole('button', { name: 'Search', exact: true }).click(); + await expect(search).toHaveValue('alpha'); + + await search.fill('beta draft'); + await search.fill('beta'); + await page.getByRole('button', { name: 'Search', exact: true }).click(); + await expect(search).toHaveValue('beta'); + + await page.goBack(); + await expect(search).toHaveValue('alpha'); + expect(new URL(page.url()).searchParams.get('feature')).toBe('preview'); + await page.goBack(); + await expect(search).toHaveValue(''); + await page.goForward(); + await expect(search).toHaveValue('alpha'); + await page.goForward(); + await expect(search).toHaveValue('beta'); +}); + +test('End stops safely when a cursor repeats without progress', async ({ page }) => { + const requests: Array<{ cursor?: string }> = []; + await page.route('**/api/session', (route) => + route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) + ); + await page.route('**/api/v1/explore', async (route) => { + const body = route.request().postDataJSON() as { cursor?: string }; + requests.push(body); + await route.fulfill({ + json: { + rows: [syntheticRow(body.cursor ? 2 : 1)], + total_count: 3, + cache_revision: 'cache-repeat', + search_provenance: {}, + next_cursor: 'page:2' + } + }); + }); + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(grid.getByText('Synthetic subject 1')).toBeVisible(); + await grid.focus(); + await page.keyboard.press('End'); + + await expect(page.getByRole('alert')).toContainText(/repeated a cursor|no row progress/i); + expect(requests).toHaveLength(2); + await expect(grid).toHaveAttribute('aria-busy', 'false'); +}); + +test('deep durable focus and scroll restore through real back and reload with 500-row pages', async ({ page }) => { + const total = 6_000; + const requests: Array<{ cursor?: string; limit?: number }> = []; + await page.route('**/api/session', (route) => + route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) + ); + await page.route('**/api/v1/explore', async (route) => { + const body = route.request().postDataJSON() as { cursor?: string; limit?: number }; + requests.push(body); + const offset = body.cursor ? Number(body.cursor.slice('page:'.length)) : 0; + const count = Math.min(500, total - offset); + await route.fulfill({ + json: { + rows: Array.from({ length: count }, (_, index) => syntheticRow(offset + index + 1)), + total_count: total, + cache_revision: 'cache-deep', + search_provenance: {}, + ...(offset + count < total ? { next_cursor: `page:${offset + count}` } : {}) + } + }); + }); + const state = { + schemaVersion: 1, + workspace: 'everything', + query: '', + searchMode: 'full_text', + filters: [], + groupingChain: [], + presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], + columnWidths: { title: 340 }, + activeRow: 'message:5500', + selectedRow: null, + inspectorPinned: false, + conversationAnchor: null, + scrollAnchor: { key: 'message:5490', offset: 7 } + }; + const url = `/?feature=preview&explore=${encodeURIComponent(JSON.stringify(state))}`; + + await page.goto(url); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-5500/); + await expect(grid.getByText('Synthetic subject 5500')).toBeVisible(); + expect(requests).toHaveLength(11); + expect(requests.every(({ limit }) => (limit ?? 0) <= 500)).toBe(true); + expect(await grid.locator('[role="row"]').count()).toBeLessThan(80); + + await page.getByRole('button', { name: 'Settings' }).click(); + await page.goBack(); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-5500/); + await expect(grid.getByText('Synthetic subject 5500')).toBeVisible(); + + await page.reload(); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-5500/); + await expect(grid.getByText('Synthetic subject 5500')).toBeVisible(); + expect(requests.every(({ limit }) => (limit ?? 0) <= 500)).toBe(true); + expect(await grid.locator('[role="row"]').count()).toBeLessThan(80); +}); diff --git a/web/tests/files-viewer.spec.ts b/web/tests/files-viewer.spec.ts new file mode 100644 index 000000000..5c24b9dcf --- /dev/null +++ b/web/tests/files-viewer.spec.ts @@ -0,0 +1,253 @@ +import { expect, test, type Page } from '@playwright/test'; + +function exploreURLState() { + return { + schemaVersion: 2, + workspace: 'files', + query: '', + searchMode: 'full_text', + filters: [], + groupingChain: [], + presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + fileSort: { field: 'occurred_at', direction: 'desc' }, + fileFilenameQuery: '', + fileMIMEFamilies: [], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], + columnWidths: {}, + activeRow: null, + selectedRow: null, + inspectorPinned: false, + inspectorWidth: 380, + conversationAnchor: null, + scrollAnchor: null + }; +} + +function file(filename: string, mimeType: string, mimeFamily: string) { + return { + id: 7, + key: 'file:7', + entry_key: 'message:11', + message_id: 11, + conversation_id: 21, + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_type: 'synthetic', + source_identifier: 'archive@example.com', + containing_title: 'Containing item', + filename, + mime_type: mimeType, + mime_family: mimeFamily, + size_bytes: 1024, + participant_labels: ['Example Person'], + participant_domains: ['example.com'], + content_state: 'local_content', + content_available: true + }; +} + +async function prepare(page: Page, baseURL: string | undefined, row: ReturnType<typeof file>, bytes: Buffer) { + if (!baseURL) throw new Error('Playwright baseURL is required'); + await page.context().addCookies([{ + name: 'msgvault_session', value: 'browser-fixture-session', url: new URL(baseURL).origin + }]); + await page.route('**/api/session', (route) => route.fulfill({ + json: { auth_mode: 'session', https: false, plain_http_warning: false } + })); + await page.route('**/api/v1/files/search', (route) => route.fulfill({ json: { + files: [row], total_count: 1, cache_revision: 'cache-files', search_provenance: {} + } })); + await page.route('**/api/v1/files/7', (route) => route.fulfill({ json: { + id: 7, message_id: 11, conversation_id: 21, filename: row.filename, + mime_type: row.mime_type, size_bytes: bytes.length, content_hash: 'a'.repeat(64), + content_state: 'local_content', content_available: true + } })); + let contentCookie = ''; + await page.route(`**/api/v1/files/${row.id}/content`, async (route) => { + contentCookie = route.request().headers().cookie ?? ''; + await route.fulfill({ body: bytes, headers: { + 'Content-Type': row.mime_type, + 'Content-Disposition': `attachment; filename="${row.filename}"`, + 'X-Content-Type-Options': 'nosniff' + } }); + }); + const state = exploreURLState(); + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); + return () => contentCookie; +} + +test('Escape closes an authenticated image once, suspends background shortcuts, and restores exact keyboard focus', async ({ page, baseURL }) => { + await page.addInitScript(() => { + const create = URL.createObjectURL.bind(URL); + const revoke = URL.revokeObjectURL.bind(URL); + Object.assign(window, { previewURLs: { created: 0, revoked: 0 } }); + URL.createObjectURL = (blob: Blob) => { + (window as unknown as { previewURLs: { created: number } }).previewURLs.created += 1; + return create(blob); + }; + URL.revokeObjectURL = (url: string) => { + (window as unknown as { previewURLs: { revoked: number } }).previewURLs.revoked += 1; + revoke(url); + }; + }); + const png = Buffer.from( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', + 'base64' + ); + const contentCookie = await prepare(page, baseURL, file('pixel.png', 'image/png', 'image'), png); + + const grid = page.getByRole('grid', { name: 'Files results' }); + await page.getByRole('searchbox', { name: 'Filter filename' }).fill('pixel'); + await grid.focus(); + await expect(grid).toHaveAttribute('aria-activedescendant', 'file-row-7'); + await page.keyboard.press('Home'); + await expect.poll(() => { + const encoded = new URL(page.url()).searchParams.get('explore'); + return encoded ? (JSON.parse(encoded) as { activeRow?: string }).activeRow : undefined; + }).toBe('file:7'); + const filteredURL = page.url(); + await page.keyboard.press('Enter'); + await expect(page.getByRole('img', { name: 'Preview pixel.png' })).toBeVisible(); + expect(contentCookie()).toContain('msgvault_session=browser-fixture-session'); + await page.keyboard.press('f'); + await expect(page.getByRole('button', { name: 'Filters' })).toHaveAttribute('aria-expanded', 'false'); + await page.evaluate(() => { + (window as unknown as { previewEscapePrevented?: boolean }).previewEscapePrevented = false; + window.addEventListener('keydown', (event) => { + if (event.key === 'Escape') { + (window as unknown as { previewEscapePrevented?: boolean }).previewEscapePrevented = event.defaultPrevented; + } + }, { once: true }); + }); + await page.keyboard.press('Escape'); + await expect(grid).toBeFocused(); + await expect(page).toHaveURL(filteredURL); + await expect.poll(() => page.evaluate(() => + (window as unknown as { previewEscapePrevented?: boolean }).previewEscapePrevented + )).toBe(true); + await expect.poll(() => page.evaluate(() => + (window as unknown as { previewURLs: { created: number; revoked: number } }).previewURLs + )).toEqual({ created: 1, revoked: 1 }); + await page.goBack(); + await expect(page).toHaveURL(filteredURL); +}); + +test('malformed and mislabeled image bytes fail without allocating an object URL', async ({ page, baseURL }) => { + await page.addInitScript(() => { + Object.assign(window, { previewURLs: { created: 0 } }); + const create = URL.createObjectURL.bind(URL); + URL.createObjectURL = (blob: Blob) => { + (window as unknown as { previewURLs: { created: number } }).previewURLs.created += 1; + return create(blob); + }; + }); + await prepare(page, baseURL, file('fake.png', 'image/png', 'image'), Buffer.from('<html>not an image</html>')); + const grid = page.getByRole('grid', { name: 'Files results' }); + await grid.focus(); + await page.keyboard.press('Enter'); + + await expect(page.getByRole('alert')).toContainText('Image preview was rejected'); + expect(await page.evaluate(() => + (window as unknown as { previewURLs: { created: number } }).previewURLs.created + )).toBe(0); +}); + +test('malformed PDF fails without retaining a canvas', async ({ page, baseURL }) => { + await prepare(page, baseURL, file('fake.pdf', 'application/pdf', 'pdf'), Buffer.from('<html>not a pdf</html>')); + const grid = page.getByRole('grid', { name: 'Files results' }); + await grid.focus(); + await page.keyboard.press('Enter'); + + await expect(page.getByRole('alert')).toContainText('file signature is invalid'); + await expect(page.locator('.pdf-preview canvas')).toHaveCount(0); +}); + +test('opening a containing conversation and one Back restores the exact Files URL and active row', async ({ page, baseURL }) => { + await page.route('**/api/v1/explore', (route) => route.fulfill({ json: { + rows: [{ + key: 'message:11', kind: 'message', message_type: 'email', conversation_type: 'email_thread', + title: 'Containing item', preview: 'Message preview', occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, source_identifier: 'archive@example.com', source_type: 'synthetic', + participant_labels: ['Example Person'], participant_ids: [1], attachment_count: 1, + attachment_size: 1024, has_attachments: true, deleted_from_source: false, message_count: 1, + conversation_id: 21, anchor_message_id: 11, match: {} + }], + total_count: 1, cache_revision: 'cache-files', search_provenance: {} + } })); + await page.route('**/api/v1/conversations/21**', (route) => route.fulfill({ json: { + conversation_id: 21, anchor_id: 11, has_before: false, has_after: false, total: 1, + messages: [{ + id: 11, conversation_id: 21, subject: 'Containing item', from: 'Example Person', to: [], + sent_at: '2026-07-18T12:00:00Z', snippet: 'Message preview', body: 'Archived body', + body_html: '', attachments: [] + }] + } })); + await prepare(page, baseURL, file('fixture.pdf', 'application/pdf', 'pdf'), tinyPDF('Back fixture')); + const grid = page.getByRole('grid', { name: 'Files results' }); + await grid.focus(); + await page.keyboard.press('Enter'); + const filesURL = page.url(); + + await page.getByRole('button', { name: 'Open containing conversation' }).click(); + await expect(page.getByRole('region', { name: 'Containing conversation' })).toBeVisible(); + await page.goBack(); + + await expect(page).toHaveURL(filesURL); + await expect(page.getByRole('dialog', { name: 'View fixture.pdf' })).toBeVisible(); + await expect(grid).toHaveAttribute('aria-activedescendant', 'file-row-7'); +}); + +test('real PDF renders in-app and Escape closes with shortcut isolation and exact focus restoration', async ({ page, baseURL }) => { + const pdf = tinyPDF('Fixture preview text'); + await prepare(page, baseURL, file('fixture.pdf', 'application/pdf', 'pdf'), pdf); + + const grid = page.getByRole('grid', { name: 'Files results' }); + await grid.focus(); + await page.keyboard.press('Enter'); + const preview = page.getByRole('region', { name: 'PDF preview fixture.pdf' }); + await expect(preview.getByLabel('PDF page 1', { exact: true })).toBeVisible(); + await expect(preview.locator('.pdf-text')).toContainText('Fixture preview text'); + await expect(page.locator('iframe, embed, object')).toHaveCount(0); + await page.keyboard.press('f'); + await expect(page.getByRole('button', { name: 'Filters' })).toHaveAttribute('aria-expanded', 'false'); + await page.evaluate(() => { + (window as unknown as { pdfEscapePrevented?: boolean }).pdfEscapePrevented = false; + window.addEventListener('keydown', (event) => { + if (event.key === 'Escape') { + (window as unknown as { pdfEscapePrevented?: boolean }).pdfEscapePrevented = event.defaultPrevented; + } + }, { once: true }); + }); + + await page.keyboard.press('Escape'); + + await expect(page.getByRole('dialog', { name: 'View fixture.pdf' })).toHaveCount(0); + await expect(grid).toBeFocused(); + await expect.poll(() => page.evaluate(() => + (window as unknown as { pdfEscapePrevented?: boolean }).pdfEscapePrevented + )).toBe(true); +}); + +function tinyPDF(text: string): Buffer { + const escaped = text.replace(/[()\\]/g, (character) => `\\${character}`); + const stream = `BT /F1 18 Tf 36 100 Td (${escaped}) Tj ET`; + const objects = [ + '<< /Type /Catalog /Pages 2 0 R >>', + '<< /Type /Pages /Kids [3 0 R] /Count 1 >>', + '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 144] /Resources << /Font << /F1 5 0 R >> >> /Contents 4 0 R >>', + `<< /Length ${Buffer.byteLength(stream)} >>\nstream\n${stream}\nendstream`, + '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>' + ]; + let source = '%PDF-1.4\n'; + const offsets = [0]; + for (let index = 0; index < objects.length; index += 1) { + offsets.push(Buffer.byteLength(source)); + source += `${index + 1} 0 obj\n${objects[index]}\nendobj\n`; + } + const xref = Buffer.byteLength(source); + source += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`; + source += offsets.slice(1).map((offset) => `${String(offset).padStart(10, '0')} 00000 n \n`).join(''); + source += `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF\n`; + return Buffer.from(source, 'ascii'); +} diff --git a/web/tests/files-workspace.spec.ts b/web/tests/files-workspace.spec.ts new file mode 100644 index 000000000..5008d232e --- /dev/null +++ b/web/tests/files-workspace.spec.ts @@ -0,0 +1,81 @@ +import { expect, test } from '@playwright/test'; + +function row(id: number) { + return { + id, + key: `file:${id}`, + entry_key: `message:${id}`, + message_id: id, + conversation_id: id, + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_type: 'synthetic', + source_identifier: 'archive@example.com', + containing_title: `Containing item ${id}`, + filename: `file-${id}.pdf`, + mime_type: 'application/pdf', + mime_family: 'pdf', + size_bytes: id, + content_state: 'missing_blob', + content_available: false + }; +} + +test('Files restores a deep URL through bounded 500-row pages with bounded DOM', async ({ page }) => { + const total = 50_000; + const target = 1_200; + const requests: Array<{ cursor?: string; limit?: number }> = []; + await page.route('**/api/session', (route) => + route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) + ); + await page.route('**/api/v1/files/search', async (route) => { + const body = route.request().postDataJSON() as { cursor?: string; limit?: number }; + requests.push(body); + const offset = body.cursor ? Number(body.cursor.slice('files:'.length)) : 0; + const count = Math.min(500, total - offset); + await route.fulfill({ json: { + files: Array.from({ length: count }, (_, index) => row(offset + index + 1)), + total_count: total, + cache_revision: 'cache-files-deep', + search_provenance: {}, + ...(offset + count < total ? { next_cursor: `files:${offset + count}` } : {}) + } }); + }); + await page.route(`**/api/v1/files/${target}`, (route) => route.fulfill({ json: { + id: target, + message_id: target, + conversation_id: target, + filename: `file-${target}.pdf`, + mime_type: 'application/pdf', + size_bytes: target, + content_state: 'missing_blob', + content_available: false + } })); + const state = { + schemaVersion: 2, + workspace: 'files', + query: '', + searchMode: 'full_text', + filters: [], + groupingChain: [], + presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + fileSort: { field: 'occurred_at', direction: 'desc' }, + fileFilenameQuery: '', + fileMIMEFamilies: [], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], + columnWidths: {}, + activeRow: `file:${target}`, + selectedRow: `file:${target}`, + inspectorPinned: false, + inspectorWidth: 380, + conversationAnchor: null, + scrollAnchor: null + }; + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); + await expect(page.getByRole('dialog', { name: `View file-${target}.pdf` })).toBeVisible(); + expect(requests).toHaveLength(3); + expect(requests.every(({ limit }) => (limit ?? 0) <= 500)).toBe(true); + expect(await page.getByRole('grid', { name: 'Files results' }).locator('[role="row"]').count()).toBeLessThan(80); +}); diff --git a/web/tests/interactive-login.spec.ts b/web/tests/interactive-login.spec.ts new file mode 100644 index 000000000..33288d6b3 --- /dev/null +++ b/web/tests/interactive-login.spec.ts @@ -0,0 +1,34 @@ +import { expect, test } from '@playwright/test'; + +test('interactive login refreshes daemon appearance once while the session override wins', async ({ page }) => { + let settingsRequests = 0; + await page.addInitScript(() => { + sessionStorage.setItem('msgvault.appearance.override', JSON.stringify({ theme: 'light' })); + }); + await page.route('**/api/session', (route) => route.fulfill({ + json: { auth_mode: 'required', https: true, plain_http_warning: false } + })); + await page.route('**/api/session/login', (route) => route.fulfill({ + json: { auth_mode: 'session', csrf_token: 'csrf-token', https: true, plain_http_warning: false } + })); + await page.route('**/api/v1/settings', (route) => { + settingsRequests += 1; + return route.fulfill({ json: { + settings: [ + { key: 'web.theme', value: { string: 'dark' } }, + { key: 'web.density', value: { string: 'comfortable' } } + ], pending_restart: false + } }); + }); + await page.route('**/api/v1/explore', (route) => route.fulfill({ json: { + rows: [], total_count: 0, cache_revision: 'login', search_provenance: {} + } })); + + await page.goto('/'); + await page.getByLabel('API key').fill('test-key'); + await page.getByRole('button', { name: 'Log in' }).click(); + + await expect(page.locator('html')).toHaveAttribute('data-theme', 'light'); + await expect(page.locator('html')).toHaveAttribute('data-density', 'comfortable'); + await expect.poll(() => settingsRequests).toBe(1); +}); diff --git a/web/tests/presentations.spec.ts b/web/tests/presentations.spec.ts new file mode 100644 index 000000000..8a301407f --- /dev/null +++ b/web/tests/presentations.spec.ts @@ -0,0 +1,223 @@ +import { expect, test } from '@playwright/test'; + +const rows = [1, 2].map((id) => ({ + key: `message:${id}`, kind: 'message', message_type: 'email', conversation_type: 'email_thread', + title: `Presentation message ${id}`, preview: `Pasta analysis ${id}`, + occurred_at: `2026-07-${20 - id}T12:00:00Z`, source_id: 1, + source_identifier: 'archive@example.com', source_type: 'synthetic', + participant_labels: ['Example Person'], participant_ids: [1], attachment_count: 1, + attachment_size: 2048, has_attachments: true, deleted_from_source: false, + message_count: 1, anchor_message_id: id, conversation_id: id, match: {} +})); + +test('Show as preserves analytical meaning, keyboard focus, history, and Saved Views', async ({ page }) => { + const predicates: Array<Record<string, unknown>> = []; + const savedViews: Array<Record<string, unknown>> = []; + await page.route('**/api/session', (route) => route.fulfill({ json: { + auth_mode: 'session', csrf_token: 'csrf', https: true, plain_http_warning: false + } })); + await page.route('**/api/v1/explore/files', async (route) => { + const body = route.request().postDataJSON() as { predicate: Record<string, unknown> }; + predicates.push(body.predicate); + await route.fulfill({ json: { + files: [{ id: 7, key: 'message:1:file:7', entry_key: 'message:1', message_id: 1, conversation_id: 1, + occurred_at: rows[0]!.occurred_at, source_id: 1, + source_identifier: 'archive@example.com', title: rows[0]!.title, + filename: 'pasta-analysis.pdf', mime_type: 'application/pdf', size: 2048 }], + total_count: 1, cache_revision: 'presentation-cache', search_provenance: { lexical_index_revision: 'fts-1' } + } }); + }); + await page.route('**/api/v1/files/7', (route) => route.fulfill({ json: { + id: 7, message_id: 1, conversation_id: 1, filename: 'pasta-analysis.pdf', + mime_type: 'application/pdf', size_bytes: 2048, + content_state: 'missing_blob', content_available: false + } })); + await page.route('**/api/v1/explore', async (route) => { + const body = route.request().postDataJSON() as Record<string, unknown>; + predicates.push(body); + await route.fulfill({ json: { + rows, total_count: rows.length, cache_revision: 'presentation-cache', + search_provenance: { lexical_index_revision: 'fts-1' } + } }); + }); + await page.route('**/api/v1/explore/match-counts', (route) => route.fulfill({ json: { + counts: [], cache_revision: 'presentation-cache', lexical_index_revision: 'fts-1', + canonical_query_hash: 'pasta' + } })); + await page.route('**/api/v1/saved-views', async (route) => { + if (route.request().method() === 'POST') { + const request = route.request().postDataJSON() as Record<string, unknown>; + const view = { id: 1, revision: 1, created_at: '2026-07-20T12:00:00Z', + updated_at: '2026-07-20T12:00:00Z', ...request }; + savedViews.push(view); + return route.fulfill({ json: view }); + } + return route.fulfill({ json: { saved_views: savedViews } }); + }); + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + await expect(page.getByRole('grid', { name: 'Everything results' })).toBeVisible(); + await page.getByRole('searchbox', { name: 'Search everything' }).fill('pasta'); + await page.getByRole('button', { name: 'Search', exact: true }).click(); + + const showAs = page.getByRole('combobox', { name: 'Show as' }); + await showAs.selectOption('timeline'); + const timeline = page.getByRole('region', { name: 'Canonical activity timeline' }); + await expect(timeline).toBeVisible(); + await expect(timeline.getByText('Presentation message 1')).toBeVisible(); + expect(new URL(page.url()).search).toContain('timeline'); + + await showAs.selectOption('files'); + const files = page.getByRole('grid', { name: 'Files in current context' }); + await expect(files).toBeVisible(); + await expect(files.getByText('pasta-analysis.pdf')).toBeVisible(); + expect(predicates.at(-1)).toMatchObject({ query: 'pasta', search_mode: 'full_text', presentation: 'files' }); + + await files.focus(); + await page.keyboard.press('Home'); + await page.keyboard.press('Enter'); + const viewer = page.getByRole('dialog', { name: 'View pasta-analysis.pdf' }); + await expect(viewer).toBeVisible(); + expect(JSON.parse(new URL(page.url()).searchParams.get('explore') ?? '{}')) + .toMatchObject({ selectedRow: 'attachment:7', activeRow: 'message:1:file:7' }); + + await page.goBack(); + await expect(viewer).not.toBeVisible(); + await expect(files).toBeFocused(); + await page.goForward(); + await expect(viewer).toBeVisible(); + await page.reload(); + await expect(viewer).toBeVisible(); + + await viewer.getByRole('button', { name: 'Close file viewer' }).click(); + await expect(viewer).not.toBeVisible(); + await expect(files).toBeFocused(); + expect(JSON.parse(new URL(page.url()).searchParams.get('explore') ?? '{}').selectedRow).toBeNull(); + + await files.focus(); + await page.keyboard.press('Enter'); + await expect(viewer).toBeVisible(); + await viewer.getByRole('button', { name: 'Open containing item' }).click(); + await expect(page.getByRole('complementary', { name: 'Inspect Presentation message 1' })).toBeVisible(); + const tableState = JSON.parse(new URL(page.url()).searchParams.get('explore') ?? '{}'); + expect(tableState).toMatchObject({ presentation: 'table', activeRow: 'message:1', scrollAnchor: null }); + expect(tableState.activeRow).not.toContain(':file:'); + + await page.goBack(); + await expect(files).toBeVisible(); + await expect(files).toBeFocused(); + await page.goBack(); + await expect(timeline).toBeVisible(); + await expect(page.getByRole('grid', { name: 'Everything results' })).toBeFocused(); + await page.goForward(); + await expect(files).toBeVisible(); + await expect(files).toBeFocused(); + + await page.getByRole('button', { name: 'Saved Views', exact: true }).click(); + await page.getByLabel('Name').fill('Pasta files'); + await page.getByRole('button', { name: 'Save current view' }).click(); + expect((savedViews[0]!.canonical_state as Record<string, unknown>).presentation).toBe('files'); + await page.getByRole('button', { name: 'Open Pasta files' }).click(); + await expect(files).toBeVisible(); + await expect(files.getByText('pasta-analysis.pdf')).toBeVisible(); + expect(new URL(page.url()).search).toContain('files'); +}); + +test('Files presentation restores a deep paged anchor with bounded DOM and visible fallback', async ({ page }) => { + const pageSize = 100; + const total = 650; + let requests = 0; + await page.route('**/api/session', (route) => route.fulfill({ json: { + auth_mode: 'loopback', https: false, plain_http_warning: false + } })); + await page.route('**/api/v1/explore/files', async (route) => { + requests += 1; + const body = route.request().postDataJSON() as { cursor?: string }; + const pageIndex = body.cursor ? Number(body.cursor.replace('page-', '')) : 0; + const start = pageIndex * pageSize + 1; + const end = Math.min(total, start + pageSize - 1); + await route.fulfill({ json: { + files: Array.from({ length: end - start + 1 }, (_, offset) => { + const id = start + offset; + return { + id, key: `message:${id}:file:${id}`, entry_key: `message:${id}`, + message_id: id, conversation_id: id, occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, source_identifier: 'archive@example.com', + title: `Containing item ${id}`, filename: `deep-${id}.pdf`, + mime_type: 'application/pdf', size: 2048 + }; + }), + total_count: total, cache_revision: 'deep-files', search_provenance: {}, + ...(end < total ? { next_cursor: `page-${pageIndex + 1}` } : {}) + } }); + }); + + const state = { + schemaVersion: 2, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'files', sort: [{ field: 'occurred_at', direction: 'desc' }], + fileSort: { field: 'occurred_at', direction: 'desc' }, fileFilenameQuery: '', fileMIMEFamilies: [], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: 'message:550:file:550', selectedRow: null, inspectorPinned: false, + inspectorWidth: 380, conversationAnchor: null, + scrollAnchor: { key: 'message:540:file:540', offset: 5 } + }; + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); + const grid = page.getByRole('grid', { name: 'Files in current context' }); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-550/); + await expect(grid.getByText('deep-550.pdf')).toBeVisible(); + expect(await grid.getByRole('row').count()).toBeLessThan(80); + expect(requests).toBe(6); + + await grid.focus(); + await page.keyboard.press('End'); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-600/); + expect(requests).toBe(6); + + await page.keyboard.press('ArrowDown'); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-601/); + expect(requests).toBe(7); + expect(await grid.getByRole('row').count()).toBeLessThan(80); + + const invalid = { ...state, activeRow: 'message:999:file:999', scrollAnchor: null }; + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(invalid))}`); + await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-1/); + await expect(grid.getByText('deep-1.pdf')).toBeVisible(); + await expect(grid).toBeFocused(); +}); + +test('attachment deep links restore with one bounded metadata lookup', async ({ page }) => { + let filePageRequests = 0; + let metadataRequests = 0; + await page.route('**/api/session', (route) => route.fulfill({ json: { + auth_mode: 'loopback', https: false, plain_http_warning: false + } })); + await page.route('**/api/v1/explore/files', (route) => { + filePageRequests += 1; + return route.fulfill({ json: { + files: [], total_count: 10_000, next_cursor: 'page-1', + cache_revision: 'deep-file-viewer', search_provenance: {} + } }); + }); + await page.route('**/api/v1/files/640', (route) => { + metadataRequests += 1; + return route.fulfill({ json: { + id: 640, message_id: 640, conversation_id: 64, filename: 'deep-linked.pdf', + mime_type: 'application/pdf', size_bytes: 4096, + content_state: 'missing_blob', content_available: false + } }); + }); + + const state = { + schemaVersion: 2, workspace: 'everything', query: '', searchMode: 'full_text', filters: [], + groupingChain: [], presentation: 'files', sort: [{ field: 'occurred_at', direction: 'desc' }], + fileSort: { field: 'occurred_at', direction: 'desc' }, fileFilenameQuery: '', fileMIMEFamilies: [], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, + activeRow: null, selectedRow: 'attachment:640', inspectorPinned: false, + inspectorWidth: 380, conversationAnchor: null, scrollAnchor: null + }; + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); + + await expect(page.getByRole('dialog', { name: 'View deep-linked.pdf' })).toBeVisible(); + expect(filePageRequests).toBe(1); + expect(metadataRequests).toBe(1); +}); diff --git a/web/tests/relationships.spec.ts b/web/tests/relationships.spec.ts new file mode 100644 index 000000000..20616a95a --- /dev/null +++ b/web/tests/relationships.spec.ts @@ -0,0 +1,139 @@ +import { expect, test, type Page } from '@playwright/test'; + +const when = '2026-07-19T10:00:00Z'; + +const alice = { + id: 1, + display_label: 'Alice Example', + display_name: 'Alice Example', + partial_label: false, + identifiers: [], + activity_count: 4, + file_count: 1, + source_counts: [{ source_type: 'gmail', count: 3 }], + first_at: when, + last_at: when, + cache_revision: 'cache-relationships' +}; + +const domainSummary = { + domain: 'example.com', + activity_count: 3, + person_count: 2, + file_count: 1, + source_counts: [{ source_type: 'gmail', count: 3 }], + first_at: when, + last_at: when, + cache_revision: 'cache-relationships' +}; + +const messageRow = { + key: 'message:1', + kind: 'email', + occurred_at: '2026-07-18T09:00:00Z', + preview: 'Preview text', + source_id: 1, + title: 'Subject line', + has_attachments: false, + message_count: 1 +}; + +const chatBurstRow = { + key: 'burst:2:70:2026-07-18', + kind: 'chat_burst', + occurred_at: '2026-07-18T20:00:00Z', + first_at: '2026-07-18T08:00:00Z', + preview: 'Latest chat message', + source_id: 2, + title: 'Team Chat', + has_attachments: false, + message_count: 6, + anchor_message_id: 500, + conversation_id: 70 +}; + +async function prepare(page: Page) { + await page.route('**/api/session', (route) => route.fulfill({ + json: { auth_mode: 'loopback', https: false, plain_http_warning: false } + })); + await page.route('**/api/v1/relationships', (route) => route.fulfill({ json: { + rows: [{ + canonical_id: 1, display_label: 'Alice Example', last_at: when, member_ids: [1], score: 2, + signals: { + last_interaction_at: when, meeting_count: 0, meetings_together: 0, modalities: 2, + received_from_them: 1, sent_count: 3, sent_to_them: 1 + } + }], + total_count: 1, cache_revision: 'cache-relationships', identity_revision: 1 + } })); + await page.route('**/api/v1/people/1', (route) => route.fulfill({ json: alice })); + await page.route('**/api/v1/relationships/1/timeline', (route) => route.fulfill({ json: { + canonical_id: 1, identity_revision: 1, cache_revision: 'cache-relationships', + rows: [messageRow, chatBurstRow], total_count: 2 + } })); + await page.route('**/api/v1/domains/search', (route) => route.fulfill({ json: { + rows: [domainSummary], total_count: 1, cache_revision: 'cache-relationships-domains', + search_provenance: {} + } })); + await page.route('**/api/v1/conversations/70**', (route) => route.fulfill({ json: { + id: 70, anchor_id: 500, has_before: false, has_after: false, total: 1, + messages: [{ + id: 500, conversation_id: 70, subject: 'Team Chat', message_type: 'chat', + from: 'Bob Example', to: [], sent_at: '2026-07-18T20:00:00Z', + snippet: 'Latest chat message', body: 'Latest chat message', body_html: '', attachments: [] + }] + } })); +} + +test('legacy People URL lands on the Relationships hub and walks list, timeline, reading pane, facet, and history', async ({ page }) => { + await prepare(page); + + // A pre-rewrite bookmark for the deleted People workspace normalizes to + // the relationships hub instead of erroring or landing somewhere blank. + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'people' }))}`); + + const hub = page.getByRole('main', { name: 'Relationships' }); + await expect(hub).toBeVisible(); + const list = page.getByRole('grid', { name: 'Relationship results' }); + await expect(list.getByText('Alice Example')).toBeVisible(); + await expect(page.getByText('Select a person or domain to see activity.')).toBeVisible(); + await expect(page.getByRole('radio', { name: 'People' })).toHaveAttribute('aria-checked', 'true'); + + // Opening a person from the ranked list drives the controller and shows + // the timeline for that cluster. + await list.getByText('Alice Example').click(); + await expect(page.getByRole('heading', { name: 'Alice Example' })).toBeVisible(); + const timeline = page.getByRole('grid', { name: 'Relationship activity' }); + await expect(timeline.getByText('Subject line')).toBeVisible(); + + // A chat-burst row opens straight into the bounded conversation window in + // the reading pane rather than the plain entry summary. + await timeline.getByText('6 messages in Team Chat').click(); + const reading = page.getByRole('complementary', { name: /Inspect 6 messages in Team Chat/ }); + await expect(reading).toBeVisible(); + await expect(reading.getByText('Bob Example')).toBeVisible(); + await expect(reading.getByRole('heading', { level: 2, name: 'Conversation' })).toBeVisible(); + + // Toggling the facet switches the ranked list to Domains without losing + // the open person detail underneath. + await page.getByRole('radio', { name: 'Domains' }).click(); + await expect(page.getByRole('radio', { name: 'Domains' })).toHaveAttribute('aria-checked', 'true'); + await expect(list.getByText('example.com')).toBeVisible(); + + // Browser back undoes the facet toggle first... + await page.goBack(); + await expect(page.getByRole('radio', { name: 'People' })).toHaveAttribute('aria-checked', 'true'); + await expect(list.getByText('Alice Example')).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Alice Example' })).toBeVisible(); + + // ...and a second back undoes opening the person, clearing the list's + // active selection, closing the person detail, and — since the reading + // pane's own open/close state lives outside the URL entirely — closing + // the conversation reading pane that was still open underneath it too. + await page.goBack(); + await expect.poll(() => JSON.parse(new URL(page.url()).searchParams.get('explore') ?? '{}').relationshipTarget).toBeNull(); + await expect(list.getByRole('row', { name: /Alice Example/ })).toHaveAttribute('aria-selected', 'false'); + await expect(page.getByRole('heading', { name: 'Alice Example' })).toBeHidden(); + await expect(reading).toBeHidden(); + await expect(page.getByText('Select a person or domain to see activity.')).toBeVisible(); +}); diff --git a/web/tests/search-modes.spec.ts b/web/tests/search-modes.spec.ts new file mode 100644 index 000000000..94fe2e789 --- /dev/null +++ b/web/tests/search-modes.spec.ts @@ -0,0 +1,45 @@ +import { expect, test } from '@playwright/test'; + +const baseState = { + schemaVersion: 1, + workspace: 'everything', + query: 'alpha', + searchMode: 'semantic', + filters: [], + groupingChain: [], + presentation: 'table', + sort: [{ field: 'occurred_at', direction: 'desc' }], + columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], + columnWidths: {}, + activeRow: null, + selectedRow: null, + inspectorPinned: false, + conversationAnchor: null, + scrollAnchor: null +}; + +test('URL mode overrides the remembered default and remains selected through a named semantic failure', async ({ page }) => { + await page.addInitScript(() => localStorage.setItem('msgvault-search-mode', 'hybrid')); + await page.route('**/api/session', (route) => + route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) + ); + await page.route('**/api/v1/search/coverage', (route) => route.fulfill({ json: { + status: 'incomplete', eligible_count: 2, embedded_count: 1, percentage: 50, + vector_generation: 7, cache_revision: 'cache-1', actions: [] + } })); + await page.route('**/api/v1/explore', (route) => route.fulfill({ + status: 503, + json: { error: 'vector_initializing', message: 'Vector search is still building.' } + })); + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + await expect(page.getByRole('radio', { name: 'Hybrid' })).toHaveAttribute('aria-checked', 'true'); + + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(baseState))}`); + await expect(page.getByRole('radio', { name: 'Full text' })).toBeVisible(); + await expect(page.getByRole('radio', { name: 'Semantic' })).toHaveAttribute('aria-checked', 'true'); + await expect(page.getByRole('radio', { name: 'Hybrid' })).toBeVisible(); + await expect(page.getByText(/Semantic index: 50% of 2 items/)).toBeVisible(); + await expect(page.getByRole('alert')).toContainText('Vector search is still building.'); + await expect(page.getByRole('radio', { name: 'Semantic' })).toHaveAttribute('aria-checked', 'true'); +}); diff --git a/web/tests/session-navigation.spec.ts b/web/tests/session-navigation.spec.ts new file mode 100644 index 000000000..2c1ad3c53 --- /dev/null +++ b/web/tests/session-navigation.spec.ts @@ -0,0 +1,138 @@ +import { expect, test } from '@playwright/test'; + +test('Strict session cookie returns on same-origin bootstrap after a cross-site navigation', async ({ + context, + page, + baseURL +}) => { + if (!baseURL) throw new Error('Playwright baseURL is required'); + const appURL = new URL('/', baseURL).toString(); + const landingURL = `${appURL}?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`; + const cookieName = 'msgvault_session'; + const cookieValue = 'opaque-browser-session'; + const navigationCookieName = 'navigation_control'; + const navigationCookieValue = 'lax-cookie'; + let resolveBootstrapHeaders!: (headers: Record<string, string>) => void; + const bootstrapHeadersCaptured = new Promise<Record<string, string>>((resolve) => { + resolveBootstrapHeaders = resolve; + }); + + await context.addCookies([ + { + name: cookieName, + value: cookieValue, + url: appURL, + httpOnly: true, + sameSite: 'Strict' + }, + { + name: navigationCookieName, + value: navigationCookieValue, + url: appURL, + httpOnly: true, + sameSite: 'Lax' + } + ]); + + await page.route('http://cross-site.example/link', async (route) => { + await route.fulfill({ + contentType: 'text/html', + body: `<a href="${landingURL}">Open archive</a>` + }); + }); + await page.route('**/api/session', async (route) => { + resolveBootstrapHeaders(await route.request().allHeaders()); + await route.fulfill({ + contentType: 'application/json', + body: JSON.stringify({ + auth_mode: 'session', + csrf_token: 'csrf-token', + https: false, + plain_http_warning: true + }) + }); + }); + + await page.goto('http://cross-site.example/link'); + const documentRequestCaptured = page.waitForRequest( + (request) => request.url() === landingURL && request.resourceType() === 'document' + ); + await page.getByRole('link', { name: 'Open archive' }).click(); + const documentRequest = await documentRequestCaptured; + const [documentHeaders, bootstrapHeaders] = await Promise.all([ + documentRequest.allHeaders(), + bootstrapHeadersCaptured + ]); + const documentCookie = documentHeaders.cookie ?? ''; + const bootstrapCookie = bootstrapHeaders.cookie ?? ''; + + await expect(page.getByRole('main', { name: 'Everything' })).toBeVisible(); + await expect(page.getByRole('form', { name: 'Log in' })).toHaveCount(0); + expect(documentCookie).toContain(`${navigationCookieName}=${navigationCookieValue}`); + expect(documentCookie).not.toContain(`${cookieName}=${cookieValue}`); + expect(bootstrapCookie).toContain(`${cookieName}=${cookieValue}`); +}); + +test('Settings navigation sends a CSRF-protected session mutation', async ({ page }) => { + let resolvePatch!: (request: { headers: Record<string, string>; body: unknown }) => void; + const patchCaptured = new Promise<{ headers: Record<string, string>; body: unknown }>((resolve) => { + resolvePatch = resolve; + }); + await page.route('**/api/session', async (route) => { + await route.fulfill({ + json: { + auth_mode: 'session', + csrf_token: 'csrf-token', + https: true, + plain_http_warning: false + } + }); + }); + await page.route('**/api/v1/settings', async (route) => { + if (route.request().method() === 'PATCH') { + resolvePatch({ + headers: await route.request().allHeaders(), + body: route.request().postDataJSON() + }); + await route.fulfill({ + headers: { ETag: '"etag-b"' }, + json: settingsDocument('dark', true) + }); + return; + } + await route.fulfill({ + headers: { ETag: '"etag-a"' }, + json: settingsDocument('system', false) + }); + }); + + await page.goto('/'); + await page.getByRole('button', { name: 'Settings' }).click(); + await page.getByRole('main', { name: 'Settings' }).getByLabel('Theme').selectOption('dark'); + await page.getByRole('button', { name: 'Save settings' }).click(); + + const patch = await patchCaptured; + expect(patch.headers['x-csrf-token']).toBe('csrf-token'); + expect(patch.headers['if-match']).toBe('"etag-a"'); + expect(patch.body).toEqual({ + updates: [{ key: 'web.theme', value: { string: 'dark' } }], + confirm_api_key_restart: false + }); + await expect(page.getByText('Changes are pending restart.')).toBeVisible(); +}); + +function settingsDocument(theme: string, pendingRestart: boolean) { + return { + settings: [ + { + key: 'web.theme', + group: 'browser', + kind: 'string', + value: { string: theme }, + options: ['system', 'light', 'dark'], + restart_required: true + } + ], + pending_restart: pendingRestart + }; +} diff --git a/web/tests/theme-keyboard.spec.ts b/web/tests/theme-keyboard.spec.ts new file mode 100644 index 000000000..5fb0b399a --- /dev/null +++ b/web/tests/theme-keyboard.spec.ts @@ -0,0 +1,261 @@ +import { expect, test } from '@playwright/test'; + +const row = { + key: 'message:1', + kind: 'message', + message_type: 'email', + conversation_type: 'email', + title: 'Synthetic archive subject', + preview: 'Synthetic analytical excerpt for visual verification.', + occurred_at: '2026-07-18T12:00:00Z', + source_id: 1, + source_identifier: 'archive@example.com', + source_type: 'synthetic', + participant_labels: ['Example Person'], + participant_ids: [1], + attachment_count: 1, + attachment_size: 2048, + has_attachments: true, + deleted_from_source: false, + message_count: 1, + match: {} +}; + +test.beforeEach(async ({ page }) => { + await page.route('**/api/session', (route) => route.fulfill({ + json: { auth_mode: 'loopback', https: false, plain_http_warning: false } + })); + await page.route('**/api/v1/settings', (route) => route.fulfill({ + headers: { ETag: '"synthetic-settings"' }, + json: { + settings: [ + { key: 'web.theme', group: 'browser', kind: 'string', value: { string: 'system' }, options: ['system', 'light', 'dark'], restart_required: true }, + { key: 'web.density', group: 'browser', kind: 'string', value: { string: 'compact' }, options: ['compact', 'comfortable'], restart_required: true } + ], + pending_restart: true + } + })); + await page.route('**/api/v1/explore', (route) => route.fulfill({ + json: { rows: [row], total_count: 1, cache_revision: 'cache-theme', search_provenance: {} } + })); + await page.route('**/api/v1/explore/groups', (route) => route.fulfill({ + json: { + rows: [{ key: '1', label: 'Synthetic source', count: 1, estimated_bytes: 2048, latest_at: '2026-07-18T12:00:00Z' }], + total_count: 1, cache_revision: 'cache-theme', search_provenance: {} + } + })); + await page.route('**/api/v1/saved-views', (route) => route.fulfill({ + json: { saved_views: [] } + })); + await page.route('**/api/v1/files/search', (route) => route.fulfill({ + json: { + files: [{ + id: 1, key: 'file:1', entry_key: 'message:1', message_id: 1, conversation_id: 1, + occurred_at: '2026-07-18T12:00:00Z', source_id: 1, source_type: 'synthetic', + source_identifier: 'archive@example.com', containing_title: 'Synthetic archive subject', + filename: 'synthetic.pdf', mime_type: 'application/pdf', mime_family: 'pdf', size_bytes: 2048, + content_state: 'missing_blob', content_available: false + }], + total_count: 1, cache_revision: 'cache-theme', search_provenance: {} + } + })); + await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + await expect(page.getByText('Synthetic archive subject')).toBeVisible(); +}); + +test('one registry drives selection, searchable help, palette, and editable suspension', async ({ page }) => { + const grid = page.getByRole('grid', { name: 'Everything results' }); + const renderedRow = page.locator('[data-row-key="message:1"]'); + await grid.focus(); + await expect(grid).toHaveCSS('outline-style', 'solid'); + await page.keyboard.press('Shift+A'); + await expect(renderedRow).toHaveAttribute('aria-selected', 'true'); + await expect(renderedRow.getByText('✓')).toBeVisible(); + await page.keyboard.press('x'); + await expect(renderedRow).toHaveAttribute('aria-selected', 'false'); + + await page.keyboard.press('Shift+/'); + const help = page.getByRole('dialog', { name: 'Keyboard shortcuts' }); + await expect(help).toBeVisible(); + await expect(help.getByText('Select all visible rows')).toBeVisible(); + await expect(help.getByText('Clear selection')).toBeVisible(); + await help.getByRole('searchbox', { name: 'Search keyboard shortcuts' }).fill('deletion'); + await expect(help.getByText('Review selected messages for deletion')).toBeVisible(); + await expect(help.getByText('Open filters')).toHaveCount(0); + await help.getByRole('button', { name: 'Close' }).click(); + + await grid.focus(); + await page.keyboard.press('Shift+A'); + await expect(renderedRow).toHaveAttribute('aria-selected', 'true'); + await page.keyboard.press(process.platform === 'darwin' ? 'Meta+K' : 'Control+K'); + const palette = page.getByRole('dialog', { name: 'Everything commands' }); + await expect(palette).toBeVisible(); + await palette.getByRole('combobox').fill('clear selection'); + const clearSelection = palette.getByRole('option', { name: /Clear selection/ }); + await expect(clearSelection).toBeVisible(); + await expect(clearSelection).toHaveAttribute('aria-selected', 'true'); + await page.keyboard.press('Enter'); + await expect(renderedRow).toHaveAttribute('aria-selected', 'false'); + await expect(grid).toBeFocused(); + + const search = page.getByRole('searchbox', { name: 'Search everything' }); + await search.focus(); + await page.keyboard.press('Shift+A'); + await expect(renderedRow).toHaveAttribute('aria-selected', 'false'); + await page.keyboard.press(process.platform === 'darwin' ? 'Meta+K' : 'Control+K'); + await expect(palette).toHaveCount(0); + await page.keyboard.press('Tab'); + await expect(search).not.toBeFocused(); + + await page.emulateMedia({ reducedMotion: 'reduce' }); + const reduced = await page.evaluate(() => { + const probe = document.createElement('div'); + probe.style.transitionDuration = '10s'; + probe.style.animationDuration = '10s'; + document.body.append(probe); + const style = getComputedStyle(probe); + const result = { transition: style.transitionDuration, animation: style.animationDuration }; + probe.remove(); + return result; + }); + expect(Number.parseFloat(reduced.transition)).toBeLessThanOrEqual(0.00001); + expect(Number.parseFloat(reduced.animation)).toBeLessThanOrEqual(0.00001); +}); + +test('keyboard palette grouping focuses the replacement grid', async ({ page }) => { + const grid = page.getByRole('grid', { name: 'Everything results' }); + await grid.focus(); + await page.keyboard.press(process.platform === 'darwin' ? 'Meta+K' : 'Control+K'); + const palette = page.getByRole('dialog', { name: 'Everything commands' }); + await palette.getByRole('combobox').fill('group by source'); + const command = palette.getByRole('option', { name: 'Group by Source' }); + await expect(command).toHaveAttribute('aria-selected', 'true'); + + await page.keyboard.press('Enter'); + + const grouped = page.getByRole('grid', { name: 'Everything grouped by source' }); + await expect(palette).toHaveCount(0); + await expect(grouped).toBeVisible(); + await expect(grouped).toBeFocused(); +}); + +for (const theme of ['light', 'dark'] as const) { + test(`${theme} rendered Everything and Settings pairs meet contrast`, async ({ page }) => { + await page.getByLabel('Temporary theme').selectOption(theme); + await expect(page.locator('html')).toHaveAttribute('data-theme', theme); + const requiredRoles = await page.locator('html').evaluate((element) => { + const style = getComputedStyle(element); + return ['--bg-canvas', '--bg-subtle', '--border-strong', '--surface-raised', '--text-danger'] + .map((token) => [token, style.getPropertyValue(token).trim()]); + }); + expect(requiredRoles.filter(([, value]) => !value)).toEqual([]); + await expectRenderedContrast(page.locator('[data-row-key="message:1"] strong'), 4.5); + + const infoButton = page.getByRole('button', { name: 'Search', exact: true }); + await expect(infoButton).toHaveClass(/kit-button--solid/); + await expect(infoButton).toHaveClass(/kit-button--info/); + await expectRenderedContrast(infoButton, 4.5); + + const grid = page.getByRole('grid', { name: 'Everything results' }); + await grid.focus(); + await page.keyboard.press('Shift+A'); + const selectedRow = page.locator('[data-row-key="message:1"]'); + await expect(selectedRow).toHaveAttribute('aria-selected', 'true'); + await expectRenderedShadow(selectedRow, 3); + + await page.keyboard.press(process.platform === 'darwin' ? 'Meta+K' : 'Control+K'); + const palette = page.getByRole('dialog', { name: 'Everything commands' }); + const activeOption = palette.getByRole('option', { selected: true }).first(); + await expect(activeOption).toBeVisible(); + await expectRenderedContrast(activeOption, 4.5); + await expectRenderedShadow(activeOption, 3); + await page.keyboard.press('Escape'); + + await page.getByRole('button', { name: 'Saved Views' }).click(); + const workflowButton = page.getByRole('button', { name: 'Save current view' }); + await expect(workflowButton).toHaveClass(/kit-button--solid/); + await expect(workflowButton).toHaveClass(/kit-button--workflow/); + await expectRenderedContrast(workflowButton, 4.5); + + await page.getByRole('button', { name: 'Files' }).click(); + const filesGrid = page.getByRole('grid', { name: 'Files results' }); + await filesGrid.focus(); + await page.keyboard.press('ArrowDown'); + await expectRenderedShadow(filesGrid, 3); + + await page.getByRole('button', { name: 'Settings' }).click(); + const settings = page.getByRole('main', { name: 'Settings' }); + await expect(settings).toBeVisible(); + await expectRenderedContrast(settings.locator('.field-copy span').first(), 4.5); + await expectRenderedContrast(settings.locator('.pending'), 4.5); + await expectRenderedBoundary(settings.locator('.field').first(), 'borderTopColor', 3); + await expectRenderedBoundary(settings.locator('.pending'), 'borderLeftColor', 3); + }); +} + +for (const theme of ['light', 'dark'] as const) { + for (const density of ['compact', 'comfortable'] as const) { + test(`${theme} ${density} analytical shell snapshot and geometry`, async ({ page }) => { + await page.getByLabel('Temporary theme').selectOption(theme); + await page.getByLabel('Temporary density').selectOption(density); + await expect(page.locator('html')).toHaveAttribute('data-theme', theme); + await expect(page.locator('html')).toHaveAttribute('data-density', density); + await expect(page.locator('[data-row-key="message:1"]')).toHaveCSS( + 'height', density === 'compact' ? '36px' : '46px' + ); + await page.evaluate(() => document.fonts.ready); + await expect(page).toHaveScreenshot(`analytical-${theme}-${density}.png`, { + animations: 'disabled', + fullPage: true + }); + }); + } +} + +async function expectRenderedContrast(locator: import('@playwright/test').Locator, minimum: number) { + const ratio = await locator.evaluate(measureRenderedContrast, { property: 'color' }); + expect(ratio).toBeGreaterThanOrEqual(minimum); +} + +async function expectRenderedBoundary( + locator: import('@playwright/test').Locator, + property: 'borderTopColor' | 'borderLeftColor', + minimum: number +) { + const ratio = await locator.evaluate(measureRenderedContrast, { property }); + expect(ratio).toBeGreaterThanOrEqual(minimum); +} + +async function expectRenderedShadow(locator: import('@playwright/test').Locator, minimum: number) { + await expect.poll( + () => locator.evaluate(measureRenderedContrast, { property: 'boxShadow' }) + ).toBeGreaterThanOrEqual(minimum); +} + +function measureRenderedContrast( + element: Element, + { property }: { property: 'color' | 'borderTopColor' | 'borderLeftColor' | 'boxShadow' } +): number { + let current: Element | null = element; + let background = getComputedStyle(document.documentElement).backgroundColor; + while (current) { + const candidate = getComputedStyle(current).backgroundColor; + const channels = candidate.match(/[\d.]+/g)?.map(Number) ?? []; + if (channels.length === 3 || (channels[3] ?? 0) >= 0.99) { + background = candidate; + break; + } + current = current.parentElement; + } + const rendered = getComputedStyle(element)[property]; + const foreground = property === 'boxShadow' + ? rendered.match(/rgba?\([^)]+\)|#[0-9a-f]{3,8}/i)?.[0] ?? rendered + : rendered; + const luminance = (color: string) => { + const channels = (color.match(/[\d.]+/g)?.slice(0, 3).map(Number) ?? []).map((value) => value / 255); + const linear = channels.map((value) => value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4); + return 0.2126 * linear[0]! + 0.7152 * linear[1]! + 0.0722 * linear[2]!; + }; + const values = [luminance(foreground), luminance(background)].sort((a, b) => b - a); + return (values[0]! + 0.05) / (values[1]! + 0.05); +} diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-darwin.png new file mode 100644 index 0000000000000000000000000000000000000000..42741ecfed0439d3975ffa1c207773791e0d75fe GIT binary patch literal 83913 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%0Ay1Km-`#_CsixLMK&O_f#iVKgd$JU@Yigqi}j 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 literal 0 HcmV?d00001 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..905d78e086884758a7b58f9be43875c106c48140 GIT binary patch literal 77039 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�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 LN2f&l{?q>f@t$+Z literal 0 HcmV?d00001 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-darwin.png new file mode 100644 index 0000000000000000000000000000000000000000..2b2c529d6da50b9c6df718bd2915470ce0860b3a GIT binary patch literal 83770 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.<bgGlgz<UC&A5e9#idOxa)b{aNUON> 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 Iz4`Dz0HwYgcmMzZ literal 0 HcmV?d00001 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e65e1aed733f47371c8e3e67edd53de91e390e1b GIT binary patch literal 76945 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%0A3=b?FijRH8h2}JT$ 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 OGOhdi_Yij<KmRw$X6*<7 literal 0 HcmV?d00001 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-darwin.png new file mode 100644 index 0000000000000000000000000000000000000000..88ef94e8caedf01b047f02df6485a1d7e7519f5c GIT binary patch literal 84116 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 S8M(j5A`nFl1>Cb&!T$rr5;3U& literal 0 HcmV?d00001 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..80cc1c370de9bcfc5a58725b5189941f5d743a27 GIT binary patch literal 76221 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%0DfZ0sES4IG15B}GpUMCrj7VFvX#hkZx(>Avr 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 literal 0 HcmV?d00001 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-darwin.png new file mode 100644 index 0000000000000000000000000000000000000000..ba5bf66ace26a435434c32ae6cf23553f90cd981 GIT binary patch literal 84083 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�WaoPL<n6bvO&vB^={~~( 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 literal 0 HcmV?d00001 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..28a55138471c2eab3fa84c8831a11a783c702359 GIT binary patch literal 76276 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 N85^1#)ZcrO_#cpbZaM$} literal 0 HcmV?d00001 diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 000000000..13549b30f --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "target": "es2022", + "allowJs": true, + "checkJs": true, + "isolatedModules": true, + "moduleResolution": "bundler", + "resolveJsonModule": true, + "types": ["vitest/globals", "vite/client"] + }, + "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.svelte", "vite.config.ts", "playwright.config.ts"] +} diff --git a/web/vite.config.ts b/web/vite.config.ts new file mode 100644 index 000000000..951690651 --- /dev/null +++ b/web/vite.config.ts @@ -0,0 +1,22 @@ +import { svelte } from '@sveltejs/vite-plugin-svelte'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [svelte()], + build: { + manifest: true + }, + resolve: { + conditions: ['browser'] + }, + ssr: { + // kit-ui publishes Svelte source. Keep it and its icon components inside + // Vite's transform pipeline when Vitest runs in its server environment. + noExternal: ['@kenn-io/kit-ui', '@lucide/svelte'] + }, + test: { + environment: 'jsdom', + setupFiles: ['./src/test/setup.ts'], + exclude: ['tests/**', 'node_modules/**', 'dist/**'] + } +}); From c51ff301afa858dbb2c6e7ab7952f1a1efb9cc15 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 14:14:19 -0500 Subject: [PATCH 002/102] fix(web): pre-QA polish for relationship list and files header - Hide the "Show all senders" checkbox on the Domains facet, since show_all only affects the people ranking gate. - Label ranked-row counts as "N sent" (signals.sent_count) and search-result counts as "N items" (activity_count) so the number's meaning no longer changes silently between ranked and search views. - Match the files table header background to bg-surface, consistent with the other tables from the visual pass. --- .../lib/components/files/FilesWorkspace.svelte | 2 +- .../relationships/RelationshipList.svelte | 16 +++++++++++----- .../relationships/RelationshipList.test.ts | 13 +++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/web/src/lib/components/files/FilesWorkspace.svelte b/web/src/lib/components/files/FilesWorkspace.svelte index 709e4a614..159fde01a 100644 --- a/web/src/lib/components/files/FilesWorkspace.svelte +++ b/web/src/lib/components/files/FilesWorkspace.svelte @@ -576,7 +576,7 @@ .table-header, .data-row { display: grid; grid-template-columns: 112px minmax(150px, 1.5fr) minmax(120px, 1fr) 82px minmax(140px, 1.2fr) minmax(130px, 1fr) minmax(160px, 1.3fr) 105px; align-items: center; } .files-grid { display: flex; min-height: 0; flex: 1; flex-direction: column; overflow: auto; outline: none; } .files-grid:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-blue); } - .table-header { position: sticky; z-index: 1; top: 0; min-height: var(--table-header-height); flex: 0 0 auto; border-bottom: 1px solid var(--border-default); background: var(--bg-inset); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 700; text-transform: uppercase; } + .table-header { position: sticky; z-index: 1; top: 0; min-height: var(--table-header-height); flex: 0 0 auto; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 700; text-transform: uppercase; } .table-header span, .data-row span { min-width: 0; padding: 0 var(--space-3); overflow: hidden; text-align: left; text-overflow: ellipsis; white-space: nowrap; } .table-header button { width: 100%; height: var(--table-header-height); padding: 0; border: 0; background: transparent; color: inherit; cursor: pointer; font: inherit; text-align: left; text-transform: inherit; } .table-body { position: relative; min-height: 220px; flex: 0 0 auto; outline: none; } diff --git a/web/src/lib/components/relationships/RelationshipList.svelte b/web/src/lib/components/relationships/RelationshipList.svelte index 1f5193e36..51e984f20 100644 --- a/web/src/lib/components/relationships/RelationshipList.svelte +++ b/web/src/lib/components/relationships/RelationshipList.svelte @@ -26,6 +26,7 @@ label: string; lastAt: string; itemCount: number; + itemUnit: 'sent' | 'items'; modalities: number | null; score: number | null; } @@ -80,6 +81,7 @@ label: row.display_label, lastAt: row.signals.last_interaction_at, itemCount: row.signals.sent_count, + itemUnit: 'sent', modalities: row.signals.modalities, score: row.score }; @@ -91,6 +93,7 @@ label: row.domain, lastAt: row.last_at, itemCount: row.activity_count, + itemUnit: 'items', modalities: null, score: null }; @@ -101,6 +104,7 @@ label: row.display_label, lastAt: row.last_at, itemCount: row.activity_count, + itemUnit: 'items', modalities: null, score: null }; @@ -151,10 +155,12 @@ options={[{ value: 'people', label: 'People' }, { value: 'domains', label: 'Domains' }]} onchange={(value) => onFacetChange(value as RelationshipFacet)} /> </div> - <label class="show-all"> - <input type="checkbox" checked={showAll} onchange={(event) => onShowAllChange(event.currentTarget.checked)} /> - Show all senders - </label> + {#if facet === 'people'} + <label class="show-all"> + <input type="checkbox" checked={showAll} onchange={(event) => onShowAllChange(event.currentTarget.checked)} /> + Show all senders + </label> + {/if} {#if degraded === 'cache_unavailable'} <section class="named-state" role="status"> @@ -199,7 +205,7 @@ </div> <div class="row-meta"> <span class="last-at">{formatDate(view.lastAt)}</span> - <span class="item-count">{view.itemCount.toLocaleString()} items</span> + <span class="item-count">{view.itemCount.toLocaleString()} {view.itemUnit}</span> </div> {#if view.score !== null} <span class="activity-track" aria-label={`Activity score ${view.score.toFixed(2)}`}> diff --git a/web/src/lib/components/relationships/RelationshipList.test.ts b/web/src/lib/components/relationships/RelationshipList.test.ts index cb5e47d60..62c258fde 100644 --- a/web/src/lib/components/relationships/RelationshipList.test.ts +++ b/web/src/lib/components/relationships/RelationshipList.test.ts @@ -66,6 +66,8 @@ describe('RelationshipList', () => { expect(screen.getByText('Bob Example')).toBeDefined(); expect(screen.getByLabelText('1 modalities')).toBeDefined(); expect(screen.getByLabelText('2 modalities')).toBeDefined(); + expect(screen.getByText('3 sent')).toBeDefined(); + expect(screen.getByText('6 sent')).toBeDefined(); const bars = grid.querySelectorAll<HTMLElement>('.activity-bar'); expect(bars).toHaveLength(2); expect(bars[0]!.style.width).toBe('50%'); @@ -88,6 +90,16 @@ describe('RelationshipList', () => { expect(props.onShowAllChange).toHaveBeenCalledWith(true); }); + it('shows the show-all checkbox under the people facet', () => { + render(RelationshipList, baseProps()); + expect(screen.getByRole('checkbox', { name: 'Show all senders' })).toBeDefined(); + }); + + it('hides the show-all checkbox under the domains facet', () => { + render(RelationshipList, { ...baseProps(), facet: 'domains', rows: [domain('example.com')] }); + expect(screen.queryByRole('checkbox', { name: 'Show all senders' })).toBeNull(); + }); + it('switches from ranked rows to search results as the query and rows props change', async () => { const props = baseProps(); const view = render(RelationshipList, props); @@ -97,6 +109,7 @@ describe('RelationshipList', () => { await view.rerender({ ...props, query: 'ali', rows: [person(3, 'Alicia Searched')] }); expect(screen.getByText('Alicia Searched')).toBeDefined(); + expect(screen.getByText('4 items')).toBeDefined(); expect(screen.queryByText('Alice Example')).toBeNull(); }); From 4d7894449fc07759421e6c40ec0a5ced5a8ea60b Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 14:50:41 -0500 Subject: [PATCH 003/102] fix(query): credit received only to message authors, not co-recipients Incoming email/item entries credited received_from_them to every non-owner participant (sender + To + Cc), so mailing lists and bots accumulated one received unit per subscription message they merely appeared on as recipients, ranking them above real people. Gate received credit on an author_links CTE (recipient_type='from' plus messages.sender_id, resolved through canon): only the entry's author earns received credit. Chat conversations and meetings are unchanged, sent semantics are unchanged, and non-author rows still feed LastAt so ranking recency keeps matching the timeline's shared-message scope. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- internal/query/relationships.go | 28 ++++- internal/query/relationships_score_test.go | 140 +++++++++++++++++++++ 2 files changed, 167 insertions(+), 1 deletion(-) diff --git a/internal/query/relationships.go b/internal/query/relationships.go index 6eb864c9d..4f763f2a2 100644 --- a/internal/query/relationships.go +++ b/internal/query/relationships.go @@ -222,6 +222,17 @@ func validateRelationshipsRequest(request RelationshipsRequest) error { // so "interactions" excludes such rows entirely rather than merely zeroing // their contribution: left in, they would still feed MAX(occurred_at) and // inflate LastAt/LastInteractionAt with a meeting the owner never attended. +// +// received_from_them credits only the AUTHOR of an incoming entry +// ("author_links": the recipient_type='from' participant, or messages. +// sender_id for sources that record senders directly), never co-recipients. +// Without that restriction a mailing-list address accumulates one received +// unit for every subscription message it merely appears on as a recipient, +// which ranked lists and bots above real people. Chat conversations +// ('conversation' entries) are exempt: a grouped chat has no single author, +// so its per-conversation credit for every non-owner member is unchanged. +// Non-author co-recipient rows stay in "interactions" so LastAt keeps +// matching the timeline's "all shared messages" scope. func buildRelationshipsSQL(conditions, clustersGlob, ownersGlob string) string { return buildExploreLogicalSQL(conditions) + fmt.Sprintf(` ), clusters AS ( @@ -239,6 +250,16 @@ func buildRelationshipsSQL(conditions, clustersGlob, ownersGlob string) string { ), le_with_owner AS ( SELECT le.*, list_has_any(le.participant_ids, (SELECT list(participant_id) FROM owner_participant_ids)) AS with_owner FROM logical_entries le +), author_links AS ( + SELECT mr.message_id, cn.canonical_id + FROM message_recipients mr + JOIN canon cn ON cn.participant_id = mr.participant_id + WHERE mr.recipient_type = 'from' + UNION + SELECT m.id AS message_id, cn.canonical_id + FROM messages m + JOIN canon cn ON cn.participant_id = m.sender_id + WHERE m.sender_id IS NOT NULL ), interactions AS ( SELECT DISTINCT le.entry_key, @@ -247,6 +268,9 @@ func buildRelationshipsSQL(conditions, clustersGlob, ownersGlob string) string { le.occurred_at, le.is_from_me, le.with_owner, + EXISTS (SELECT 1 FROM author_links al + WHERE al.message_id = le.anchor_message_id + AND al.canonical_id = cn.canonical_id) AS is_author, exp(-? * date_diff('day', le.occurred_at, ?)) AS decay FROM le_with_owner le CROSS JOIN UNNEST(le.participant_ids) AS pid(participant_id) @@ -258,7 +282,9 @@ func buildRelationshipsSQL(conditions, clustersGlob, ownersGlob string) string { canonical_id, SUM(CASE WHEN is_from_me AND entry_kind IN ('email','conversation','item') THEN decay ELSE 0 END) AS sent_decayed, COUNT(CASE WHEN is_from_me AND entry_kind IN ('email','conversation','item') THEN 1 END) AS sent_count, - SUM(CASE WHEN NOT is_from_me AND entry_kind IN ('email','conversation','item') THEN decay ELSE 0 END) AS received_decayed, + SUM(CASE WHEN NOT is_from_me + AND (entry_kind = 'conversation' OR (entry_kind IN ('email','item') AND is_author)) + THEN decay ELSE 0 END) AS received_decayed, SUM(CASE WHEN entry_kind IN ('event','meeting') AND with_owner THEN decay ELSE 0 END) AS meetings_decayed, COUNT(CASE WHEN entry_kind IN ('event','meeting') AND with_owner THEN 1 END) AS meeting_count, COUNT(DISTINCT CASE diff --git a/internal/query/relationships_score_test.go b/internal/query/relationships_score_test.go index 562e0fc99..9505e932e 100644 --- a/internal/query/relationships_score_test.go +++ b/internal/query/relationships_score_test.go @@ -1,10 +1,12 @@ package query import ( + "context" "testing" "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRelationshipScore(t *testing.T) { @@ -69,3 +71,141 @@ func TestRelationshipScore(t *testing.T) { }) } } + +// TestRelationshipsReceivedCreditsOnlyAuthors pins the credit-assignment +// semantics for received_from_them: an incoming message credits only its +// AUTHOR (the 'from' participant), never co-recipients. A mailing-list +// address that merely appears in To on 100 incoming messages authored by +// third parties must accumulate zero received credit; after one +// owner-authored reply it passes the reciprocity gate, but its score is +// sent-only — not inflated by subscription volume. +func TestRelationshipsReceivedCreditsOnlyAuthors(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + b.AddOwnerParticipant(srcID, ownerID) + + listID := b.AddParticipant("dev@list.example", "list.example", "Dev List") + authorIDs := make([]int64, 5) + for i := range authorIDs { + authorIDs[i] = b.AddParticipant( + "author"+string(rune('a'+i))+"@example.com", "example.com", "Author") + } + + // All timestamps are "now" so per-entry decay is ~1.0 (the session's + // date-diff for the same instant may be off by a day, so decayed sums + // are asserted with tolerance, and zero sums exactly). + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + + // 100 incoming list messages: authored by rotating third parties, with + // the list address and the owner both in To. + for i := range 100 { + msgID := b.AddMessage(MessageOpt{SourceID: srcID, SentAt: now}) + b.AddFrom(msgID, authorIDs[i%len(authorIDs)], "Author") + b.AddTo(msgID, listID, "Dev List") + b.AddTo(msgID, ownerID, "Owner") + } + + // One owner-authored reply addressed to the list. + replyID := b.AddMessage(MessageOpt{SourceID: srcID, IsFromMe: true, SentAt: now}) + b.AddFrom(replyID, ownerID, "Owner") + b.AddTo(replyID, listID, "Dev List") + + engine := b.BuildEngine() + result, err := engine.Relationships(context.Background(), RelationshipsRequest{Now: now, Limit: 10}) + require.NoError(err) + require.Len(result.Rows, 1, "only the list passes the gate; the authors have no sent/meeting signal") + + row := result.Rows[0] + assert.Equal(listID, row.CanonicalID) + assert.InDelta(0.0, row.Signals.ReceivedFromThem, 1e-9, + "co-recipient volume must earn zero received credit") + assert.Equal(int64(1), row.Signals.SentCount) + assert.InDelta(1.0, row.Signals.SentToThem, 0.1) + assert.InEpsilon(relationshipWeightSent*row.Signals.SentToThem, row.Score, 1e-9, + "score must be sent-only, not inflated by 100 co-recipient messages") +} + +// TestRelationshipsReceivedCountsAuthoredMessages verifies the positive side +// of author-only credit: a person who authored 20 messages received by the +// owner earns received_from_them = 20. +func TestRelationshipsReceivedCountsAuthoredMessages(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + b.AddOwnerParticipant(srcID, ownerID) + + authorID := b.AddParticipant("carol@example.com", "example.com", "Carol") + + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + for range 20 { + msgID := b.AddMessage(MessageOpt{SourceID: srcID, SentAt: now}) + b.AddFrom(msgID, authorID, "Carol") + b.AddTo(msgID, ownerID, "Owner") + } + + engine := b.BuildEngine() + result, err := engine.Relationships(context.Background(), + RelationshipsRequest{Now: now, Limit: 10, ShowAll: true}) + require.NoError(err) + require.Len(result.Rows, 1) + + row := result.Rows[0] + assert.Equal(authorID, row.CanonicalID) + assert.InDelta(20.0, row.Signals.ReceivedFromThem, 0.5, + "every authored message earns one decayed unit of received credit") + assert.Equal(int64(0), row.Signals.SentCount) +} + +// TestRelationshipsChatReceivedCreditUnchanged pins that chat counting is +// untouched by author-only email credit: a grouped chat conversation still +// credits its non-owner members (including silent ones listed only via +// conversation_participants) when the latest message is incoming. +func TestRelationshipsChatReceivedCreditUnchanged(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + b.AddOwnerParticipant(srcID, ownerID) + + speakerID := b.AddParticipant("dave@chat.example", "chat.example", "Dave") + silentID := b.AddParticipant("erin@chat.example", "chat.example", "Erin") + + now := time.Date(2026, 1, 10, 0, 0, 0, 0, time.UTC) + chatID := b.AddMessage(MessageOpt{SourceID: srcID, MessageType: "imessage", SentAt: now}) + b.AddFrom(chatID, speakerID, "Dave") + b.AddTo(chatID, ownerID, "Owner") + + var convID int64 + for _, m := range b.messages { + if m.ID == chatID { + convID = m.ConversationID + } + } + require.NotZero(convID) + b.AddConversationParticipant(convID, silentID) + + engine := b.BuildEngine() + result, err := engine.Relationships(context.Background(), + RelationshipsRequest{Now: now, Limit: 10, ShowAll: true}) + require.NoError(err) + require.Len(result.Rows, 2) + + byCanonicalID := make(map[int64]RelationshipRow, len(result.Rows)) + for _, row := range result.Rows { + byCanonicalID[row.CanonicalID] = row + } + require.Contains(byCanonicalID, speakerID) + require.Contains(byCanonicalID, silentID) + assert.InDelta(1.0, byCanonicalID[speakerID].Signals.ReceivedFromThem, 0.1) + assert.InDelta(1.0, byCanonicalID[silentID].Signals.ReceivedFromThem, 0.1, + "chat conversations credit members without an author requirement") +} From a5e58df4bae95824da65cc8914c32173250920b9 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 14:56:21 -0500 Subject: [PATCH 004/102] fix(query): log-compress received volume in relationship scoring Received volume was dominating the relationship score linearly, letting high-volume one-way senders (notification bots, mailing lists) outrank counterparts with real reciprocal contact or shared meetings. Sent and meetings stay linear; only the received term is now ln(1+received_decayed). --- internal/query/relationships.go | 15 ++++++-- internal/query/relationships_score_test.go | 41 +++++++++++++++++++--- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/internal/query/relationships.go b/internal/query/relationships.go index 4f763f2a2..aacd757c9 100644 --- a/internal/query/relationships.go +++ b/internal/query/relationships.go @@ -36,11 +36,22 @@ type RelationshipSignals struct { } // RelationshipScore applies the spec's weights, decay having been applied in -// SQL: score = (2.0*sent + 3.0*meetings + 1.0*received) * (1 + 0.25*(modalities-1)). +// SQL: score = (2.0*sent + 3.0*meetings + 1.0*ln(1+received)) * (1 + 0.25*(modalities-1)). +// +// The received term is log-compressed (ln(1+received_decayed), via +// math.Log1p) while sent and meetings stay linear. On real archives, inbound +// volume (mailing lists, CI/notification bots, co-workers whose tickets you +// merely receive) grows far faster than genuine reciprocal contact, so a +// linear received term let high-volume one-way senders outrank people with +// real back-and-forth or shared meetings. Log-compression keeps received +// mail a positive signal without letting its raw volume dominate the score. +// received_from_them in RelationshipSignals and the API response still +// reports the raw (pre-log) decayed value — only this score composition +// changes. func RelationshipScore(s RelationshipSignals) float64 { base := relationshipWeightSent*s.SentToThem + relationshipWeightMeetings*s.MeetingsTogether + - relationshipWeightReceived*s.ReceivedFromThem + relationshipWeightReceived*math.Log1p(s.ReceivedFromThem) breadth := 1.0 if s.Modalities > 1 { breadth = 1.0 + relationshipBreadthStep*float64(s.Modalities-1) diff --git a/internal/query/relationships_score_test.go b/internal/query/relationships_score_test.go index 9505e932e..aacd0bd88 100644 --- a/internal/query/relationships_score_test.go +++ b/internal/query/relationships_score_test.go @@ -2,6 +2,7 @@ package query import ( "context" + "math" "testing" "time" @@ -31,14 +32,14 @@ func TestRelationshipScore(t *testing.T) { want: 3.0, }, { - name: "one decayed received unit scores the received weight", + name: "one decayed received unit scores the log-compressed received weight", signals: RelationshipSignals{ReceivedFromThem: 1, Modalities: 1}, - want: 1.0, + want: math.Log1p(1), }, { - name: "combined signals sum the weighted contributions", + name: "combined signals sum sent and meetings linearly but log-compress received", signals: RelationshipSignals{SentToThem: 1, MeetingsTogether: 1, ReceivedFromThem: 1, Modalities: 1}, - want: 6.0, + want: 2.0 + 3.0 + math.Log1p(1), }, { name: "three modalities apply a 1.5x breadth boost", @@ -72,6 +73,38 @@ func TestRelationshipScore(t *testing.T) { } } +// TestRelationshipScoreLogCompressesReceivedVolume pins the real-archive +// regression this fix addresses: on a 2.5M-message archive, a high-volume +// one-way sender (a GitHub collaborator whose notifications the owner merely +// receives) scored far above a genuine reciprocal counterpart (a partner +// with real back-and-forth) purely because received volume was linear. +// Log-compressing only the received term must flip that ordering by roughly +// an order of magnitude, without changing how sent or meetings contribute. +func TestRelationshipScoreLogCompressesReceivedVolume(t *testing.T) { + partner := RelationshipSignals{SentToThem: 131, ReceivedFromThem: 110, Modalities: 1} + notificationsBot := RelationshipSignals{SentToThem: 0.7, ReceivedFromThem: 31000, Modalities: 1} + + partnerScore := RelationshipScore(partner) + notificationsScore := RelationshipScore(notificationsBot) + + assert.Greater(t, partnerScore, notificationsScore, + "reciprocal partner must outscore a one-way high-volume notification sender") + assert.Greater(t, partnerScore, notificationsScore*10, + "partner must outscore the notification sender by roughly an order of magnitude") +} + +// TestRelationshipScoreMeetingsOutweighReceivedVolume pins that a +// meeting-heavy counterpart, previously buried by inbound volume, now +// outranks a received-heavy one-way counterpart even with far less sent +// activity, since meetings stay linear while received is log-compressed. +func TestRelationshipScoreMeetingsOutweighReceivedVolume(t *testing.T) { + meetingHeavy := RelationshipSignals{SentToThem: 5, MeetingsTogether: 20, Modalities: 1} + receivedHeavy := RelationshipSignals{SentToThem: 0.5, ReceivedFromThem: 500, Modalities: 1} + + assert.Greater(t, RelationshipScore(meetingHeavy), RelationshipScore(receivedHeavy), + "a meeting-heavy counterpart must outscore a received-volume-heavy one") +} + // TestRelationshipsReceivedCreditsOnlyAuthors pins the credit-assignment // semantics for received_from_them: an incoming message credits only its // AUTHOR (the 'from' participant), never co-recipients. A mailing-list From 76dfda7973b6eed9852364ea7cfd97878017b6a8 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 15:17:01 -0500 Subject: [PATCH 005/102] feat(web): flatten app background and unify the app bar Remove the decorative radial gradient behind the shell; the app now sits on a flat surface. Integrate the top bar: quiet nav tabs instead of a detached pill track, a compact brand mark, ghost-style appearance selects relabeled from 'daemon' to 'Auto', and a tightened status chip. The Files shell picks up the workspace padding and width cap so it aligns with Everything. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- web/src/lib/components/shell/AppShell.svelte | 76 +++++++++++++------ web/src/lib/components/shell/AppShell.test.ts | 2 +- 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/web/src/lib/components/shell/AppShell.svelte b/web/src/lib/components/shell/AppShell.svelte index affd5da61..043774d07 100644 --- a/web/src/lib/components/shell/AppShell.svelte +++ b/web/src/lib/components/shell/AppShell.svelte @@ -856,10 +856,10 @@ value={appearance.temporary.theme ?? 'daemon'} onchange={(event) => applyTemporaryTheme(event.currentTarget.value)} > - <option value="daemon">Theme: daemon</option> - <option value="system">Theme: system</option> - <option value="light">Theme: light</option> - <option value="dark">Theme: dark</option> + <option value="daemon">Theme: Auto</option> + <option value="system">Theme: System</option> + <option value="light">Theme: Light</option> + <option value="dark">Theme: Dark</option> </select> </label> <label> @@ -869,9 +869,9 @@ value={appearance.temporary.density ?? 'daemon'} onchange={(event) => applyTemporaryDensity(event.currentTarget.value)} > - <option value="daemon">Density: daemon</option> - <option value="compact">Density: compact</option> - <option value="comfortable">Density: comfortable</option> + <option value="daemon">Density: Auto</option> + <option value="compact">Density: Compact</option> + <option value="comfortable">Density: Comfortable</option> </select> </label> </div> @@ -1065,34 +1065,54 @@ height: 100vh; flex-direction: column; overflow: hidden; - background: - radial-gradient(circle at 86% -20%, color-mix(in srgb, var(--artifact-ink) 13%, transparent), transparent 34%), - var(--bg-primary); + background: var(--bg-primary); color: var(--text-primary); } .brand { display: inline-flex; align-items: center; - gap: var(--space-3); + gap: var(--space-2); color: var(--text-primary); font-family: var(--font-sans); - font-size: var(--font-size-lg); - font-weight: 700; - letter-spacing: 0.02em; + font-size: var(--font-size-md); + font-weight: 650; + letter-spacing: 0.01em; } - .brand span { color: var(--artifact-ink); + font-size: var(--font-size-sm); + } + + /* Integrated app-bar tabs: quiet text buttons with a soft active pill + * instead of kit-ui's detached inset track. */ + .app-shell :global(.kit-top-bar__tabs) { + gap: var(--space-1); + padding: 0; + background: transparent; + border-radius: 0; + } + + .app-shell :global(.kit-top-bar__tab) { + padding: 5px 12px; + border-radius: var(--radius-md); + font-size: var(--font-size-md); + } + + .app-shell :global(.kit-top-bar__tab.active) { + background: var(--bg-subtle); + box-shadow: none; } .archive-state { display: inline-flex; align-items: center; - gap: var(--space-3); + gap: var(--space-2); + margin-left: var(--space-3); color: var(--text-muted); font-size: var(--font-size-xs); + white-space: nowrap; } .archive-state span { @@ -1111,22 +1131,30 @@ } .appearance-controls select { - min-height: var(--control-height); - border: 1px solid var(--control-border); + height: 26px; + padding: 0 var(--space-2); + border: 1px solid transparent; border-radius: var(--radius-sm); - background: var(--control-bg); - color: var(--text-primary); + background: transparent; + color: var(--text-secondary); font-size: var(--font-size-xs); } + .appearance-controls select:hover { + border-color: var(--control-border); + background: var(--bg-surface-hover); + color: var(--text-primary); + } + .files-shell { display: flex; + width: 100%; + max-width: 1760px; min-height: 0; flex: 1; flex-direction: column; - } - - .files-shell > :global(.context-bar) { - margin: var(--space-4) var(--space-6) 0; + gap: var(--space-4); + margin-inline: auto; + padding: var(--space-6) var(--space-7) var(--space-4); } </style> diff --git a/web/src/lib/components/shell/AppShell.test.ts b/web/src/lib/components/shell/AppShell.test.ts index 2a832c558..6975a78d7 100644 --- a/web/src/lib/components/shell/AppShell.test.ts +++ b/web/src/lib/components/shell/AppShell.test.ts @@ -464,7 +464,7 @@ describe('AppShell', () => { state.commitNavigation({ relationshipTarget: null }); await waitFor(() => expect(screen.queryByRole('heading', { name: 'Alice Example' })).toBeNull()); - expect(screen.getByText('Select a person or domain to see activity.')).toBeDefined(); + expect(screen.getByText('Select a person or domain')).toBeDefined(); expect(state.current.workspace).toBe('relationships'); rendered.unmount(); From 561d0515953f69fc71ab947f0e04e4cc9d3d501f Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 15:17:13 -0500 Subject: [PATCH 006/102] feat(web): give the Relationships hub real pane architecture Bound the list rail as a panel (background shift, hairline border, internal padding), cap the activity column at a readable width on wide viewports, and separate the reading pane with a hairline instead of floating card chrome. Timeline rows get a title/time line over the preview with room to breathe. Empty states are designed: a centered hub placeholder with a hint, styled list/timeline placeholders, and no orphaned rule under the header. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../lib/components/inspector/Inspector.svelte | 2 + .../relationships/RelationshipHeader.svelte | 17 ++- .../relationships/RelationshipList.svelte | 38 ++++- .../relationships/RelationshipTimeline.svelte | 45 +++++- .../RelationshipsWorkspace.svelte | 138 +++++++++++++----- .../RelationshipsWorkspace.test.ts | 4 +- 6 files changed, 190 insertions(+), 54 deletions(-) diff --git a/web/src/lib/components/inspector/Inspector.svelte b/web/src/lib/components/inspector/Inspector.svelte index d77a04432..e3d0970f4 100644 --- a/web/src/lib/components/inspector/Inspector.svelte +++ b/web/src/lib/components/inspector/Inspector.svelte @@ -358,6 +358,8 @@ align-items: center; justify-content: space-between; gap: var(--space-4); + padding: var(--space-4) var(--space-5); + border-bottom: 1px solid var(--border-muted); } .inspector-header > div:first-child { diff --git a/web/src/lib/components/relationships/RelationshipHeader.svelte b/web/src/lib/components/relationships/RelationshipHeader.svelte index bf1cee4a8..fcd701b18 100644 --- a/web/src/lib/components/relationships/RelationshipHeader.svelte +++ b/web/src/lib/components/relationships/RelationshipHeader.svelte @@ -178,9 +178,11 @@ } </script> -<header class="relationship-header" aria-label="Relationship detail"> +<header class="relationship-header" class:has-detail={Boolean(detail)} aria-label="Relationship detail"> {#if !detail} - <p role="status">{loading ? 'Loading relationship…' : 'Select a person or domain to see activity.'}</p> + <p class="header-empty" role="status"> + {loading ? 'Loading relationship…' : 'Select a person or domain to see your shared history.'} + </p> {:else} <div class="title-row"> <h2>{displayLabel(detail)}</h2> @@ -307,10 +309,19 @@ display: flex; flex-direction: column; gap: var(--space-3); - padding-bottom: var(--space-3); + } + + .relationship-header.has-detail { + padding-bottom: var(--space-4); border-bottom: 1px solid var(--border-muted); } + .header-empty { + margin: 0; + color: var(--text-muted); + font-size: var(--font-size-sm); + } + .title-row { display: flex; align-items: center; diff --git a/web/src/lib/components/relationships/RelationshipList.svelte b/web/src/lib/components/relationships/RelationshipList.svelte index 51e984f20..775b86d29 100644 --- a/web/src/lib/components/relationships/RelationshipList.svelte +++ b/web/src/lib/components/relationships/RelationshipList.svelte @@ -181,9 +181,12 @@ onkeydown={handleKeydown} > {#if loading && views.length === 0} - <p role="status">Loading relationships…</p> + <p class="list-empty" role="status">Loading relationships…</p> {:else if views.length === 0} - <p role="status">No relationships found.</p> + <div class="list-empty" role="status"> + <p class="list-empty-title">No relationships found</p> + <p class="list-empty-hint">Try a different search, or switch between People and Domains.</p> + </div> {:else} {#each views as view, index (view.key)} <div @@ -224,15 +227,18 @@ .relationship-list { display: flex; min-width: 0; + min-height: 0; + flex: 1; flex-direction: column; gap: var(--space-3); overflow: hidden; + padding: var(--space-5) var(--space-4) 0; } .toolbar { display: flex; flex-direction: column; - gap: var(--space-2); + gap: var(--space-3); } .show-all { @@ -243,6 +249,27 @@ font-size: var(--font-size-xs); } + .list-empty { + display: grid; + gap: var(--space-2); + margin: 0; + padding: var(--space-8) var(--space-5); + color: var(--text-muted); + font-size: var(--font-size-sm); + text-align: center; + } + + .list-empty-title { + margin: 0; + color: var(--text-secondary); + font-weight: 600; + } + + .list-empty-hint { + margin: 0; + line-height: 1.5; + } + .named-state { display: flex; flex-direction: column; @@ -261,6 +288,8 @@ gap: 2px; overflow: auto; outline: none; + margin-inline: calc(var(--space-2) * -1); + padding-bottom: var(--space-4); } .results-grid:focus-visible { @@ -290,8 +319,7 @@ } .result-row.selected { - background: color-mix(in srgb, var(--accent-teal) 12%, var(--bg-surface)); - border-color: var(--selected-border); + background: var(--selected-bg); } .row-main { diff --git a/web/src/lib/components/relationships/RelationshipTimeline.svelte b/web/src/lib/components/relationships/RelationshipTimeline.svelte index 98b58106b..1c79f0993 100644 --- a/web/src/lib/components/relationships/RelationshipTimeline.svelte +++ b/web/src/lib/components/relationships/RelationshipTimeline.svelte @@ -36,9 +36,14 @@ const MONTH_HEADER_HEIGHT = 28; const OVERSCAN = 6; + /* Timeline rows stack a title/time line over a preview line, so they need + * more room than the single-line table rows the density token sizes. */ + const TWO_LINE_ROW_EXTRA = 16; const geometry = new RowGeometry(); - const rowHeight = $derived(geometry.height); + const rowHeight = $derived( + geometry.height === undefined ? undefined : geometry.height + TWO_LINE_ROW_EXTRA + ); let gridElement = $state<HTMLDivElement>(); let scrollTop = $state(0); @@ -178,11 +183,11 @@ > <div class="timeline-body" role="rowgroup"> {#if loading && rows.length === 0} - <div role="row"><div role="gridcell"><p role="status">Loading activity…</p></div></div> + <div role="row"><div role="gridcell"><p class="timeline-empty" role="status">Loading activity…</p></div></div> {:else if rows.length === 0} - <div role="row"><div role="gridcell"><p role="status">No activity yet.</p></div></div> + <div role="row"><div role="gridcell"><p class="timeline-empty" role="status">No activity yet.</p></div></div> {:else if !slice || rowHeight === undefined} - <div role="row"><div role="gridcell"><p role="status">Preparing timeline layout…</p></div></div> + <div role="row"><div role="gridcell"><p class="timeline-empty" role="status">Preparing timeline layout…</p></div></div> {:else} <div class="virtual-spacer" style:height={`${slice.totalHeight}px`}> <div class="virtual-window" style:transform={`translateY(${slice.topPad}px)`}> @@ -205,9 +210,11 @@ ondblclick={() => selectRow(item.row)} > <div role="gridcell"> - <strong>{timelineRowTitle(item.row)}</strong> + <span class="row-top"> + <strong>{timelineRowTitle(item.row)}</strong> + <time datetime={item.row.occurred_at}>{formatTime(item.row.occurred_at)}</time> + </span> <span class="preview">{item.row.preview}</span> - <time datetime={item.row.occurred_at}>{formatTime(item.row.occurred_at)}</time> </div> </div> {/if} @@ -299,11 +306,28 @@ .timeline-row [role='gridcell'] { display: flex; + min-width: 0; flex-direction: column; gap: 2px; padding: 0 var(--space-3); } + .row-top { + display: flex; + min-width: 0; + align-items: baseline; + justify-content: space-between; + gap: var(--space-4); + } + + .row-top strong { + flex: 1; + } + + .row-top time { + flex: none; + } + .timeline-row:hover { background: var(--bg-surface-hover); } @@ -317,10 +341,19 @@ outline-offset: -2px; } + .timeline-empty { + margin: 0; + padding: var(--space-7) var(--space-3); + color: var(--text-muted); + font-size: var(--font-size-sm); + text-align: center; + } + .timeline-row strong { overflow: hidden; color: var(--text-primary); font-size: var(--font-size-sm); + font-weight: 600; text-overflow: ellipsis; white-space: nowrap; } diff --git a/web/src/lib/components/relationships/RelationshipsWorkspace.svelte b/web/src/lib/components/relationships/RelationshipsWorkspace.svelte index b540ac387..f86f9f9af 100644 --- a/web/src/lib/components/relationships/RelationshipsWorkspace.svelte +++ b/web/src/lib/components/relationships/RelationshipsWorkspace.svelte @@ -399,41 +399,52 @@ <div class="pane-center-and-reading" class:stacked={layout !== 'wide'}> <div class="pane-center"> - <RelationshipHeader - detail={controller.detail} - loading={controller.timelineLoading} - {filesOpen} - {onFilesToggle} - {client} - onLinkParticipants={(a, b) => controller.linkParticipants(a, b)} - onUnlinkParticipants={(a, b) => controller.unlinkParticipants(a, b)} - /> - {#if filesOpen && filesReady} - <FilesWorkspace - {client} - predicate={contextPredicate(predicate)} - identityScope={identityScopeFor(target)} - sort={fileSort} - filenameQuery={fileFilenameQuery} - mimeFamilies={fileMIMEFamilies} - onSortChange={(value) => (fileSort = value)} - onFilenameQueryChange={(value) => (fileFilenameQuery = value)} - onMIMEFamiliesChange={(value) => (fileMIMEFamilies = value)} - onOpenItem={onOpenFileItem} - onOpenConversation={onOpenFileConversation} - /> + {#if target === null && controller.target === null} + <div class="hub-empty" role="status"> + <p class="hub-empty-title">Select a person or domain</p> + <p class="hub-empty-hint"> + Choose someone from the list to see your shared history across mail, chat, and files. + </p> + </div> {:else} - <RelationshipTimeline - rows={controller.timelineRows} - loading={controller.timelineLoading} - loadingMore={controller.timelineLoadingMore} - hasMore={Boolean(controller.timelineCursor)} - error={controller.timelineError} - restartNotice={controller.timelineRestartNotice} - selectedKey={selectedRowKey} - onRowOpen={openTimelineRow} - onLoadMore={() => { void controller.loadMoreTimeline(); }} - /> + <div class="pane-center-column"> + <RelationshipHeader + detail={controller.detail} + loading={controller.timelineLoading} + {filesOpen} + {onFilesToggle} + {client} + onLinkParticipants={(a, b) => controller.linkParticipants(a, b)} + onUnlinkParticipants={(a, b) => controller.unlinkParticipants(a, b)} + /> + {#if filesOpen && filesReady} + <FilesWorkspace + {client} + predicate={contextPredicate(predicate)} + identityScope={identityScopeFor(target)} + sort={fileSort} + filenameQuery={fileFilenameQuery} + mimeFamilies={fileMIMEFamilies} + onSortChange={(value) => (fileSort = value)} + onFilenameQueryChange={(value) => (fileFilenameQuery = value)} + onMIMEFamiliesChange={(value) => (fileMIMEFamilies = value)} + onOpenItem={onOpenFileItem} + onOpenConversation={onOpenFileConversation} + /> + {:else} + <RelationshipTimeline + rows={controller.timelineRows} + loading={controller.timelineLoading} + loadingMore={controller.timelineLoadingMore} + hasMore={Boolean(controller.timelineCursor)} + error={controller.timelineError} + restartNotice={controller.timelineRestartNotice} + selectedKey={selectedRowKey} + onRowOpen={openTimelineRow} + onLoadMore={() => { void controller.loadMoreTimeline(); }} + /> + {/if} + </div> {/if} </div> {#if selection !== undefined} @@ -463,14 +474,16 @@ display: flex; min-height: 0; height: 100%; - gap: var(--space-4); + background: var(--bg-canvas); } .pane-list { display: flex; - flex: 0 0 280px; + flex: 0 0 clamp(280px, 19vw, 340px); flex-direction: column; overflow: hidden; + background: var(--bg-primary); + border-right: 1px solid var(--border-muted); } .pane-list.drawer { @@ -493,7 +506,6 @@ display: flex; min-width: 0; flex: 1; - gap: var(--space-4); overflow: hidden; } @@ -506,16 +518,66 @@ min-width: 0; flex: 1; flex-direction: column; - gap: var(--space-3); overflow: hidden; } + /* Readable content column: the timeline caps out instead of stretching + * shapelessly across ultrawide viewports. */ + .pane-center-column { + display: flex; + width: 100%; + max-width: 1080px; + min-height: 0; + flex: 1; + flex-direction: column; + gap: var(--space-4); + margin-inline: auto; + padding: var(--space-6) var(--space-7); + } + + .hub-empty { + display: flex; + flex: 1; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--space-2); + padding: var(--space-8); + text-align: center; + } + + .hub-empty-title { + margin: 0; + color: var(--text-secondary); + font-size: var(--font-size-lg); + font-weight: 600; + } + + .hub-empty-hint { + margin: 0; + max-width: 380px; + color: var(--text-muted); + font-size: var(--font-size-sm); + line-height: 1.5; + } + .pane-reading { flex: none; + border-left: 1px solid var(--border-muted); + background: var(--bg-surface); + } + + /* The reading pane provides the boundary; the embedded Inspector card + * chrome would double it up. */ + .pane-reading :global(.pinned-inspector aside) { + border: 0; + border-radius: 0; } .pane-center-and-reading.stacked .pane-reading { flex: 1; min-height: 0; + border-left: 0; + border-top: 1px solid var(--border-muted); } </style> diff --git a/web/src/lib/components/relationships/RelationshipsWorkspace.test.ts b/web/src/lib/components/relationships/RelationshipsWorkspace.test.ts index 5e7e9f525..52c04afc0 100644 --- a/web/src/lib/components/relationships/RelationshipsWorkspace.test.ts +++ b/web/src/lib/components/relationships/RelationshipsWorkspace.test.ts @@ -84,7 +84,7 @@ describe('RelationshipsWorkspace', () => { render(RelationshipsWorkspace, { props: baseProps(fetchFn) }); expect(await screen.findByText('Alice Example')).toBeDefined(); - expect(screen.getByText('Select a person or domain to see activity.')).toBeDefined(); + expect(screen.getByText('Select a person or domain')).toBeDefined(); expect(screen.queryByRole('complementary', { name: /Inspect/ })).toBeNull(); }); @@ -403,7 +403,7 @@ describe('RelationshipsWorkspace', () => { await screen.findByText('Alice Example'); expect(screen.queryByRole('grid', { name: 'Files results' })).toBeNull(); - expect(screen.getByText('Select a person or domain to see activity.')).toBeDefined(); + expect(screen.getByText('Select a person or domain')).toBeDefined(); }); it('walks Esc back one layer at a time: reading pane, then the open target', async () => { From a27221da3dc6d3dfb908c88cdf24888b18e353c2 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 15:17:23 -0500 Subject: [PATCH 007/102] refactor(web): soften hairlines and quiet workspace chrome Separate --border-muted from --border-default so row separators and pane boundaries render as quiet hairlines in both themes. Drop the 'Analytical/Archive workspace' eyebrows and the amber-tinted table tools strip, scale workspace titles down to the office-app tier, and cap Everything/Files content width on ultrawide viewports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../components/explore/EverythingTable.svelte | 2 +- .../components/files/FilesWorkspace.svelte | 10 ++++---- .../shell/EverythingWorkspace.svelte | 23 +++++++------------ web/src/styles/theme-dark.css | 2 +- web/src/styles/theme-light.css | 2 +- 5 files changed, 15 insertions(+), 24 deletions(-) diff --git a/web/src/lib/components/explore/EverythingTable.svelte b/web/src/lib/components/explore/EverythingTable.svelte index e2b4ab6b8..62291aad4 100644 --- a/web/src/lib/components/explore/EverythingTable.svelte +++ b/web/src/lib/components/explore/EverythingTable.svelte @@ -587,7 +587,7 @@ justify-content: flex-end; padding: 0 var(--space-4); border-bottom: 1px solid var(--border-muted); - background: color-mix(in srgb, var(--bg-surface) 90%, var(--accent-amber)); + background: var(--bg-subtle); } details { diff --git a/web/src/lib/components/files/FilesWorkspace.svelte b/web/src/lib/components/files/FilesWorkspace.svelte index 159fde01a..99d2bf98e 100644 --- a/web/src/lib/components/files/FilesWorkspace.svelte +++ b/web/src/lib/components/files/FilesWorkspace.svelte @@ -425,7 +425,7 @@ <main class="files-workspace" aria-label="Files"> <header class="workspace-header"> - <div><p>Archive workspace</p><h1>Files</h1></div> + <div><h1>Files</h1></div> <span aria-live="polite">{totalCount.toLocaleString()} files</span> </header> @@ -563,11 +563,9 @@ </script> <style> - .files-workspace { display: flex; min-height: 0; flex: 1; flex-direction: column; gap: var(--space-4); padding: var(--space-5) var(--space-6) var(--space-4); } - .workspace-header { display: flex; align-items: end; justify-content: space-between; } - .workspace-header p, h1 { margin: 0; } - .workspace-header p { color: var(--artifact-ink); font-size: var(--font-size-2xs); font-weight: 800; letter-spacing: .1em; text-transform: uppercase; } - h1 { font-family: var(--font-sans); } + .files-workspace { display: flex; min-height: 0; flex: 1; flex-direction: column; gap: var(--space-4); } + .workspace-header { display: flex; align-items: baseline; justify-content: space-between; } + h1 { margin: 0; font-family: var(--font-sans); font-size: var(--font-size-xl); font-weight: 650; line-height: 1.2; } .workspace-header span { color: var(--text-muted); font-size: var(--font-size-xs); } .file-controls, .mime-controls { display: flex; align-items: center; gap: var(--space-3); } .file-controls { flex-wrap: wrap; } diff --git a/web/src/lib/components/shell/EverythingWorkspace.svelte b/web/src/lib/components/shell/EverythingWorkspace.svelte index 46c6596be..70386e515 100644 --- a/web/src/lib/components/shell/EverythingWorkspace.svelte +++ b/web/src/lib/components/shell/EverythingWorkspace.svelte @@ -481,7 +481,6 @@ <main class="everything-workspace" aria-label="Everything"> <header class="workspace-header"> <div> - <p class="eyebrow">Analytical workspace</p> <h1>Everything</h1> </div> <p class="result-count" aria-live="polite"> @@ -695,35 +694,29 @@ <style> .everything-workspace { display: flex; + width: 100%; + max-width: 1760px; min-height: 0; flex: 1; flex-direction: column; gap: var(--space-4); - padding: var(--space-5) var(--space-6) var(--space-4); + margin-inline: auto; + padding: var(--space-6) var(--space-7) var(--space-4); } .workspace-header { display: flex; - min-height: 42px; - align-items: end; + align-items: baseline; justify-content: space-between; gap: var(--space-6); } - .eyebrow { - margin: 0 0 var(--space-1); - color: var(--artifact-ink); - font-size: var(--font-size-2xs); - font-weight: 800; - letter-spacing: 0.12em; - text-transform: uppercase; - } - h1 { margin: 0; font-family: var(--font-sans); - font-size: var(--font-size-2xl); - line-height: 1; + font-size: var(--font-size-xl); + font-weight: 650; + line-height: 1.2; } .result-count { diff --git a/web/src/styles/theme-dark.css b/web/src/styles/theme-dark.css index 958389ce5..ba0d1f779 100644 --- a/web/src/styles/theme-dark.css +++ b/web/src/styles/theme-dark.css @@ -7,7 +7,7 @@ --bg-surface-hover: #26352c; --bg-inset: #0c140f; --border-default: #77857a; - --border-muted: #77857a; + --border-muted: #2e3a31; --border-strong: #a2afa5; --control-bg: #18211b; --control-border: #829087; diff --git a/web/src/styles/theme-light.css b/web/src/styles/theme-light.css index 416e70569..6b6c11f8c 100644 --- a/web/src/styles/theme-light.css +++ b/web/src/styles/theme-light.css @@ -7,7 +7,7 @@ --bg-surface-hover: #e2ebe5; --bg-inset: #e6e1d5; --border-default: #737c75; - --border-muted: #737c75; + --border-muted: #dcd6c7; --border-strong: #56625b; --control-bg: #fffdf6; --control-border: #737c75; From 719966f4a38d57180a43cdb96f6c34e035ee2214 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 15:23:59 -0500 Subject: [PATCH 008/102] polish(web): refine hub selection, chips, and stacked reading pane Timeline selection becomes a filled row with an accent bar instead of a loud outline, identity chips get quiet hairline chrome on a subtle surface, and the stacked-layout reading pane spans the hub instead of keeping its side-pane resize width and handle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../relationships/RelationshipHeader.svelte | 5 +++-- .../relationships/RelationshipTimeline.svelte | 4 ++-- .../relationships/RelationshipsWorkspace.svelte | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/web/src/lib/components/relationships/RelationshipHeader.svelte b/web/src/lib/components/relationships/RelationshipHeader.svelte index fcd701b18..045697bfb 100644 --- a/web/src/lib/components/relationships/RelationshipHeader.svelte +++ b/web/src/lib/components/relationships/RelationshipHeader.svelte @@ -372,9 +372,10 @@ display: flex; flex-direction: column; gap: 2px; - border: 1px solid var(--border-default); + border: 1px solid var(--border-muted); border-radius: var(--radius-sm); - padding: var(--space-2); + background: var(--bg-subtle); + padding: var(--space-2) var(--space-3); } .chip-display { diff --git a/web/src/lib/components/relationships/RelationshipTimeline.svelte b/web/src/lib/components/relationships/RelationshipTimeline.svelte index 1c79f0993..ce80c4865 100644 --- a/web/src/lib/components/relationships/RelationshipTimeline.svelte +++ b/web/src/lib/components/relationships/RelationshipTimeline.svelte @@ -337,8 +337,8 @@ } .timeline-row.selected { - outline: 2px solid var(--accent-blue); - outline-offset: -2px; + background: var(--selected-bg); + box-shadow: inset 3px 0 var(--accent-teal); } .timeline-empty { diff --git a/web/src/lib/components/relationships/RelationshipsWorkspace.svelte b/web/src/lib/components/relationships/RelationshipsWorkspace.svelte index f86f9f9af..a4b89886f 100644 --- a/web/src/lib/components/relationships/RelationshipsWorkspace.svelte +++ b/web/src/lib/components/relationships/RelationshipsWorkspace.svelte @@ -580,4 +580,21 @@ border-left: 0; border-top: 1px solid var(--border-muted); } + + /* Stacked below the timeline, the reading pane spans the hub: the drag + * handle is meaningless there and the inline resize width (an inline + * style, hence the !important) must give way to the full row. */ + .pane-center-and-reading.stacked .pane-reading :global(.pinned-inspector) { + width: 100%; + } + + .pane-center-and-reading.stacked .pane-reading :global(.pinned-inspector aside) { + width: auto !important; + max-width: none; + flex: 1; + } + + .pane-center-and-reading.stacked .pane-reading :global(.kit-split-resize-handle) { + display: none; + } </style> From 99acc3d6904d50b9933b808cc39685a50f0800d9 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 15:30:45 -0500 Subject: [PATCH 009/102] test(web): align e2e specs and snapshots with the visual pass Settings row separators move to the strong border token so the rendered boundary contrast check keeps its 3:1 guarantee, the relationships e2e spec asserts the new hub empty-state copy, and the theme-keyboard shell snapshots are regenerated for darwin and linux (pinned Playwright container). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../settings/SettingsWorkspace.svelte | 2 +- web/tests/relationships.spec.ts | 4 ++-- .../analytical-dark-comfortable-darwin.png | Bin 83913 -> 57923 bytes .../analytical-dark-comfortable-linux.png | Bin 77039 -> 51622 bytes .../analytical-dark-compact-darwin.png | Bin 83770 -> 57698 bytes .../analytical-dark-compact-linux.png | Bin 76945 -> 51578 bytes .../analytical-light-comfortable-darwin.png | Bin 84116 -> 58207 bytes .../analytical-light-comfortable-linux.png | Bin 76221 -> 51412 bytes .../analytical-light-compact-darwin.png | Bin 84083 -> 58027 bytes .../analytical-light-compact-linux.png | Bin 76276 -> 51360 bytes 10 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/lib/components/settings/SettingsWorkspace.svelte b/web/src/lib/components/settings/SettingsWorkspace.svelte index 8a9723da4..e211cbfe2 100644 --- a/web/src/lib/components/settings/SettingsWorkspace.svelte +++ b/web/src/lib/components/settings/SettingsWorkspace.svelte @@ -299,7 +299,7 @@ <style> .settings { max-width: 72rem; margin: 0 auto; } section { margin-block: 2rem; } - .field { display: grid; grid-template-columns: minmax(16rem, 1fr) minmax(14rem, 1fr) auto; gap: 1rem; align-items: center; padding: 1rem 0; border-top: 1px solid var(--border-muted); } + .field { display: grid; grid-template-columns: minmax(16rem, 1fr) minmax(14rem, 1fr) auto; gap: 1rem; align-items: center; padding: 1rem 0; border-top: 1px solid var(--border-default); } .field-copy { display: grid; gap: 0.25rem; } .field-copy span, small { color: var(--text-muted); } input:not([type='checkbox']), select { width: 100%; min-height: 2.25rem; } diff --git a/web/tests/relationships.spec.ts b/web/tests/relationships.spec.ts index 20616a95a..8f3a3814b 100644 --- a/web/tests/relationships.spec.ts +++ b/web/tests/relationships.spec.ts @@ -96,7 +96,7 @@ test('legacy People URL lands on the Relationships hub and walks list, timeline, await expect(hub).toBeVisible(); const list = page.getByRole('grid', { name: 'Relationship results' }); await expect(list.getByText('Alice Example')).toBeVisible(); - await expect(page.getByText('Select a person or domain to see activity.')).toBeVisible(); + await expect(page.getByText('Select a person or domain', { exact: true })).toBeVisible(); await expect(page.getByRole('radio', { name: 'People' })).toHaveAttribute('aria-checked', 'true'); // Opening a person from the ranked list drives the controller and shows @@ -135,5 +135,5 @@ test('legacy People URL lands on the Relationships hub and walks list, timeline, await expect(list.getByRole('row', { name: /Alice Example/ })).toHaveAttribute('aria-selected', 'false'); await expect(page.getByRole('heading', { name: 'Alice Example' })).toBeHidden(); await expect(reading).toBeHidden(); - await expect(page.getByText('Select a person or domain to see activity.')).toBeVisible(); + await expect(page.getByText('Select a person or domain', { exact: true })).toBeVisible(); }); diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-darwin.png index 42741ecfed0439d3975ffa1c207773791e0d75fe..e1b617a02ed320b0132fdda1a2a00ed997bddcca 100644 GIT binary patch literal 57923 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 literal 83913 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%0Ay1Km-`#_CsixLMK&O_f#iVKgd$JU@Yigqi}j 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-linux.png index 905d78e086884758a7b58f9be43875c106c48140..d5aece6eae7990cd7fcb6b95893890e7244b9da2 100644 GIT binary patch literal 51622 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 literal 77039 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�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 LN2f&l{?q>f@t$+Z diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-darwin.png index 2b2c529d6da50b9c6df718bd2915470ce0860b3a..ef40e8bd540891f103b674de5d0010e3759442c7 100644 GIT binary patch literal 57698 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 literal 83770 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.<bgGlgz<UC&A5e9#idOxa)b{aNUON> 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 Iz4`Dz0HwYgcmMzZ diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-linux.png index e65e1aed733f47371c8e3e67edd53de91e390e1b..4d098e4305fca062a253f0c8cd526d3ffc758fde 100644 GIT binary patch literal 51578 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%0D~g#a?xuux1iThUpvu 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%0A1w}Oe?<9-BBuaN>Q`O%iO|7 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 literal 76945 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%0A3=b?FijRH8h2}JT$ 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 OGOhdi_Yij<KmRw$X6*<7 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-darwin.png index 88ef94e8caedf01b047f02df6485a1d7e7519f5c..7e574b0f2663eb089db22dd86b84cc9cf43028a6 100644 GIT binary patch literal 58207 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 literal 84116 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 S8M(j5A`nFl1>Cb&!T$rr5;3U& diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-linux.png index 80cc1c370de9bcfc5a58725b5189941f5d743a27..9b1afb1c6adc71560ff36542b95d91281eb67bfb 100644 GIT binary patch literal 51412 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%0DE=2P?*m`sBdU) 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 Ai~s-t literal 76221 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%0DfZ0sES4IG15B}GpUMCrj7VFvX#hkZx(>Avr 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-darwin.png index ba5bf66ace26a435434c32ae6cf23553f90cd981..2e9bb45338cde5f662ec13d26fe2e0f5810bd1b7 100644 GIT binary patch literal 58027 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�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 literal 84083 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�WaoPL<n6bvO&vB^={~~( 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-linux.png index 28a55138471c2eab3fa84c8831a11a783c702359..a71dec9647478a4db6dfddb9e926cd5265098981 100644 GIT binary patch literal 51360 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 literal 76276 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 N85^1#)ZcrO_#cpbZaM$} From 43926246a492fc84b2140e7822b5222f07c45006 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 16:03:40 -0500 Subject: [PATCH 010/102] feat(web): replace olive-teal palette with neutral graphite and blue accent Dark theme moves to a true neutral graphite scale (#0e0f10 to #26282b) and light theme to clean whites and neutral grays; the teal/green accent is replaced by a single restrained blue (#74acfe dark, #2257d6 light) across links, focus rings, selection, and activity bars. Status colors stay semantic but are re-tuned to sit quietly on the new neutrals. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- web/src/styles/theme-dark.css | 74 +++++++++++++++++----------------- web/src/styles/theme-light.css | 68 +++++++++++++++---------------- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/web/src/styles/theme-dark.css b/web/src/styles/theme-dark.css index ba0d1f779..a4c6398e4 100644 --- a/web/src/styles/theme-dark.css +++ b/web/src/styles/theme-dark.css @@ -1,40 +1,40 @@ :root[data-theme='dark'] { - --bg-primary: #101712; - --bg-surface: #18211b; - --bg-canvas: #18211b; - --bg-subtle: #202c24; - --surface-raised: #26352c; - --bg-surface-hover: #26352c; - --bg-inset: #0c140f; - --border-default: #77857a; - --border-muted: #2e3a31; - --border-strong: #a2afa5; - --control-bg: #18211b; - --control-border: #829087; - --text-primary: #f0f3ea; - --text-secondary: #c3cec3; - --text-muted: #a9b7ac; - --text-danger: #ffb0a7; - --active-ink: #6ee2ce; - --link-ink: #6ee2ce; - --focus-color: #6ee2ce; - --selected-bg: #17372f; - --selected-border: #79e8d4; - --artifact-ink: #f0a078; - --status-success-bg: #183222; - --status-success-ink: #a2e7b9; - --status-warning-bg: #382b13; - --status-warning-ink: #ffd27b; - --status-error-bg: #3b201e; - --status-error-ink: #ffb0a7; - --status-destructive-bg: #401c1b; - --status-destructive-ink: #ffaaa3; - --accent-blue: #6ee2ce; - --accent-teal: #6ee2ce; - --accent-green: #9aefb7; - --accent-amber: #ffd27b; - --accent-red: #ffaaa3; - --accent-purple: #d2afe9; - --status-waiting: #ffd27b; + --bg-primary: #131416; + --bg-surface: #1a1b1e; + --bg-canvas: #1a1b1e; + --bg-subtle: #212225; + --surface-raised: #26282b; + --bg-surface-hover: #26282b; + --bg-inset: #0e0f10; + --border-default: #74777d; + --border-muted: #2c2e32; + --border-strong: #a0a3a9; + --control-bg: #1a1b1e; + --control-border: #7e8187; + --text-primary: #f2f3f5; + --text-secondary: #c4c7cc; + --text-muted: #a4a8af; + --text-danger: #f7a49c; + --active-ink: #74acfe; + --link-ink: #74acfe; + --focus-color: #74acfe; + --selected-bg: #20304f; + --selected-border: #74acfe; + --artifact-ink: #e0a37f; + --status-success-bg: #182a1e; + --status-success-ink: #a1dcb1; + --status-warning-bg: #2e2612; + --status-warning-ink: #e6c069; + --status-error-bg: #331d1c; + --status-error-ink: #f5a79f; + --status-destructive-bg: #3a1f1d; + --status-destructive-ink: #f5a29b; + --accent-blue: #74acfe; + --accent-teal: #74acfe; + --accent-green: #a7e3b6; + --accent-amber: #e6c069; + --accent-red: #f19b93; + --accent-purple: #c3aae5; + --status-waiting: #e6c069; --overlay-bg: rgb(0 0 0 / 62%); } diff --git a/web/src/styles/theme-light.css b/web/src/styles/theme-light.css index 6b6c11f8c..b1fbc8edf 100644 --- a/web/src/styles/theme-light.css +++ b/web/src/styles/theme-light.css @@ -1,40 +1,40 @@ :root:not([data-theme='dark']) { - --bg-primary: #f3efe4; - --bg-surface: #fffdf6; - --bg-canvas: #fffdf6; - --bg-subtle: #ece6d9; - --surface-raised: #fffdf6; - --bg-surface-hover: #e2ebe5; - --bg-inset: #e6e1d5; - --border-default: #737c75; - --border-muted: #dcd6c7; - --border-strong: #56625b; - --control-bg: #fffdf6; - --control-border: #737c75; - --text-primary: #13231d; - --text-secondary: #35483f; - --text-muted: #52625b; - --text-danger: #7d211e; - --active-ink: #006b61; - --link-ink: #006b61; - --focus-color: #006b61; - --selected-bg: #d5e9e1; - --selected-border: #006b61; - --artifact-ink: #8b3f1d; - --status-success-bg: #e2f1e5; - --status-success-ink: #165b36; - --status-warning-bg: #fff0c5; - --status-warning-ink: #714600; - --status-error-bg: #f9e6e3; - --status-error-ink: #882d27; - --status-destructive-bg: #f6dfdc; - --status-destructive-ink: #7d211e; - --accent-blue: #006b61; - --accent-teal: #006b61; + --bg-primary: #f4f5f6; + --bg-surface: #ffffff; + --bg-canvas: #ffffff; + --bg-subtle: #eaebed; + --surface-raised: #ffffff; + --bg-surface-hover: #eef0f2; + --bg-inset: #e5e6e8; + --border-default: #6f737a; + --border-muted: #d8dade; + --border-strong: #545860; + --control-bg: #ffffff; + --control-border: #6f737a; + --text-primary: #191b1f; + --text-secondary: #3c4046; + --text-muted: #555a62; + --text-danger: #a02c24; + --active-ink: #2257d6; + --link-ink: #2257d6; + --focus-color: #2257d6; + --selected-bg: #dbe7fd; + --selected-border: #2257d6; + --artifact-ink: #8f4720; + --status-success-bg: #e1efe5; + --status-success-ink: #17603a; + --status-warning-bg: #fbf0cd; + --status-warning-ink: #6f4a00; + --status-error-bg: #fbe9e7; + --status-error-ink: #8f2b24; + --status-destructive-bg: #f9e2df; + --status-destructive-ink: #a02c24; + --accent-blue: #2257d6; + --accent-teal: #2257d6; --accent-green: #166534; --accent-amber: #8a5100; --accent-red: #9a2822; - --accent-purple: #72518c; + --accent-purple: #6d509b; --status-waiting: #8a5100; - --overlay-bg: rgb(19 35 29 / 42%); + --overlay-bg: rgb(15 17 20 / 45%); } From 42b4dceaab5a6d7cc2526bd79fd2dc1d99686857 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 16:03:56 -0500 Subject: [PATCH 011/102] feat(web): switch UI type to the native system font stack Drop the bundled Figtree webfont in favor of -apple-system / BlinkMacSystemFont / Segoe UI / Roboto. Remove the @fontsource dependency, the shipped OFL license file and its test, and the release-graph font expectations. Reduce uppercase micro-label and filename weights from 700 to 600, which reads crisper in SF/Segoe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- scripts/check-web-assets.mjs | 3 - scripts/check-web-assets.test.mjs | 6 +- web/bun.lock | 5 +- web/package.json | 1 - web/public/licenses/Figtree-OFL.txt | 93 ------------------- web/src/app.css | 1 - web/src/lib/a11y/font-license.test.ts | 13 --- .../components/explore/EverythingTable.svelte | 2 +- .../explore/FilesPresentation.svelte | 4 +- .../lib/components/explore/GroupTable.svelte | 2 +- .../components/files/FilesWorkspace.svelte | 2 +- .../relationships/RelationshipTimeline.svelte | 2 +- web/src/styles/tokens.css | 3 +- 13 files changed, 12 insertions(+), 125 deletions(-) delete mode 100644 web/public/licenses/Figtree-OFL.txt delete mode 100644 web/src/lib/a11y/font-license.test.ts diff --git a/scripts/check-web-assets.mjs b/scripts/check-web-assets.mjs index 8be429f6b..06504b284 100755 --- a/scripts/check-web-assets.mjs +++ b/scripts/check-web-assets.mjs @@ -202,9 +202,6 @@ export function validateWebAssets(options = {}) { const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')); for (const reference of manifestGraph(manifest)) enqueue(reference); - // Vite copies this compliance artifact from web/public. It is intentionally - // distributed even though application code does not request it at runtime. - enqueue('licenses/Figtree-OFL.txt'); const files = []; for (let index = 0; index < queue.length; index += 1) { diff --git a/scripts/check-web-assets.test.mjs b/scripts/check-web-assets.test.mjs index 8d96e2b41..edb778932 100644 --- a/scripts/check-web-assets.test.mjs +++ b/scripts/check-web-assets.test.mjs @@ -41,9 +41,7 @@ test('validates the production Vite graph and staged embed', (t) => { const paths = releaseCopy(t); const result = validateWebAssets(paths); assert.ok(result.files.some((file) => file.path === 'index.html')); - assert.ok(result.files.some((file) => file.path.endsWith('.woff2'))); assert.ok(result.files.some((file) => file.path.endsWith('.mjs'))); - assert.ok(result.files.some((file) => file.path === 'licenses/Figtree-OFL.txt')); }); test('rejects missing and escaping release references', (t) => { @@ -61,7 +59,7 @@ test('rejects source maps, external scripts, fonts, and embed drift', (t) => { const sourceMap = releaseCopy(t); const manifestPath = resolve(sourceMap.dist, '.vite/manifest.json'); const manifest = JSON.parse(readFileSync(manifestPath)); - manifest['index.html'].assets.push('assets/index.js.map'); + manifest['index.html'].assets = [...(manifest['index.html'].assets ?? []), 'assets/index.js.map']; mkdirSync(resolve(sourceMap.dist, 'assets'), { recursive: true }); writeFileSync(resolve(sourceMap.dist, 'assets/index.js.map'), '{}'); writeFileSync(manifestPath, JSON.stringify(manifest)); @@ -149,7 +147,7 @@ test('requires immutable release assets to carry Vite hash-bearing names', (t) = const paths = releaseCopy(t); const manifestPath = resolve(paths.dist, '.vite/manifest.json'); const manifest = JSON.parse(readFileSync(manifestPath)); - manifest['index.html'].assets.push('assets/unhashed.css'); + manifest['index.html'].assets = [...(manifest['index.html'].assets ?? []), 'assets/unhashed.css']; writeReleaseFile(paths, 'assets/unhashed.css', '.unhashed{color:inherit}'); writeFileSync(manifestPath, JSON.stringify(manifest)); writeFileSync(resolve(paths.embedded, '.vite/manifest.json'), JSON.stringify(manifest)); diff --git a/web/bun.lock b/web/bun.lock index 843f84e85..9b31acb4d 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -5,7 +5,6 @@ "": { "name": "msgvault-web", "dependencies": { - "@fontsource-variable/figtree": "5.3.0", "@kenn-io/kit-ui": "github:kenn-io/kit-ui#1e9dc7d45525a471040b72b894432c4956542c38", "@tanstack/svelte-virtual": "3.13.32", "dompurify": "3.4.11", @@ -18,8 +17,10 @@ "@sveltejs/vite-plugin-svelte": "7.1.2", "@testing-library/svelte": "5.4.2", "@tsconfig/svelte": "5.0.8", + "es-module-lexer": "2.3.1", "jsdom": "29.1.1", "openapi-typescript": "7.13.0", + "postcss": "8.5.19", "svelte": "5.56.1", "svelte-check": "4.6.0", "typescript": "5.9.3", @@ -67,8 +68,6 @@ "@exodus/bytes": ["@exodus/bytes@1.15.1", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q=="], - "@fontsource-variable/figtree": ["@fontsource-variable/figtree@5.3.0", "", {}, "sha512-VRVodD7OiG7apQwE8/2fMjaqpY/a0qRJqL5HtYj8CKIVps+9amaFcdfKmDIf7iqYQx6iPLlArVXAfKeIZmVcNQ=="], - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], diff --git a/web/package.json b/web/package.json index 1b368b4b7..f73f854b4 100644 --- a/web/package.json +++ b/web/package.json @@ -14,7 +14,6 @@ "test:e2e": "playwright test e2e conversation-content-frame.spec.ts files-viewer.spec.ts" }, "dependencies": { - "@fontsource-variable/figtree": "5.3.0", "@kenn-io/kit-ui": "github:kenn-io/kit-ui#1e9dc7d45525a471040b72b894432c4956542c38", "@tanstack/svelte-virtual": "3.13.32", "dompurify": "3.4.11", diff --git a/web/public/licenses/Figtree-OFL.txt b/web/public/licenses/Figtree-OFL.txt deleted file mode 100644 index 9766a6f81..000000000 --- a/web/public/licenses/Figtree-OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2022 The Figtree Project Authors (https://github.com/erikdkennedy/figtree) Figtree-Italic[wght].ttf: Copyright 2022 The Figtree Project Authors (https://github.com/erikdkennedy/figtree) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/web/src/app.css b/web/src/app.css index 421487bc8..1fae817b2 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -1,4 +1,3 @@ -@import '@fontsource-variable/figtree'; @import '@kenn-io/kit-ui/theme.css'; @import './styles/tokens.css'; @import './styles/theme-light.css'; diff --git a/web/src/lib/a11y/font-license.test.ts b/web/src/lib/a11y/font-license.test.ts deleted file mode 100644 index 92f62fd22..000000000 --- a/web/src/lib/a11y/font-license.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { readFileSync } from 'node:fs'; -import { describe, expect, it } from 'vitest'; - -describe('bundled Figtree license', () => { - it('redistributes the authoritative Fontsource notice with production assets', () => { - const upstream = readFileSync('node_modules/@fontsource-variable/figtree/LICENSE', 'utf8'); - const distributed = readFileSync('public/licenses/Figtree-OFL.txt', 'utf8'); - - expect(distributed).toBe(upstream); - expect(distributed).toContain('SIL OPEN FONT LICENSE Version 1.1'); - expect(distributed).toContain('Copyright 2022 The Figtree Project Authors'); - }); -}); diff --git a/web/src/lib/components/explore/EverythingTable.svelte b/web/src/lib/components/explore/EverythingTable.svelte index 62291aad4..a3cdccbe1 100644 --- a/web/src/lib/components/explore/EverythingTable.svelte +++ b/web/src/lib/components/explore/EverythingTable.svelte @@ -649,7 +649,7 @@ background: var(--bg-surface); color: var(--text-muted); font-size: var(--font-size-2xs); - font-weight: 700; + font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; } diff --git a/web/src/lib/components/explore/FilesPresentation.svelte b/web/src/lib/components/explore/FilesPresentation.svelte index c73272899..09debf56d 100644 --- a/web/src/lib/components/explore/FilesPresentation.svelte +++ b/web/src/lib/components/explore/FilesPresentation.svelte @@ -327,7 +327,7 @@ .file-grid { display: flex; min-height: 0; flex: 1; flex-direction: column; overflow: auto; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--bg-surface); outline: none; } .file-grid:focus-visible { box-shadow: inset 0 0 0 2px var(--focus-color); } .file-row { display: grid; grid-template-columns: 110px minmax(160px,1fr) minmax(180px,1.4fr) minmax(150px,1fr) 90px; align-items: center; gap: var(--space-3); min-height: var(--row-height); padding: 0 var(--space-3); } - .file-header { position: sticky; z-index: 1; top: 0; min-height: var(--table-header-height); flex: 0 0 auto; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 700; text-transform: uppercase; } + .file-header { position: sticky; z-index: 1; top: 0; min-height: var(--table-header-height); flex: 0 0 auto; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 600; text-transform: uppercase; } .file-body { position: relative; min-height: 220px; flex: 0 0 auto; } .virtual-spacer { position: relative; } .virtual-window { position: absolute; inset: 0 0 auto; } @@ -339,7 +339,7 @@ active/selected indicator on hover. */ .file-data--active:hover { background: var(--selected-bg); } .file-data span { min-width: 0; overflow: hidden; color: var(--text-secondary); font-size: var(--font-size-xs); text-overflow: ellipsis; white-space: nowrap; } - .file-data .filename { color: var(--text-primary); font-weight: 700; } + .file-data .filename { color: var(--text-primary); font-weight: 600; } .file-data button { max-width: 100%; padding: 0; overflow: hidden; border: 0; background: transparent; color: inherit; cursor: pointer; font: inherit; text-overflow: ellipsis; white-space: nowrap; } .file-data button:hover { color: var(--text-primary); text-decoration: underline; } .notice { display: flex; min-height: 120px; align-items: center; justify-content: center; gap: var(--space-3); padding: var(--space-5); color: var(--text-secondary); } diff --git a/web/src/lib/components/explore/GroupTable.svelte b/web/src/lib/components/explore/GroupTable.svelte index fafeff0ae..1d400767c 100644 --- a/web/src/lib/components/explore/GroupTable.svelte +++ b/web/src/lib/components/explore/GroupTable.svelte @@ -410,7 +410,7 @@ background: var(--bg-surface); color: var(--text-muted); font-size: var(--font-size-2xs); - font-weight: 700; + font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; } diff --git a/web/src/lib/components/files/FilesWorkspace.svelte b/web/src/lib/components/files/FilesWorkspace.svelte index 99d2bf98e..9600d6367 100644 --- a/web/src/lib/components/files/FilesWorkspace.svelte +++ b/web/src/lib/components/files/FilesWorkspace.svelte @@ -574,7 +574,7 @@ .table-header, .data-row { display: grid; grid-template-columns: 112px minmax(150px, 1.5fr) minmax(120px, 1fr) 82px minmax(140px, 1.2fr) minmax(130px, 1fr) minmax(160px, 1.3fr) 105px; align-items: center; } .files-grid { display: flex; min-height: 0; flex: 1; flex-direction: column; overflow: auto; outline: none; } .files-grid:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-blue); } - .table-header { position: sticky; z-index: 1; top: 0; min-height: var(--table-header-height); flex: 0 0 auto; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 700; text-transform: uppercase; } + .table-header { position: sticky; z-index: 1; top: 0; min-height: var(--table-header-height); flex: 0 0 auto; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 600; text-transform: uppercase; } .table-header span, .data-row span { min-width: 0; padding: 0 var(--space-3); overflow: hidden; text-align: left; text-overflow: ellipsis; white-space: nowrap; } .table-header button { width: 100%; height: var(--table-header-height); padding: 0; border: 0; background: transparent; color: inherit; cursor: pointer; font: inherit; text-align: left; text-transform: inherit; } .table-body { position: relative; min-height: 220px; flex: 0 0 auto; outline: none; } diff --git a/web/src/lib/components/relationships/RelationshipTimeline.svelte b/web/src/lib/components/relationships/RelationshipTimeline.svelte index ce80c4865..4512cbdb7 100644 --- a/web/src/lib/components/relationships/RelationshipTimeline.svelte +++ b/web/src/lib/components/relationships/RelationshipTimeline.svelte @@ -290,7 +290,7 @@ padding: 0 var(--space-3); color: var(--text-muted); font-size: var(--font-size-2xs); - font-weight: 700; + font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; } diff --git a/web/src/styles/tokens.css b/web/src/styles/tokens.css index 241a57353..f7350540a 100644 --- a/web/src/styles/tokens.css +++ b/web/src/styles/tokens.css @@ -1,5 +1,6 @@ :root { - --font-sans: 'Figtree Variable', Figtree, ui-sans-serif, system-ui, sans-serif; + --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, + sans-serif; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; --font-reading: Charter, 'Bitstream Charter', Georgia, serif; --focus-ring: 2px solid var(--focus-color); From 73f421ca9db214b43c21c2bfb1229de7c8c4ce5e Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 16:03:58 -0500 Subject: [PATCH 012/102] test(web): regenerate theme-keyboard snapshots for the graphite theme darwin snapshots regenerated locally; linux snapshots regenerated in the pinned mcr.microsoft.com/playwright:v1.61.1-noble CI container. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../analytical-dark-comfortable-darwin.png | Bin 57923 -> 59818 bytes .../analytical-dark-comfortable-linux.png | Bin 51622 -> 56373 bytes .../analytical-dark-compact-darwin.png | Bin 57698 -> 59613 bytes .../analytical-dark-compact-linux.png | Bin 51578 -> 56145 bytes .../analytical-light-comfortable-darwin.png | Bin 58207 -> 58684 bytes .../analytical-light-comfortable-linux.png | Bin 51412 -> 56167 bytes .../analytical-light-compact-darwin.png | Bin 58027 -> 58519 bytes .../analytical-light-compact-linux.png | Bin 51360 -> 55969 bytes 8 files changed, 0 insertions(+), 0 deletions(-) diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-darwin.png index e1b617a02ed320b0132fdda1a2a00ed997bddcca..767808cf164c9449bb8c0fc160b0e9a5e2fad7a5 100644 GIT binary patch literal 59818 zcmbrmWl$Vl+b&81gy8O$;6VeyH9&B8XK;6y0RjZK;O_43G6@8CcXxNU-Fcq(-RJx` zb?U3V`wtY=)7@)&t$SVfC4|V!iXkHqAV5JuAxnsVQ-p$o1%7=y_WmvK<14A;8z?9= zD2Z=^%5G^#%dmQrnuPDxhL)4`9rd~byT;Z}M(kYw_Yr9OYf0y|5&7ygojHq;&@;1D zYl2l`l7f$G^<GwSI<1)=mh7J+$0xn!>$iW7IM~lnRR8{w-uKOC^nVUxc%<(k!G8}L zDLp9Zf6vr-3$6F>#|JQ!VaW#4;_~C-C{TrF&~R}{NlB?O{`r76I<#Jnw1kWdvTx<j zZgF8R?5|Kn#Ka6}e=q!u;NnX)0Io5)qC+JmB^3Pd_j(W5z|C0jt9NUEZrk(xJ#W0y z;5wQnR)31ROGfbT%^^Q#p)ieE1|&7M6VwE{|NF-`pB1Qb|J3cGE@AyW>q<)9i{d{+ zS$BQAb`;26qOs#D_U}mcR<m*nZO^2>s3#MY?P)Yh>L145>TGVnk_D8iLA<kt0xox% z^iJ2ZDhf_EH;G9zkFpDLa%x<jMZLV9spUutP1ZMi<c{WBM~h>rU!K&AVm%KZkO^dy z*_VfpFL7O6KOz^E6dVi$xEwFUGpdv{r3FQiz?)lb^`R>j%FMU9OZlBAGU-??)a{R? zx59{oMMTIv|B*EwjHlHh2ue6Q9ZY0mvz)=WP3g*egYr~3sUu-Q!Ic?_rYP!*60 zc!x)XpkrCTbasn`VMDx+ORT~J0z~Q$*!=v3{rvrm$Sr3p42`WZ@zI+-jy;r=COvOY z5b)Kv2UG~yY&N!(Rds|nFAL>51;kvSc57YHHef-Z%*+uM>mGX3-GM|#T{OjyrX$+; z?qZha=F{b?P_0d0U;KdAv9hw#d8!GqSDHSUwkJaaCdJ`>z2$fGXU0A4+|v|H%F8YF z`BL3em0m*@6*TP*a5D*?hjz1r@$&=nTVrKnZWq;<)@B7Gqph=Se?DH`m^5__4SaN| z5$)y}EzL+GF4K_=udTCn=*Qc2b2~eECnx1_GP2OE6>e4v3T|#HUS93H(m-UwlB-qE zZ^v`ZU+C$j<?CXp<-iYD#cGu+cn#I{%p)dgZYe*<#<JG*)@?0UJN%22lb@2JNCo<C zX62&tuf~g&5?ihh!@4M?5-gUQ`l~+rKRrFc1h8;$$ejrfEO2vMYcJr?>4H0(syC9f zn;BZ14kOba_r`_6#zI0uKai|O+U{p&*f;F%5wH)ZO9d?c*sZU|tU*PFg)KsCPgnR_ zm6Bj#^|Y-dyO)EbbFGdqZVtw=yP_4M@m<6SQ<H{3Rq|R|Vqnc&)pGJ6RO`hS%I1B{ zxF<6;>+zcDxc=A`h-XkC4Sbi75bVIh04LY|!$XI&Gd4>}%k=@M-9w~9;Ud#iPfxv8 zwLC7YKC`B}{&C|32OFF6sp2gxLQcdT@a<-2`zVu8xraY5wyFpV*V^g#0+Zmq&o4%~ zm7i;Ra-*-aPxbZnp*Rfon>}oTg5DpY623unEReKa7nV8jeDM77nr2HjtCOkUxzHb} z7f9nfJ3GVt+SxIQrIK-Zd~)v@N`7nn`Ms;DbZSne?RQ$K=o}F^RDA9W*Tb3=P0gY& z$<IY>jJkF5;)b|Px=l$fU@6VVhwH;C{VpT9NwNpntuO<_O~s6N<H0|fv%Cdtj?Ctj z8b@u+(9mzy{Jx)6eoYlQZgEDBh$7;jnw+fFY9uMzYqXm7m>$3uBJg}ZP;paN&){`8 zHY8xR3hq@Zlzn)(b28f{Gt%Q+CVLPm<9E3dr!?M9t~FU)Siq*$zMG#c?1YH$es`{O z9*Ac;)ow<R>|+9TEH%A6I={U<;zB}(xWWwf@wAA9CD8QCrSo>!cfEsYtlb$ZU(H&N zJYF0___=ijFX^p>`Gt3SW(Fto=5$dkiyoSJdf}US)XlN>LY=+)^NtKR%&+0pMyvUj zdQKuD4Dj(ld<5Z4p-GR+F%Q_@)#r(G*wgko#QclN_Fy6^^!fR@S!s9U#X<aF=cxm| zVxGjLNe~LjpJMl4zXS{A(mkFZR1Qo}C+aP)3F?)VmF-x(E_bf~oNsy;rzZ9FESOBh z6_fI}iTPqa)CRJYS-XAvQ;gXg3ufy#q=!CH)zN8(k1dQK;HO|bN#sy_D>@&Z(r9rR z^v3R(`+61P>3Q)-VIEJ4F7+XTV70^RlEcL;Mv)L|Pz<t>B;2@4120+1qJ>0nn8D{6 zPa6;Y_ANe?=~rgvaT3Kzpq5oz%0;HF#yaqMUW?j@2@7|v3g~rTP|A_K7M7L>zdXH< zM@Ej2NAP)mfQ8MBC_gS5k8x6sFPWB1v_F_AhMph}AP%duT@)qS-<VKc{RJhl^LTp_ zT7AA(y-7rLA>i{;=5~%TK0ZEMse^48MJ5u8g2yT@Cr64c{{!Jmft;{;)cJgku@=N0 zvxnR*iNkedvQW-qx@0n2sZirffuY;=g0rCDZ(7=cW{2&bR&7on@5!EP2}We&_AE1? zo{eTgev;N2op01^4@_66463Cl@Os`Lsb<Nhx5yB1kZ_u1MMo>x+4VJ8YpIoMMyQQu z3gF`51skB3i;#<kC$U>Uw0quq+$I=#98MK~F&>CB9!$JGnMSnF4G=kLbv<=CniB*k z?oSp@SLhJJt4Oc5d!mMOIqrl*AU>t4<q1q6-7@uYPuEkGGG%nat*PQkV0m@IP*G74 z&Q#V`)rE(I@PY5_az!H=Y!+LL&M`hB2Y>p2KmX!!eUSfjM}f*dWk|qg?iUR_W}#f> zQiE}Ami&C}?k|_eyLJD$ZT;hg>FDw3Onxv{40qdLV&zzeVc{?#M;ezS0cJQJThI7- zwd?4~+$kotoFGiFh(tl!XI(3E5<U~F?9Rv{d|s~$ihfs!<8Gwm_Q3W)Jj8yJJ0pQs z1tF-n`*Qc{>Pod-!}Vn;8ToM5(3M*Eu7vH--`_tFl{mba-D<(_RA`mI{d1ec^}^cP z8hTe?-n9#ze9;GzO6?X3_WRK^{&vA!QHAIjeKxl1kXY))xw(>(l81)}`Xm8VXY?(& z0-4Ne+tUnxxEsTi;z`pF1qU<vm;^hE4VJHW=bJeqVGnnANPDw|YN!0b?m0U#(HXQ* z?Zu<2`_l!Nl-Z&#IhiZ;J$Tr3H0@*FY@+6!J=CUGrA{li{>GNe@nYDU&j_q#Z5<un z>gs#vxxF_hGD$)bU~zKm3?5gTDXhlRrHCH~A)CEhE>0MW!*g?P{WaL58T|dJqq<^Z zyN8XHr3p-8*I01z>U9PN6hGA}HSHf~7zDGetSI)Mfr$~?`})-EC>ci^@B#Ne`A;jf zv)O$NDPf7pu%;Wg$<bY-tqyfKVHo)r8XCK;GyQ(-yFgJ<&JjnWQQv`?d_-JYwQ`TA zy>VimhWtfcDp&+0q`j%^^q827Ir~jR14EP1G<;H0TzvG8dAQ*2v+|?G*2d}(`|_0f z!>Mvy0)iIP#*IpN$%uz*!Zkv4G_((dhGu5!6<}EKbHHj&9>p3VtL+F;E3eD2R^8F8 z&-cd0+0sNNA4~nWex@2v+9h?9hia8dq)?k%Dy_2Z?Z9+ih2X9p+GA4P>`i-aUl?~X zb%rP|Ao0<&VJ6b5aMSbBH$$l_VAaglAv+<c&~I$-+@GJf!Z3X@zSH7q_qaD0>Qg?B zR+gNWslpJ0<oGv&GMi3YYD{(FUu`%&AHi}NyrL^>XY0a1S^i26#{$3cSyr~ohe+`m z?#kzEjh{9>$!)SAf|Cw$BDA|$sZh7g?d5FUA6Q<S`Op&x1H-Aa%-U61c?pK5iEv%B zl~NVDmWLY-w<pB(_w%)dy-}oWECYejNwAn%H-Y+jU}MDSV)g3I8|y`w{x?S7)zayB z5aT{(tA-L_8&D&)Hr17?w)gCgW}+Ug7UmcGJ|O$a-G_)pk%XBYkNHBo+N=(wFB<&% z_4~xeMvW2FDJ714yxIr{?eXw%aB$$4_nj`hq&&3bAeua=!|O5BL%)*WixPA1AiKuW z)HEoIjFz^=qW*v@fBm)!oqT$lO|??feRt#-0DIXvpE^E~`2ri~QoAD@e9&)U%3k&f zr1u!CY;2TW3oWXuszv$w=Htxc&WF=oV&eMdp~iYys|C)M=as4q63?eo`$`7~7Ddd4 zbH9I+>_=0}WyH}a7;NN)nB8bKTf}Epq6#emB}9TfAcLd<nk*C=4u#k20m(UdB3~+G zxkRhcB4C?{3v4u;!q)c-1{Ss)1UVT@wB2}Ar2&y#E5Aa$0w*>`vwI%y?~}D8l*MGf zIT(gT#9pd(WlC7elQ67aJcuObDJv_l1VQYN?wG^>bVeW{2h{mmeZMQr&82&C_35W@ zugVULPz8eoKxy*r?R<pnR&fBl7R<JzA^SE2%7P+`Ef`KoO-{9)uiLM=!jlENftnrg zS0fMDEJH7GJ}FYDTP%bp-(SpwATGyO#j$4Y$vm!abi2oO%iQ1v1Xdk)M{??QpKvHX zxZsq3<WsIO9gWoFw;-8=+@<JYt`~nWKaw+8=jYc=NJ!vhKO!b_a(0#(9zqjXyAy1? z>Rx6bZdgoDjj=1We!M9<|1c#eU#GvU$@*%z6|PZR-N}iZ&hPwo7*44`HWBNs1qLTp z_dN5vRSzURq#oeaJ*_PB0K++*Dn^uq5&(f<f{Hj?k5`C;=-8LTH5|huWaOR1A^enZ z$kE{Q$z3Ui)B9S}7w?z(JX~Td8hNx<yw#0%PnSnHm^!oR5`+BLwAmQmJXBI{EDVhB zpBTN^BY77*-2KM|Ib_LDa1*&fsAq|ErWLX}6kdgdY&L$;H1B|IvoxoS*KQ1rQsh;X zLv!-1$zYp+A^^aY>whA2!im$jPQ>Le$jh*`kZo3)yf>QiGD3;VncCk-I>5dRX*izk zlL&YOx46YyOjD$afaLB7J(ID>klvV?iCbw~Njo9EGkVTOqsqQEy<K@WBgN{i0}$Hb zjSk)y%MoqkeWqjU#cIo&v}U|o_d}P&hU0OXsCE4=F)9wPMiRbm%=R{*sJ5N%%4Lp~ zxvzKLKOj>)TT06ekKxpsuD+qK)d`C9t0P7}^@~=|veG5W!Skn*U60CkI~ps#+$x?# z&Nnp@T$u4MOy_o$Xd5XGav=kA*A-_w_YV$E=3Da!Y6hscfCHMA)6#*G!QjvA78*Ti zI)br#ywD!hB!SDQnUh@{l7ob)$Gfn=$~x8RbQmbZXY=$e?s((M?7FMqS3wV6DrsVS zFaf*G)A}<{-Sc`~0H?uV!tlXFqz+B%AGY5a7Sp0JuV{^z7ucm$=eP_y>~?2pZK2ae zI;|+G^*TW)b31OdT+x(YDx0DnQ1OzeZFR+Z$=qng{q@^^7~9M@KpRIlsoozQ+rQV} z!2e=OO@lYC+~~cD)Aqv%exzAQ^MtLi2b(%GZP7wI=%q9$YKae{P-6JMT!4gz%64I` zx~4`1v4HW&Q@7inD$W|2MU*clqp4h`f~_d4Oln6{&4d{vDXnPG{S36{tV*UsC)0%j zJy~4SofnUU9}y#V%<bQXXR<poQkl)SFl6iu#d~gGNoZXbRbz%e&ov~K{xn3lFRubi zgGyy}J~}I6Lopp_I<91E!RLU`4fI~ef={Ny`lFP4nM*5dtgWpL42aOyqWWA;(&mG$ z>}fkS@_>f4=;JnkifuFb-CAr{`Hhm(($adONQ2yOkCqLstpkwWVSE6pO}E1zkJ{2h zEui{}dzjW+uHtmz`5Q-OekL)kNo}+Vgn=XftlQ$mO!bRC3F=x?aKTc$0S%wjHuLs$ z6>-9{D1*;y=Xjq|Ueu2l&q4B#-Ac1fdBVx{Xir~oA_0W`Xt|s+-N$gW+zO}ida+G6 zFgjgsnTT?%Et|&atq0i}ieBvO934HqGM8{TdTwp(je0IjM}kG@XxZ!uU+HkBTe$zZ z@=2#9{Ae10GubZn-sKeNnzgFd>te5SNqc+SQi%;V^We*Z)8}G4WpZ3**AaT6YR)Q5 z9~=sVcUOn4-p45)q2VM2MC!2;3e=0RA_@2;pHe^TzwJrmw8w~VTd1c*mwiNu`2lnd zrRb!FO5yl?CmR7_xxM1D)x%n>n2Fytg`OX-fr@@{eK_kMgrc818yBb*tS8|06dV|W zOvt`51VGdudCX)+_7xS&Eu$%Ho_AH~c#OI^ZX4K^^Eul?_<a)+12w7)S|4~tWA683 z3xNx!@|s`x1(?st5R>X3KOk?cmatjIrqcKOGo)7M{T7js7qYS2TN9kKw_WYP#<HI3 zTR%CmQB|EOUu>e+>Tu{Dm^KiW+vp<n+?_0pN=Q)cgRE<Z`5$!NI~L)5und}7IyV#p z`oQm!P^yH5cGBN*f_$GJ*M*Tu*;)G=MemMJPfcx4KZrjFaP(}BlLu{6i`%TvR)V@d zD$k9hpF>O9!YHehstWrN)B-4;A1KU0QKSis8sX$<*yvN$>^3W*k{GJ+TJIR-i!g?h z*)TPRj~5%nU5uMAJ~3GXyQ;Wb2CsLU)xCpR6dsG)T0^;(XcUqA{)7mzPI?Tb)Ow!F z`O`gscaG=lF-71~e*U<6P^4blJd8;93pFWUa6WSfc#&`#r~M5Pw_?$VlnW&)cX3c+ z9@g7xh`e#!DSq^Yi|74DcT9Jf$w*RUZnvZ+9Y*C-ul8t$z~K}CN)u;3Ej6Frey9>V z3jL!K5E>tdLg<^7FGDC%PSd{}owX%aax-ixVcFLmhHJCj7VWkUj{pW1Q{8f#`{lb# zba?%Hx`@{S-GVy*f-ynphf0lF0tQYqJF)%QGWReQy2RW4WlRQFaTA1zbP_DArDns& zv#K+X>qAt0Rwyx%eHP=-{QdigxGq?=Z2er04_GPM=mznn@-K_CmFYY#zi_Jr{R8h1 zN{dVNpQZ;BTb=hQWXp>4LqdO6R-P`+BkWZK2NJ>*aXD-y@fxROW4$}OT5Pnv#dbPu z1$wsK0b^fZ_}GH1=4`Fmkv_$nlSMfh867PtMCR8a{K=juVt)c2%^I}xnGzMxm8E$d zZs+jOP$&8$8tb$;@F1`>{5M>?TAbam%YVHOulAB(;@TSZ+24RA2EKu@?S`5-AIm>K z<uYC%KA1H%2BRJQN(DXL-M4!)&Rq}nGO6sn7=Mn#F|{Eb?ZeW;V^m?!0AWLZB-1t< zNxbMM?5CsIh;()<XQOLRy54p<GmU+05A3bIgj{MB4)+rQvcwBsZm%TB1sfJgsmVa^ z4}UHF=jU?EZ5(JlI=2TvVWnETk-ECNm{c-26AqPZ#50;!S#Q3S9yxn*JnYl+d0hKk z+^sh;lb;3ahZifBi~bf0z#U0#HG3fndV-bYi671&4~nc<IZ9UA9Zlbs&&r|)$0u&F za*~jdkr&F6yv|(9jIKOq@_F*=ABkJ_N2Yg(^#zIsCK)fz$WThfvwvn)R8bMM3puZ- z1jRE@7whPG9vtj<fM3cS;w|Qw98F|E?G75-wA0qdiqoALC5&UxvzZ!m`8`?Jh09=! zo?kNQL`cr8GZ~&qZj8zew8}+4Hr3nmX;JKLZBMltZ9eX6)|sJunG-r%SrNSja3w{$ zkUhajJRR-?|9iYKD1Y8}EWd(%yYtI+T5;y{B;zUG$QhfO+9To2)k#bOT$@!dtxB3$ zIG(<#DP3OYpju^*TvC<ktqqn1qxB*}-0!ttXBStWM{OeYw{%A`xX8t0f@0!6uo%|# zA6@LA%Rc*5*zkEMm#Is*#2Tr@$1ODetP*lQJDg?OP-3#R4Mz`BaK<!jZak%!c1rU; z3+BX+@bP*?{YCor`@T{^mOb2Ndpo2tLl0_OVS}TSnd&CGi$wtdkI7Wctn&_7??Dwf z44R=`HFVoH+7R7lNn<ZBuPyU)<cGU+;T7JPPP6M2l^T4<?I8w~;AYqH9huY?*Tb0{ z5l67_dHYJeshq<pT_A^Cej=!(lyZpG;4+lBDpSCRQT(*s$k38bXAyM+2H+UcCy*Mt z4oGJw3+!q$DNq}7#ZUoO1yQDLlUg>WE}hSl+i+oOi&5~&T09vApB2rRfL2}nZEmvd zR8lp0AhOKG8NbJs`0EQa9hcMtgJBm5m&4qQYz>FwPTwcU1uP=Y#Q5~}^)>n|PT|DX z3AF3=Kzv{XK_cVkj|+AdmW$udj~A6xjb~E0j7}#@^;Yu^!2;2)BG`q}$t;`~=Ztds z)6q_IHU_<1-!@fBRV?SKB(ggL7MtpX$<%)S=q2qo&k%?u0&3(d(1jrY?e(VoH%2T% znegqZaaDRPTJcI|%sh7oW);R*e$WAow2Vx3w0#R!7?aidE97eRv7J)Ko2pAz(^tH0 znS|Hv`<p0I4yyCZOODS!>0Npwk6{sT{-B2m#5qvQ5v^8xm1dcxvRCwyveaE4OpB>D zNYt9l+G;!YF_~9NwDBl1+52YlH0C|LY!BWp-i7JFfAcYBQd)4o+e5!u1Hf{N%dv@} zq2cM;cbZagK<J%txTf|(1Eu&PXl(87ro6N`x!BE1^_1Vso`6BE1H7>hcXOJ0kt+H_ z$^(@9Spa<Yp=5y1<7R?mtk=OQ-%If<LKKQhHbj(?^)^ezekn=8j|>I|Mw&>#W+Sh- zs6slvK`=Gd^!9Ft*}jwW{-oZAjD<`{MFhhv+j(elu+%lv+pq3?YJ40WIwK>);M5F6 znrYA-n#+emO#n_kS#ECy-?JF?MT2D8q~ng;J#Gaz6GLlQ4uV{5q*|URI`n;OY9yVM zF$B(h(*`y~5YN~kZtLJuRqKIxy4$UOOsid`LW;z?A3tDL+q?ma{3Lxb01;PqyRF8H z!uEP#`<jsTV9^%Jg005Jcr=p_)6*WHn+o;Tg=#|3opWgeVwzncZx$A~si+EsSYE)D z#pXrDPaNB+%T%TVwQCG$jwE~z@;_MvNa;b<7(GA>@*cglv5{IdlJNO?hPOA0m=-jy zAt#3Gi64=D0>W6a>DgRtgqh58#@>|6<isJs@H@k^b-K&SGkzU=X}`nV1;`71JNdC% z>Zs@j<}Y8u!k@f{-=^RNMi7;jl&CgXb#qjLKEf$gfJKmzhYzMHtvl`puxQlxo?A0H zY6@wgUXF-4-MZ#QbSx=@ebJw1XiXMPulp$~-=H8Oce}%`S}Qx9`F4ZogMVr}>3=`H zFP<!HZFf`1`s$0W`10nBm**`TdvqNc92|LGeA{68Vx#e!v+2@VJEsk!s1NvJP=P>m zs&A&pX?b%Sb}Ayy@;>f<eUVN7NnMycciM6$n@_iGQsU<ox2Q&Kk+{od4xj~;C>MuL zU=|bpsP9AVTTPab;L<k@s$AuD*cpgueE+k@ZN3IyB+pF*&5!hHyFay2&|ef_rU*IR z&bC4@e<*+Tz=Y%NKs>;(07333n_0JV0$6`?j%YMnP`+LJE&MJQ-9Ca9_F!ixvQ9Ud zMfQ`e@6VrOYL&X1TR*&fyHDnyU!m0RM=MC&Z?5ng<eD#xy9=!~>#-(~RQcH&X{0Xn zpp70C+Hjb3JAw8G#xo*?m8;1IlEo=b%xb*?zO6_vBKyD-1kv%l`G_CCv>qY4r`!IZ zT&fF=zlln^lDqTq<!SW!@%9g*l5xxJ(Wf3-zr4jl8wY^`=esdMfY#Qk`=fDxbNnOG zo~qGKa6)w>C<(LwK!>M6YhHb}LWhHwcWC&y_Rd1nwlOdw6^lX9X=Z4j6O<zx622N& zJXu~w->nt0Gj#tf&BR_0emY}T^)NKToIJ%wDSvhOp7=XNv|;=wwo`@TcK;?dY2Nzz z!1f>pk@)^ieu7|WP_zR3xr04SezU`_=mA?ZVJ4NUIN}5%7G^j);d=~Qj*s}+*&qGk zkjKWy%jL5TQX3c}QdzmYG#yS^1?_)og@r`u)EjU`zSd7DGmz=R9P}TfDEP&IMDBOn zUtlCKGw3Aq$V56^txHu(Baia0RtMt>jOr~UV$)(`+^mBe6Z5>I-kvQ-b<mCrbg06& zuv2h&4bXC{3GtxQwro;K*ENntVN4GXBQu1A6#j9=q$|kIR;W?_^ds1h>_alI`^24u zQUO^8I>ql0xQuswY%Z_aIo+Y&&o?qRx*I*s-p?sPg+^Z+D02-u#7ew8Tx`dRB#K$` z?IX2Xob2Vl>cxb4J>4mkFVt5{kSswGm$b-RMlvDak3O;dz`;r2j39d_$f!w#k4svb z6LS`rSiO;+8FSEhG3ma|pxHu3MRl<55BJ{P2tPWB^me`1L3vA(0(sxPSE@zh=?RRE z8S30kk@ytk$-vFR&BCiFEG5joGrXLsomU=H6!V>Vj==sk;w}`2X}rXFu@{xpP14mV zCMH$%iAxbXKQGMqoRP%OsPWH0W*!&}{^>)jS*HHGGl0YTZY7F|mv?Da&Y`E-W&0v* zcl3v{xC*YBAmh7+&*PZAc|^}ISRE4pgmZLp$>6e_0Z@PRT{^R4PdZP`jEAsr?h{H3 z`!dk2^o=^4Z;0?R>d-bbjoe@E!uI1=Gv%xTyKZw+9Wjer@Kfh`l2}SvRaKp|KAW<N zk~4Q6<n8LQrCMbccDd5-h+iGsd*lk?KomNK912;&*4vZHy{Ff(rK!AC6*lMVv}T7$ zJT^@nyhriWU1ljR^B`&bbA)Q3DbF4vW_G|*<YUt1m9vqNhhM3-9E~xiLu2Rlp^~r) z3@o^zX04*CH5n~ds$6*r2s<P0aDJT0q(4slK(N19S$QOP^z8Qm$9<V9f-pC2xye5F zgZX0YlBw<JiOzJX3MsGI82}V;>60?I-cqVf6_tK$z6Ic`hFQdi-%CqvI;}1>JY_Gm zVEsz%4!@o9!`VszdQ?<gA#8p7^icC(E&xK;?_VxDobp~Q2!(Wg^YHP$mZA<3LHTSH zFUujyVr-9j^I*C%DsF#|*|2(67vdeew9cS0-5YheGhCXT?Y%YjQE=h9r?;0d+axTZ z{AZs|wFTP@esq5+F4MV63X8SJvd8uAkX%D7*MU)=rG}Q4;oYy5c6ePGn{_w%=xjHG z4wvJDWTiqot4$ut75GoY4O-GptevOxmpf`@Tj2uL<QstYkdvDWZfhqdx^Uf_Dt0;M z)@gC+`W;x8jkgw0qfnyX)dPr0IJP9wa{!}NHBKd+1_;RU$+f#=gAq6MM)CG%t|trS zlG7G<W?GG>+VvhfEnda;ydR+FiyhU<Mt}VluA-8{kznlV0PkavRKo&XSH1Nri5NY5 z(^GhVeCCU{H%(&I*L%9Lm_?iZ^66j&!>TX^KCjym6)Q-j;uUdVjq#TX&3b|X3nDkp zKsuF*@aRUH6>$gG_aTzr)wObAs^!P|^<i=AU1U^(rF%OJi>j46YSoIA_TZLd;|Hyi zrRIn)n-Pbz95(8t7#J}^ii`pK_ARdTiWL3(<2x&L8kYq!si0<x;xSE4O@8+$vvEK7 z{(7zTxeP8Z0->gl!a)FRMMUs3vzYjC&fmeWtD6y=d!{T81C!<-h*aB9%~vJoaPsv* zO+&=}vLM2AB&|Q5&h?uc21GcTTx^7SlRAUn*`V*&n?7%B`m@Z6P5Wx*X8Mj>vBg$X zxs|qiyPCzzP23oH3w%I8l=C?mN(Lxrl~~<p-(fcM3uUZjKMJD@oADBC1i3Z~D=WWt zs=NicU6FTMZSTH$u!Wugy)z(ngs^<WImp!^ET1!TWz;DMe>%<rb-4NV|Gen>eR(rX zh-Lo?#B~wmX+3(yP!p+kV#^Pjcjw>Uhj`ofJ_O&!{L0-r@dqtM+ly$0H#Fd|ROI%` z(&*;IrI!;Q9JIRFcs|q42Xk+mcYx#D6yhz|{%{cRTcn+R0Wd?U7DOKmPD|SNy4n-e z);_u}1-8hJqS8dGNAI_gvoGQo?rVzf4$l%-h<u(mc)V_QdU|UM^@n37{s|@k6Uz(K zQHf}>NH|pDZ(U#gMyssmLjgAYqj~r`HXX$>TlWn-Ma{uf1;dL$(B9;(oH*9)sX&$! z&_WM#Q$E}CH^ave`W#k(AT|qi_~IGw<MjIw-o%ju8-1ll>BtB%&tGEVZBDq7Mq$l% zUSXQ0xKBTsFHom=-7oO^^U=x0a_wm@#3s=#3^&}SLYTwKz=Ff(uP^OIFF@anO1|3W z{>3z5>usu=1fnh2uO1!=iPL_=4KPvsRK>{aen?TFi`BYsy5qFx+L-+H9f~4F-nqws zl)s6IszVV{C*WHt8bzv>{i_F=u$#w+h{vVO>+uHAR4_6#xIAaPpC3?;d4@orQRM)9 zNUi&sp2Fgo-+p6jt6vEXx)SvInrS!JVr&_e!54^1dUG`Y<)ec8heecCsg=7QscK+u zy|rJu?H(K2*f=EIJ}*znUrcG+!pfco`lPBL-y}$^INMbmDzs~iK0WDEM3#o(jvyig zC@n0om2$i8&r~bn+9QP)bcf-5s88KmXApnfiKqY6+@aRy{3V=-84Qrc!3wbE&*qbX z*WzQtnH;7gOZV^1&w?h^5d?k)bAJnpOb7aT0r1lbCa13ENSG&We*j{PXn!rxt8V@w zA|wn;qewBVud5r$<e_%oUuv#fv7wG6t*x)`Zmxa(?z>T<%jt6xQy0qRkY^NdIGdRE z*L#G+m_%(tC6~#sQfpG~Ld>maNXA3E0(1Dd`)vQ2UaJKrNbpUa1QuhsaR#@$_EB39 z%-v7@E>9`jR9qq=7SoX-5G|QEp(+xbeyvGNfqoe~LOY;|hNux`eHETpgy?r=PgtHt z%VY3Z4N|xRpk=f7qmOqTcX3h4QLOIlnU@%K{qi4Lw++pO1Q|jr>toGE``@G;&44nD zqIwktKbpybJ0BI3FS#lAlf-i{krCa;hVT3Y4I}=^OruLM4ChkU>j8yi4D|4+!{;0m z_s;FtewIj>DyIoK8Cj#%3LpXKZShT*GAVgHjIJc6<&tW*JGfs=nVVq?edMwpxE*BK zga}aaUc{z#xQ8W>3V?sU#v`;L3gz-WqP(BT6y$c?xq7I#gz3lD4O(>F3+^GmkGz7H zixzqU2rsLnBa@+&<#tO`5gi?-kb<75sECOB%ROAnA4~-N>33hm=@OY*>Dq6M7N$M{ z1pGp?76y`ln;i(83uF0uyr`yptyZS#j|oRh@pyYY7=8Jg7_*)i_>Pm6ZYY@z8eUP_ zzwvH^vA8{#tweUYg>$oS_1*o|eyo|&-a@_B?FpZ}a!YG#$g%$%6L9t4DS9`6gDRU1 zO(D%nii+g!r8rM9L+zoU@Et0Ci_3E0$#M&?#~s>kR>$)Lc`jY*2S9$kADE8GsM6J? zOJI8Ka7$C$+)yo7UuidGf|*la11O|`%=_5(o4uuH4p$I14QOt5`LQeZhpR8n=tXUw zYdu!peIfoNmGw$fg-+1OH36%rd#iD5-i+(c6+EHVB77{R&Mykc*45q{7qM!Le3M}v zJam$ehjP(tG$OQg=d7z7{#Y2z$2=~@Dy@trOC8pZerf^V*m_erDrbvl*9xw5`~0>H zP7PIO*kcPNB)#QOqoRrt_u?FOYOuTm@{508oYX@e;0n>6aa`!b3kZCCAT`F6w?khI zWE|#OguRhKEl!_2T`o_STY=(Y35cjgjUlz*$D4BDph!;B&D75hTfq+71Ml9!=yW*X z3$k2vspQ`xV^9!hQ<{?j3{faHB}0vw5|`6nR&gESB7i`SkB_TI?OYn`YIvQ_vXY`a zJ|3GzY?F@%5!_@6`jha3=L#aW27F-=ag{4oWps3i=Qjb90YV)98fz?{CyJb06a_TJ zkqoQFodgqed|^(GpTGQ(P4xHT;^d=+zD$-c9KFm54<NPG7B4luEFTDeyM5U^7#NDX z`x=l=ig+8yL-T7by5uw#w-6x#fliwn$??(#t~^P@Q|8m&Q;W0Z^z@Ydj1X6=**KZ$ zuB2g4IG^{^E`;uTkJ`s!KzdyN8l&yKc`mcwcpsfe*BDT3-;l_pJMh{k=@8@^^O^Z3 zan{F^$lT1Lq_jAMt3@Dst5YcfWf9rd7wb*J>A?e)y_^UGgY~@E!Z#Wmy9cyZv_hyL zU%+cNry!G&(Xl0qvmqzl|Fn{*>5G<|6GG99Ene{?ee#&P<syYbfd2F5H9vPlZ5_&` z%JuBxLa3eVx0J+5Bn%}aC58JSviQ?6wQNdGNddR<1ckJK)gPKEl`+2$kIo5@j>Jzg z>y`$3W=Rh(`S1maE|@ab(2OVwfYV3WyPJiakB?88Hm1z0Uz`zDNeOpR5%X&dG$E!l zwRDyK<PKmsJARISBdgD|xG?Vyexz!hy}e}{pGJ<>P5t!LQz&k)TLyoVdxx)pB<XW~ zAYdDzR$pHiV~NAc$|6)iA!CW<Bc!DCMX+98MMXy96F>LEtJ>WD-X2x+<JPki0YySr zXJ^1_K)s@>LZqZ~`dK2h-e-=nb9fY`KHJxgtm@iY;U&7oCP2UV)sVsJevvwBt6HY* zYt;=<8IM;I`y7V~s9_-?vS0y1L`1~8L-JDky!Rhn9d7+UJ_y%9smrI;A%0h4clZ;` z&S))PBbWC1d~ChTPY4;GrOB`tkPFjMZO;Km006P_cfyhj03)lh%rUo<)ttOTqH^`O zhlk@sfIi2p)5B1B_!@TR7Z3)Z(Z}1vSe*7Q8pMZmC{CIF)q9Pfx_Xl}#)q!OP3gD_ z@09Z;Ral3EmwmCeZDD`)4J6PgY%JYh%1!Jr0<t$wk#=)vLc-2+t1Ga}#tby$L18Lr ze#IzOE}kGrAsEf%Z_Rx2at)>-ep1v;-z#L%hC=EN!^ZU~*dEUfDwIu+l)q|c4Q8o_ zU_+To|7*CVxn?l{{I;Lu)s&>3N@X>az}ni%p=vmQYY82_-;gbZCN^ZS$W>C@=f9R$ z;HJhbZRlZ09I$`SdB%A6>szuk7|aVTAV~0RGM9<`ccaS*4*|@wJ6&_Aq?r(1(Z6n4 zh@{GgoS}E?AZ6vanz)#lEx|s5jg7PM&2Ml2UTB;Nc#t>^W|V&%wGWg>Z)N^|>f5*f zw+$G$`QLNV6%u|yCH-Un4X%L!9^juNj`II;77I@2>xlXOd&VB9(8$Vw>K{We@W6+2 zuqpq4AN`-3lK;m~^M8FwcnuA$;?h#Ye+PPH+pqb?u6m<;G>t0)B8p5%$@RN#f09wB z?FxWVIyyT48TiY`62A>1Ub`%_YJ-qo-~cR&2>Xn38Ex2fDt~?fmLp&hGwWF!$8*^7 zBVm2oaJ%bU|5M8u|H)sBjjg^j0AaPwS?M3oG+B;M02#ENpoB!9_(Fs8{!}sMyUwo8 zzt-av4XW@=PS25}L?#f+1LZ${=!ae!R;ZWtv322&75<vSb#!zgD*Mfyh)`@gg#V0T zR3aA2?Yd;TQny|5^TpHsrHP3NGcz+_ZVL$w?ZhM0N+l19(py_;e@y3botU0Z7x2;T zkD;tJ{(^#x{8s{tD~`?N_X>p9Y_Q_;esV(^N)>?UXyIB~5()_T2nb{@H8Xs!0r0i~ zswpL9KEPuZ7ngdvy8~ZiYHDhjEuZ_vR<&U-3O?_*29_j;$H-&K(8OOxD=Ykf$&<_F zD9PyFad)~z2N4>ZR{1>=64vbfAnvWTRwFVA3mzQ~F|pU-OgXU8iHV9Dus}omyI=0a z)2c|y$&Jp=;?i+B{1I3BqU7q@$nAXapCQNT=dH?Ab%5{v&}PTRsuXfhIkGGkdqmU@ z_9v+UM;cJ9$~0<m{G{9Ra&vWRl=}fVy*R(H{wYV^exTa0XRALJ7t_+x((C0(gJ>S` z3flYDTTHVIBp@4hxP!asEi5cH+`cvAnOhmqYbCQYGHwqfOpc9xrLZzFX))@L@q<Ow zYYWH^Xp**HIhwCE<g{G@1EvyeHKfgGGTjEN<PQ-!QAAvRXN3AyH8nNG#rpswTwYfG zS*k+2`ClV{PFZ19Z+uwVf4Km|@uPW11boF5m|s!}^!#pX-;ZqX&erjoEw*XTr+Z`w z!k_{EA2+YDPqWqKc%wVaaW?@b!u52;dZBC{FxJ7CueDe|vfopZzkqK~mJ`agW&sZn zc;3O@D4n~$+jnO;H4NHvwhXAWqt}G9-Gz@gCxonaV?Z-tJSeyq=H_{Q!1nRuM?cm$ z>**F52U57-3^jlHuK1gLKtxK#ij=%4_$+@WCNQAI5p5Uh%r^s(`>YHN{#_q?nI?K> zoGwps-Gi#s-o=8sRDd~NKg+5afl@LKhb4w}|1HCcxrV5emL<?^bJ(oBM@AO5Xf%t? ziQ!OHsc&fL{PlQy3K$WAp$F(okJT7UA@PFGvW)sMfGNYu!J!t@+BgH4{10X;sXYnM z(7wmjl$R412!7CMsWaLHiXvcL7y0R8$Ox+7eRm?zl)08MDwn}8@*~_AV5b<=tDv9* z5V0|0)Y?7R%C;`H2mjJB<>e85M7}on4%>g!;E?d_4yHaKrSUsb#k(&4v-HRY@*H*> z@L0fCvrM2EuyfB;$i{cWXaoIqeZahc&JxZF{~cW=Fd(o$W2Mbmim1u${IAlF{&i>2 zG;{U0V7Qg*(Nf48B_&M3h0FV{Ak>SnPAOUAG$T2n+AzG*c8<)HYmwuWYt&K+DFO8( zyv^k}<?IX4cH+QAlJL>K`<0SH&@V%TK@hs9Mg$n=a_2M%n$3WDE12>dCl}P@^~hfH zyQX6H-#YM0!oYB>!_(;P_G+{Aqc{X~%%gV(qy~_XBM`9SjPMy8w1$${`zHnpv~G0) z%NsvG|6sxpD$;vAPB%Ag?X>y`<N_|2ExYLhwySI8rR61@p;UZALakPtXNzgekG2o{ zn?c)BxMU+3SRZBHt_k|Tqwavm`QRT4g$HfDSYO{z(*wnS25=23%F0IOx*A0xW;oTx z;^M(60ODX#Uoh10_I>}Ic?_7WWFldq<Z5NwpPr<{uNbvse3OzgWr;{}sR<(^!s3y| zByQi#5W%k(ks|&f`8S&q{GwA)Q3<Q|duoWjUkz}Cv$3+F5-*u%@WHYC(&8xZ?em6y z2Woa&2lU!w9_Rf@*R`Sj*l2te6&1oS4(hoD!vq}G9R~Acs8P0<)T4HNptIq_MCSu` z^)|Z^T-V!*Wls%_#WrUzavGr)QOn@a=jW&J8sk#U{7`_R?&0-((MBSMhK5E)LOQcJ z$DqU*-P=>A=!XT`7*97hqtT3Zr~L^7ew;U#U2v!v(ghjppXLFMBa9=3gxzW{9RCaR zSS3gxAL+)h@87rOqjqz|#m35dg^ZNKZdsgL9?REzt7Wv@?D&O+Wn+I+j8Zbe`}u&f z2S(zpu<({=S63Ie%Zc~x$T455>z$h?B0>z6tChuAm40i3wXu)*=zuP__eI`^eU=O@ zH8ou5RCXJz-qsHLZgPXo@mw+Tz;6$?e?l-TDk^MO+R%B}sZ9Zy(~nih%9Oepbk%&h zYs3Vk7DA!RH5-5P(DJe_*IO2Hj<tJ6NhLCZn(XZN#<Kqb7XX$)uf({%xw$yM28fC2 zfXmfWIU>yGTw$B1D4$J;D<qxM=BgWU_hP%2RKPxEE?)EF1O?Q|Wb5se`J|Cgt?6h& zd!6<Jv=G^k>mU?jjG#x<JyIO1W~(N^sCl~5?s>5#@$z&Jyf{*RFwG%WdL9t1`P17w z$lw!T<pwaH8&a?cTM&=y5-?E(8i2(XT8<xON&uz8+Nw-gNVt0y?%z<<z(x)kc|<;h z>hR=+i=CaFp7rWzKlUV30O&sQd2_QB8W@*7R$4thf46A>9AiU+I}EIX85BKkFja3? z*Cx<0ZJaH8x*Av0^>A2?jSUYoFfz(YPjBRJ_C__DPl^(~oTI?6zFj{DN>{hORSIAZ z-UcmHI|d!muFi4x`)t@rTXjnLbRU8@z3VvsbazwV`)(K*ZqFKZ-knDfaIDl@&H@0G z!)8vtMY))?wio_C>$uA3?N^N{7a&GJXPF9Q0St!%>q`mQ%scG*2N;(<JG`%j*vo$^ z$KkG>23hS*?K|!;s)weX0m?rx02X#;Ocso95FluTh=P!SYOHj#_TrUmHUGKFX<zX? zVFX@pvAC#+hL<C3ZK*q-;hk!wuJh3xGtyKgi1hHgFF;__n-#D9`0_70+{vNHCc53! z?F3Rbeb9vtx+*Gc*x1J1J@)vFP%o5ls6=*snINFVb?NKt2OwaF%8B4;Di9G7$x1Sr z_oRAwc#sXSGc#j@2mm4Vw?Eu}QfvdCC$<RIoRqTk1eqU@k&zF_vV}cFA%0f{Gn1x6 z!66}nsiFXp)xF2T$Y`@PXCLnUZxW0pWhnj43Baa@h7QCQ$^p)R^TreY{QP{Co?_|h zoNlttF59J+>#g%mr^v4V#NgOiT>qPci<3-lMP&VFWatXC%>M`Jx&Kd~sv<aUWo31_ zz+*IMQkI|ppTOK<XIIw~*b9(;ug^C<efKISSUxjp+6Eg(b1%Fz5u(Dy+1tLko9f!U z?1JWl{rlz=wp>1GRpivv#I&|*eTVpb`}X6XsNYrP<kS=^>wt;AIX*GA*W-+{?;exj z)uP^(M7kAMfRA|5>uhfA0S)atRU@eIJq~VK00;H{vs@KsHuuz(MO3zDhT{y@nwsY3 z<~Rj|af7{+V?sj#Xd>yrl<1gj2rBkJ8?|q*K3qr;^dqA=BG4lw<T*kRh5Vh8amE0` z6IW3g9vGl%hTkxtT(<>-wZR3HW2;z9=KRsDSro%L;@0b+xKce8K|w4RWIJ8YpxH{@ z_y0_2jU-?+{BS`4S4VMgVWl08Cv0J6QCw0)=1XCOVQ+8GT+JMfzOL>=NlEGZLP8`m zKzkt3afPaVn@R!e%@_dt3y^5x?jHYz>>5ds%u0Lq*ezY`T#yX13>%yCM<v0sii)xd zEK*W*{NH#<EKC9QT;}HH=M`%IJbrh!WLf@mEUB=9f(j-};ZW<&H?r<mB#eLNLH-BK zYq0Ex%L=Y&n)J7NfcM8@rozy`00)<F_xRZPs4sh~BJ3wKJ9}hsFcLB{I##*7ey=Sc z20uI??C({!nR8bS#&YoUzX0aLxBZ_~7WVIJd-e6#FUTXpYu3ZgfEUphY)44g>Un#D zM}V)PsmbU5B>qn)@IdI-#{U2YqoHpY#&!LskBXICa2oM(GinGeX)#>>-MN}TBEz{o zpk_7Qay+;@>T12}q4#>yXbS=am0YuNgMOK)HT|75Sn^)!Bu?W8K&=GK(69j(D>*VR zegQwOuGLmpBR+(dZfT1URjgLor?(*gkj~M#OfXYNpU@3cwH_LB=!;TNQX*vb(B7t1 zsl_Kuq~D&Z6OjrZN*sNo^cQW%)zch_4zzn-)2dfxJ$-EvpMIdDE8QB3*V5tBVR9C= zp(0n7LBw(R7Qht%4{T<d4K`s9*-zqpe9tA*7DYux@AvmM{d0Q@)H>?D`f|IzFjgFV zhZLRZeMG)%=DTt9p*4asCxOz^&`>K@H9R@m2eKa?=NtGjI)!o>>TPDaxm3e4(lJeZ z-&0a7YH`YKbRp~xb6FCbV&w2k6<*Z7EHOZYF8-62E|G4V-pNHiVu1A9Qfn&%+9%{@ z2aPPwR1Q<B4(LKTVp1N9roYQ6E*#is+C8onZ%l17nfm)5Q1?KsF4p&N@PGWlq>@Eb z93J~+_jqG{PfP?OVm4N^UlV`WBa_r(wtukV1y`_7TnhvQUG`VBh!_=WoUT)xf3WJA z8PyqFiz@pY8zxk!t5phaQc&B>6={?&Tq~)k1O%v1?v~8Ic-Gbdom5Jr)A-mF)`S!o z{Pg@B8(tp9mU><_oP#QBjqBFlzJ0S5cP1xw$vC!6#|;1)ejqXFod?N0U2bh0j4QQ! z>dS_i+~ky3RqgP3WxRpg(X6#_k0e_CV!lnMT%u4LS&*=^D@Qt8p-Vzc><?WHU_Gh= zpdG9R)+4|BMv(&h<~q5UfY(Ky__#q3%2BKHr3Yc+4Z!*-C@6T}&Nxp7Is<7jb!C#) zHa5#?k+3PPS&qY*vN(`$%{E{{Q~&hg0~E6O`uchh>JcF4eC*NES_HCABV%KItE0Aq z1=j$s=<Y*^PXjcb2$hSQi-jtidI%)5>8%<Qs6x3}(#mS-ICVCPgo_3O*d@y=mLInU z(s}6Vul|ga1D%V6&1C-ydTF)mED9@W-dW`B$qIu?i?o~D9FUSbUFzThPc^~ao}>x5 zzN=L(*UkOb&*Eq*BlUz%LE50W4On`KxEy8Ec~{69jp-5_G&L6l1$|RFDjXl5xXy7Z zR9bb-3v1@Y{@Shi96j0}kaG=lsy*Du6J3ZAu$+`taaLLklqw^_!Vbr(!$M1F`GL|x z((rmyo?0ar?SDLz8&w@5p{$I>K60GO5wrLf{-&v=WoOSmR+SO-)V+*c2}EshTg^u+ zjzdt9_IDkC78{T@Is+G4O?v^`e8F&9STncl@j1YT6Kioy7i&Sl-2){hw5uK78m%58 ze!|KHn%602zkg?M?C!RDbhB8UQQ$^?vYaY8)^1KIDLII+RKujFu;qUQ(2IfIMO`v= z6sbt9>~_g?wV2{v)g4wziA8#=3ocKxfcKr=?1=T>eZUy`JszkH+-!Vla=u?Eml*(3 zF86SDbgcapTbSqI!ROVEK`9CB4IkEUUXUhI#k*FToi~A4F91R<7Rs<KcA`l5(I(!N z7y%L7l`2)me6eA>(-UGL0(nJ6qm8rsTI%OVr^iEq5+eKqQv8k7MlcUDF*$EN!?Lmn zvVSv7KdNdkFF*6^W!ENibT*EN1+Fcts*13KW#g<G2c)^})7+ylyHsz^wU|+3B^ox4 zlZPOAX-q^1KLqXIHh=!=QQucLDst%Q<{&h*Bt;~;MdTn#UM2C{bv3hs>uva}-RbuR z{gE7G(Je-8;XA?!Ug)$hrm`H36dl!|r6q@*zGSzy!a}&5CSV0mS&q?|RxJ6xd8yQu z^wyp0nW<2SHQ}(;711*RLX(e?I?Oc^lQ;kea5!7mV*e}&WGT=QN@(cFt@^C>o2>`` zCX8mvSF6OUH-b=47F#_F>yZ=2l);H$6ckUFQ%?5{H-1kzu>O|XR~_NWsUdA09mD4Z zmG>f=jmLU=W?9+U^W~<bAX3usZ+TOtwmyOj-8uS5_VP~u<pOdGfWBVnLuggu>32cA zPZdx?f67zYwL9D@avZmFx`7ld#Iw@-;o%+OY^6J3*}7D<mX?V<St~F8<lg)1*Vneq zLOD_%r<0P3?C4G&O)UT@G**HL*=+)&Nmn6s8YSUPG~Xbk0v&P}<l_8g#l=xk6bUHE z@XqF|``eehh&#g<*VoqbCDt)ocRdlMW`W;+v;n5uwXCni#DKk*SX!e}->{d=%;}fQ za->Vs4C67gqLvtkkFOu@Xy#(0X<<aUb#l!%jKu9)gZ=sW<?hh8%Ws=h!z!`OiFzA+ zB%vxu@U)v08t->%>IW~6_BSd1E)|x`^Yb3@x}51m_K|Y!4w$H50XY&L0|SH5DgeSR z(<LylunwrUi%vlBB1g4yRnrG?M-6lJd4iYYW3@R^D1KA|?@(+D)K@L;x2=at65DXC zm&*sx7s5)yVlCsq+5pUc?+LB9YbaYAY#B7$zOA&~{IfXlK}*Z1#N6~RA^?%lWamAS zHoqYSWZP^eTbvOt4F-e)5OPw2KEdush>xUkU0!U5!J(c2W>aKCAcf$vKS7DaYCeJ2 z{Dzvk5Qw&~?YG_kH4M-`B`-D8p8+bc3Pt-!I#<2>BscKS7Q(9=s?+%`Uw~bua5yPZ ziW`}>LaA^xoz<8src^j6%6z<qL04p=$t@(tjZIGXQNs~;Js&nqEspk>BGvopk9m^S zR#HfdhsXUTuGNa7qOevC7+Db|_yd(pI%S1uqD}i}4EF_Z=pd>+tFo^EdiaA7cKhr? ztEImlqhf$C+t+8-QwFO(%DhQsX<?y1bQZlS8lLfe)Q_#PdbrkY-olz#A^>gPf&R^5 z$U^bY4p!${{2eDZ7mxGy=U50Y1P`4+Po*p)#95*3>g+5$aaR?mY^Bs_GKBP$xTV>P zM%1IN+1qFJTNs%$t+Q5XPKmv~NNZA&_>c48pv-jbxYDmG(nN&B&id|J-yq$DQO-pZ zo7daOQBlL_t0R}!Y(NAYW0<PDw_96dmXGS-^y!Cuis|=gF075%y-PGg+*Iw<-kf{K z)Os_OYDMGS0Tp2(zecO2?|0?O_|$o86}lb4@Mj_=S`hSiXrc%c>V1QYrGVpFH6h{T z<OI+E!=J@LOlXlX9Ekho!ZJ_e=cD7}{K8V|j_z_ojtq$~f60-N=JGO(JpJev?OKaZ zigtada+z!#J}*MSw^P&8E>?>eq@=Beiv`h!R3F3v2vC<;*n6HE#5~wiPbU^h5+$#p z-3d57(HhB3El=gM)Xy=ZNLFxrON)xO2I2`zk8keDUnCP~b^6B7H|}dgIQ*<L+Qelm zG@9wz*>Or08m!!%*{-ox?00V%`u05B-GQLcC$Y#hAR6`?N4No>Df?)#+BjAxgEvIr zt!_kU=t_%wZWCG?xk2sEwV&~WIAMkuw)$IL{%_xs$qX;?`}{A)&N{5Bwr%sGh=ibY zcS=ilcXxM4NJuvbN;lFW-QC^Y(hU;Q-Ob$iyw5i?-!aF`@Rx_^-fQo**1hiQyw3A? zMd_{*g$YOAG&!kOkVdkWsAR^n;myexg&66gP|24O@a(hG(9n=<e1!VAS}vsh!YFES zUCcg?M)$F;Wyv=fB``SH<K$j_ki`;NmP`zxX`7p~7A}hC_0}G<DTUYwQB+g8e=n<% zO>vP0q&SNX_RdN_at}!@0!6hbJU+F|&FWGk7q7stE;u@!CK3epa?#G|;k~<eIFzl) zd3|E#gM2=rv8VpR3or2^kv;FP2_GSW`ZgeOx;eZ6BolrG!jpv-!#5QOP*8qrk|3wa z(a`W=h!gl2I(*?WC8mK7i)#rulOrSbXK0rITY%nT_E#`ET__g!r_sZu^`216>7u!B zZ{ArgH85Mu9)nX~qSj(?^^?0J)#dI|+S&QKt-X`i(>YveYpO6#z=t$01_6uVbcu6D zc@VwUV#(^bOuiH9s@orw*8JRPF&nYP3mHAkz=kuT?Z#p?zuW%c26j754i4N7r>4QN zw6q5t=@N7@UV2YfUb?L#ORgN8s@!gy8#k4?k>&BshOm@|-9a(G#v&~eT;Z{ZvA?Gg z5HVzkj<)m#;quh!UK?IuvwTo(SI0r4oA$W=t<#90J-`;zYQJjX8#KbRtP?q&>c`AD z+TyiB3osxhORgZK_!(p(W{rD#$e@vzn;nE(a8WIcjg6J)B56b|Z`zwVQyMPck88&) zYo`9xIDfqS$*M|%=&f81m8~(%hlSx?Xqgbp4yV-Fcdy)Fm>Azjf5v63Nf)tr$3jWe zH9op@_n`f~W!^99u;I1&IgOTRRPvdf8`j2-M*d#nVi@zuB)w=$fSlJ!^SP9z1WPKl z*u+k4%{%y0&r}*!2*Raomlq-Qqg|7bh!#F40WA?|lD24F<Oa1H0=~H5Mqa8G9X&A= z$#8fh+EgSg8X;l~lmzqX3TE3RXm!#|&vPlhWi^~%6Ejb@e+I*uZ@l(bx1JuXhWPpU z%_(761&oX!Gj#dqY4DJZMiY>GDSpZ%GibBGkI#T$J;*Siq~1y-RsV`64T~gg=H_b2 z@D^oJb+qe08B;&}%_2$mDufO5^_vV`8)mD;I<f@r8>g;X$;<X~{<3yKLBZfS>QI-D z$A1dlygPi6yde3JUOct#{Ap|9hR0@2p?A^v+WNH2&8fZ8M5|Gy7NyDdu|MZ=mlnY= ziTE3h&OYMrBl0jIdb1DkoRP$bll6DM5HrhDMpM{5z!@V!kKe!(wG#=|s2&RnY<k#J zrTdX274ZnUas~fqFS985Sm!0^%+$Hvp-G4^)z>%HTh7<9P|(w#aHN&Le~qR=g}ive zd(t7_<KpTZK-^l_2-gZo7&edln4FDGY7jV3vZ-9l)rNal&CivOIv-HqIUV=ELeSC4 zLbkOsmFDKEnwrL!lpIcwM=c=C&CZL6iaH(5NxN4&7AFp>7R`cU6SNz^4GGHEZ;^ne z(7O)guIA=uUtVNXRA&95vH|7KMDM;gpqXpIZH)$n1&J(%g~)_zIAm)>S6{%B1SXf2 zl@+Rzu)lz8i=+Mdbo_V#@@50kk>}S7^&WO}r>In_C01s}#Xx4I<su@&nS03}#b)WV zeR5si`8YnmIYal9|COBs?e6$-DP<u_@<0UP03SOyexyeY{^04ckI{VJIg>%=4_NrW zN)S_8tT~5U>mnu?zHk`n5Dp4@_GUR8?2_^;*gbj{JAjl=r21ToGLkzUPu;uns)&}V zb7{%iWSxpRqbEALm6iDeSC1sWpW=^{QG&1KKRy~MLfyDePJV1@bXtrvIh|`f!d4lb z6~KW$j}Yp+Wj_s)tz1xzkS;L!M2xU)G#J{;wv(N|LpS{DH}Fvb`_wwXF6is0S43VT z#nPzikKNA&<^l@_rBYpAprI)J{r(Pem;5yY!TlEp50Lh+GuR=z2~4Urzq_3}dvJ5| z_4OT1OzJ9*7Z4Pb@?1QJ4y8@R!pgrPB<gaj(0AwcXl@n|5VSE3!(^B8tnl&0jnqQ* zcgU_c&N4wHh8@rPY#OlfCK)K%aL~s;hSv_LKFDp3ChY9&M4oauySbVUXhWnjn$TKX z%i`l7Z7hE*_rE$qrPG0hIL>@?bv)VIOMW8yr9?sdaVKSg^~6NqEbvoJSW06kHaAy2 zO;Pf;4Akso5AUHlna)a*mLGn??2aONns|ldfh+LTxL)oDVhoW-Y~a0v3G5N2J=pjz zX!<Ze)h`|Ua*iA0{p!(5Z7yHB0F7pSm5o`e>z|4)<V@euf%#8Ur`e`*c{61;<p$k9 zG>cM8N1u3GPUu!lsQP8SAFAz7+;O0?B{=m3>l-~<h@=rgFI!&a%gI{z<HMV0MI%mW z$?M2Zfsn9WcZu|2tVE~z@;H5i9D}<>ydcvCylRiI_xm}lZEPG~M`$mwnPN1dE7B<5 z^gWAmn;?jP_x3HHU;o8HQr#@u4(xra2QQ0CqR%a25fSTGrPWUVtB7^}3VxgqJ35r8 zn3ypg928Vk{joTSL&Z9tugtsGJX|1mq|3Uzfp^+|cV4kc>IgjYI<7`fJ-7h|!YDsF zudfdhPDxol^zOtZ)6n4iIqJs4>O^zrbavuyf*O`4TF=T>FZs8oWwYI+)hQx)QPiSo zNzdE6H@QT&#q8H|EWrYYvZFc+vz#Xk7gEJ17;6vWD@sU*P_8YZ2=7?RX~~Q*%sk=C zQd5s&sN&wd!&cAybO3Y`cC-%~g)&Pr+ODQ%pA!;{Ww|_@)r3F@&3v-p+Xi+N=qfA2 zARu(C-mUEzg8nZF$(ZeAhH+A4W-nY+*qZ@Ud;S`<cJZjzfrrhDy&$kvvek@_kAI8* zu|}rZFJIk+y1*gH(R2xvwS+#apaIhC;PFjJH?v22ikT_GEt$ox#5=tmG^NTm3`ix2 zs;p+xkp*~C@x8TzRcPa1q=Fs%ohseW!Pt;*JoE%*)smd<Ohdt1h0d>Sy#;(v$XgLg zthMDE#8n01G!1|KLQK?{|IlP;0D64=32=cMiw5sDNO*nj&($`gcPb^LssCKwoZY=U zT3cU_TIc{mKs;;v5lpk4L$O*u<x7Vuw#gF3Cp(564-5`>dmt|Py!>_u3M4U!W@q=F zf~n6WrZSSY;hhJXtS2JzdrO)#C}fgI0s}t2IFVZ2=d;()>bg8$Xeby2vEhMNJ`Lwg z$nUqU%|GenNX?<d-mi7r+05vTHdKA4yU!ru6|UT*d4mwkq{iiX{(?N0Z3oZBY$gBe z`BjxhjTx1W4)7UEo)ln!wf5Hk^_w@GXpTcktVey3#0reuQOLKqx3Ma*K3TFE<ZI=m zl5sfPjzb+CL@cyu;U2e_?9RvUckv`+#SVWUArXnI!~jlB?B@qdm$TV$Jl1-xn;=R` z%6dD^G?}}Ly|{NrE15mMAAxYd?>I?Ro9*sO{ZT4EH`nWK^Wv486QOiUNAy~;l;y$c z04qF@ug80)M`&er0CEY9T4yXzoRM%iO(wViD+*MVtsys&Q$oYP%1v$H;3Ov}7MFNP zS&s_iM$6&kTv(Wy3sZ-<vnLQ38f0Z@8p<8gdXx4Zb#<EAY8e>1SQ|ZDP?ebnGcJCn z>g+!5b1`(zuv{+j)(A-{2{l#u5|L3(&K&etP6YqZ%F!RWpZA+P!jIqIah-UWtDiW- zZf>rZ>=OgU{#z%jCFiG0=>%I;3HF%K@K9kayY{98p#RzLEZcQ9OyTq1=Q=T+Ca)Fy zA=1N*x8xX@9|9*&lqOa6!B=`}%3U{J?=utGnIi34`-F-<Sh6|CYU@#294P}7Z>IJ1 zS9eIV$f?`8LqO6mva}?ZP6;&r$z>zel<W)y77((b)g8msT>`f|VVDQb@vqxNnp6L4 zPXn|QJV_#a>!b2@(yK+ci_`8Ys<=PsEv|o}cplz_rc%^5zVI1;Gk$Kd-ZQ-1<Z`h# z_vD2I2jfCY@?RN&4crA5;ydjoqYq8%pvw66%?FQH<`VbmEHEoy)m^8m1ZI4Z4)3Ge zS~vkO`tr$1=Uwr9RcGox)(lDQF=#IVBn^-4(D#IiCokRhrAEhdW@f=O+ND4ppoz&= zrL>}pqthjS9xH&y>Mz5B9(j0hheLJjnt9yynn)5qwCQhLsh>)d{{a-Y94>XAVnTsq zts1S@Rely)tv&VT7pugis!tjGN=>jFCpvX54;<5WLv`Y6Qy_G{yR$=Dd;I3=h+~89 zm&Cj#f0N|`o%pGuR;y>V>Jfx#%zF~tYu=T52jo&T+I<X6yvwy4M$z63Q_yff_KY;> ze)|@_8*T}7akf_?>WG&rj0ZoBB)gn%BbEnO#21RSZw_lp#85hHo}0b#zX#Tqx$4io ze+Hu@c0dvS+(_J=%x_Ce1I<q_8>ge0R^9F()Cf$G>0I@*PYtdOfS`i(*!FPe6tqa? z{Z(%N*By}R?M!3|g+SS-zE#34UmmSu^)B1p65af#)8V3k##=9)50@$6;B?f~7W9dG zsI{47RaiO8ylP{rhkva`4gAdQt!2!Wg*Ah+zTFrjR+^_1^YRwnI69ii{wxbwD~4OF zTf2J~9q4KRKv^i|3Y|T}$5$V2AQiCWXQoR(epv~%6r`oLrV1-}IyBHNXM{@D+FYLh zct4=gpQ7#%LPKS*lU`6_rdwawn}i*&rYzW-V>I!#)Ry(_)7b6T{({RnPrKf-vq2J8 z@<H`7^D!vmAMRI74>m<86d*YO6ZAC`6JmjGII5Me9dz_*G%E;BBxi9p=PHSNB;RMj zjF+?qs2RydPEC%F3m~h)T9edwX$iC2#C;=SSIs}vsy~emT(`HkAES*)!>1e1(ocsG zH8(dC{_Y>#+2<98!ELp;y?Xuj#KvZ9Ox($GW^ytlG!(Iv$uGeUEsdt_VtB7h(SRAr zi{r-WURm4|64S!P5*N4psfGe<C312oHN7w`%yIhrJ<MHB@5)7r=Y@k~q>d~3#AOl& zUgx~k&r<unMhrR%1GL-HR&;vk1n^-Bce#>1y`l?3MjJ;fW3(r^Z7pGeVUg8j11dT! zU$rl1j?!hp#`jcf-eIxM(;<pZSU6n-t2Eby;+`Y<VR%R?azy%p8oDhbqs0l8g^4LB z06K2UJt_$gwLZEKt-T#$9JWt31AQ{MVykcUVLeBV0qhm>%yTL(eJ&8Q&{3c4B_`XE zwmP#M82|K53PZF?OG{h!9e?a=fX7~BZI{%TpI^=2{#r~FlNkQtDrZ+b?5Jr#@Y(^f zokw2ZyMat);HjAtX)Oj+T<|GR=h&fd;glwR$*bImlc6%gN>CQ;dd;WveKIWF_aVv> zg<@l;)8+E|FvG2e*Mfw%(n5Np;B)*!UwmM!l;Gf+^4>N4mIR#&A9H{NGdKswdJvsE z;ES9O0XB7(E3+r(Eo#&aG(%L<rw1pWjhZiO+p6zb9d`SUdI4OlSz=S{J<`DmZ?aTG z<0K?L|K$gTSvX$qHX{R)^j&=I;nZtIlG&Mxm^gU)XJ^^~(HG!{-B!J}-o`jyXDQE5 zeteg%u<>&;JUz(iyG-_Esccj;f3^c7VeznbnUuQvhkopV@41Jk>$7Uzx`7XpOczqe zan4P!8t;|hkwPYavAkE{zhC<OcH0`N4Rd06PZjLXsqUW(+&RXadxbe~IhW;qOm+4U zcKhSD)bHo8?SD&}9bsFq9Fim5A1C5Tzn^4Ar`~?Pj_}Oic@o2feD(W#M%MQgUua5Z z*2GwU+b$r$TeGI-zs||vW+=9y12#ta2L4Y9+BQI@Rg`*5-25Hm;S(+FXBXIa@8X{3 zK++%BFR7uG*HRtbLj$10gMvar@A^ZF7*L96rZGR$zBm2^JNV-6<`x6X`0V;p`EOTm zpdh3I^X``PPB4aYprG95ql82h1{;r#eyeN}PjvL-bg0`7?-vx9^G1}K1D#gagoW{_ zf8edg#%QrBc^KqZ1wjQt@3HZ*v2(Lfo`@5NP-+QoWPcB`T_4FS$ji&DzC11CqqBU~ zeLo=U=QB!55zz7SH|VY!jYd{}iOi72=l#bsQ-RkDI^x6~yt&ukN+>A{1_lzYTarRN z85yojwoYT55FgnOM?uyjqih8NjXj2@w4h+~@Nn~JE9o>m<ZpBI0UPkELMt#eH8e6_ zx^G=dgTC_^1HCoZYE({G<ng;e=nGz^Y+L8wr4LYQwkS9+zExLS>bSWX8Y0sazrCG! zzW0QLU{Ub!Huv_<cBiOnW$__1T46taDTgAahMl>7b1UHg+LiF??sp%xuyCt@24vgc z{~{!WDVK8(VfB0lz&X(E{=a)v{~xYUpMo?~7M2>916+_4n;suW`^U5z;3@3v9G_Zx zlr&Sy<LORE5gk<?n2^S1JgBUsqGIdh1b2KD9uNSisegYweRFbh(&bs;BnP+iwijwk z;DR#yUSNz_+bd0ilJ-B&h78_+8}8D+-EFXqP_sQfGV1CHV|4%64nB1KAP-kko+<)r z*=u>V7s)fr?Mn}p&*eGz$sz!dmKz)#@T*zrGH|pA$5-eS+Q9R*z`0RdJ!ohYE#0RC z`i6iw3M$0GvNHOA9C#GMkDe_(b5eg_6L=818+EeZ3AHL0&fMR2KitnTLO>)GjkSX& z-Sh~;p4aLkX3sz0bTq^F1dVyvJ8!~AgfE-l7C}A}JhF`&w$aN*)XwM*4=)j3KMQi; zEZBFwU^^=Ziv8mfCm|!K$8k^F{+bLB<vA48&`l*+MMUNp3=dWDrg<l{oBv@CC*U&v z=ad3d@hd4z->9gHhK2?|Kfi*+L_T#vDSLY%BO!Ywdyo(N`z1v8jDL7hQ&CY9dH*Rq zGB&p3a3z~>byXi&bihO1;qG2MtrcRdG%O6kQAjU>*N4cv0*c9T+jP%h#^2A64D*%A zaRxDNnf>!2E2N!6f0c};#6`!xJ8swJ)lEG<{zAAuHa^b4!ctv52n6uk-;dnh<0**^ zi6po18?xdiG^YD)GU4cJQ&Uq!#Kg!=i6_Bf_HE<OW)d;)@p~*iC4E63pAwz=Yrq@p zRM9UtI6O=()#AZNLsMxI5(lr9<M+u_Er3s5K}{{s&+n5@WJ1Qa*c<AU=c@>X!t#|K zoMho_d_2%mpcA^98*4#zLPJgcT0*zL+<gBg*;s#uyzZED0u^`XYxc8Adn{FQ;l(U4 z8^6P6n4_Eaygy!^rVZy4@{;{T%`pxkN&b9tc%y4(USMMypPgMGGN6KD-eNA~HPNjb zgW*^tf*Qy3#rKe2HT(hqf}bwC-CSK=GkZctQx*giB?~?LWx&cK&;b4J-Ng7v##IvO z0KsF{_I0rNOz}*mDn6hn10-SkBoa#KKL>;_ah9Z)?N?E88mPFn@pOBRt#=gBszAC0 zu$;u}V?Ej0`3l(|jRF4@Fhz*&;el!!CX55zZe6*4P=4AC_Gk=&iOio3{wOjsP|-6p z3Pu|k8`H><vAWE^ML`GFr5~Z}w)>M+n3cX&(OMsv%!leAUO}nmZ5I`0E@Y{niG(J> z0($4Jy~!4(K;lPc=21X+^!#0GW@;Kst<KTj_tF;@fFIDuRRN+H4uyi`d8z-TSR^D) z!sgpbp5pn2jd{7Qh23kjitT=*AJ7nZSghyisi~<!W3a+(AeQDxsir96@Y87GRGv*{ zT^(E3?$$^Ou!OZ%hh7}7cCuQ`Qrs)|^z?wrQ<A;{Fi&T#P7R^nVO;|kK6!ZRy}c1s zmBo#@CMTy28#R;tSjG+L$Rj@Tfv!5V3>P=|mk^lfgb!5WiN`&mNs~DaXQp$~lhNOq zVzfSN=%w<ut+aY9s9+q975}KH2#VkUKp9Y$1H*Gh8i8WY==9_y&PV&S>ngFa3}0Au z^(wtE3%+aZe^9Y@N6TJTGQigL4cN$WIBmtmUd5`YfNl|o$F8%_Np{M>{M;XpleL8B z+YL55BV)WLyV-EeU~i_x^<g(V&6Gx?MyJ|f2Xw0?;yz%bqF!vTdJuXT2fQuYp74JM z%oAu&?-SG(W)J7>_;@}teEB6e$r%$J9kIb517|qzv|V-77P(G7TC!3%)zyV!$l-A4 zdcEDBKVhOz5=Gy4YPr;c-5GD(_Y1MZx<7@JHDg7;(+{0OuFQJHbE;5vlr2zES66qV zXE55v^>kzB+ub|*uemT8E9W!$isZ=1$TuII=hF?}tX_$&Cm@rDaeG}VNou-HZrfaI zzzXr2?g6(YC(ns<^sDQ&b?fQ<8wys?{|#IPS1`<Y2V`eYPgJJ;%>c7xYzayfrR*=R z`Y$IHhU6o^-%WsJmYmjC2tdeK7+G1F7&@F487tK*w~DozRm7BtlnZr`t-Xsb9>0lx zo-PQ5LBM7A?j2Aru$-$=l!GKOM0_yd6IE3eL$@;-70cX4x817Xd&R$Y0-D6S^wzO| zziwZPm06e{G}JTzgW}7il2>N<`0}-{eVq48JqLJ)X8dC%L1}5=eU#jKLT}OrxyOat zQjFQ=&%MCyHlUjKt0#L26y3lkVQFq2<VvXn?EK1#ii4@GZ8vr%AScV=xCjYuSJc5+ zF1On%*U&_e(n0jpUT%t7C|Pc%5_e7J^=dIG1qtP!Ym&X;_#PL#f1ddf5wm?13v-nw z5pO|(HP*%-bv{@Oppuh$-rj&_dNf!0=2c8GRbX&bzDhAx?AJdaqT4DFGk^g|aNs3S zskctnMJ=_p6m+;Z!d||51wLIyMkY9Kf#}en?CKsblPZELI69T2&~3q)My6YprYU|r z7gd?Bvf?(M?6X{`(a#Mq1pnSpB-{#I)5n@%QH-NeXL6tY4QK#spVbBCVeICI=Ac>L zCk&;u%H0crjKN~H32z2m7e=v$WDPc!9Q|-|JzN(b^q3^*;ZVq%UoF3iH@;dHM5R<g z+7ACy=~Pzh6oGGe6!G-VZ8?X4jBNAv;<It`Kn(RoYDs;kUpTdV#s+8s$y>3ZePFOX zuPa??S>vN5mR>e6K%oQf`(FCQ{uXx!*$S?m#cXVz!IG4PcZVx+<kri;i$Ox+Yh+z( zay4>%d|dWRF615T+sTO;^ucmS((yKaK>-1uIBK2uSXkqwPXstaYHVb7c6Uu~TL}D! zl*;+Hk4u_#vf;LWs2=SNinhuSGq;9xxhyxiAo1twwEp!4tYRo;eo7(i6e0BIa-US= zvYe>t44FIoG@|~iM9@UNeynkYZ-~KW;BEPqUAT@1m5OaoM%jC9?fzU5@c?}$d^I(V zXX@beqHm{YC?h?S*zutnTP%2+MGX+G7&oGeW&d(aFBR_4Ch%pJ$zuF1qX911s(|Hv zHus5OXgp865-5fChx5e!fAk6+cczgINfdJ$h@YP>Fi{hE1C`{mpN8YHu(9K=m2d3I zBZ3s^3w()1y+#!#^OSQ1duc0#r>tjZ3B9}xx+T*niCn5bSt{Yly=gl|d<|>%;nnN5 z0A<G`Zi>gigapW+JRbKu>*PxMwbl!3mp6*du2E8;{mv*~*#rcX^78VqurRhiW$EA0 zhEmUNhd*ce(R+xA^<CR!Vln64?av+AUjdg_Vr%OP0JI@;TpmvCT^29k;YGc`m}&>E zW=>AdH*fr<64?+9Yb+N+46_A%gFs;AV5-t25Q((K^Fg8Z=f+nGr93(Bmp30J9~vB5 zJs&7r@5!XG@VT7dCoBMDs8l>XgyDNW5n*A%)h6eYH7w@O(5)0FFWWmhGVF3`rIVPd zYe!kl#z}5=R7<p69vAB?Wd1BJEwNh9liUMD!iYZm^t6g*Em0_$Hw4$i^$BuK`mu?W z6iICx{P`6&{THzl15%lvTrEzcB_MS24*Mgl*=qm9r@Z|9Qr%iIHi|mKTv~u}ogSYS zNd9)T41giOIOGZ)aY2sVNV1a}1UP|}Nt6!>4=-r3Vjs);*3w$1wS%=BJh9dkh0^CW zG)k;>;LM<mnTSTm40{ZZ21PhxXpZ*w8b5D^frSC|pTQFqmUxNBtEReISSXQ3vj(uk z$a5<$Z}#QYbep~q_MZwh{jqh|x-ZdQz-G?-mQw|p9kG8flFZ}lz$gFh1fZ^Arsb$3 zms{L|GZAHh^s!jpELlkgzrMEC_2PO-Q?)7wCP*$ztQ_pams%Vu?RQdjY1V$r{obp! z1{6m&+iSjqhKr9-bB0D`tkzn!rjHho3D-z8$*fLe_v~!KF^*Bx@00erD}nnW1Rfs7 zih{e=?DL+fsVUT(*RS8aj*XQ07J78Fg^h@}T<O$$cXcR*!;00Irg_D4Y8~plK<M?z zUYS#>QxC{&yETq3Wj1BQ*2{G~9=k&D@D~yzF*dU}>~*~K1Yd^w^no`Hc*;cl<~D~T zNs^hFnNC{7Y2OqEO$Uju_v%#_7g>Uh<8v(_ft&mVaEQMZ$V8P6qy{V6!Kx@Lc2ciw z0eR&Su?Qmec=t~*cr=VAF(M<<b*wvlj+Mn=rR1O-=or@!lf<NW0qpgp74B=gd|#hr zb8Cl*#fM6CD)GFO3nUoW^M(THJnoNmaMY@8+3|qwAb5Xea)%Kh*F13*q)@3iMw6MT zcxX$WzCHr&O2kja-3)*ve*V+}_DaOc6fx50Ow80FIKv>w1s<$-=Q|T1;V2q}>agC{ z_LWk(Aeut`6hx9v2k}`Q-Azqri_{sxPAY;}6d}ara^D5qiM7@%Xt>A|#hOm5=%hMn z1pUG*p2x@SgQ}$?DZIKlLr-?bzPNDf+e|DU0VambR;6OcD=9o&7Bm!jJZ?kMoVG@A zuRCA-tg&9fql0OE4gZGI@sQj7+z31pxw*N<s^n|5T=;F7t?@)f#Z_9>#@58(44(iD z*y*W-dm=}o;}INYP$jV#AfpV&k45nugMk?lG-zjOBcW-vpsA{%!4coAw8QZ7q5RD| zk{Me9s2((tyAF`V`Cf$r49=GGBs4U|C&=W3BIchz69=FUzF}mvv)mc}0V)kkQ&S}n zDni-o4acjgDW}nFdFgkwTpwfPQeibr$>a$bCHhB?orN=2OpL6^p22EF`rIJ%Wolr+ zVf~mPC@N#WyC^KjRCV!tZ-48^KZlh>#>>^3()}2Cq^9#Vc~h$hU)WHKc-lCL^5Nm) zDyH!fO8q`K0Ij)?R_DhHtkwZ`+Aa1Fujj!zdK+5NPj}FBQP`c`%or3y;c_r<+{CFO zGRoJgpGn3sKfgKmRLLMFw|-EdTDr5Lgdi>d8dl=a)K}Ub6zyH(z{}LLEZ)=s2-wFL zCy__dG0yB@G>B>Q;d14>nx4d|!LfJ>ut$#c=^N>W<$9etjR;Ei@e;*qb7n-(R<A1+ za6xX9sipFI0Pcj^Ztm0CrTWrziTaNxT|fUIK&|_;kz4P00s9z)LNnjuK`tawtWm!| zTdsU93?@)CG~8;|T6E(VjMZ8#?f)w8jO-L$%ckcSQ_Eu`HIEn@Te-5=(xN_<ce~uj zX8WRdhDKwx)PjtTy-=jCC=?G`&yl*1fE7kR6Emn9fr%!#XFM3o?Q)wXy~on&hj6f5 z?3OLjI?c>_^76Hr-N==9p6ShJ)9UPE1}iHo=kw!p=aYN(jNuIp4+R*^QyE-NtGR~P zW+q(1=L_YAZ`?`0^B!*we>R!{p)gk^*Xs0&iY(Tg+&B_ceDv^>QE)%gQ+rNiWEdXj z6%>$-U=w>_;ga?b<_}!t^9iEhee-!)j-#}_9uwW<Wp9<s`yB%#!G&Mk`Efr#;u4SB zJ;1ubo%|B)3P6>CfXl__Sy7oQTWx*-w%xdEos}js`e|A>uy}ZQY`=d{%O20xNK1kO z5tKNS#hPASedU5s7wC^|1*n6-0Tg_(KVR$yDBEDj#`jqy@Sdm>XzIlxG+d5{x6M;; zBk#eUL?ONf0EJDJ1fr(b7gT$Rgu5e6Pm$lqrKLX*wNv+L1izA(he2MdwO-1bNke2o zPaHp7s!!n-l?ge&0Ay=0GJ>a{bVztsw8b+@r2MlgZL2eFeM(GGt+J9)bXs)R(ok2a z<G|p_<!LgvA!Q;<S<2pIA+N^(iF@Po!lwXvV|OAe+wLr`{>P0}RGm3H>3S%hquw4f z7v#Qx%i9AqWW*5Q%*b9De^u=4oxxsJzY9x=0JN!xf(@D6%$~Gn;8pu0_KvJ2{j!An zZpL~M3j@PmF|iT)p}0|y(9-REdsV=fB+gV*Z9^pcIOX&BFcLHy=xq7J3`Bnbu!2hf z5-0LNLBUYVbiMEhaJ|}haCAg~g%ufBV}T<BI&VCq({kO(`dBmw0eD9O*EfZFh7q{w zfi;%U@m9K-1f4?6N?Vh~&CyQqRp%~q0k6yHQ^{{&%M=b}Y2$ry1R^GejCN<dA)dl3 zTwD>S-I<MzoEf_16W7+Al+uZ8R=d$KV#h(Occ{ERg?mYg<l)#D7>Nl9OU-uJKbSu+ zXMi?Sr+;`yAt)eGO{~W%l))p8OgbTK6+E8$(cFMBX~>825e(MhZf>%$-1qtR?X|-D zDwQnRbE-t{&kgR$-wZpKm($HE(SS#HzWg)(07S_B?EzH_B3f)L*p&({1}lJ}2*F_W zTFWLG{E<+(dBE#DIW)O{+8unIC?p~#z;mM@c8$tz_lF16+Sf41ATH{C$rFOeDM|dB z+DC<>9^dYk+jJJBM09FD{Q=tlajo@ei9i@{{hoJr_SCxn)jRw}Iyl!q%`OiGM)?Z! z^BSfrxZI8cCrH@}I#`LR5qp3WOlejU^l;ma8?K+Ok3N#}uy*Kolz-n9j>pYMwfZjz zD3{6+L6(|`K|xZJ)Aw$fR0jcRX6o}4kIjCckBi+e*w|sVr04o6C^k{?%h%M@ja^Db zJFelQ2;O$2=ag54xXc8Up9tV_cpm>=t{{bbdH+ycYzfo@Z-%z~z($0_70p7G6c)?* z^vwnU@$z{Mt6HEmH&+k4n*)}nAn13p^bddo6k(=bZVT_X($Mge%~R;wq0#9Gj4c=e z(*Wc|yGlInw@(ha-C$8Eqn&*l?GGAEu6_b9@M^Z8KZ0&E<&Ag7g43Z=vBm|cHP=ks zkAL$n@t1GLTL7$frOEL5eWYiT)7IfWdnKpS;dk?uSQ^dmx{(_%+X25Cc-f%CTLEtJ z(~z1?7Snw&Ht^|ynDX7lk$)~68Z`<EB)|2Y<AI@}!6Q8o@;O*GhZ0*XZgvls-1OO~ zsrQe))dNV;=n_|ISK7p*23^->QaJ)6>T1q|?!UHu1?mahIM*tu5rMWrM+;d=tzA*( zl==wm3~A=&4eA`&XqP^GjDYGKnFK&rbU)eYIx&nb)mQ+?A0BZE+D{|bkAuzl%dLVV zXCde4GeC%=QJOtjtY`GevY3mj<C?3pSqj0d%#}$EgaP$m9GxaL38n92BhQConwK4b zI}AF~`b#|wQ9QG~;XyNC#*}+GaAktATvp3f>Bn#0r}cDo5t*Z=67^ZnH)T8Dl{>HA zCH6xJZH@di9~;OvG9w4cP20>?S-`dVhtOgIJ?=Rcj?WGBBguQ?Xx+^iNpwz!1J9fB z*Zd7mHCFS#$6E~7bA~^<>#caXwIs8BGPY!NNi<q;b#*4N9iiEwC#wCjgU#)BTPI$* z$+$(0!6{+;hBNy~_I>K2?JwK0EOp5tA~8ef+R@bKb?w$zT=jFb=Lrg^N_tnpD)kc$ z2j%=pSTYU_5g9Jwb`8bi)So{80A0}|n;euVWh(aTV$bV5IF~ou$l^3fwVL!-{pV58 z&Lzj#882B+RF-@#3_|4*OP`?fG3@N@NbD5imlwGjfoJ2%i|pLQ^?Wz=x-#t|gYWTW zsZQ(dPWW5GK`acEYe^R+KqhdHfZP$avty*AJL($Tna*U5;%Y7D!%=JCXb6D%k=#8w z3EVrPggYyfV#gmUFq7OL&mYm_2-%UU9PIBO?l(lSu#>KDJzHlnAdbI!3vQ02IGpu3 zblnCX$&d(CFOSZ!;vQbg^p{svT%0{Tk*)<%a>%{kGu^ws&AD7VBN;^Yc=Za03#LW- zH2W7FKXG(IeL`>TcaZ*TRIr02Q|EYqDGJZY5m8ffwna|sS9vCn<hS(ZXB8KxXq5;0 zQWa8&K3GT^Fw6<#<ZB^FSVZ64D*&}KJAX3S2PlO$c(SKiMI`AVQWeLQ8W5EJ!0V2N zQZXVxMxuKp)D~-d4!5T4=tV(9w8p=C_~(b#_XMAa2ohTPA4eaGBVH4IOX+BMSY>YT z63$Zci98)cFh7W*c9ODKTZTAoYWDO}R21=h_<33)OVEEx^6aRKZbg599p}Lc%+!63 zS3g1($-;uRn$z9al~{qybfeY$fFiF3gupoB#@lZ|$sLz5muZiT?Ee{^U8@u%Z6$9; zV)^R#@i9HvQXcE(e|>ZSb;4?^Lq5GRA0SAx(;fVlNRa&B6<gN!)2lN^78X<nRAgjD zUS3`AfXAOhYW7*LVr8D&0bW2Y{_SL~ZMlC6n-HX(U~%&8UoGHgll4kHtkC(DQO|$L z-M4~f*e@FEJyej^Bx7xn7y`$$2f=OS8Tm*-o1GiKZK}-7>|H^!e;fa_oJsq?3Cw3J z5T0M2e#X`~3U3tvB^w;OfZuo2&Ru0-{sPnI#yam_W;X`(LE~C#gO{+hOzCnG?L!e3 z<<Q%CKGH0{9UJs*U>^H3VyRvy_Ho^!CvOZEQ`T86zBKiyvDUf;coWZAg+zrxVb%+G z0xWSK$Glu;)CZ^|?u5^SG3cn70^K-`@Biz=D5TlnVv;yrjb0-ML?Pni`@a{{7F-%q z3in8_L|}3xxC+(|e?`y_qh|k%BZ+)zJg@?h<Ao%U)(3?8M_2M^#Tzj@l&KZ!KX?-g zV!ap1H`jM}m{?da37FIPsRDl+c0>}QZ@)68UL7sZ&(FamB!m_(-8miouRRZ!hh1X; zZU{!DOTav9Q!^=Y0X|CIR#(F3cLr}l?7IX>#l@usFgrd@^b&3$3JcD7xf6w4v4}Kr zriDPx+R>5!BHjP;mWUlgU%I;yVBx+#xO;%@JplVq%FPR&Umv$~-~Sr~rp>hIY4POo z@u4V!I|trSJ59BJB9%b5HPAQn)l-yv58{!)AsP@E_CYHv?{5PIOjGQ|2}TPJfquH* z$xI`l1O#CpA27(QvZ|`0tSl%zJUlQgEHDu9xgP<3{`R%<6I|MTd<_f@4i{TKP=x($ zgvm`$gPz#|7<O1yRrPOsOZT}l_@B)@Uj_I73fDrMIQ`#8PyQdnv;RltjItb)v^0P< z;W9IWxH0O#_!-r%o*v*(_4oA?5)pxh&HnvDZKX|4Ts$Bs1o|CZXm~jKKOYHvrl=?o za4pEGsqym@kpuL0wxIo&#s1r@Iv<F*09w`W-+tT!)b}5trbpD*_o=98@%Bz*1LuU9 zsah#InwY5b-%rhphKW}~l9y*@3Jcn$w7<YtDj#&1`VUtp`eX-qU3RxYVh9Ae9%&_? z3V<gL97)8lh=||6qsf9pr>DkFROQO@%a$^8a?tS5DF3-~j4=b8hlhsN*Vo-2?hP_B z*Vfh`C2)ap8_-R`9dzq@`;3zG|B+11LM9?DstQiGgSU#j{Hnv>V6ISfc6^dactFVY z<-unqBgo7ZU7ul8(m8uwqD-Q4%pXZ_^>bc&=>@dD`llCn&Ity9MZP)*Eu!sr*SF5Z z1Km9+<9Kd)&s`?O4}3gs{s4H0iG>N{rTn$EwQvu6hPCwj+a1+f1*j)B;F$qEMo>$C zec|;=u=DqC0>aS$M5u2SZ%)}Uu#<q100$!JSe3AbT6sWRynl6g?=3phV~@%}AO0IA zCMIHH;@7u^vmsbPjZ=28|DgwM%l_$)J33rS^5VWl&H6CW=wwYeNNEI~W-$z>y%5T% zF(9@)DO;LRU~U>M{XW{r&~|gk*d$I>Ocb13AbzPR+&+lCrwSp3hDF83&Mc^d^H7XP zfAt$s0nsVc#I6_Y9WDx#3iT~4sIx*TrwTOH=l<SC(Po5sVzecdm6a2$^zX+Jr%6Ix z4U!TQw}wsV?n_W=6kru@YzCC`vhovzVC6M63&8|J0R}2-Ao#-8>M9UuYcGM(maLZZ zO<=qPXqr&2E2-e<mgy0Sii#5A{uxMV<n#71H*cLruY2y(z<KG)y1lt`_xgfyuf2o# zg4jNp(aR>zio3Nny8`wni}^V9pL&om0sbqLS9&-VV5lXWS71WDOG?89kd+j9C3jWT z#KrSvmY_%_V*d89w^tN0KNW-K1+{vO`OM+N9;?+9pqW1+Ilza4;e{|>Z{_8hZ$lx4 z1z)v;PMt_7Cfz+P;1NN=V{59Zk$OJEKs+;hfHzRQ#2t(m43489&H6~<SvZsD+YI!n zK`K?+yK86A{@3^TKGZorK0iL7fdgWsU8{m`e1|?flt4K4>86=JM_d+V|D1ye>3VNS zKv-DMm-db@Af-7T&Qcm7C0P0XZJ(~XUhR0DZH5Amu<>Z5$J8!7*syC<8`K}O0<n$_ zjV7nbV~_4)MjE94502G#-`NTTV2OldL~Td{Tqp*kZ9KTN7KH;I@8>Gf>3*)}1HYB) z1@`7p!re)x=;;qIlW%Tl=<O>&HUZ;WQNBBCYjcD7r~q96Qsz9%;4dXbSggjWDJXum zwbAqNJpO8bsF=IBfTv_v1-5{WE+7CjxNWhBpiwJf%xNeqPl4@OFgopbfB*hg!%G}? zU^n|V<N~Il0^1lc+Z!5%;nagDk8dPUqk%Wx4Lm2n<dN=uS77x70av9K0byzB;veK; z{nME0&0wS+u*eI8i$gwnba{0zj!ul(uvHJnT6+Z=Q>0-TvQT*sXkkq-aCc84Gt<+% z)0x&_&?-p4;qkhcIgQHP?2yYO*qm)TkeZD`8XNyHW89+AW};(bD{~udVYl5B`0jqv zQ4d^*1`<u@HxqB*BqhO|KHx(K4!n3O)x+Pv(~Kq2ZtiZT^VECmC72x?s$Y0LNn<|~ zMujiYE)i}P>g<0_kAbK=oBNj)U_URH2#ljMG&Y{75hMp$2pqdcCkrPBkI+ck15Q*5 zIRFLdJ~-RD*i%V&5cTv044e6?1G(8Td2#WZslhE^G3=!d6AogroR(0OJOlQ{zjlF# z;P;L^kt&y5EiNHAENrwqIyhe2ec+9BK;OlO<GB6X9jm1pxtN!X<Kew8fZt;Tp3mPp z6uveyw_nXL2DMaN7+0D8gGRrY$AfQhoS_lO@C=|10)FV(1T!Y16%t<VXkw$sUXF=u zYF*WyT3*=()(H6X;cSh11X-a*ty#Fn$K6`shb>ZTz=)Xz-4B%_CDdxIA)niW4H(0X zxIjwU-Ku~{(B*f&-GEtr$EI513PcA`*P+pBZ2+SnZwem!0+`!3l*seC(|91ZP$~a( zagLEpHmTm?gh*HSNJhIMSUbgfsm?DnmQnTakozuxsuU21r1edWHVzJ&&AX$1r#-FM z&323z2=<n^-nzE5w0u^ffqx5)g3>!Vi4tzGPr%{1FXm$=5$D*!^KieTXrWaU8d|J4 zR{`+G6q+SXIk9M`J#!(U!$ZTT8^ghRNMl9i<$>X~wYj;u_efx7YPjGb2qyuLwwmSw zfVcB&3pRr#2?FAg?d93rf@-18`s`R|#ZF`-t!@huLNU0f0M{&rM)PqyvJ=|49OwHl z0E+=L^Tx-@`+!hU{t{>nA>qMp51S5BQbioGg(b9e*D}WCBhQ)d^Z-(&Qh8+gk3<aL z1+M$R?7>cRJs3>N7c#A2;_xlVzu6y*N$xzwxx+2FT?n4Lo%gkaacePP+052}zfmt! z2UwrO<(5C|2fiL<PBV^AFT5a!fIIPMzWxA+`wqL)y{hsY*@@XP!~KS$ogD&&%B85= zN1&fz251AjQ=iu;O63bL@0?25x-v6}29@(Dp3s0BHZ(L?y~d~=Y)-*_-W^X8N7oUy zKbLFmuC*U4z1P{<(P)2^59S+`@@E1z5m0E(y2^=Frl+Nos>RAWDA<Bf>1<XP=hk}> z(5W|O)AW&<p3;8F%PT*VpsnB}|87nmg#i+3Z>jca{vrwV%ih1o*uEux%V+s(2++V6 zRDg59a;}0o11m)6@G$r;E_N8(dZi}Yta4LgT;dG0Esp~}%oJ&OZ)lF-u%Fm(?z?;( z8?)446}4w|?xXL*=GFcIXr-$*`+2L-#)T@Sk*$2*ukB1|;yJNjIR|N2^^Poo5kx#z zL(oEgVC$Y&tGO_%l&@j95DTwJ9?HthJn`##v{9)<Vfeo{JAgmV<r!*}&)ej+GSE%R zTnQS-v%h|cH93NbxUc|^_&$r(NaQtkBv;K=)`=n7a4MKo9sKnrM3coI#9IKab|1f% zk3>XFX*`e6{RBqh9IiAn+ANyV(T!Ry-9f|p3ei?n8*EK)5a<TcpLP$T1JAg~0KLDx zZYr<S<!|o?5E1|ytv5k)jZ!ijJ^k@o_n85YI#e&}@@uoELRDH25<zcNXle$qtXdw= z+wILu%zk|-;-uyA4x~mrh%=zT94$2m3yySj5Y_s=7SU;SM4j6KK3tF6bHs(?iQIQ; zH+kFJKz8qUBI*OcD5OE)#o)&q8y=QcRaJGgE)I^PHRwNDY3?kK=HWd~lGurZ5b>J^ zIZ<8I`+gCF))r6z+)hzpINDRI<V6_zgixiOz;MpacAcLXir-;61`^cYr`1(d$x#4J z1oX4sa(Uf?;PmI5%}oGh5V#Hsp=}8Q#4UOGG1E8!;`d>GgWw%k`sldWDA+_tl0n|j z<>9pZr#5Gl*5d^Eie@@>anbve4EP+Od<KnqjoCQUB%3c3@A2>))#k5W(72qO*zoWy zpHgWxIg4{`-Xd<JQEOp{Qif4}-&50fcM}auNF(X`%S$=8PN27X24Q2fn1>XBXg?hK z5Xd4{U61=+6avOo7!JqpwQ)4{y4sQwx2yf2kf2>&y#ViY2m!<{r1ti9F#0x;(G4nx z%k!S%vCBpN^~L!y4wu?^F?zMch6X4W5^k$)0ZeN-WdS^OF?wG7Y-n-$=EUWoFrInQ z*Z#0ZlX<#DxBX}5MgmI{Xlhx8TP3G(A5>e2Udp)BzRgt8O4CaRr59{#w#QvqUW2Bl zW~O(V$^nPkkL&By$xy`g)q??*yZ|f*@c3o1QloK$kx#9tfK_M#%8y#5D|=3K>PG?s zzPE2862bGb)`H8x?7NFA)Ga(V^Vrz52#=Ip9@wdNl{g1O?h(r>7XW_!De=MS20s+4 zo4R|!IdNP-8RH2O9*?tJL&y)sIa`d&)5`|iov6t3P5slsp-&b>*en1H7`}F1@lj%& zQ_0QE4JIn=k38t5AVvAhe*a$2wtuu0e9fL@pAZ#}Ln5C3MgqUv-UceNL^~~#SQxfN zCB=13(dMbU+tXSgAUs?dxO+uz@2G8TJjdaIH4k}aW`z?;NLVyb5;2Q)EOr<P8JXL9 z<n7kLRK)qHZkk^HxG@;zL+ysz8AGW`D_2H84STb-u&i_UuNDAfC`wlA&Oe?*G-t=} z&F{?2Mg6uHR6PXK?6ik>s*^J_<be(<v5O;`HGJi$key>6KaNkuR=w&TT!Q)QN_&LB z!op&yafFuc-UdVyc(9p`eccB7GJ9<{<u(PBQ5-i_36*eY6!v$QG#iWW&jLz`P6I>5 z*9x@p@gZ~ZFzVcI)<9IySJ%5XI5Zg9|Kr!(RsD2lVrJ&4?QIMPc=!`iBVuD#<>|38 z=_VYh-+Scw4?2yun1TGOPJvf~Kk4;@2#R-AOXU@7VidmoIqg=loOf|^y>)i$cTyD@ zu)GY(Hy%{YlPgz9alOcuO(5Y_y$|0v_9-r)91gy{b%q-!xB=XE9|0FP7j>}qac;zA zPn%B`h4%f~2bqfT_;?JXG1_Eaa$e#v$SMR7L4ZFon)pPc;EC?!#Co5Qkbq9DG&?j& z4PwR6O_7wmWnQU^N+)DwD~5ViWxr2m%ZXR@S1VM@qpo}TY+J2(A$7qhJyXT^>z1V1 z-#@}lgV_q2IA{}15Z(|f0)DZ}yx*!^r)-6?*60%*f9ZYA191JY7gdC$Gn_bMWIiIG zDFY~Z<*!K4-|{v*tv+Znp6#EC-;<=X1D8a}wE?wu6#+HVP~SFh>!<I2_#x`GuB1p~ zrU`rjNgbzPr4FCPxY^*WwMxT95ET?Mx&{J^gS~w+m8x*#3~+>S*zZ%fcJyJix}M+| zws_jOeR(SRi5He_k;>!Q6+iE=hiXebUh>Vz_0!*i{qZAKVj>(Gt=Vi{K0))jlBjC0 zWTG-M;FCdoB)4S*GmdMs5Gz8fhriujnE-zW>dg~Lx__={BLc2`Iki?@Uwdb7r6l7I zu_uF}#3?EOF#M#ikBNtNIS~^WONKW*M9QjW>v7f~)IXl6fbqrS40cX$!{PQFj!Jrz z-IMo<HR;9m2{F(U0fdE0HGjI!><kjl9k91|y8*QZ%puMw`AMq~*I~_ni^$JEK?a}5 z`gyX+tTT@8Cu@{dd8&Xp7V3pLvpHD#l|9|PU-}lz6ohsyfX;y&>gcCr=|^QCq=0JV zeV=lvy5w6YVcpic%&2g^)sFf~fPQ%U_U&um+xr_kC8exyf}UA>kyE1kdZwlngDFAx z_YV=IHMO;)KZ)HsMMC$c3+e6y*vDApA6wCF$@1mb$>OOP7?AKqBbX|>>Kf};<@i-F z_^mInm=l?;(}`+xk16p|QVQb}5<sdl1CZL;l8;Y5A|1Q|BXIx>2NppdWbgT1q0^<` z=r_4z>~C$E=;?iR#<t`J8xZHc*?0^>L&>8TY-|*!y)`ryxqB+5e0BmLodN=TXNSdm zfkHDr!C;#jQifTcQJ=v6DKG%~h*eL_Uq+jlm<ZY49lg8v$*VNYlA466&a<-zIaf+_ zVx;|*+eWVY!>%wO8J;NoY`UTS$%~GO8QF1Kj<8T;!Ss<Cm&3pwq>{C@SJJ9?j@vyz z=eJ-cO+J@)>6?!ag2bJ`VCixCw>w_X`>#+1-rxK|zJ$QJr%{h2s2u^fF;DEQbLcN% z_`%TU<twMd+38A?BT%RAbAAz07k8Ok^Q};2u4W3y<K;FxtqMjT<@LHmF}yrjSXZi{ zCZknt@v*3|$rL59f3|@Zv7cEjHyy4L_TPr#ax$n|Ne;D`UvS`CTjSUB(X#guWr%PX z%~BYdf|f@Z(@s`C<B7lbL@voysYZn<Pe#sKw<KXD7S%!e&e2w*c{dVdzt~wa_rsH$ zR<|T0Ypx8A;4GKR{7^pQW@6IT_4R7=vF7_IfQJ1*bngar3O*+Aj$`pw{QBT_e+*$h zxz-%^+RPC?{7`rC^p8V4yGc8E$A~rCyL<H(US@_20N?2?1WmZL=3dVstX78!sPAtp zH)$<a@`LyGRDA@}%YQG<Re~K8z~%~51b;mPo5OdHcXw=JS^nR@>%CDf+78LPIMnXn za(z0))sUrBElErqGzGzr^z3YHFuIhQq;2I1;Gn?X{BiBj%FIkT+nhPe2NbVl_-TIM zA$eV|<r0Sq4dTedbN&637EFpdf#snL4h$&$%EUl~d-B5V;uf_AX-(hZ;^`=*aNL1b zE={jLDmy|*Dxw9=b3Z!u(o9xXqKE90579<n!UA1BLR-G~kuu2sSnSIl_J@g7)ZsS+ zk*q2rNILzU$4fT9FHCy)<TnLBTUzdEetR~!1GaftncUm}bQJB~vnocR9;K--^%c@? z)@xxa;#`ED`dI;!CsVD}>0~qsBi<FG$J=4(;sVoCfYG}mvRC#e?yu6gxHxq4@KaV1 zBx`h(_Yq$Rfo(ZG{VCracHE8M-`}6yMVHhl2ADD9*lNFH<rguM`Moyt5p|z$R!#K> zV+NeRjOJ_k_DCm&;xdC(t<*5Lq|wRp>g>Y7Y=O{iBza9c4w+g`U6`GcK|u`2EZ9HU zQ@T_n5;oQ+i=5W3BPU0rY~9m~i%~D1WLjZ##t|fiMYN$fuMQS;>$=MU_gx{U?*Yu; zA!UbPsM<9*UtN9D<u*1(-Dpt6XHJuU(L3ekbtC~JZxs~>A6!*c9eE>pjI56F;Q;F) zbrzNC$8}tlQkDXKA}IhCyIW^FIZ03_mG{?{9)h&b9&=m2_<L3|(ML{vL_FGiSe%D` z;Z`3ex)eRBfkD|OifG)5H9-+i-yg|e|B9Jot?g464*d$oM@#hy1n$CzgUn;fo_UF& z0}!ODM~0Egc8lt%RhYmhmw{CT9oQ0;oxI^?BP!Cuk(g$Fdwv2khyVaRfu=tMYR1e= z!}mF^F0O!t@lqUj(v!7B_s985uk2nR3R;lnx;a^A^FWE52dgiWUDzl@*t$n*ol|-2 zpl7VCyvN0jvF*VV6}k3uLnQtk5_0dWYb}yNJ63m%0x%7(51%MS$!<8u%M}F3A_*DV zp4$gP3EIo<mo8`X*b+yLcj$yQLVX9fn@$`j15SEUE*sW&Q^n`x_~f|2b==@_i-8?y z*3tSFj@<}Oa&O~y{OL2M9%#@JWeg$gNZ-RGs7HC1yv)>HO`VlE1s?uMW~PqL2^Q;Q z)U1W>^c@dm>gRt9INXAAdTC%pnfGISdL$xCPQ>NE0`)`KuV25M372QPl+t+ho?(FK z{-GbTxPkf_78<IopfDikRQC^MR6>zwc{w&#g=4+}0>Tq0|AE%-lKp_GH!(iW!Zegq zkaK%8;lDv97@yykWbt92Oig4?3bd^2Tc0Jd0E)t~sfmfHv8l;PCFMWbu?kdVWX~^5 z!bC#C#^&3v1b#6!1yY%>h2>@sVc=n=$H(;lqqwWX3)DqtvfWym7wiT^0)pf$bDO+; z<VkrsmWIYZ!3@MP{E6|&pwM9cvJVsiK{YjDX#~KJhXo+_yI4Nw<gNb-+`3ud2jpAw zvr*B}v2n8c8U?8U1rif%-9X0&^o{<T3g06n>hI?(Buq(tMWwJoI&Uxy=DuhF71)T8 z<-aFvz&P5Z89G2%p%X*y0L={QSJWb{AY=n@iZ*t4DA>q_ZPg_IoD%AnW7$Ck1=+qa zIW^OO68E~hyDg{*YBpcXB^=a^9(R;oaR0JEw^<32h2HKT9iii+TOa*C?p~6XhsfzT zd}(QDh>nQmt!%U{`S;oq@`6s!E-iI3GWwn!6PiW<1pc%6g~ocX|Dc5fP#`{*Etu2- zeAn~Qy%>odCL!a{_@_BUq!1CmO@VBv_zbp0HwE*3y;D;Eo;nZ*iH;^|&w2hp|LE&N z(UJeZOi`c2#DdV`Vuhe97GN&>0F>jts^{1L`!x^^$W>J_b#{W}_a87d-O|FMYxPHO z_`za5aeqJ1FaT&|8)VXbhXVh(?!l$3NV-J{W=n(J4$Yr_*Q)~)OG|F2%OWr++wovg zz1H(vhcB+L7vjHJoTnl{5J#t019rV{xiYxnVPWF7iD0TSF1zhEK&WHz*xZ|f9v28i zoS^7`N%eeiaBu(?=I4o`G6adOU&a7Nd?cB}cNIRVCIp*vo-N7d$@`mtpn#N=bSvm( zov#XF=2iezl(WVX*m0zCN3`KM(WWORKq%Dx>G2>72ASTlcSIXJ+cGjTP7n5GV32!+ zMY1T>AIRe&{<*Vm<3OCF%3{fNv&aQNdt~nmz~u|9WCux4M^jCoE((D8Uth1UY!$fM z4(FP&@jg=X#>K|Q=J@5X6%`Z|WE5rOl=wx0Q9{@Z&T099`Nb*PrFhz@V6I=DxMKhL zyatdK&$dPq{s((+85C!<t?LpY!ASzaB?JiscZU#yTX47F?rwnu2oPL@hT!fljk{ap z?v1<mnb~Wts&lLEt+mg-=g&E{^CwbipuhgUImaC1eV>=b?N4avU&jhyN&{$Nnd_*Z z4u8Fvh{$UY^(yZ1k9db4KOVd`X9ht~n@(AjgWoZd)-fJHKj<|UX_KrQs}`w|;NS#{ zp&3Z`*}TNU`es;C6A#cTJ^Pw9ZEbC&^9>;}C0F_J@ldeYFM>C5eZzx8AM9ykXlOXB z(==CY5gu{-aPRSP*5IEH_t~Kb{DHW<M72aqmI<&B-th2be+qh4&MXp)Q&r>gSvC4; z*YWXjxtQJ0pNYGdITDLpJPaJN92wL+yB<G@iLvN0&<I8jK@z#!=|&2(h4VGa>+hA_ z|Mbyq>w+0xgZV}<NG_;rc8gwr3XG*TGqar<?zpC(Ai{y6J+ZvF<yA<!n$_l^k**y~ zT|LIg$jCH0e`?Fi?L!IJ{g<NnJ;DG=GL6^6ef>zkwN(h?8WqVboU`>G_wWrTFa{sa zRKY;N!RY8HjWmbN!5)ob&KjmX!&n^<pEcS(G`^|Bb2Wlm^TRkz@vyKOoCnRXEsq|& zZg0-^Ys~tqfB$|nV{f>lqrS?ibD+HnPXao+E3l;2oA#CiT48DF5uk0On921{cSwK! z4scAM_aya?=k|~EUt;Z8-|l!f<`40WZrIKafGWI8B}oTUJ|VpiUyq)Hh4r`@45DeI z#|uSZp)gg{;1)C{P?msS$Y3T2Izlph0=?U+5!r5VrW=218nUJ%g8$fQfC2W^zc!11 zo1pRU-HBX0_3r;%u;l|Xdl06Fp<@4Be)=N*eaObAJv9D*^#cC!;~OFW>#n7i()-td z_TR>TD3k5y<>e**`U_10LmV(@>`f&%`TSYW_H#s85VFqs`SHtlR~P3}T3SS1cz6W4 z1s3ieZozBJR16$b?Q5Ezo{b;O+B-TZYW!nsYQXI;4yK4)uXx236{R=^aa}lPN~=Oc zo10q;;u*nXub}1^R$T1sy8>lAD91OtRiYq4KC$>ySU8_A(}21)<m&F;BbskvIz6)i z_EV3`3;H-lNM++S5!ln0ARe^5eD)gy#CX_vuU;kVAu{QFR%!e2;j53S_>a4!T|&I| z!KA}E)vJ^7VOjh%P?&0`Hct1G3O0s>g|*^=$<Pz8Z;ZMS(qPcyLcu`pDhnj1NNf5Q zpOf<f_i$$e9}9~fLPSigElf^sclgdle0kZ?wuh8};AZ9iwzgL7C%80$!F}+Msp)CF zaGRxd+@z~Fa`rgtz4*ZXKyD&?P5IqF;O6$WH+TeIQd(S`o*ubJBPl<_8AI(w@sPbN z^}Qr&4?8@ApNa=mkebPF0OJkJaNI8stn^wgu>uPV1A2ZSNz?eOzJR@2Ed|SrlhgU~ zvYwA%T4|}R(8-2^#7Y0#7XZiJPkGIp`!qk7y7w`TgnV`}O##P;;&~iUi-38pU{Rk! zS3Johrqw@yeOahex7U8_a{mkinC5iZd~p6m%ceBY($Ws5@Czp|uY9}Zt+8o6lS!-s zN`V9(vqmlfK0Z1D*^!ZntGny*DH+0!mH_Mmi_68U{O3=fZtw1X5)my?uR=?I;~W8^ zl<;idHYRhpJ$@F9O*7S(MIZc38pw#(K;PivV9{4KTI*wM{v<88Dn4+p?S&fNvQde? zikq)>KEJDDJ+b&=WuRH;8U*HLeW7JLqXjmm;%b&!XzwrorVE}e3v_XsPE>;2Xf6^Z z<q2C?qIn3w;V0oO`5cV^^BR!aB7hDV5Udmh)IP=w=M-7V%E<}*W4;rG=nB+Od!q%q zV4U{(^5X>d_u~-UH@1=|6^hx*T6N|r?&zLgUR9NBlauT?j0phz9Mp~=)}Oobt<&rV zlfeD*qUg!(<&Qw>FXF(Zm5eHv0<e#(ZZm(tbXYCJ*y{L7;v~X!wo!S9MYdI_b(g^W zhaOPt`YT|x7Bx3FyBc?%xjK#dai3Tu{PQN++1QxL(!^}c%*i2r{hAmb{{gT7!N9FQ z`16c?NBFlM9nX5JeyYOo4>4h{d=y`-Iv>nF**I<HZy*Z6wt}Xgj<!Dsj)m{X{2rI8 z%8IDy$Sh23?$;-so9Gt)hel3xVGcsC1RHGBYBcT`e*Z$Y5;h%h2Xx>4-Pf*Fs%Z~? zl@~#gsU#%*@$_S$+5e9_DNmzHw6)EjovjiqjUCaOQ6i`Sx<QrdEZ=jsJ=0B{biC?+ z+HqmcR+HW$6am)ny)+dgnl-6#1hve<%%4nD9v4JXI76qAQtCawMX%0w)=cC`-r+%v z#-9=W*xa0Xj!rz9ZRK%XDHiO1$P^`#E0J;Sc~e*iIKwlQb}4Tp7~Ft)AYBH?HLb1t z<LFv!XY5}mvRNGO?HEijMQMJF>hMZjm%6yPj;x5<h(I*s%gD^+bGR|gga*e`XJzLH zM5?H8yr$(&<M%oNK@hI9S(?L}^qNZ$mv<Z-D&mU^bDWr@JYPR}t#2R8%5E^(oF`TR zQ4JmFeuR4VFv%tGp}+h)ri{MZi=j~}*0=}myAa%gPNp_helPx~Ei}U{9-B&hVI;ii zO25ia9@#!d#sFeOUf{NoCE|TMH0HBB@(ewRPMuB86PDgCMoWWyx;X$aF&)p{`YL{B zJwx}yOJH|dNzvpC6DM?0cq1!)Rzq=>-ROBtgg&3uALHb8UoQfyCKYHDI}15B{iv)W zpUPr~){Jq*kshEI|E|Q<9Khh7_3b_lS*SK5*XhJh*ZC%jlzPlACV9PiUXbu=Khx_5 z{p0N(KwEn6^nd)Mj-}CZtP0UzLwTKI^iJk!OF8n0UqClF>lSKL2J!B4n*J11zr-Tz z%(tA%1etF8K~xW{IkLZ}aapR*P`^fUOh7r+${d+xH6&!ehHi{i-w%Y7MtOom%aeTu z5UCv5MuwT4nTbtlHU5fXVKfwwHd?+y(ga_r^elpPWvSjx#`FB5h!E-P==%T!t~yGJ zJeB?WM`MfDU_>uH(!gaS;IU_V&CHl8$f(w1cT$O8qTAf)b(;-Kg5sGabz_7BiWE*x zo!x!D@WbOcl_{F7h8QI!a9j#`O-m;35x6?qHip7&SQdiXR|Oz~eN?hXGl+c@0^Y8H zPmBSJ1}Fz=-8W9;GIUMHtG|KyXtVE1@xG>F0$nSazJW!^iY8}CNC|^BpRRaH$Md=` zLA1yoE{8FKnpj!k9sXN@<cW&&ejd!f(SY?xBN=Tb7Fv=T(sU;87*4|csrvh4MAV&a zNuE!DlXUXX)UYZ(Q4IPd`@|;()5C{?O^HtDXUBNN?HMUQut8nhHpIZV8`{^G4`kPE zir1=&xqZemfMoWXPVMTZik6tyC5g@3(QhzzG*cI#{BgrYq++hFj=iF=lDWNDfIp(? z=IrwLLe}2z#}8X&Wkuhyub|XTz-5oB_FkxU7a<Bq5d%^+W&jzc5oZXRc$rD*cV(U& z1B87jQsPmOk!7{i$MUJa+%CXmttVV4#M@IV*&){4pC|<AF9J%Yz!p$dMae1(uWe=q zjm>aiqWKv7w{s+j=IVKSvG<F@6gn~TGf0+4Meb;ZreeC@O%Z4wfGK4|shID9qgeNJ zJWiY_u*|N^w3bG>Yv*_^5b)lXBSyxeh_1hoA2UjLM$pF9M){q&i+f5v{c{#ud-O9T z1b|GHR8_-&{)|WwQc_la8(k-=ttlp^<yQlR!=5a-pW!^W#c8pBy0dWx!05PiS{)-J zFG9So`7*YMeo$jw-|di}`~s;%z|x<dcL1~%xv)MRne_Kb?CNUjWE~BF<6Ok!q)gew zj*lR<(Co&a6olLs4w`6`P=#{+g~W7nQ6T;4E!K|~l3}Rv&sx`Ek9oGbN_j~I&ZuW# zCJA);J=U5VTQKsaXmqUQQ3kn1ASpl^(~SN7aJ<tVh#eP^3DS3{W^A64Ro2(f!s1KT z#S#;LK|J6Gi%q<ViHX7iq3`9(*&gd<qW<mFpQuYCZS^L}MPxouSkZ!7-rar4#`1ST z3FD%($k|yhIRzGv)6T>3+Vf*)v7Y<4WIs3gk5weVu<+re^M2*tBS?_f2nY0$>pgec zda=5trKJNxi8Px(Qa)`l6=*LDzTq6wf3NT|63&<iEm9BaCaBt-$W<q#p^~AOF`|5( z8K$iKp!kuBi$Xx6q2LWB9mxRjhU4SorwX{e-I$a*d80~4Qgh})$-sBISp}V(6rKo< zz?1p4S|uyw>l=p?xB7H;b{3t0qse){T#dihWy^V$VTAg6v_+%0t`C%W>)k$gM?-V{ zAl-{FG7`VOS>)3%)b|m<G*F<H2Qc36{o=Li=+|{+ir*(0;o}pea~!B;P6Ip!>jQey z7$br=P+Wjs*cz4W{#a4>84cD(`zw49Ln(zjtXixf<87GQ==SneJgx4_<KaO<RYb`r zU|6-NlM2q95==UZQmC=7dp~>f`7KUQt91l|hQrat#l_{R)nYk&SEJ`OpFd^GB_)y{ z3<h0AxX4T3ytvyWTN0jZO-I1^DVO#zj^1oHZmgnGoy1v)j*6+M3Pr*|>F9B>S$caq zRV<v-)Zp^=rD~D8jG*8W;BW}phkqV!3ll-*76=OorB^Ae1<*n)q+FXO-~<B5z^s0* z6fSRyw|sXiO&$QFelS&W2#EGX#365feMO|Y{<~-a$u}W3cB9kjjb&F<5=DsD!cwEV z|MFCpS5C;*kcwk5op^&vpK+_YjIJ)kuvdZW4gkqCtDSq37?(CfVIerQR7-*HTFH(t zif9w%XR8luO`sTFUf;3@HNY0Lms1eGF7z;GSb&X1Odo<`xKw*-qut4fDDN(LBqOh& zU_M^#1*V3w_Xh8zttV$UHcm~rT==|CdS>b*zlq9#;nDXlF)7*taPa;i#l$+yz(B@E z@yS^~B(S>%Xnn{D$7>}Vj_bW<*Z~(ro}kuoMGHU2KFU{WYpxnb0avhBo+(7`eXq=3 z#Y`SZrq+*LLn{kGzyk;5%l(;!*(~jE0im6&DyzC2Ja&5^o`6zG=>h(vb21;=Mqa&{ z7u{_LeH__Fc}=V6?tVke<NPwP(_^Jrr_6DAc_lQ74U2*}oSdUjlfL2F!yckYsp;nm zqg&|~@CS;*!-Xng4kI0p=RSgvr~TZHdOm61PZ}<F$;IoFME9c?hp}v$j3>ve`EMMz zCzK=Z%H><iJ5f7#YAiLNHp`!nWkCKCSlehX@hQW_Fo;29mfAu+I>z48+V0KGS?}7U z#ni!q3xbaO8NihC)<oTc0%C8jslaLO0zCGwC~#43QUzUbo*;3RXxC|hv6t^-M8sbI z2F`UtYdb!qv2Q3e+2B+`^a|-~gY%((WFW>t{NGjc!;`-Hzd6kNi&>g-LN_qF@~LQS z3bzP2$IN@OR}l+q8r4xr9EGtEfi!TexXK0!9Mqj9W!kvq(xql6R+PDCp-CjOkjtw` zVeay+T(w?&AFi*LhjaA2N$Z0q;etZ;ugeYxb`%Yzx}c{gov7^QlS3wlN1Iidgor*Q zkw3=5N=izuzWD|DY*W{P65)GMB_Navy(%#ZLoJE<DKqd?WP849E6gehv5x>KJgX|J z)@=+5USLtl0p8t4s`eZfB-2Lk_n*S3t1TjC0V*EJJ4A6eAchd<eX~KE8J3sFjYHDz zk$XoA*00F^Soox=kbCEE5Gq?!Q*-sLiRQDseCsRQO{qaLeZv@fzQ=HX)Vzo`A=wy2 zpxId=?6^Y)3haR_?e70Lz(*A0>)n{VQO!OoD0)!>THgW^n>e3({mXkp;gh74Cg(8H zwYBJurn3m6le4oI3S=M{Dpv91s`Ib)OwR}`BOz0RFUO#5xC25rjj+L@%$hBW4Z_Fx zdj9tO9vF=NDMhA6{m&n(|KAkO|5Hx@BufI3+F$$S-(N=J!DwIo6{0(RT+k~21uA+- zc&u3TpNsKS)xr(_S1;hl|3{?f|MboOD{X>*^M3vxU(!dcYL^|f5ug641alPaMm0M= z6rVz%Hv|(YEN^YfEy)I#!=V)3ng|XwbTqM_csgZY{GYC=7OF8jZhs~VrL0j`Db@mb zOFiFm4$pJT{k@o2cy3sFcJ@@J87sPs%nB~M<vfMzK_x!~n?|8h?RYisS2+-JfOek0 zQvWYj)zCzCf3$%z@kn6)GVYI)xnj+I!e%-me#M%%^fFHupI=)WO*KzJSxO2u?XR!q ziXP?q>lsvCLmdbSvc277Jb3Es9~)W1T@kdh%&Yj^PQU!NZUWNmo5z#}9l4_(7sL_w z`_olo;s(ULPQ{<o$K>8c>y{PGxj5}iX}^(-Oif+vjebYMR|_%Na^@ou^j5lr%87tJ zAR(9OZ?1VjJSo<0$hPmYTkB%btX3i_R<2Nqe49&)^KwkIc)qS41j~Vh%Iio_=f%?U za+=3%RuxllqM8HxYAgY#HQik>AOl#>SEgI1@H{s1=9*;CEi)gFv#Ky2UZ^rzY7v6W z6R>!8P?x!3$M%2>-p~sC9OL3@kmBv=jFVX<hZ|pAHGXAlz6jEDr>wrG6`@sHETvmD zIDgD1(>s{X3puLM$JG;~Aki(>y0%+;7smk0EU|fW_X|jGE3a*i&j}S4UqWhF95xv( zC$nY1G@0mR%Vw-lUBKgZg7Nt+v4`L>3k^9TyYt6y0Wwh@RgzVSV40ESBeC*~&KXQ* z_DHq-nV+w5nd${%wz=$1&Va5JfNI}=7R`EZsMJM8MXOpA$qx!d^Q|$BE0H3NDz}Hb z9R+A7*Ap*07oWSU$PW^VC`d=7oW?(|C^$God#Gdlz6pz>5f<9$O@nmM*(z9Vvzx<t zpOG&$K0rKxzO)-)1qWfhBV^09v9S@<zpH&oE_Ofmu`ix64G3jBLvT<bxgc6jRCEc8 z9HbNjWKmFHU`|9B5aWVk7$~u*?$D`oebyF0Af4h=USUJhE$CYy_N@cvv6Y@F*O%!L zAcOe5Q>VDa0LTyCSIa`X@t^Y@cfjXfS67D&`2%Q4oLszG%|y7kTVwT<B>oVwY8E__ zk&==KWOX$S{l6Ey&=om3<~imz<6W6Dt!klqlVlIxy6Usu{c{T_(ApEU7in_9vkdZp z!0Y;CW3Jh$Afi>^P1XmA6UZ3jpaRIq06+>44>x00j>u3tpcpOEPW3-E_HgyInEIof zR85@9bkLK_VvSH(NYN|nayW~4&W#3=>QErXs>9Zgvt%tTljEUqi(POED3~g2a6aFi zWiL2c?`1Wb{Ru){=OI2NQqW?sK`SXLqM{)su$ismy3~L!Z$WPU`cSN<q7>}9!URmF zagr7pwHv>V394F$)SYhXHo14s)Ugb`1xi-6a@WYaTj21OP352$yC8bGvI4S6`Q6WK z{a%Lz2cN}iG};P*>2zNlZ6HD|c))?K4+!=_kZgjW%K^Ba8kMHXO~#=K+js!i)7<1i zmNQZ1fr*YD8W9oUf4ar*b@McYfK&eq*m9WxyXh8r4S<`vz7zw+NI;<f=0I#e6l!wb z7i&q`y9YXc$J137E+CH`osff0vwmW~AD57D4lGyAp2y|N781O(#oD}pgzy>oJe3Pn z-fA>EY=u=+IDF7sZbL-9+?ydMc><UoU?{<Gl7Z;*;CJn04i&YlG(Jx>>W?@0>0q?A zGoB61cTb-^(;onFnUPi;ajB@xYulp+CtJfy>B6^wj-b`x%n#z<Ws*36_<mXQXnQn6 zHiajIGAUOylmOhqz#;bm!R?{VzjZ7VzuB5A=<Oi5H5LARlIU0m<yrR$IeEs#-Xg!j zrnH?7Fe=a0*zCYyJ|I6{B0AgG*B8*N>>ONd#%g;9!650X7W8=x%4aH!K_F3%F8pB{ zNweA#K-hwVgGJG8I(|f+Xbvw5EP)3}T1Mt*zt-hY2Uz*bQm?qwwlIKJ<gsTw@OFUV z37M>**K8$~Op>1bE#_VtkJ0<C<psfpI(Dlu>~?Svpw+I&jv51YMrX4%;Jr@I%&fFq z3jo8Y476)N<LM4d8TrU;(1j*F-{^L#kR_QODfN2*P`Q$+iveEg3b;}qJ$jT%l1eTX z4kC_;1(fXd=H_^%llihh;Jk8yl9bk+^->+-I2s-oQulNeWmL4tZ5VN9$%;Sf7B~da zi+?Hoz5#C&ks3MrK5cNHJ*ds=q}AZv4sfxcsmH(zS!U`=2O<&sYh@7@pji>%npuhH ziUKlNiquk)d5CVgQeMxGAHeWS!dFVFNF{I_B=sS)(y}vuhSlVUe^(*5!?$mK$NsUp z_{lCuO<imW2zBG2MetTEbpM+}L}e@W3J*n{GFBS(){t)QpFeztBOeMU^Vwk_*{aTc zQgWRhT%?k`yda!?Geuw=LRw7%-C@wca%uIHs75id0z?ubZikgyF7x#t9b2Z@T+@gN z_T9yx4Y-HH`S^^Ksq|x~WCH41cPR1x))uE*UnK}A&Ugx1tSl-B<AqDLoeTR8i=Nku z5FbJ7fy=mVpa!e5nkzQAt#(BnXm*2L0N#@U@$M8Lzj<(imUG^CHW1W(p-dBSWAU1e z@$*Ls#pi9dN>WKs6;^au%cds}#O&;ruomlSZw;`w?<gh1>$2|(OByMWW1O&k1!Y@T zJwI5OTkTuU&_{1@gz4nj$5H}Qg+^ngNw{^ZkWhIB#>RBlIgCNOJ~umC!0u$7&y8XM zP0;J+2lhv(^+M@V=RT|B(eE)?qYk4leqbA)%2UO3dPTHL%~7^l=xz7`$jR;Cd(I~v z@@Sk^i;}8hCjFb}60&A`dZq3gES}6SxB5hPcVGYy2s#+A+ktYFPCL60<jFZYc1h6O z%b36KE270IROq?cUf>KoYzzy*eJFO^e{5xDURo`8pS57#`!ID@tXfs-gi`U{-+xuF zP1n_xD|JY>7SCTm66X%EK)lD}ia;r#R_}=zOeQotinPXTJh|&6{>ES++3i$Y%R3z= zuacdWrC#KYeq3U!FJ^f({!UJ%ME=Pnj7&Jt=l%q7(oPA(e6l!1qi0nFb#h|z8Aep{ zo2sfRyT>i~8za6?0O_q*A*&B`Wse6-G>UkJ6n67p$;ukZpK|#;5Dlepz3hV=R^qds zIiA~fiJ&)V2VwwCXKzt@Vhkh=l3ILl^fKe_j)Kfi&6O)3H|iWZ`Z1-wyW3<Zs*~&T za(nW2vOga1cDig2o1>mxdEY-@cG#aSGdS%EMGEZtNys=9OP>@OUjq#`lCq22AIYou z@N0gq=J2T*9tT*G$Fig@cjjDnqOwd+!0?D+Pfbk%MabS%EddnrAlw0C8O)afRcW?o z<a1*#SZ--uJ5-lvCoOU#f<lTE>Q2Dw@mU@=SHiek0+=EF@e;D$z)r5Btc=fV3;=o} z!)cHo1oTv0KS>3>J03GP#l@}j03*(&1o`eeU5I!4++ZrdRCSwL+V$zy&hBoqfER&H zFcaZ%>6?4wnVAit!K;n__|45tkPXupHwg6EgkZ)8PLVHi0N?4fp`jro{hi5BGS)j8 zY!fyXwzifnxwPY~p2Ov@E0VD($1)wk`*Ssz%<#zqC?6jm0f!|uYzL%BxdK+zPVqv6 zHaKtD+wTH?*qi-nOUpa{qN1EYf@OGv0EhYfc4tWZ^kV=>>kt@ly|f%7y7Kc!eXm3f z2Vnx5H8%Q`@=KjP;Kws4Ly8~~@C0U^V!%X8??tO)X<Xmgaaw6Z^ku0_;<R4xiG1=7 z=6((X^UQ@>Z!kD#bg^Fen1qU9Ed7<~4d2^&X!pC`RhP}5X0D$+%f0V;?0lV_ok26* z8@8)aVNCD27DWfQ8*5c<nLHmPGHZ_r2*{2*>s!HRHNnTn`*O`?rCg*TGexgHQ6Q(= zp>(=A)avpm2kqxeIkoEQYPC0sX&RL=PxT~HuRi@iXm4vXpUk5bAO6f$F<<9E+uHXV zgI>pf6l@`FVPT-MpRYHH2B?VNt-?`SffSSi^2ORs?>SWpRZ>!$+{vTnE6ohR$Ye0F z%GDMQ>QAYNj|oh6<%ZWyx66%<POwdo+a6>0KIFGlgttW5Q;J*wTT0E)ZFFS__)%dj z3jq^kOLphOS$`~AmKMCjiy5n~pX$$^-MBV@x&EKcul%&)uvbVlqdm32Ej07fl@Xh~ zCmeZ<4*#R$&(Wy~oRID*1_p7i;RFGzwVQL`RN{)I&&hw!y%2H=^4_FW<YoB%iOCKx z29vm^JEXcTpm`z0-xp#GWrV$zjc8dN>erD#O!@L|Y`?NH#bF_6pOfQ@Y1@i3h4FCK z3^(0gwCh-nRpu2HEd)4O)L1SWT3LO|FoHLGO&?C*P7Cx<_r$4p_2x-b7=w9uTcUyQ zj|)T<K^6*aVty+tdOJK`IaN8vIvUU&IX@_}@B`sZx<C)~5nH>)yZwT=fzRjS`)G-D zU|*O_3Xv?oC$vH(=ztyDcHx0k5{g!u*IZrotJVJ<X<TY;LG~ZNks8Ixw-<Fk8TsxL z=IKm(LqcL51%!V28H>lLQ1iMw!^SFHmVlL!PJ;;!3~nKDbhLqm%agRG6~KhX4FtTn z4EA<rAs>*k8aKm*e<@@a6^%EE$=O|<Zpo1cpf8jeXi{$VIcyDyjq-UXXS%S>(rH$i z2He#9rmqBElJ1?XUo+pE92=@b^xMHhxL9j<f1U&d#eHuq>~`O0s!01=CB25U9Thl4 zy(QS}JVuHb9IRl}tzVxR&$*(NFQ*_#2ioU%kT0NoW3ZZo!C<lP$<5UGT{azi6|(H` z22$Cvtm;ABoTyCyl&n3AFd@7Hd99<)wrgx;;^_R8_$0$Sd{eV<I+8q)$<o|7_%xS` z#3ATi;~uv?oB8~w-_q72zl8v9NOT+Et`zUm8OGAR0&zi>LX&%y^ZqQLQ;MNHez}(w z5^q?+W<GJ%AJ1qqoLUCDf9&l0OFk_Hs*Eqg&v$O&%^~0{=zg<K!}1XW0|Vq$Gb(1I zVYHEph3(DO-GUrsgYKVZqm9?oc58rx>X)GCE&8GI=g$sMrfJq#e=~61=zIO}@KCP$ z(g4uo(CFE7-+-LCI`B9c^~DHyTofKPiP^dFfr#WMutuX(913qRs^@citI*u@`%5Cg z)Noi#@!34>F>v`;FJQ3|fIG{E<5eHL01!+*^M^tIR8btgc7W;KaYnbOsHhtLdax?k zRx%4Ky>6=m&<T1j&p4RaT5eq3Traj4pTNF(+6NPA;sMET>$gB~@MN=?DijSR?7!46 z*2sE~!|pbQjfTshgh%6o#>E}m+MOq#LDlR+E{fLO<>=(JP-%u`EQT)tRARm?+{t;d z$}uk!oc1QNgu91^)A*Bkj9#I@0bFG=S00tpcBM@OIKe1olWVU!gAl^GAj^&hrW9t~ z>NU>$DRVWp#<~zYhgLttNMR|h2f(Ld;Nd~hr8WF!Wa0N@dSU>LTh5R@Qe7k|Cat98 zF5lU7%TMeRiz(CYY*WK@6vhvQQliQQI-Pp7Yjd=BZmU=e^-dLEM0)S(^2IU1-8_{3 zU80fY^Pf*aNt`xs%iEazbjnhw>zpFu*MscGyUw{XoVF*BHJ^}CpG;2XE1#a8cJ~?J zK%phHDlwmK!88X5PvKovFu(OV)oM@w6KWFXy@P{l=OT&}YXd8C3a7BHcb!)texttx z*^h^fKfz?!_gIY<OHuLr$TaT{<4B86jTAlx?z%K?ubZCqZzrs8LyynrR1MEdbQ}JP zjDGGZ0|CQM#=$REQ~8(}GN1{PY9x*-@TK_b6`K7N`@B1xm{zOy=)3@`kZabXQYCHn z&d!<0Y&x7&z`I4D?-y%4*Q+WFrT3y3q+$Fa{ELNdsBC3>Gfk*Ic;VT-Dn)bCvor1< z9{P|qiYUE?^kc*#Z!jr-S##HTjT*^;t9F021qT05pIvPYT7-|Y%~}FhBlQR-d&msE zpxxzsbhtPoFK*?lhw8I~$4#ew4#O=GideJnX=#ZY&&?;QQDafmvYGhvC6CA0>Ixf{ zfwRj`_#+T`%U7x~$AICqo=7upPBP-@)*rHJpel5)cdr}U8!CXmj~Qz6pla-#=!@;| z4kPrr93s{=YkIVQshB-jT3M-dbJ+QcthIIZgO*k!7ZC|n9nxoxQDBJe@U_u}%+A~k zc3Z@YvCP-N5oYiV%U+de7R^>=S!`FBU)v|g<wGOp$s8sVoyw6<HhTlRP0dbZDgg#@ zlV0O35+(+Q2E>^j<MjlSK%)m^GXNH*2h_U;D$Hmhy|bmlqK+2!@M&^KFE`-?rz!~; zht-Qiz>GsX4|jr1ks(-*)!H?_p-})PHb&X-dW=81Xth9mi%^)6@om6%we#)GYl{1M z0WxD@Z{+j$d{)$yvTqh`7$pGZw1qrsV0G?XGG<GNS}jjOWCkjE-EH^dFeO0&C!-q> zhmgiF?!MR<K7A53=^X#P6^R^CqG8x4_zdu+q?0&`6gq=-#$XXOhx78H?((Fs9C@9s zadVjC8oVVU499T7)0jxi^zYnUUEdOjq54HMj^_*`MR_(*h#8t4rzxl^Dn1D>=X{*$ zIZ7SW8N9*WzTh)?kivUxVs1WFXHf%+x@yY>0=dXGk;fN6vgz(#|J>U#Pe$e##<1T_ zNJR9rg71q)S+|+r3F#x-Rs2pG;FEqY0I0pve#p<z@i*7kDH@ck_9_FgNl7}rf6nsE z_NH8YdXBj#W)9ru`3f8FZI<e@ZJVfvgbsb!4*MQwu1^W+*uU$`3c<Bns^ev1`p`Jd z<q?*wqeHU#wC7!jF9Rrgqp~E687afUf8I5>K6&ymUL8Bn&4cKcMY#x4LpyV*MNnVA zr@-*=cxSiv4UAT5Vsw0?h8MkuQx{GctrjX@l0U~FnD=RE0p`NN1P=CI7il-Kp5H$^ zTUGL+PBX;9N6USdmmjdr2_%Zmzi`C?$=i#2lqh%Ed7Ygbko=7j^$<>2nElzOrTF;b z<W-=c&&~JlyK71jo&r)hUc7S(5QD!9C4G&9qf_{)mxEy+q?FyChctZfJ6dcn5srNu z$!s^+zp-SuQFm06<pOS#ES1Tzn-Ja7{Bi{{sfwJmMuk-Gl{+_}tUCc|<;{ZkqpZib zUw_qIHzJ07X8$DeqN1bYZiu}-=EI!xekpS#FPPX1-)D#vsQh?gJed3dgrA_o$7Ymg ziR~{duf`EJ&2#|dH^a3B0V#71fZ_Fo?IzAQ29civk<{<U`(yQ4cj9c{`vpa^p8EKs z@?MCt6vIKL+m8_Y^#QWxL|#>E&tiy2Jhmh`V5<t<_Wy{?P$|}^avvm6$^lAW=7*Vz zEv^7S81O}y7JP#cG#(S5@h58R_1i;1@iB5%lR+NOqg!Rz%gC3#GI!D%VgvUX!cWH< zbG;4(Z9jMh@0L_JP-Z4|h2g9IF|@nZWr}iY@xDFKrWdj;tpQBYMqgi{Zdqb*uh0i% zA0PLUbVZ@=rV7x5=FmP*R6#tj<D2W#)YTn;BK|qX)uIQ}1*ArGy3PVu1x-dah1coR z1i#a6jO<vL+bXfH{2>@dV_EqA`2rpui1!mWV>F*r;BQqjG^A)Ynk<-%2t`Ih0}}x@ zrYjxYPadC0czKWg%!TYvb*A7Ch)wR7bq-q>K=u@gp2TX=wOKT0WkmsbW4|{&3J!bh zrqex3OT+!A@8rc$Udl#oz%T2*!v`pJ0xbc7T1O)&8h7Y%vtU$9k7pdMs`b($e-x{K z##-*EL9|DAjGSk)fPk7Yh_1G@vWlnIj+*a78ms26d=%8q_4*I9xUvX4J1k;82sEp3 zlRMGSLjkqG>+*ou(w4b|TD&Bws=C_yepgvdjhdx_aH<4XA?EL+k(TL8^wx*sni<I5 z-!7F=mVpqRN~@)2`DiM@H7VE8duz!448X|DMZu7c5F{nec5duo^;+=)_teY9HTh(^ zbM_a<dIh?W=Cl%0=g*&NGi_z-vLjglJ&TPkf2|a|-_YZEAZUsLmk(w;z&46XH+<PJ zR}X!AJPKP|3%z(_r`GrZ6S3JpHtU0r900c~PfnDYAohx2X)uqekuwO$PVKA1pgy9^ zz%o0WCLnYc0?Nw6D$9*81{VDYcqp2QvAyxPtqnH91aaKw%L}cVjQ$Y%CZA_{GMyh6 zh4f*={90a{t^PTrT~)c%)Zze&O|%>%fpxjJ_!^aY&lzrED_dSwfd#jf#L5($v@kN_ zH}5FT&FzUC!>R~Kq{v#^SqU=h<BFBKZ}T|Y`-bHJc%zn<mLOaTZbcH&1hxo#CR?T< zbigE;Z8Xv}_h!SyH>>=cCW?)|n?NG7Twc+Plyd||1Cur*M?hz78ihNoCjJu81^iBN zcK-%SKD52vAvLnF7zb@@(EM`-9r?NHqTG<$dhc*2M(uh-Q&T-dQ`h6R&KO+39(3i& zGQoq8)ahQVC~x=s+jEPF%+Yx_he#*^#lB^DJ$V%ODegc#{1*&dFW{HKrO_}P%aj18 zOCshkpHk|bj7rSSB#M>CCno6D)p9MMnTK;Oo0aR{nf&e-cr1oJ2MZ0iX9qw~PoLQa zAjxHjAT2kIFY*l5p7<L_2aG1dQg|=6XYMzP2&X@tj0=Lg2Mlv|^YeilzFV;jmF<rT za6<Tp;2g}>v9>ZcfF6%y*H3&9RJMDMyWt-D_Z$F<!*?>N)0t+`z=y3>_g+FFT{Mgh zV-b7K86119=A;fr-&9j6mFN`7z;v4@1&!F8-A*uwxTY5uA7wc%xSy9bkV>o#^VZm` z3}_Xo0M~#`X5-V28o&sZI_Or7riG908CjUnqQ*TnYr!WXnrN``k^WrliO3ci0{tD# zmbt_WW&jk(iVrjCX{lFW7z6r_%0l*t%j|a2tFey&1BX_n@H=ZtRn=fyrxa*mrS>_A zpL{YiHV*T%hK3OjrwZ%~+P<jn9VFzWASdK(#kZY3`(@B2&Vb~8dto(c&R)v<!i8R^ z-gB=OS)y5F)uA%HlKf|d!^S#G^r}X>FTRZiAcatTS8RrI5)6Uv?{vO0;in;a`-Q5f zJD9rk>eY{*01jTO&?{W5ccS?z^^UDNqS3)z>bn+iR$BvDA0{p0{g;Z&dW3Huql_^d zbQ`2OAC6k=R^o#(RsOCy^C<H}b8{};uY|;4#Xqk<sH(~}-^CXWgEFu2iboIn``42# z+%hLB=DqA0hpnvh5;d$CYUO&9!-r=9&k#|t$EHS|9il(AU9rq=^qN?w5;wF=F$|Y5 zoC5E?NJxB3W3D_jZw_H8g|kEll>#aI*(^C{8fo>lR<yduON6ewb4|T02ZXz!cAJA% z!V<{Uu4(QHBQ>4<!%9*P2~|p9P;@rCtu>a<6X&@w?WJz1nnXJ0w_yF;|B3()O{xLZ zW7)Xh5oywUm*TynP-q&vNAaGVJ@*%=HK)y@t%HMRnWr1bMo8|9<F&OF%*O^N*A#9G z*$-cr;L{D5Of<pY(OW{-bH6^~M}CI(+y0d_bpY15O_w85@bOP-stzKd&wku*f{{f; zKKOB)ynI5PoBLyn2DGc%PoM|e^_F;XM!ucyOMxb7={YXh_4y4s&H1T|e;}@S4``$| zTgfioS4xCT%g6I_3$FK7FlesbWz(g$#{#-ywk$rk{S}HX{ZEI>L-=RBQ}3%tvAGn- zF<u)8yW5GqqMSa3#{h0xnxlPS=@y$!S^rwNK$PJEVhwC!DLY`e=%D5tZOwtxc!`0U z6RXV!Q-4Ts&qiP4JW7hedbV<By;qUv;To5h#C175sr(Ff`S}JHpU2U3Td+zn&r6?G z9*asEX&kb`v}1q0;jv$3g5*)NFvUs-f%`VAL-k1Q#i>6WoP$Fr7^KW&$#Quwd0irl z?9N}8crZfFr{)Cjj_$LQtldT88S?7l@3sKO?YbJABgs5d-8S*j2`1pn?U>In=*CE4 zB;59fyKfWUrQ#i#NXVj4a^DtK$nu|0sI!{r*tXhpM(s>C$ko{!*&Mh#P}@>((bqW2 zl|#Pcdy{yM2<azM%MClV)nJ7Q+~hLFpFcc56uj8BSHFo-l)c}{fd`l^4y7=n-hZX7 zu;F#-%gcGVI~^WO;>2`nuwD5xabMi+$xJm6`2E*DD#La{X&B7S;<7Y)-qiv6?zzDD zN{nLn&+FCBNODEFP*P5YJ0iZTx;seh?HOT!p2l8zzl?UnhAur3fqx3TJwYh}wP~yQ z)}h2_a8eam&ILSgw5E6dAY(NXGkdBHDe8ukl18a<X`k(1j;wB6?rxw~Tl$lF4rAg+ zxwj@$BI0aTYkEkhuyA{ho!1s^`5~URJKDYMkH9_?a=U)5I*`nKdc75RcHpF0XTLE7 zb8WRKX+Bx>IIOnvSZ%tzMBj>@Al2GlEYiICfHr7%=!N-R@8U=pQ4Vnn{`28t&Zg~b zr*6vy?R3CDcJ~HWjM2MN>~XN~+Umd7d}H2%6;5f^6ywz2VqemD_a${hI1p`7?;H9J zzeo>(&A)m9cjB&jN6h?=+smI<(mYQj`r|wTREo=lF0?OMSXJ|%zuL^IupW<CV3R0w zC#du1@hm;O4<+Elps(_QO$}obzq)9Dm*(2ob;Evh?!)F7*Y;V;d=OXkCz6NR@M6st zMj2z%*+;xoB(JCuHZtU#gR?lzp$E{Sy=QthY72`E(<v;3>Uwbx+~~NSqZ7;Wk`Jup zG}9iBp|-c1Jaoo8aRZ~KdWErZv7s#C)%w~vOSbmNPE?XjUD2e#Ee7F@*2|JI_?(B= zUU)_`oRUVSZ(`AQEt4YIe%2=n2bx2IS^Hb4JPt@<3>N3>(zg;zXD9NNEda6HjX{P{ zI>8#<x1wEQzb{6ek=4O$*Lv|<xkSfnbhQ%|mrh(o6i*sGqsSi-8i8XzRmg31-H`gd z<JHZo|LboMpe3+iw{E<TH-IE^!@~QoKaK+k`oWkK<NY~QS8<%37W+m{O2v`BcQeT* z6^JeMuC;4Qb<wgk$_<Z`1tF|ynm>G2I$|GCYa}k9ra98T$_-;nY$fi?R`FT0aW5G3 z4>(=)+i?6DoP2!}o$mJ_sO6v+a(4f9~KLAB>JkjUP2cZR8gIBa~PrqJQLKdeO8 zpf6^l`EhijCI`m(<qN95Hl3LJ&`gN^LJ_}5VEd8Zsp_4JU3-h-H0hX646f?~7-oGu zaFIq%W1P`lrdwvEm1d%vVg244oO!)GSFhh*yimVndta5u>zc(Vo$Fi5cgc&>geG$R zTY7=qeDg$=T(wY%rqROJB=sz9bOjfeUMs;zoIx(HEnzmFv@RWv!$E)fLQ|!TRw;qi z!}(xRCI5NUI;}agJAvCG7!+*CCg~43-&C5p<x|J-!5hA3=czQgrw;ZqI?6kKf&LtM zn*=OF7mIwM3RN0aFDS<jFU(?SRh!ML-n%s$%LSRv9jpi5E~LeLAG>w=8e-yaf}^6H zTH*6D3+~8x^v%ux<og_7+m_wKYn&`%qb(cqPYo^=HlB$Y@ODg2t0}fm-TjP*3LUsK zl0#^xzeM1jZI4Y(wmQ3LW8O*NXeLHsUoq&1ph!b{Pbe{WI1Ak_7cMvHkXy;&1@t{} zCh6O^F0ZIS%*73?Ad{@?%;ReGWTSt<wcSbN`h4VEOaB@5(OsILEtQ#|ZtyE!MU>lR zUf7<)#loSk{wkZ?4SgX~NcroMzn2qDnUi(N#ZDMcQ-7DX6i=i#xbyakVcF^Ad<lil z`Plu9Ph;nMTy}VaTz^jwM8o$SCo!AaOKHa?6q%mo8mfEBn0D(SkJ<a!U*CGC=Ipj% z5J7SFyZjv!=4Ul1{!~Ir;+|52N|Ahqz_r(;z?^u-&XA1Y{oeS~I9y|mL=LNq{hFm1 zCAIhbVAuy1qu5fj-uhk>w$fszCj&g>1G`s-S!{TxoeAG3rEju6=w}<9ZOcVLGMw(8 z44JUKNN4xLHAO%`P@{dS2Y&nW2H^=<Ul1SRAs~FEMrr~7^hpTe83Mu^l*dfqFY+(G zK0-kF;fH{RfbaqP-@RlbD(Z9<eNYk8d~%Tqyzc8*CdHGZGc$Z-@TFoT8GQe%U--Z9 zv;6l<{#QS}|Gr}XeZ~Hl9{2wvZx{*$gpLpY<rVJ#$hZ8zZ-M{sxBuTu#Q*(E#DD&I zk)%TyPU0;2k|p^c6;1@`*$yZ1GSZMy;(X1K>CaWKP<aeWAIePQA`Sd;A1O)kAjIX8 zZl5Z*h5CQIqW*l>i`838;UoA`@UyiJ5mem$%5gy~@G3PUnTHw2hekItODf7tOW=`> zjw+>-@P0VV9E44lsFpmG8}&wYO8Gqn|5-&eOEQ*o)-@Uk4H<RHw=*!8Xcdi{Sd988 zDAT3)(K%%$+sb1G25vMh>-H}~ISQ-fWAl|?{rxuhKrPar@Gu8m>$*Q%!!(-emxN?7 zfVvPF;dAgcZwvT8|37NNe=Hf)@iEE!g!`rY&U4U0^#W?z;RNRUq!JKicxhCon_h~m zeyUPEP5GtB=2l~7_fE1`AR;41sekvgd%im{zx;aHkd=r$iWW}+j}bd@QkGlvqpx@1 zirFmX9#bVvxNU4ivYHG~_uUXq6wdtVv|bUCg^-=es_ec-@r-0I!RusrY2h*lol@CS zCAjb<%F5AD2~<#+glz}C4R^Ap4OUxLP@AMFIGm=dN<u@x&h23ET{NB(%@BC_z3mlJ zx|u6a8gP7-Ou2Fl{#2-RNC6KibnB#c4pM8FYp$;5fsB=6u2b_*mh;3!<?D#aVY?MN zXv%jyj&IACRrl&P&RZ}<`27Lg6c#`<=&;#$PX7@?b~$OfV#n<M&P~T~kXENmszYP` zf=#n&Qblu8&F9__T?2OeA*?Mw%l&*8ozPk_`&4gN;$OW0>0il|njp?Je^S6rWlrx~ z;=5{Ink@+L!E|POUXE&k3O&uIWaXx?RO%D8t?ECej}Plq3rbnHbQEUud5o%3bEUSZ zo**D<U~#tQYnD9p;Ofd$e5^8`)FgGaeb^whYP7rR1P7#dGx2Jeq}M0-CaJM8?x3fQ z**SFWvZZ~QrCz5#zKN^lYIjmIa`W|xFyVq7)JqPXXyS0CJ71+JF-B6qI9q;=IEwx6 zFy>P5Xr#sD*=60@fj+qo&s|T3V!tM-?PsJKH<iUYvx+r2Q_W^ie+u4tXGoRFH2-^* z!kG0rxvAKEo#-~T4_xyMkk#ExwL}h!hvAf@Y;|wPq_q>iD(K>^b>*n21hSdKfE~Gx zYgQ|cX7+=}+F%Wur?Gf>l=)<S({M9fa_YA%uJ-Kj*0^2*C56ewg5i`pxd~!fZh9KB zPU@9J8!m(&DtWc2XR<QR7}46-@g=I8;j32V5-FPXOB}mv;Wq-dx(d7gLCtjzK7t1c z<ce^H-3jKwzBzXDN{@d2Xo!EsFgN@g<{}*OO*741vSGH$qDHVzSmFKtwBc5Yi?vQi zY=f4Y)q6i2xg?HmtNIyZlT>fX+&HL?*T&iS@Blw%ayMwPx?iO~r`Ktu=CeK-&IS^7 zUU*|gwoJeH$^JW-q;iP__yAA^H7lnnY2rruKt85)h3BE?b>`&a?c~fEsRT0q+1@zh z?gRoWhA{J4PZf%cXAfF8@5~xf`U|ytT`UNyE1E+WOYJxM4x4YC%b&$6RRun=8%SWZ zmz@abpB4O%%RyibE{D3rB4YhC!C~!Mt(~q_{f;rBfM%U!EYtY;c%}qYw`R32h1l8D zzC+$BU#W<o-iWb@>r08sElAPX|8}|Lc9ZuAR-}3{f68lBwh;&3iPw7ibK&CJ!hYp= z-C3I6L=Wy}tvdTBY3{2(GQwsYN}A#sv`+>U<-$nr?G&`6SmW;>ZV%mXFx<S?JA#&M zDh8d?5iS=zhC$bD9^CU*a+aj8Mo-&y`KkwBqT({3H7$P5-<`}K1glDU2=Dy~j4@C5 zZVB#fJ)#$Kuwd<dyU#h^Xp@l7^YGpIHLWrM=RyDTLx@S!`BcNklt46<%)C3yipBf3 z$fe`}EQdwP9_^<*&a>db5|p_)4in&yHdI&J@)3k<L4>{?Legw6iJPKVQ7Tdl1mJXh z33ssdp?JqWt!g@x_U>@P8pUWTsW>`(KG)CiKoaTl=j_(|Gal{eoG7S)82Iytt+&y8 z5{-*q2l+q~e$lo?vJ2(3&<`bOvVri@fvDGqrpx))yYybnyAv~e^I^CF&cjz$<|T91 z>ZN)@`h$r%?^#&mH<gw-b;AK~W+A0POWS08u>O48=`y7W0=NEHe)3RuEx0+DMw-aT z*SvP2ljifFZhBxe!w$0HVwpNsfij9FIw#p<2%qdAK>xQz3FJ5-0}~E?c%B?b9NQjd zcOqfsGOwS*cPrtmOl%wk?p~*^%Aa5<3Jd_gSPMK>lzx|`rj5>cXCQ8s%xyWSalF6E zz?96-Z{Plqy9i7s^RD-k=K$|KS1b%%4n9{84WG3`3HI^AIwWIhmGhOZi#T-&A3UcF zH-q-&%%&PUfet@gCh213Ik-88H1kkOK9{xK9yYUX&-nJfpY}+*2iSi1?exGvb0Xf0 zkTcrAd;P8K!NW%WJ-CGF2CgUuDtZcmyXokc8Ur~~LRlnT&xaXu%*#P_l4Q3jw>eI8 zqOd`MruyMjcHXT?Npsrb=981>r0#5JCPa2u@`sJ57YU5bYAYq{GX@TEfd+!Njy02e z>J<mg6;K<<J<im?!Uz2n+hsV1lleZnMUL#Z&eGxL+reL~9<6sED3mwd>K3PAFY#BM z<@U0Pa0agH{Kkx1n7M=Hw1Ll!+1UN%qSL`OZUDM^rRhrhJqc10zK$(?>UpaD>@WRZ z?V8J*>n%7{XO-bj6yLjDpzL3qf?Wil*C2-rmqM~&G7@CZrQAIZ8+7x(hhH3W9uzPN zH}gZ*IwvhA68Jq-4paG!hZ1zu+N%VPmbIN{?&FG~VDiZJ2tnnW0`rr`4IE7`?u*m5 z)TXV)=O4vukNg~>-%{(Q-vtm_Ihv%Uf=;+&51e=rp5E+oR%dm|e}N+BPoliNqzkMM zP#w4HpA|zX0$v*1gO(LmQz=r;)>ELHFK9JwI~V6sy*s)e@Q%b|y(60EdrLC5YHVMu zO>Lg$u5Ld+JfRkKp#gYv;I~&COEOo*!hwqIQ4z+^`HrXexE<OWb;a5Bd#j*JXRRKG zskZZ@id1q3v-eZUx@f;v)-SBahbuuJ@gWl<NAPw)5vuLrCf-l>$zLKYA|~5Jy5Zy` zM0i)A%qy^T)@O+45?0c!J-HF}LME)I{w=3P3bU@Kvtkd^Ibr7eOjptHhxI*&;lsHS zIQN!QGPlLlcjTqb7@sv64l|e4<NKR){)1X9g(W=8s!IX*?X{2o>PApEDS!PSbDrBM zNzDa{V0$9wyo+3tLD(Ffhjgaq<y;({&fOta7_#;-*khvVKXQc%hjX!<(J<~fdT-;~ z)J;yAC9=yYXTG;Rau}YsT~JPc@4R3YZq2Ei_Ujk^#jn+@CB2c~ChbE^rbneo^-eZb z!_E3(oQ-<Bd0_imn$582J{`tD&KxXLkB6!t&8d)Cz&@Vr!EGTg=-a}W4Q|fv{ywAH z&0qYP&RFeMF}q1Ul*GTP>YR7q=}%+nc%5fOB)6+1I~=&>`BN%P#?q>)HyYaIrtOTg zB;HlbC_W)+y4W3Zf=$}g4p!%pmmS_;^4T70!)M_wpZ9qW&W1;J4xPuV?nxRiNt0|y zs@arRz`ETyCj;HD_vkOGP!v1<9-!lMf8O5Ab{t3=W;5sP4+~u5H_VT4{upYdU@~r} z>!qA!A1fJK@I5C?V6*m7_$4Oc>+C7vXZ`9V-}^8F74X!!>)P?T8I}DsnE_wZR}TH^ z5+WQ|JiQi|q+09!z|XOjM;>OW2ux%J#kFVsWxC1Uo7@sp?1=sBpLbhGc`r^aN_L~L zRYPzZWYtE~<%`_UKbIacTrPQ-WOVUTAtw7p-SNr++1Rb%Jm;RE7_pc1k3Oi@BsUQ@ zvFvrmZ2Jp^t8*sHOP@gEUxj{5EC~*OFof;(d%fYaJ_SY97$!R*JWgPb2*_i^#da;t zRBce%FJyt7C<H1FOP7low~uJznI^DXOg%g&Qc`R8Z;ij#Bz1(QGyxujn%9&!0tWF2 zlf%iW4=QmdrlMPVgTn>)7NtmZWF&aMj9{ulb*2JII@<3bZPsO!nz5>$xu{ary}7^S zwrPBjoGFJ*7AnIk<irlkVW7C}3|4PH;^nj<asPgM;L~yuN*I`Wyy*m^EO=wMFagPu zVs*d~xY#DIFc~Ic+uk|KzV<Pi8#$%bZ8jcE#MSmx&y5S^T%_2_8^IdrxuSmuLLsDA z0)iSw(>#ebvlqR#_LCc;1UR?zm>RggT%Df7IFaD!tQz_rnEb1``IGtk^MxYjDV$5L z>_3sD6R(7kUP;(#J1ED)iksX)l;iE8PfL3^-y*0B9a7zMm+r}W_yyPNm>!Ll8%?=( zu*wOZw+0@jEgk<!N#a1=;dCm<8RWR_ZpS=Fw+O>y-h*RUrz9dQeF>qbRmz>t8>iH? zu2VMcDfPbjBK>e4PX)JhKP&5o+^wSOA5h9vY~}@j_SOGQCLIxQ{VT#jz!knQVg9Qh z4Czr=HTGvoF57xm2yH5lv*L9gw%6n~`PjiYLr&D|Zs9xf-)gBosbqdgMNK)7Jgb{t zdu!m=`1xki$F_OT(|Y}teJ|@IL7N8=To;UtN8G&1K}t?XyZwv-zez~Y+&A;UWQo-C zNI$*09a}f*{!+djdkHKlbQ+DO<AWRv&GH$H-C@Xzk^0e!6~KiDMWrtvbe=ne-n1NP z&RH#S(G*|o%V9W-q7$$o89b74v5wp1IgMEYKSNw(*_y0OV$HPEShygAdN~*Puo%Qh z9okZhF1(kU8`^!NZ)=_jax&53b2y40H03_6d0U|8P(ek3ORo|0peILT8IJC3QD>6X z7mLY%*8@H^XfKB4H6gXD-TD!Tm#65gnw-+~I$H_cf&LP-(Mee2KP`0ldxG@#I+Q`- zLEK=}<O&URus4mjs$MvVjn_rmKkt)bblmZj-(_!Svp4+_F5oVRra)2fMcsHPh{q`b z&Sm$-bTGB)a5`fnmF&rI3NI(kx}L=0xvK8a`_q2LzpAKvbPiY8AQS`^+E1^DfeA** z!KM?cW$jV=$icee&Dk!q#pb|M1U0Lv4$6_d2DR75fyB;<puS?GP4?dH_1GI_8F2Bs z+1Un<Hm7=|(Jys_t$urY2`5V7XBdjjJ`CzA>016SOYWy_A&jW4EFGDJ_HneNT?<== zx-*~#u^IlwN=sAhcDl9X+Rlzhmv-rKG3<u;pwii=zQmrwcMUyaHP8@EDCw`$a+ZI| zlb4<o`|G;9QD}NBd8Z*uD&7dLrQqCDp7%dGJNIy=`!|lS$J5ENr;;4fQ^+aut0cmD z2+vwn#^}+ooN9z}wzgFcds6g7p&XXN3PV<gVMRjbkZjB`hB<CDEXMZxbp5X1_51(# z-}m}n*Y}_A_j}#<>;1Xk_kDYtVA)Kd)|2-MSI?&%(%~$p#bz|Y(;r_dokZG9RVV7d z>nebw&{(OA(mw3qc2L-Lp5Zjzm+HAsb~A7KQCDhAwEC;QDDDT}ARh+hzx}#@1frA| zIGy@Sq4m+DHiCZz>c-4c%<!m-g0LS#+lHPlzL99|RufAJx^x;)Aj1G`g`^#)#_M{{ z_<JP}itdOT81~6gRg?hIyt=6-lwYh}>?GNS%7gf^J?`bbT5`?7eD2A;U@Y#M6?eUa z?>VBjyx0s7bhR0J;yeX;&^5<baP9S0Hx#z9=QV<%3gwrPC4=voI`B4azdeWD_u4pN zU<Gq!uH?us*@dgI>u#_c^ECtfQJ!h<;6J`*vl5C@3uyDdwf;ri9>t;9FRKBp^5;*` zMEo~APb?8uEu&~TUK42CxzUr_bV;2KU}ta>{a4zRm&s<<^KXwiq-fx~{vc+@_NyA! z_MB+fR5w@`nKOva2?Rl1M#RLQk}U>jKRId1|CR8il{Or0w1F9&k`rc&=X=+>8dEI4 z-HvPEYLDpa5jg1^)u_?h9D;)*YU^*#ffbl~v4jrD%H;M<Ho*zH(#KV@T=@3#q4nhB zv;oJg^oxBTQa_DBAKv$CJKCu0of)qbcZig%sl0{UZ~y^9`kjYyiffEe_F2enmcMBS zWF(Z^u4KLRXLRVo0cpn9-p;Z`#Z$@qrGC$Pw}&M*CMxW+T1#uAUw${5*u+@n<->M) z@Hqyp^?BiYxul~JH-D(CRbb!si-zWouoGciyZd|UCH?R+#>&liYAgh<gms-iI8y9V znmuDnFpmtHN=;lH5cBKAS!Ik;)X6<&-yo1}G}xIxT(B?w+K>lRBqtY%fEaoy9?+$Z zsG$gvfFb2}e?(#hX}-AW8ze9Ms!pwUrsvNrC8tH)_`&`7ncz84J!60@>M0iOdrj8> z4fUW`z6e3cdL|5_N9rax`QArG4n6m8SEBk1>`|!v5xz9#Q8T=E?Spx-WA4%FA($?a z&AHKhD2|x&3ZXmu;v-NM@j6O_1i|0GH8!rduG=Ik-PE|%f<`;f=AIJAtM$beCnlH% zN{TX<xS~AU5%2qy2C53`W(?Jzo{ubcE&f6HHqMl0<C;+-U>>t-eP&RyA1_%SA*DXa zz(QdNR^GI2sH{?l1+TWNQ;~k=)ruOcScEMX?4yZfvAr*#W{fjM77k5G$~K#=0mp>Y zt;SA$RH^C+cnReMi|r}4rB}lSKV|tko*{B7FXaEr$6C&D2fnz^%c2iZug$T2xE^%# zTk|oXhm^Q-4(l<r`QyOok&`$Or0%Z;-SBncPv}<8n#3->0uaZDZXI(xF??Kv5c7L2 zS|t^WAqZ(d4URFqTRIdQzgR}+PYL_9HU>1pF3+SUAD12ULD*Dgn-PNboTGPqx}DU0 z+AvU1CsK$z1+ujh<F7xL53DJwt_KMQYOY_PA|+g790$L?NUW>q^hU2wB^8&#kyHQ| z(f!u~fE{J_DLHDJZ4$cv#LrM$HsOiY<+xdA=u<sEg)mqYw~%o#37v%Xm(*5+RRMbC zqYd)Hb~8`9Pa0t|*a7nVBjuCow`NUTet_VVeeJ^8smIXOac%;oasug5w;20@psg<| zAOq^2;L7CkS-h0A{*6=m=>c1;f7)V1H5<7m+R$%{4$gAWeH*QO?3_8e!EAJqN5a*# zm14tZzvx<OidcOy92JJL?bK*O%Ohb1sb0#b3p>T!GBYFiXKg~qmxB11B1Lkxb$DM( z7tYIsfF})Gbqb<KfijexT@s84%6iXzE?L0YzE!vIy+DRr=w5<N_Nv&+!;M0!dBa3a zyvyFWs^#<&b><3op&lT9ILI!>sAo@WRGjW*S_QJxWHhCngbr&VlWH^d%-rV_iY8qi z1DiS2P8<xh=O>$aif7v1jDDbY0wdqFRMq<Xgg&Bhyd=^!qb-;r%;gaonZBK;opQ5H zm18EJ<X}bBhRd_yfKZRwM?M0}e6w9>gvK7rFvelt`{ao(x&OoxkY*EtW1Hs~CypCw z`2?+qx*2MxCdc4qRl=14{11uPcVcLm4%(HY6mR*5uDm_Z`Os;lUU=L2{KT+Ax?g9L zkq3UQu6#q=(UquODV1!EpW9tH#IK>A$F%B_9@H#Vv>mA*uNZW71EE{X1$FZ8;JJP+ z7jdeMJzXLmfyroM^I1Tmf+1wx4|h=sfgj5AIum9Th`xQVR6O_Q?uG*)1DskgbJ`U( z3*S&M^dK?j`;w14`h2FTkNwpY%MqHq>RkUUJ#j@=Uqlzw5Bc>Dh2{3&ARm%qaNf}% z+R(HK*RicZb_133W<%jrgoW==TkN;@o1~bbwaMV2fp=Ec?PcR5KD54@#XX2ld5x}W zID6XI5|3|r{^EGo=l%$h)gh;KbV9LXTjrir&xg5DB{a{cQNyuX`cqYKL}dU8Um;ZP zjaS%C(vr!LYFw%edg5Q+ILZp=`J3y`khfZMq@#Ec-I}JsDW%1)^*IakJd)oKru2QB zPF<3+LB4_XDVbWoyui*1(U1xvucug{gIBC*vVm1kj}<}}uS$2FpD`Jwhcs%(`~oT2 z;)e;Ty5YPt<>XACo0le-Gx}gqt}&j)R!~oLa=t^`u8?*IT@m~+onl<``>dvsgC=gy za8%i!2~0@QkJwu^j*eFcBvnad=}i-0nHS>FrOV^iR~D0U>RrIZMA;)%4bvkHl~&AG zhncxZg|v%S;z8DHE(iRI9bbQ4%_w=?P-zkFOdGC2g98_ntVdK6`Mcqkf|Az=vax&l z?@>*Or`_vflP$&Pc1*&-K!7aX8dJ>_!z4i&?<4igH4R*gb`Kl#7(jLsT*t4aT<ADB zoK{$ldHqJR7BLgFHWVM?w)o=f;N~_j7W&{)l3|(odRJV7B#kafo4YqQ$LM$xb&d>T zsN14}k>};93^%iZAmOh5O7(>wFI|g}imSYOy5O9GrOv9FVV!B{3%xAL`={N<?NIkU zF7{nBdfW2CPA;rNds_XfpWJ%S*SIZKVkTgI*T*)AJN=Vk6S1?F&8W2s%0eyb!FTc_ z+_u9l)#9kLcecXM9>}_UKzl8FS<Z4ZWi4_O#IX23Wse>9A{r->4kj>6@PG05H&zga zn2+paUXN42oEY(=tqN_R*e?O36sL+)OA54BvFvn#=QtU*tC7BY(G8jDdz7kUhkS6Q ztlY)!Ug5P$D;~>+^UQR_lvy(#a`zYYf~oGH<)!Jb-ycik2_ztS%6Hrrx&%#SsJLG{ z5IwFW&C7BR?WO(`+d{qb-`D!W>u*o@ckvCxnCT}q&+AUd$flEv%sBZ@ZB`=LU6)ox zq$bXHhBwd)rDH}gYI4{lv(%$(8110J%7=ZWjojLJ34R*{5)*P+MmMQkIUz@{dv}CU zupPQU!6=o-Xd2!w+<j7h$6M&@Mv&S*hZUSdC&=qCh3uFjLmUUWQQBhYI?5rQ9*#P^ z;0;TvZQY!EcW_=6!miz!waKZ1;X$8|OvbPecdHx<U}j3%d%4XI#T9*J7y3Z$!q8)G z=NmQ+M+u6+7x?aPxp=n7FGA1ie-#70&Bi&dITg=R+PiC8A*b;=sy~fD&wKt76Se}c v5QGi+zb!-SQ1HA|>an>u23&0Ui7+yd72Ppk#Wi~`a0ddjb2?LM<9+wvE+YKD literal 57923 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-linux.png index d5aece6eae7990cd7fcb6b95893890e7244b9da2..6126614aab0a98a1527b86b797de9dc6b10d8316 100644 GIT binary patch literal 56373 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%0AYkY=22*?f7{bcbWM-;H?@%=vIxQN+F}Yq9b{ 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 GxbZ)I0O#ZY literal 51622 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-darwin.png index ef40e8bd540891f103b674de5d0010e3759442c7..ed6de4a374a96dee23d43e103fb9bcf696caa511 100644 GIT binary patch literal 59613 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�+Ip>ecY^cU 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 AzW@LL literal 57698 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-linux.png index 4d098e4305fca062a253f0c8cd526d3ffc758fde..3ca230f729111f496ad66a981e5674cd4c948a08 100644 GIT binary patch literal 56145 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 literal 51578 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%0D~g#a?xuux1iThUpvu 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%0A1w}Oe?<9-BBuaN>Q`O%iO|7 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-darwin.png index 7e574b0f2663eb089db22dd86b84cc9cf43028a6..ccedc5c20ff7a59775b7d100413055f8ec7739b8 100644 GIT binary patch literal 58684 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­T>!SG`xBepTgi5PEEMX@J5L5cS3QMR zOZz6=5~O^-z?C`hS0^gEJ{n)d%j5p{CuSm+bR@LHIoqpq_87&EKK|ywznMOe#Wq0E zWtkZVkyw^9SDz*EtN~H6zecmU&aSF*I8p&`<x>ybLGCZx^vh4uHNKrRAaM$fWFFw? zN-d`tULxbbPL&+*dC!En8DcO#D}$8J!mO|`squlZFuDEdG~#joskYW;X>2#_UMi)X zP^^J6V)6HC3JUdN(Dn5J;L{jP<;b^5nHEJr{|J$7zDhIx(2u6XSe$w@v(%ZbNWM^U zYQL$4A!%+UGhQvl{{`Nz)_fV|uPQquw3Sh-Ce-iiW>y5aC-r@^uY@K1p>gzezwSm( z79H=^T)=(DZN2*?;ySMG?HI2_(VQZ4<kj9eF!G*LY%%w71br&_jv;b5*DA`1;DM9y zlzn0G{M#n|Bl9a=%jp8<wg@>yu9ssgd>(ftKIGa~2aA2U6fB-w^N|V}ZLe^sIBsJw zjAD#RG%`~&GyPBVK^2|lK4#>)=e9VoAQQpq^Y}2DLYW@+!eSfPtP`C~<(aheusc$s zbg^t=sD0J}?Yy^Wsm%+0=k2YsYKNklC)ycqNbI?oISC$7a_W03*Q^$Y4O~n}=!K%) z+p15rX@`QJH~+T!c*pjL$=6zJAaGczV8tcO^6(<jj7K2>uREJX^e=LWI8-015Wt6D zX?}Y1gzNT>;R#m@@1MH)eSy%ozaU&%LwiT$kU{dqTRa!z?!%&-FR~c_?Adm$urw~8 zWJ?J1UXu7Ykf90iw;5)9zbx%lA33DyS#XoIRTp&p!sR-euvcw7<<e1vVnzm%N7pOd z1JZvM(%1d?_=M(4!8_?FHBt&XM27bMX>`qpJltDC7neOpHX!c?<I%7o&<H@HT%377 zz#*Ktv8?Fu{jAq1Ew(b7ov~H=Ee#K9)dRTIp1j68aZ{6d^mj8B=CJRVQ3?l?bYbo@ zn&qUrjOs;S6NVw4uPl3T2yvsFs&7Jut%dKDTY;ZnyUp1@sPQ(*CQhy72eD{2?!@mE z-dkia?Hssn|CpRyenrNuQ_}b3B```H%TH}`&bj%zt-*`xinzobm@@*}K4Zh7zLH%j zGvb0;QV`TIH94Q0uf~4J(n0j{sw~d|Why8U#S%kYN4A;x`V*MxNBv?K7V5#yOuNQ? zFR^%Ldq_XD#xm$^tE>JOr;YddHd9wdBq4{aC;4tE#BpmrHam=T6Kn_8R&LMn1id#~ z3^uk~Zjr;dCbYD)z?Rr{u1O9B2th#4+_gf#(Q?QiJ_I!QXYy<iYc`EfzUwoleg)Fs z2n%dqoiU2IW`w+vA1-$nKP!!BosXtJgf(w%O_l4TYkvXFFeYtwi3wC{Vf+@;qr2jn zGW`bkLck>h?W}~1<wD<VUU}3kqpWTjf@;b5FQHl^>3q>agTDmutZ0QE+l58lj+Xh1 zcVD3-!#JJ+sqqmoq#iF<qw9~;$Z1a&F*QD*r(pt1)DOrPj5?e6JSHydS*izFeKKx$ z4VVL8f-w0AkKI}v=t+Y5BG${<!NY{M^@<!=*<XE%dG~0;8hms0cfu^}@y62dQs}b> z7z8Jbpn_5{iCNX~s7dZe`{gB(b1dNXFoCYY-a9K?lu$eZ>g{^kw*9H%P_9MXcQN|J z@fED!YTPU)l8(41)`;1iZ+>L=%F|S%yjDoS*Q;=!YZ&WncG=@LTc)r+T94`mYVN>S zSZ3`w#WP`U*kI8!Jh;-GB?)ad9nyU(XvmNmxU>6Cld9gLp~X2;D#vVJqL9-@SgOJU zorFiPMMH_)fg5ASt*F%=`aZ{O!vzn_R$e)jzxA>F)z+3?RP?Ly=jJ~2gBi;gmH}Te zhFhjL&@Y_?5YQa$$f#rA+!{y*ye2S#Z=KCI8&0=dDsyso=k5;}B|(*EPh!?<VvrBx z#ZrnnL^7r@-ebdSL!0Fw;<WiRAm%NtBQh%ox>-;_+NMifYJM^wpX33S2QXJTgI;1~ zs6-291Hqh^?|FxV<MlE;d643aZi6#UVRBzV0Z<T%fBLk5JIL}z1jX;9XM%Hzkh8pV zUpY#yxv=Q*p33A`(%Y0=xz@_K1>LL;swD=kyI}e3WU5~;l3c4*OEnd|FBdX|`;)m8 zfd7e*-6Cin>#;;AiTreJ<8M%&85pc|8Po_}9nJH49e)#SZ30FC5Qq+uiEcBmO<>W- z1*KO?ok%`#V{e(H%|U`5R0DdL*uDnBdD3mJ{NlE~$SM4=&k?7S3jESW61|2HT|!J` zCo`$^w9q!E1mnKnzM8&|xda!+d{d>7xWNs~@jWsKKoA{7ssp8sQo30lCo78Wm6&?s ziXi3aI4{mWEbK-loFw8m|H`dXFQ3L{L{0x|zSfeeH6~LOfl05bME^-R5(_Dj`Wo?g ziNkd8My-@uVQ?Qr@(ScPecOCxcX_c_?XW>RxA1fpE(q8<yHm|8zsWnhC_rcC699eX zlFZjHRXSZmv1qkx6ZCR3Ru#q8pBGMo@MhN&`aO1H06;B_qsCrld4vAk?#215(u1jc zXhpvq(e&ls0z}(Qs;tm5vA+*tKtaf&qv2UVFuV1n>^oY_K74#WQ<ePTr5=SJDw!f4 zP!Wfp-*Nh*@p%a4+Lb6nlAq?r-V(BT(@Aq1tQ!k^dffKo$aKefc}n|Us>k$9RfD7E z9yrN{x!QbvsN3Xx3nD*W^!L4q9!eE}s4kG<y}vkI*1Q@GX>ze7A7#+5bM9!|EM5Ug zwdI=idtf)3zSK;W%365`kv}4=-hQ{cT>Jzm+{Vh$WPFaGFj$|<_i3J{-W+C0O2P$d z*!>B8$A|FiA%?(jk5^sl0P^8*WNd_un<rPieSA!6!zX#nLZ@FQJAf;Pkwcx2@+}K% zYdx0{RQnN8y3S`HI^=G)GF~o)otV?T{X)|T{&`#WU#h(9g?gLFyp{&OH!rRZmXz1d zu+p<ZBB0NAX)7>w!B^zQ8!iP?3$enklvb=1S%gP@5L#BMBmk>EE>Zzm_Ul_(@^K-i zQ*K4db<R@GdKU*1Mq}eRH^utpQEzRAa|>R69w{82zf9@UrF>A55hW$WLEZs}$ZZX@ zmY{pHb=+v$oObUW55=(W{SKE9Q?aMk5KnU#$Aq6HqP>=8`A!jPj_pPM%MY_zqastO zvge2SWWh7xH!5P4)Z*D}-Ctf}e=0&%`dDP1_58JDx!b9q8xiwP;ubd=_gUM@h|h5~ z7at$kd#B7@r*Ku{J+~Pa7+L$HtRUnx|CDw)z5??qBeyObRkVYgA6HnQ%|bNKQkESD zES_X=S&(+#M)BN9tQnhpBiWlh79!;F9L#O4KBnc}ZFeozu78E@nd;aPD0QOiv&3X@ z%4t1qbA*%bH%ySJspofYv*w^7ax%a??^QmJgc*jde^pbQK;QGm&)>H7$c?h?PQm-v z^-fZluJKWm%UXlxaBeBz`2Lhthvo=>avmN{>b~HknHl7^C$z*Wq(4cH#n{+)sp%3J zjF+Yz?(N^#I_63T>Hcl8n8@R>n%UMlO~Mw(;j~#!J$~;0<YF~la%=kM+uh~9YJVF( zeQfI<t!yf`xW0nG)}}!;ze+-ov8kzTZCVJL9H5V!voCe%;Y;HnaCIEVKNZ>^GHB<? z?i1O@+o_joO;H^C&{IM53LLlkgIZ;BrE#3_XiCPuCo&?p=rbqqmwMlB#>nF@3BUs5 zMbl4au9r8fiMVW0BqBC-r+GTMSbgJmh{ED#P@CA+{6z8Tkh3+&#kOpV;Sq(f^Pcv% ziR$joPCYQtCJTl7k@;bG4F^FZ$)f~J_T^H(nip!<&0_uNZJ)y19`}Ck2wV@w{|)z7 z#-llS$}%?H_U+9|&ZHwt_<IuCUu_j4!2H2U*<T`kGGlBl$Y4g^$CW#QK|sLf{(5my zA}-YpJyErA9lh<lU5e-1+i<Ojbn$dIbi{{6<&GaVyQZ6kS&C;5CzzJ*OLvzZYP1m6 z%`&WJC+q&ULRw<+v&Do;%c=c-Rfc4y<KL3Ay`pvV+2WTksE_7}Y+LV#^=YL$p5`N~ zv<0dv9?ys$!|5*bz5K}rwG(L<w=o2x&qr3bPbC9k`qg=IP6`C}Prnfngf!}H)AX0U zjWF;&CkU#gn<w7|1-%>=i;dQO*`~{uLnq~bf;nAKVHf^a_bkR&6g9)^n1@GMgoX8K ze7tJwC*2*41&KC&-;U=xq3;;us_C$ZS|%CI$6irMk91wEvGh{#-;ZeSjW@mYp)A8; z0850FAgi%CKKD?(FEXR-EtzSH4TGqXAf4#c)=D$-{Ms2q1^%bM-@ET`hFPB2D%#;c zK|S9}E$B}^oR9`X&qvh0v<As3Cv3D_G*1MEYUlr6=qOezjhjU?Kuq?P5i<BnQfxXT z@C2RN8TY%%9j!PIGWa{;yO=E%-}C9jwyKmy!MlD9{pksjsw#W+2-28qo5o9xlO!EI z96~w{{f+k5OU+F}Fx&qXQv0EXfeP&iMEk2Q#fTerHKim3n=Zm<%bTnB>BrI|{ID7O z8*)4?IM4*xKV#bbS1;h@GkiF>uXL~4!5_qg;Sk{9-oAd$3Vu*}^Ys}VT<AAA6gaq# z*e||-UuC_8g9nfM|9XhXt=M0W%d+o(5v#Wc6~N;@T6~Us)^H2ErvX3wR&hjP2mk-C zKmY&2Tl(*Z{8zuk|2|^>eZ>AtSN;EGC(PSta34|rE34Z7WuNc=^rHUXAOFACga1FT z2mf2wOJ>(|`zZ!hwK5%$)eQF)v&wL*zeu&13J@?}uJXEE!R}ICB7wiqk?Z@l{<cl? z!KBUs54m@>5&U%~^&I>~x=%tm4;Ue6ltiT8!PVPL6v@uLXMu3O2Q?Vn!chhbL<vMm zPnT-bNK5`yEuNv076JBcaq*sm>}7@&x`{U3gS*QEwQ@D4e|vyocYh*yskaf2Nh}$z zhipR9GBS+Xb;domGVkE*mRop!`sa`QxZSHE0C5CbwU#dbyob6^Q$?!#({uT{3*d!n z>5@xjf4&ckQ#1r6q9hz#?vE$<|8gz(|FUcZorT4H@Lk`$UVbu%u2X;d+30$tm4+r* za9|LE>xC4=WW2L5ZI~RY+jiV+Tzbw(ZSz)wzA%X-^Frm)*7J9tQN4C}7IuTPn#(xa zJeSNl13cVqq6QqPkil7MW5zZ2Rv_r9;F!RG)V77sc6?iG{gml3*hW{xA9?QfyV4<M zrPYj&dZ~+VZYJs`oxoHTWkOuhvK%_L$Dym0QrGk3jw1PahkMgPT@$$9AE{lDF1*OR zCy!2tKb>d?@^n6&_?;?MCM&LrxV9N$H;Wz4yR4{MrRiIk!}CZh)E>Ud%_(x5fd53= zyrhL)*`8Zb6z{s&rfI2807s_?P6sgGv9?;F7|gi0tTB8?{sf+89S1y3hl@hxv`n%u z#P;#7OTK1c;bT-sk;&uTrNIyL^X(xX<)nAIynZk|4^!=W+a(DypVaf6j!)q<kZago z2jhp~|M?L#{2@1I`k8Dy>7VC|cr8Lm3uJb{L3%k)p#QDb`}X^XVL_p86cE2fdOqCn zJU;{voJqmGE57wNaRcS4+!l^mllS)hy_vH-LJCh%1$tB1c8cAy@6J-!Q{bG_cCgLl zPH>8sp8H=>bo7M^1JNv%Umvc|K{_Ih>-UI=h~8i!4_HvLj^74TvYx}#1wjUN{>Y8> zHe-2fy4iL)<aWD~Hba{8=8bLnp3}ylX7z^;T-0Z^@k_ZyZh7|1Zx3zlB8b!Ik`(gf zGB_Wru_q*2;@rICeqF#o>;)F}=7d82Wy|jP2}D2X;rxUpvf<qAnyPN>dLSt}g_XV% zOW<yHBeRsoH7l;@$}Gc6@Y?^b^;$?WA;vJle#ed*E>x}X26va>gQ?u(=EU>#+4qKZ z0JZDoyHeguPwTSR;<acz1&qK#{I?osJB!sLvTQpYK`T)tT+C3RwJ_Qz`#TGPbl>%{ zwN6r)H|*hB=!R8^LGH<Vn>It1aedI|ga{kK6sw!#a>61*KILkb5<S?G=U^}c-OJhi z*kP6b*Kz&ID0x7CA|oED-{a-X(3Rc%d5aT+dRao>jJ#`d(eh8h2Ycz8$KhGSba^d^ zZRZ^-k+SU5f{@<u`D8);GVE&M1S}vn3!J)WM;DW18B)d>uWx&0D4F+M91Mwq9QtCj zmq8di=xU^0-#!2bFZI72j0#%V#gqI9kD}Y5yJA)#c6K12N9VJTgVJZ(zr$_rB7WLv z*Lxluee)_K`tbgkjQyq^?dYElNGY@B_TZe~<9+BBquM*|wugv}N8`y|wiPBl$9<{Z zjH`|K&i46=-t>o?cngl}#q)7Jyu-#FQnw|im9K4XiS)|bVEsypjlGSZkoVZSj4f~D zEfVQ|Eil4Y?X$ocw$7m5W*zhz=S;Q*)}v4A{3Rqdd&P3R0ou5eTI*N+xIOZ;?vF@@ zcDw&wUkCVNpU$=hLj2p-6a)qAmLE7vwRL^>y1~*V(^lbfpmq{La=ozfV7cOxlH<O@ zs&Y9=mabdrxMh%r-n#lw&1p05Jak!KV>z`RfSz|alLM_)*H_Aw-eT=yzsWSv_p5$8 z>2Wf^f>unnZPDg8p^!JkecFDNeBJa`RtkIITM%+*Q4k_{wa2L;E?egF6&!RkX7QFY zt%CFF;hVY4G&JySOu8RJX=&wr{W4mwU;kzGu%tv;7l7R;pZJ^_{5T){;5*-_oG*5D z+1Xi5%W1Ch^zD2Y`7)^qAGB?3M^`gtl9-q3r8We?8>=+z?%=daFM9R&L;iC<l0|41 z@^GVF`zouN_oWNuVQOdwr#u<ryf;4YH6?G`OBua84HPz`NAlCBz_ZZtM&JBF7yZET z)}(cOVjL{>?3-&6$88%|tl~?4tE<MH4XUK>mjCJnKvr6j@tFH3<X@NUkGhk3TtI^( z38P3K)z0CsAT={VsD$jVCLj+p5Hg>KutukCjwQPlMrKG22<4$xomxBmm@A!_=gZsr zbaH|+#ch3~Opf8gU(mUgtiuY3pB1!aTCJYl-xCGod7w8NQE2BvPstDn&vqK%piC8O z*Ms$+biUB2szbe8&uJa0H*gMf!QilYqrB<tOcACS>2ftkdpQ`tg6!d830j4(qwkl5 zHWRR-v~P~;_qHZFGL-Xt@4A#n0*kn_v}c?~?lgxlR>SgP?BHf2j(Tw2*yMJ(KjoOD z_Swqen8z$w>G4|V!M-b0eMInl$K~L#dMbI-qLOE#v+b$RNWq8yGM@K-vT?ge#%ijF zV0*CDzk}bP*KV<3j0+ieaplA<%<v~bRxEJZjE|3h9>LY>*yUPb)~-9e-AO0l^z^^i zP47AH(0{_vk~i-$$PGsI<1LW{CytlCt3!3(eelaD&+{XH(coZuu8YPZy(Qj%E(GQi ze_p4FydBts$QgUTf}i6Zw3j5F&e%-q=*X>?Rt8_tN{e{Z8XA`i;>}R`99unIY#Xln z2{|mW>(+W&KG=2y{^eS6wz)mTGJyLS#)w1NK4IoPo%QLwBc0@Ib@UCUh@mX@pPq=w z3_s}BrBB&wfl{+{pS3C(=9b49t|<4*$kWMz^2i6+{nqu0+x1^s5-$7GPOoDnO0g)9 zxoZRYA2;A5mkMS)Z5`OPc<db2>o~4?prewcw_w(~<G$0mi1uqseYiav@S~;;7F5*o zyvr613A`qRo-rEqx7~As1G)^_!6+NP<Wqt4j|eFH)3sJV@&W6Q9BHS8HK!qjjChjU z1Yw}B@6}TO`7lqm-^$8RDu>r$0}{DasaCCLxmIm3%05xtI5G>2a`0;RG=IL<=7V{A ztJQR=*HPryUyu2EyM^m?KEV_oFTFM*<#6T*sbz;5foK#WLD$8b7*40-2IMXMn%xBn zQqR@6aF!Oo(U?t=0E1N!z=UA1y48U^j9$sa!+h5(B!K4|!XtV^cGq{B$fzDH^mMU! zzPGp68H~pBIH`PpNmem+bT)qgCIfaKocq2i>gvM|im_8T6jZ4gPY<hoT~}J&2Gc)W zcSh$edvDZc&XCT_R`xdVlDdB}N2y=*ykCJq#~*IbJMrx_zMK_;FkC%ngOLhNp%f0Q zecFsb`jqz{;??7WH$9eWA+N?L*dt<CuZL#-xkMud<*H3bEK+rg)6n*~PyG?&?bvPZ zF5;tvj-wS&PewBz!~O2P91X)%F2Ac5PmE6>pf;FA3s?p!%OvY~!`flj-keU;Eh7-g z<1lr-WyZq@^1;AQK38)kl_&BI*JTT3E$eZtuOPmicg8{b_OLm(mCKYj7TbQ%axsS> z&wKa%Nl~<8_sQ5L8rj9W+#FCdzWT{P`Qby(bfeP_iT%fkTC3?Y-3*S&r+oXxT^He5 zAo^!Pn{doS+`}2~EK|w1O*2csml@8Iwr3L2Ybu%PenjN20Qh#JXdchv^BucN9);Gs zy8=LvLTNckk3y^^f_0H~B=8f_HExj2wjBw$!FZK)GFxjcr}!`V*K|b;jt{(cP-h+~ zi2q{UUNt|MAV<<3Hm>dZ7>rcwd)OGyzPY|`zMqU&aNiuGjT=D8kLegl;aEDEnh~e2 z>#W)P;D|Q4mY_qyIPNFtyPktc*6_5vvczTw+xeS<{CZLIvv$L2v*NZr=VoSRbdXS| zr_0g!UMAw*)O_{!kID_?1^czl0U1iY)}m|baTd5k?q)%_5H$0fLjCg5>YEjxYu!-4 zja~&wY`ORz)Xv|A1KWryrCKH{G+-KUjgKHI^5nH`RvjhO<^{GBO)f`qm25_HXC`1` zcDLF|zxJm;u~=mD$KBtWQFS6dTSNSdg$6dGKi`7F)};Y2>jBrKA66gN`QtOb{qADp zgc(T8^_z<8mgz_dUY_bx%2qGe<g*y};!z-(c-&=3WK3pCvGnq}yyWtlEEsFkO#urr z>B6H##?Y_$R8rokL`GHKy_^jZJB|l}Y1^ec7rO{=tGVj|ku!ziiOY)NwTO?BFZFFD zt{-bi^Dh9@d-?wGalhD3$J^p$v~s{>KOtTLMYznRI0>YQp^kCMBB5*5S+i{)RXXkX zkoIuKlMF@Yd3!rvr2_i6-(8qQJbXT={aRg@nv}A|Su`5nqP+I0u06u+8Cv#nGvq!P zdn};*MK=1b+jK}7C-Zsd>ZUNsd=T`f@Aa}n-ycL8Gsbwj?HG5(>h1J^yX!}X=y=HE zRkOCAQ*S$B@FxFMncM-w<-tsm=J{`UzIS>pHf|`mYEuRZJXf>FnUQJk9Z&-owGs^s z8d9eVrM1C<zA+GP0i8@0SEW!TaTdDkvs5h({^+O`9pBNXhrf$l`}kN@LZsO#X%T>M zd9_gQkXWP&ZsE|9Ij2riykLq>JpwHeiUX0Amj>a1YsYRqQRwBrYID1BJ8zJ%u58As zuzx#j^o&4GXRv2}(gp>HZUm#64X2X)l4#~EjmyW0+n+2vKg`fi<-hIJ!%HJ`O<gSC zk8Nd4bL^;WTyRAy`}9WMEO2GYQbnP)nigl;Fd)`Iv_$iR<He7}Vl~<m_YsKyx9sSB z+<Doo2EjDW>`&+LpGTA{KX=;~u?3!<;IarlwUpcopOJk#OJ*YO>15-vPPSj(%)yFA zUN|VAM2DPQyU`@im1-wfkfR+r+iRE_@+CZ-I5c@v+&4v3be3thZvV}H!bela(;fYg ziY|>^Rh{E%cvDiSwIw0ptPAmdJlkSya^-O@pRZlPznspWFnc%{@WX3ZgFQ~E2aplF z4#@dfP8OuY!MXlOb3k(5cgO*@L{6Ju`_cm|%U1_1Z4d7w+&F9}3mh;I)?pLLLoOtC zRg6DlV>R2n8&m0txJ*f>8<kh?5sp-gU3Z2x@wRLiE7+Jyy?2Lbt3-{aELAbPRl!s- zKh;?Twzp@Yxl~wlxN>U)ZZ8lklj!ZKS)*vy{U1(^)=CI3I$kF&yw`3#+j?KOII2;m zGq;J^QPG)q_(d_>^HMQR6n$^@x54yOMJ8vzDMZHewx;xD2=+-j^s(xMh_isq*B6`{ zVE=PF{i7cJpXumamG*13I-NOVuUC{=<Pk1HcLltSW#@yl4VCT471wiSC%-tVi?-I0 zhs)b)mv_LuUblQ%5R0busd#14Wr?E|d7<RCm?8pk8%>1TaPDl&;8vHHjL=tJ=%`m+ z-^(ZTo7agdUnTWERh4!(nA?7D8=O>q%y>NrOM7@;+V*%-u2MPvhXj4z4)WNh-}WGc zOdnQ+HeRPg>b*d6&1pIMTXo<m3X!H$bUNT{OH>B!pxJ$Z3%*phn&x3SW=YJ6Q|^5G zp7#5}PqPekNQmH>xv8t%Fj7F1HbV}pZnG<FwKKZ&w%lHe;rgPuyvccd^O{!P`{Wky zX&Q2ZUWUq?p(c3mrgdzjg;drJiabQ28+%Y(du-1MDR`c=mg-riWOW0jW1;vZVW!F& zt%s^B3^k#31@CE7KGgMt*^p(^idtDbx<vZQiqC!X25sBTOtPMb%On_u(Z`;=Nq|`M z93<XXBq`6oo(mbhs5v5`u^C(c%8$4YngFPzvrhrhf}U1`v-z-_{AWHVr67sZfKTT2 z-#p9&Mr~&6ynNWH4)=tcMzZ|crnR|HkWE9(zC<g}_F}c~znzjK+|M7zh4ek#XS3J- z)DV2w$MsE7yFzVeQht{&5_F-)q@C)~GlGEP{l&!<(&+w+bHVN5ri3LnUBGM^6j@$V zWjMZ|JY2@VH02f`%d5AXY5|+gCA1UAo3*q~R%@->i>@fV;B8#LdhWRlG@opsA_{sI z&k98U@@XamNjA#mj1hG~_BcJ+A~ncX^VS0vK}d4}Ah1mq&B!6MQ@dPm>6E$GS*Xe& zJJ@QbOfDg2G`D$w{~&(F5vaI&-aK3_dzGh}tq`8e<A*%$7ymz<o%=u2eILhlz@5@k zAqly<OK2wLFf2J;E|%rAYj<3`mac?M$obS#SW$O!NDdoH)EwqK%qbH>MwqkR4qd~B zoW^3?{ptP_?q9!;$M>ht=lg!Y-p}vr^ZHN=u4UBYQqDc)J-Oc|Hwm=D>xt1OOX6X_ zCjE0jdB!CwOm1|$r6`^aK@=n3=jf13s=X(*im@nWh3;E+lm?=))uU0zsP-=+lS6PE zx$^}lFUuVk-&;)v6Jk*kOG;MBcypi_)+JL2c?HG2JYTBYhM1eq`%Lhty0CNbfsYG2 zg3<#EHL-OZa=(DsUuea8GT)Y2eRmm*jCo^)*Tp=I!maK8my;G#qdwii3um2%;4CWp z$gkfcuu)U)#Ne(=cQel%szPP&ie87dKM(u1SvQK=a~badyIlsr6{yosHa4ny>_0Uu zoW@t^J`?l|ykHB)*%Q$nOYUJ&yW3CQqKDK~AiG|R8tYfYZvgJ9D{THg!v^k-zyaA| zmtq%9rnpAQP=`CS2<$d-DFN_f$h*)-p5*Z5mVM)D;Z`(lYHiT`8Af2c_d(~IiS{xb zRa$6mVvWie!AROARko@Gs3cHq`Ao5gba-r~s(YE?vl-D^!4+JzFjyqi2_EJ9u{KEM zv#m1m0ke@ive$Swr)xKQM?znS`d<!XBZt0%f72d0x?298w!S?2vC1yfzz{EpyUn3Q z;pc@ya@0(K`xf}RZ^bB=0@1oPvB?Fht+P>O&%>j%eKrpn65XWG$$|AZNXx?AcrH~F zUjvYbYK9PfS+Y}J9FZMyfBu>&Kqb+o0_!*bLYZvlNA4i}y;pn~nu4ixF^7wWnD9Vu z{}dQtP45lVdu06%CG>0fJbdGmjzUvhP{%P(PQk^31<)*3!l&SMIbT$KvOsf={a(qW z>jtqIkF)O8F6ZSjq#)r&8~N15=<r)^F2xShtz=L3D!pks0Gv~yhnnfZg-fCBh;`mU zAh4U3SjA|10|taaLj(hrgafO87jP49WJoscJRio6Q^hR~Yaog;{=sfQM-VeSWNA!a zk>BM?N`x&cwVz--PC*$piG`4c4W^vRSao+s38&6e_T=t&>(M}RAd6Xa=LH4Zx1Mbd zF?}5+5R*i+T4}Tj;H(`BY{`{o{D}7wuIG?dBXZ(=S*B<?RP`T0AK}e(8X=4lz=(%d zt{}~J%X(>(0Zd=o)Cicg+iFafN0!$)gaClNVvXwWY-ajUoAfkv=b9?&uVw5-S;sYG zt{eStx2t0sguQfi6LhIl9diid0n6vz@D%N0nnk{0fEJ6%-dZmyy>gV&0d;9?ATY_) z7CXIr1p82eYo0<k!j#tewXXVGUThuh!1zIcj4lBi&Quw4xp8Z1@`bUU<Ar1A?3Y8r zhO?=+7Nfih*;B#Ha1J>w!qluPwtk0)ef#Tk({xPi%g@Swbx?dk(*3N<-W%rpbfkv8 zU~WKBEb?xwESHejZ)<C1?&1`-I82*Jx>TOenqg?F5sW;#TJ{KAXAdup69<kk)Ci01 zT&n$Q@-CF$4HJ{17OPan@QJt%PEO<<M}xkaOt=q<%POfc9w$c*zSj5bg_YK6W+xta zXLr1c{qDTm3snt%qhFr~hX1)|BIpyuOjBmUCqd^FF^4csoz@U=z-+o4)cFw%{hT@u z?!;<W(NmtXVKTc6{h!|CF#x+QG8m8lRP=~Ga*8m~Z%IVR@h9dauYd~PcaqxhX4uk_ zxFr^nhKlG%PD+G4(JT60k#J3)2GU9S01;=eAnat`+9F>)P&2+N`M`dD<}6Yq`l?;Y z=~3bLwe%@!`X6wUS6E}F<5mJU<*l;`z~v(EeE7!g+qdyk*}rM8jbK(2NMifIP!{9; zZ}Vus;B8t<FvL<u*#Lh?rPwM&h#R9bXBkpdEM4b1Q%{`g>onp@48>Ihd8hHn_}(`E zqX1<!sr$tSUr5)+$I7KWS2mbymb~~ZJzgq>quQx^k=Y$MM<qHu12;mSEgUHhr~JaL z&U2|dY0KJ7u--vswo=Yk)uXjBEO5T^ykST|5}h<hUDVATB($`Ad@L2qcU~Z#@pjKO zYB<fwoI5p+8>)+Q#!f?J+&Ux=Dk1=VAqBTzXW*Mx4CldWqR3%g&E-zx2c#3qK3TdV zVe{|hWpFngL#JKm{L1T)S%2nPj6!Xa2bI0t4n{FoR!ju>$Hr(rZmAaW*waEH6v}*% zUXP#tCjWJS#(A{lOZmPxG-hw;tNgtf_%l3lAF6j(05R7zI3-awo4PYpLOW+?F%Lrt zwo@AU6f}JJz2i0yQeId_-k{H(i!;yDRUOI0X0^Uba=ot(;vDdUW#)3YilHo1%ZwDm zFADtyJ-#|2F@Bjl%)T#Lc0azjZn2=s`aeF_6BYlSSj_JH@hWq{CWM=5wk?mCR(xMp zeRxH+U=Gf{c&BIFMkV<634_m-nI4r1u2(NsG{G;h7l}{ff)VS(mKjk<RQZi{7wZ_R z{c(Hup`!{X3h>vr5Cq#b%qEaA1t?+FPQT4JGq#J4n@I8=0W7~aoPsW?_n)T0L`&~9 zoy`ynv45GU4qoxJu32DlN&dN=_n?wFYv;i=SpHC_hF~%@Ibxr$a|}c2kr`+{N@ikj za-~+Byq?&NGrHoG?i*<*%31U4j;U|K=9cLluffI6`e&#;khdmV_SMuVN#t6kj{L*_ zmmQq}fS}Jav4L?PuymHXv|8A<J1Ma-wpF2G-Pfla*`|Zr!d9>ZX~_mjZ&O{m|LqV# zi7-Ss%$1LqSUbx6VO(VFExnZWE<A0|!(BVQpBA61AF8=x$WIp#Z~y;o2HGZo_r>Cn j1OEgeBob0Ti^@KU4w%AFY$xm?cp_nIjj}4WxcTV6+b}&c literal 58207 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-linux.png index 9b1afb1c6adc71560ff36542b95d91281eb67bfb..14f4b8c72986e13ebd6e9f1287d332ef96e79c83 100644 GIT binary patch literal 56167 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 FV1m>{x$Xb} literal 51412 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%0DE=2P?*m`sBdU) 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 Ai~s-t diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-darwin.png index 2e9bb45338cde5f662ec13d26fe2e0f5810bd1b7..6d25bf645ff309a21fa93d9e695733e7d0bf18d4 100644 GIT binary patch literal 58519 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 literal 58027 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�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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-linux.png index a71dec9647478a4db6dfddb9e926cd5265098981..10637b0af8dd3f536078bee0d84e54b287d928bf 100644 GIT binary patch literal 55969 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%0DG1 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 literal 51360 zcmc$_WmH^C@GnXr2^t6xBxvy9?iSo3xI=*8?l4#q+=IKjJA+Sx26u<SVUS_)L5G)f z&b{k@-&*g({r39HuGPD{s=HdMcI{t9epZ!xgF%9UgoN})L0(z|3F*~e5_t*@`S0Ts zrQ!=Dq_;>4(h^!e*(d9mMw)xisAqCp4)woZJ7E2MV>Tiq@k;%jb9sR;E4yW8>WX+v zpxs_cqcU^L%tc~Fie5oUWM=su>i{PB^GgYd_HvYVw4I*cuUDrK*5A+^jOM;W#9(=K zfhQ+|lfDz~xuOUDlkRM#&+D3af5rbT-wuoo{~5{O^5x}QB$R)tZ|a|ru>PfxWM91C z_>Urf_43z$)R%Yv(~yG>=`33#g>kNO#;xTD@$|6kyV&YzsQEhSH;;8@1NuKbzKJ=W zQGEG!Fxl<5hvEBOwuX8z+?->Xj1(U~d;K4Y-#T2-d49Q<OQ$+#PqW9GxB{;K>xlhV z<RqzfMxjv=y=;t0KnEV$KMX%PqJ4Xj%U{X0w)jo)p9&;Vmn^bhm!(EURI)fJ|JHQ; z9ixadqBAI-SXArZ8A5vLrv80}^yM7|15!`dvF<(P3!j_vXeGtWdzY`C5_a3r&haab z3ajzQ<wB;PMxc~w3`+ec7gABhCR?^6^F!#@$mTZQ<_zon>(<gEK&F98JiHjIL1OwC z8R=7~9QHR0oJZS(eTC<u=MR&wQ<T}#-XD^h#e7oeUD5A*lGQ=@*)COEuGSLFrRk)! zJ>}>tNzz@Dzeu4Chpm#b);O>*DJ*TvjJEct{A4IfJPEYF=*np!vMV2>G(C@<PeNIL z5Pw{RCuz3#Zz|&TB9?s_J1Z5PPqWW1emL^m>&mQvq6q_g=xz?RUscRTH9x;*7tFo? zLdv_~kZBxvbqc2kJ@y*9myYG!^e*Xwmy<p#V=3iyS%d5EI+Ln&)mcT`cCXEp;uhlw zF{l?*$wh*^{by#Ky5HC#jsO5VzBx`!26lrcR_gBWIjfq7=UAued^R<_03DsTNJs`t zMe8WasNH<3<_vk@6v{gCm37bEmopv1YyqNYu~N0na2Ul<B*q)Tx+6YUBX(i>?+0kB z_A8ZnCeVD<c%e4WzEW*cUwfk1MvI?vrSjOT_~xu6M^%m=l;hEbZ}@dDofgKj##Wpc z_(95tOWB|>yy3{7jv{3V-6zL}7_*);r!+}_W_9PI>{6TDNGtG$XSLiMm8NcljSrrM z#y2vkV!=(W%enc%JyrCPltrQZ@B1c#LSU($;eFi56m0S&L?kt5nJ2InnK=vtXVF|m z&z5r3^_(lOs|@mEod7aX$2(mMA-%+*DK}B@j$iW|1N+$ASb~s+(%l=d3aQjAWiFq& z0qPhaHb6{*=X$<YWX3_U)^Rm+DPSdpjZhmu3Ljo0YQb$`@JTpl(1b;>nXW_CSMjuz zhzly+r87UDVzk=EBvANyyt&@p9Zrb{fiX9Jh%b?K6dBA+EtU<Z#EOrbq{2Q$6v@84 zU1`zdZ5e^iAX#caOd?lrw@g?rXy<IVR+FHnm>$+iLk0QPpouJwwb2eh6zvJ{GJBE! z9{r>FTY6Lfp0-GZLzhJZh!Odd8+izm%y|wo5oG$yOjHGzn)AAM5|O~Uw(I%E=zajM z?bA$~81yZYIRBh4?vUzUM0m_avzhB^d*plFxa<))n!m(X)p&E6yp?XtA@QLkMZF(h z)NDH@CS%{Z1)YM!JsS8JQ$w855dnUBTG$Qvf~)oN5^GyiqSoflglWT##WE*1PF9Dg zICAe+1%cfBzCY)|JFbm>WS!Xl?$AQaI`T(;^?{$J;LFw2GZyp3p5T_&aLp1q=-qW1 zYdHH_&m`<atK~ZraStY`U7C?w?}t>)_u9jXPP&$X?01`6dMUh8csCC?eiN3s_Ugih zsp!+=WrqRk6_Q{dYR}Wq;Ifk1@9dr}6|N-JA8yXJFzIe`$i|rQ6(%SU52cZM4t-jd zrb0K9sT((senlZ>uo>`3$gb%dQ6Ftfiv)5%L48L9AI1}{@3C1^z0F+k^m>qaV^nQ8 z3>(BW%$j0tMQGR-p^^wE?_BK(x48Me5hRpqWe1}!g`*DuvB}$vY+$r3u{fgX`R2(c zOZa43PpquZI(iTosh3GI*WF`c)kEQKoa)U9eRijXzKa6>yp0SFcVK<&%P9N#aao{x z0>hGqiiVG)H{?yHt7e(c3dgu(cECd{saUn!K~344aPn>=^8+j0mOBj^gc!A%fbc5E zH9^)pu$Y(2&}6|uBQdmkH-Uj$x(`LvmtH&CQs2-zXPMGuUvzFURX@XUX+O_7a>Li} z>yRJNGmvCnFl~-S3eh6vN4<;eDy$^5+8Yx5EIZ?VeqsLi$Q(WqRL!uT2&OnO66)LP zc6|^!fe^IhWDSo8KbvUk8}9bYFxcEqT;b>l!?sw3y(pqaX~<BiHrB4p!dRDx?Yz7R z`6hCEOiB_!C(*c9*pM+^+qR+ErjpFEA^Y<;1{)-<S_#h()8CE90Nvt2_FmQe2r=Rc zn?-QkX>6AFtAZ9Yj_TV9W06jwQHweb<c$oD;=H=5dJ&<kMj24xM7(Edh3z{aoUHA1 zsxH3Ccm5?kD7daO!r7@XOD89gsrc`SbaFOqD(V;h1?5B;+b@t8ak=#>(=Cg+*sXFV zGXY&|siy@7J`P2`qFfj(s;w{pI9v~nQWHPPs@;R8b9LSiEk1j+_4jRLZBSF$9<Q=p z%p{ts!Pb3FCLQ#9I)k~BDvfazhZE|*`k9&*y!XYkJhX-zH$k?JZ+%|W(?6Xg9Is_I zaaYTj)M77K(f5DKBthB`=*hD<&?A4+sf_u~y;I~M>@$~blIm*pFfofBn!ym<v*Gda z`%-6z9+AmPli8hGxJ6_UFsnviCeu_W=oxx5i>{~?A62y9xV8iey0?X%V!mQAOxmsg zS<jQ3MH-*sC+0EKv*eamNn`K3R`ByDrf-+(_QmZ~%Q2q=hG3S<<cti<ZYG|7Z$Zs! zp{1&mc^qSck|gl7UCZX6x2w^Z6bZ+X2;{w5z<d}Ae;4YFp`0zU;(ms9av%YnqGRxK z{$r^E#O-?=oAD03J0GzL>#QQz1UMPWH@B;a1O2oUVzNA7F*y7tJ=TlAOIhwe{Yj9F zFX+iG%;K-@r?cXec7opixT%H0WS-CZ5O^C@h?VMnR?IZR7W+kd27kFfARzgD*QP^3 zhdZQdUP4&ac+~7tg{wDE+-1C%KR^0@?SQgY<nux;3cvW{uhVGyK&cf=asgtmi|rON z_hZ!I$Mrew3~vxoJ79c&D%zEQ&q@d0828lj(6;8;W-46D)1@lbXE$i|#?B4NpCxx$ z-J#&mM5>il+FMLg>{;3e>8XogJJ5IIW(hS!3M}n8@k9tvY;hA8PvlQNdgkK!mC)!` z@K&U12#J3=V{hXWP1x^dqugP88mM$rJ9+c&zI)18c+N2UYL>`e&G6>fB7uN3E!%N> zPQk{4-D~mnuL3hO>z$H)qon3pQh2`$;LTBSPuu<~%81GL9=e0Hx?9)P276!Lo@*=G znq2triwHhdDC<QbHiD4k#yLu6bL#X)MIih-<VC$AiF$kN<Dcuwk3SUW9kp^iTYYAM zT1gzlBTIdL6H3Vo9&48N!(TYwuaJLZas{fA?h}Qg26&$In3}3VD}sB|uT7sbPlO!W zq`(yG=9<H#MC?XHRjxKeY!Q#}FCKO;ijGIw#jw7i2ahsGV+qnh@utGXvZNIgfvBPT z9y#Yu-^hJGb0YwOmWWhb@fs25Y3FZQ^W9e{RcTKy%Drcp`SCudxzD!e6u!@=a3G5> z#=Hz=DCCJ?)EvmZ?m1-d5(JefyFWd@CXIJPew~qLcqjE|IC^Wy0>)+RcWbEBav8R# zAGwmTpS^lM^jo+$@ac9&ly!snh)?TY{t297QRZfhinFIg3OT>xU8m^k>feW)8@L4X zb;(j!^=1Kd>pQpGatSIPWsDXCBAP)FKl_H%N?nkV15Pmd{<&l3&GasyD=j<R#R<B^ z!PYTnc<1fXBms|M8BGH@?g^21A+XP`7|Pn|E~j(tXUFeLt{U1~+7(GWueQf`AxXIW zm#6xx`b>CzlhSm5#>rP)GlU`)6admjx!S^6;hl|*S4q}fLN7(IzG;UpPOg6>h-kKr znym}X>v)?{@FlSmH?qV(jJ8Y1SFw%)+{8eAu4<y%v(gdVG;R@pak4~oWaB)<#b1}3 zXsYM3xlv>+B9t)n*$cjVC;5zCcjXP;Tz7U6_quB3*rv?yHkSe3sLqtFx|zp|R+|Bq z__q7+k{Fy*F+!-_Wh@<k-iF)T6?P;U{qd|7*)wEmcQKM&ao<g|*L66*Ca;<VKDZ1& zo4j?`-{+&AM4)sUaF5+W+hR~Hf4-Z3@n&P&<jL@-7```G>!!(8>?|&4qU7tziBj`? z$pHR)8wjWYxChts7iN~-)2YfRTlu_NvV!mQLA(9Fg_02Ai<n}yy+0&P!x_l+t$6s< zsvkQGs2qzJT~-ZjLH^RBjOWQH#6@N7WKLDTPz$(D_Q%KcgT7NyeQXb~#-C9<ZK}Q_ zi$eHAm4!B*&0ZSve-~m$DEp(Um(U-4Mix6Zfnvc9jD*?Egf%jz7d0^=TYjyan1E{_ z#ay}HXid9x`aO5G->FeWisTJ@ysJOmQz&GUlLL=1aqkJaWWDR;uOL+u@%@u+p~-Mx zBdw;~?m>Teg$w`QXFmsyytbE|iEJe`t5hRH7Zz^QJimJ>s7_SXOYKhF$p>Iy3A7k& zRSx@BAm;0}u@$oMoKt1M_dQoKJ@pYkJV<vsL$BupiQr61-&aNKfKJ{RpXc*H2Kr%v zhdy1?JUE!AH3-uC{IDy}DfMNz*d0yqYT!F111J7X04eSJWB!X*R7rpEZ(h|z;#@(5 zYa92KX3Xorb6`Q*$Y@J0;n-~X&Gv$bji!?t7tBKS10~GW?k=0eaGAi{Fh{(t6W-{- zXvlQdDwK#79bJh`Am8qF=}-hEA1c?01H|OkX{izDMHV0A`TaS9Q;MdysakF<cUfvx zxJ;LBA|6bhc-mmIH>2*MQ%uVS*@45!qAmkQRUYV8gZ6<NuU4kzEJ%q0{5c+P{vg>_ z4mv@u1f%^Owdg#wY$a%?J(EI9aL7%HZtZ5AZXfdoZP<IdNqbIe@}IWYy~ztJvedYZ zTJq%uFV<sdQi4`h9?dS(#0Yc3a0n;`i2?Rk=9JMf=|Pi~odGKwCV{pCt<Bi(fwwjq zey`O_(23a41QXxhBCA&@rT2jzEflS>jg85}y;kw@@PsEiL9vg64fV)n6@8s>N0^bv zq5o^=t2KCs?d)1H(bwsq-~7kO(8KQ=`EW6GQ8)XOQ5?+T$~$PU8g@?GJjepFb>d|D z*i~&ly<Ctt@g$PPgCp~}Aov8-L9pg_o2^Udj5<J4^UKs<@m;2xM+^qF#(95zfEkJ- zo(#hhc`T^@cPzjjhVEV(drri8)w*^1ohG~QCR|aL<;czzzOscjW0?Rdr@bd#H~043 z2&%6?o*B`pQQheD0C#i%Ww}=16k9Fg0#})H;=gJI7*BlS*4qRrR`O(}2olfxzEPxB zQ5Tj-k@2W+g?gFv_C`cpRhVZnZ$+jW?{{V>{&nHz;ml6Drebz2max6!GVj90zb2C( z+GWplpuN%U#cKN181HFS(*{c@ozM&0AyHeNG92oZKQs$0DF%%@q*Bxk@YxjEON(69 zeV7JPBeq3R-9^MqnhL3<4u9`8>F(RiR=i{FRw>|}DQ_zDc4!Lp%x0(Y-??C(O`9Z? ztuyle?R)!{{hmzWY-LxMD0Vi)d6^i>VSXWCwp}Xo5c!$x?Nl&s>xI<tqciH)>Z%-{ z!W>#^1L#yqu;o+VPdi&JoAf`UKolu}&2lA&>@T#S>lRk~zU}HVXJXAV5heO4(G`7b z0gr1aTWjD+!Z-+<+~9dMwL2X9Rlo!5hOD2jr-=k&EM9EnPlvse6`$3T%Y`)Y1mgRI z+17BVl+TgN`pRlE(+bs@*_+9HFBa@X>;T>=r5qm)1D@&HMm}URidsff{8n$JxHDmB zx6(|6s9zx}%lpEJD&vMD76qa6^b@P6dNL7LoZK5S@7u<`he%iilKR$pzGXNvQGkOk zjL>MB&Te(kMQJ0UsG~6nry46Zt0mA1Cv5jN)g=@Jsuut}Y;#NXJoup1otfos87><6 z`y#T!me}TV{ZXC(cCfuw*Lh01nJZ9TcA($X?Q1Lb0~x1B&KRGY>mBCF@JF8xSj=+n zp#)B;^y!-(3ITVazfQ)h5;e74Y6Q%g9e(FvwIPH8I%>xB#Q$mvZV}Cef~`+nI4c?= zMzG30(=s4n0o7Ylq7{FjS+(&Go4ibjCCi>HH8okU+ggo>c$6IC%xZVJUT&1G`+XTF z?4A?ix0}5Q10k<XDaG8Q{C0GYco>nqPN(5Bd0DFu_dwnnKY1UJQ4%5k*7M-A#{pif zp5GVk59o$G4&bei7l+fBnmKqnpvS?~@}@+OiRH_Te*5iG<7nZ%ek-?b;k}Rv?1Hkf zHP0^v{M<WJYv`A7ZSt?KU9xe<4W8FMQz5n<+f5IuyX`SKkONw+ZjjroaLzrPpmKse z=gbY#)0gOKL8^y3Vms5A!ZkdaN1(RW7GQ>h{WnBmNp*F0T`wOikUMXA8a3>(SQ?ik zkMZt(71-BzK3Ez{7SQk<jAQ3{lo87emtIrX^8fm<_^d^6ZLA>MV4SZzPfkNP(PKX9 zf-YKg<MPw-fUXvz!^*ODLOg_t$=my_q9<I;Q0Oi{f3#e;Yz*zht{|(rxPD*Cme@g< z-*w*cP^6)bdpXajoMKO%pR#nTZeiLThodQAqajC%kE|_oRo&y}(+|t%_(z|kF~h@* zS3poY*LL&~S5osf;_A2eP(rtm4kA4rTa@7j<Ienf4-4j`3pdsA4(tr_3!rPQhs}Xy zAHqvwi$tZgHSC)+^)C%c!@BZ6LUlUUdaN0;qN>yRa{NyO8RRd^{cv2MeU=*2cvn13 zDfxH6<WQT<syr<J6tJ0`qdxHBNd~A}ZOk!ephjvH^*H2v0`{O4GG&PqNVGZZ{FA0y zTDHXao?fHH_>*>()sm+x&_a6Am_>6}xc2zQQm#-Vz>k(P?jxZZcDd(H2?FB|Hva-a zI2u1x(#c5O7(zpMH&i1+mO%DFh~DgB0=|hZqgK?hFEn?_T+5GS<&B0`-qp~miu226 zu|=*E07y@@!D^{cEY`<(J()S;Ts|}lDv6r+x^MKKp{Oh2vzc9#&+A}L%%V!DtO;0W zidTQX;E^|0j`l61Hx*nuC3G%gV$9=S%`FAZ#%<6^bk%hGxV$3%>xaZgd;SEWE9LFa zL18j!!_lR&e+~bgwGMrJy4d_isoqcGzz>48k6JnXJNqxk@|OGQs$fPjpI?pXt2g*v z08@L2N)2-*!7i?TnDz&3q8&VUa{2b3DWAn}P78_{g-Ta0*1fNiM>Z89Q?)^LCjCgn zSO5{lnjUvqQ|P<_O+_W9<*=2Zo}qAb8CJ8^J6+M3;Az!I7ZvUAh%ptj^FCo80$FnQ ztMsLTFxvvn1`^(2bI7CJyf<~+!NbI3?KX?`x1U`gUg`j?0K?B35Q%iJ0ISLBHmOcb zi}T7eNG}D-0F8K_she(MU|DqdUatLc)Hg5Q3o;VD^ZxI6C=O|MYCYc*Pr()3bex(q zn`D_|qgygg_~I1or4|O_xcUwJLOQ9RMN3RSS<aDjc$ac9u!_zaJ9zR4$}BHdd9h(J ztiLREm_8HaMlt@VGLmp6^qy%O-B1~|@w=qrPCDiL?BufAk%Yaqq4<U;H#<s(gRUj{ z*u{6=Ox!FYDaSiQg&%u8w-|9`6$d-~cmao=NyuNg;|R#Nb2mB)V9o8k7GH$!OBiNM zIhQ(sGI||1NAzE~B9{#N1z0Xm_eZF#%$;?W+{EhAsZO7N2z+(_gEc8d(Z?`uw4#um z^s|7PA{b?0#hx&9{k3=F8qvd-!)+pZY%~MnR`x(9ZZ;mBJx<p|{_t}1oxawmE}NU0 z%3lL?a&f4lq^$SN+ab$!UY{Shzy>W3aDn^sjUTpVW>m!I^I`1>-qX&{bO@chq4kLI z(Hr?E>)I#ZPYkkanT+-X#R$1au0t_{gra~a*}VX3It&6cL#^x5`EI6?r?TlmJSlPT z_QO524bo@$BbAl@xToYnW&JB}Iq#AAc<=yo@c_x|SPqt>-KM~hav50)hx_=$_O0U} z>^!K?$ML{jzZ}Rd-{jyA7>^xvlJctZTuRq?LtmQ)$LcE3_v7=d1(h`>o-@aN(28cc zc#v$cy*k_wlN3|qXO0|>svVJWVwO%m0N~6yy;^Pq&Gc4E*;SfPq3Krcq~Nwv?P;`8 zYb)+ossX-Py^VwX*7JjG4qy+>KxN10u$PXQTZMclZ3{p8PgTy}h0@(0%@2C}P=cfS z3Wx}XBm0A_aI9rDnTlzDE#5C}JyA)o1WMzun_>b*AEe+6^oAZeM^4j^Zw!MxRcc_m z)#S6<S~Xn;Id#5zZ(_o$^zFf~7rWa@r5x>SZ}U2LD%hAiRyf>Aj?Rx5Yp$YtjPmM- z>?_{fquCd$trM6{+3O`HlzbqF|G?Cqb-Br7*;MyI1QUWbvQk<&&A|d`qCHd$l~vdH z*p6rHni=AQRT1IL!phQYJBKCY*7Q|3N7<}gi9iN`$Dd&!!!Ijw&0fDv64J4!PWpA| zPgCpi1@uTix|wtSnKfNat(`eVNJwC6C-zErH*!yjIx%uxfUjHj-mdP3gj2NCpO@SG zAUD=|Afezgwa2lBLFzsmCjWaRc=}Kpui!{@U1>6)6)ShD_SVHbwWF`^NmoHaELdW( zvcy<>Xt23;A6G%4+wiktH&$!ComPgU_3+dwZ&V%Bq>DF-@tq(CMl|mgg`P3@<o?+% zX}MhZ1Ql+$y{ap&0G~j(EDcR^u&?FG(5A~0ZTn}{j|slxxau-9;1<6mG}B%AVFD6B zZbv&r<&&$RL**%TWkY**RA%uK3t>WvuJ!Py-_73ZJjIa<r6}uLW!GY!ibf5Y++Bdb ziHsY(XPf?w8{ajryhp^U-$iwVcwonKKGqfGl^me32LeP+(lQBkl#S4jja<QmIH3Bs zJp6*0vZc>Q`-<5hNUQ4X1g?lO_~|aV?)`kh<6|T!ZpvGm4NtGyt%Ja=thC>+i1{a? zi#pzSSe^I0?EIw`%;&hwv9R+S98t@hVpI|vGOBd}pf>Lxo@7<p(6zp;jDH1-XEtkw zlW&%yZ8RPj^xR0MmB%XWvtm~TnnrZcGL$W?(EI|T#21HC#!Xb(%_WDvVw_0WPb;@? zPK>I?=;J4EPpw-J;|iZhTIS7<Zb5LpGg^i3tvtCF2M7In8$>m%m~2PF3KU}SSJUI^ z4rf+CTgOeMJA3n%>%#NGHmpr8C(G+WV{CpTsKL9E?faew1&K{kJzyL>7AGYqU@P@f zF7JB|hN|P5YYIh;wx#hKgZeMiTRiKOcx4tF4dk<}HFu&5VHC2pYq*?{q%Q;dw!Q;L zL-$+iH&1fc3<n^Sr_q9@G#tn0^JDy(GPok?Q|yrww{yj|y)Za~*Zn*<#2;Gr<Mxbu z*v=l1U75?L>~s44#X_VZAH@hBDmE_H5o7CIPc-N<SSKgX$A5Kztb3sK3XP0T8E3go zG<QpUWU-c!ppcr-3B{jPvp3Fq>6J%AQ}Zdwhhz$I+@X`g01SD=Yz14^ec%A$0=W-{ zg3lGyyhZ4G8)5OjW1v8nyMDjR{$TR<tbPt*%M(`VnM`XE*+YoyY6G2#7ZWZz&l4Ni z=-8sD>v7I18M1GGvvNyvXe`mKEQ2C@n^@j*94JhT>*+8#=!vWS!X?^c;B1T&2&3Lt z-&U`i%oKOOmo^m+d-Y&;Hc*aG+Y8D-uIq?oWmXmDbJQmk@jiZxRww9${D7R<zv&t^ zxbFX6|84U5E=wDFasv5N$<?;f)p%sL%Al3q=DatE2L6WYL`VoFDep#Aq+Q&WPkXP# z2hCtm#qQM6dss!ax7#6wBzV&L7?Syw2km84bP$XlbM>0K`xr{kA5TTm4S|e0+zhij za>9)Rr7deJHH&B(t3^+$U{y7puWU2T0dndM!GGd0)#xdl900W+tv1&PE)OQqs<EV4 z?Dl>6%{Rc1A1fui-x$_D)!hK5>nzfa;%{^w`5!=Qn`A78-ht|4*UEHHK>8hk1NP!< z-_BNYc6xo#&L!6lT};occ<$Yx{fnCwf!5Q#-ptqda|Y5fVU3l}S7K)w+kQ8=mSLf% zNoS}4VUFge=xV)f33e5K`|VqG6C=Lp)no=|(cU7mkM3S|i`Fv|b<d=*8A=+dzyk41 zF3JN=vy357=ACEpm+i5wz?{Mfqwb81V*y+9jT%!j{rj63rKk2p{0G_;6fx}+fC?Ga z8qjxD%=}LBbN*7yAIl66pHU*;&ZUIp8f$#*{!rCHPmgy$@xvFZmkrjS`xCJT^U_-O z4OndCRt!e#L|$PYm#5P&(j>%#9FR@&TwK)8O6#ePhhmI)ge{g!<HMh2)XG*s9PlbI zwUw+T7j!(f=V7r&T*i-NO!7m0K9jIu78{Cx1~Z?kk7Lm>GieY;^++;f;Lmv-s9gBz zY}&+0U)6zDRIL2hjE0LM?;kCcOUC=-@cIvLOid*7rU=^fO$`M_Z?Vby(EJYqj9g=5 zRmH~7&W1DLNce%Efd|n0uS(CB%dTsqIg$&-4{u<M)sVT2TK2$>4*}DeuSC74@;=6I zQPv#8`jv%E`Nc~$H`8+ssVAdNDOM^f+xXT^Aj6LyPiMH|URf<$X>y>Ds6!4&Cas#n z|BeOB)+zBJ$nNhJ%Y?2e`90e_c2sU=J6@ifDV0x`uh<rkca*Snsv_*L^+`oQjd!@M zK7X18cSKl-9l9OU5mRPb`Eq|!v7SFYkEB^cSowx}0n^8klupyFc45D~<%tSZ3VH^( zrC)qE20BDJiGTPE-|YZrx4^P+?@`g<A@xr!YR8sKo;YXqZHcGipQ>vztelpvwTOZh zuAZYJ<u<d^ih9;~4nkEz&;tB0Sv=wuGPbFdtgZbCwM1cKu-8)rHj5Xr1wXbYGGbwQ z6}PjMqy6#w6WHYfC;0qRJM*0-nMFhPhi?p?NuKz+gTVxI(PsQ#r}3@_^VjWlLxpjO z@2v7YlnP(1&z~6-GKX2m$39=ZkF+OZ7Z}T@VH>fSctq0Bb2hIsnYCW4OwprT2Km%@ z99^^{aMW0B=Vl^OqOF{#DXzFzUX5ixe^9kO5VsH9ieqKo->YJz5Y?&h#<8KgARr2C zx4EI(U}WA$ZKjEzoLJz4tj;^LrdykvOQPy}^vh6~&MQ7mSQ*yWte3Lp8BCjUW_|X< zq84qog9^RQDxh~tV$e%M^`@KG*NmrnapX(Ydxs$Kc7#;uMXPe}tLU}+gGUXQMZ&h@ zl6_Q6cs3RX6Sb1fPR%6a&kW%DJd+z@I|fh8lAKVM+Lu9OnVLWA2vweZI<j8TY%CV} z%r*icDfx1-j8J0H$)Nj_BN4`U{sSz|O|Oa@TGBr;=BL@0&{l3#r?2wT{tvb&gL;{Z zv4p&L=Mq(E(1lhhO^L}!cjO_JgqJmjgsLB7SJwRQl__;=ACiTCYioX-E@upISRaTo z7H7jM`sl7H^l4t7W!?qZo*jVO!bLk0w$N*Ea-ee`P(13u?lgZ=N2i_gS)w1`>WWHb zY9HjMc=2&W+maI%QT%-7dv|?bXu4c7N!bO^)l031w&;RVRaQJb8i4vvOEGEId2kcI zE6vgIOR;7D7W3xJX=pbkKP&k!1!4RP$EkuWw^GfzPwWa)7kZ$L_*zmk!;BIpr_0s8 z1!;^yC&3OoBvC90>xcd1IyD`v*XU$-f*Gkzh^40#pvYC#U0$fscmac#p9HO70)Cq% zybT|}62l^ku)}7luy4f~Wij{OwU~<SPq>b1PF~;xTZ23Pp@@GSt*EMoJPQRl$dFN7 zR3Zo<#9W~@&}zSUyT^LuKMbA23k#MD_lhmiE`j+4si!X{YP<Izb%usm>$<nc|5Q`C zoAH=onQQlUvc!Wrc3d03NcX#0<q!PK{<zijDLfp1&)Z3!B)e)DW5U0C^LQKhd|O)Q zh_7*ZB3-Nx;j0{4(}-`LGOyr?)&l~o&arX<D;&0V4J|}cYphy}PM-0at$-lQ>yjX* zdF^(aZqPQKt(z>m+FHSHUEZS{cm+?`Ys}@T)~QQJOt?>c72?gx63IO1$D5A=-Ckg! z0YeZg`ldG9o~@In%>bm9Mh^Pnojw;<j-L4SdVR_$twm4PvNkL-eOt03)6x2;?gV&z zqI8E@Xn}gXY#KwIh*qh%-(c5D7yfwM$E#fiGj0()-ji|f;JYoP@|38gmGXNnu{PXp z)tmUE6GK+GQ`SU7EBkge+)r9b9Qz9)A=k+Xg^pM^&iGF}iEyu$OJ!}2Z2Mi?Ur>!2 zI=;^;iuz%Rco+KDs?#628!yl{6!bPO%Scr6J0s+n%}f+?Wj8l}IjuzhpcF(ZP0=+{ z{yO5*Tcn@DL$#Hf&3H67Tw%`As$9#pqqWNzZ_Kw9J08<62b?_7c;;}_ucb9fRJt{< z!)|RscJ-AtDqX2aO0V)R0OHTg$C>0f<ej|IMG8CTK)T^rQrgaBMRJmN8WVqTs-%;X z<Tkx+oCt2ol|PToXAe}f+2EcUGVy%ch((=w2x{0LHBsqUELleWg5-0WOzU7okWF_1 zhndT5V>_cK-3N8qCDgq*^?T`~F@wp>A69X4k{)%nD>)q#Z+&NoKP6U#IE$N#=oenF z&>2IwSxC~vLEfYicy+Vpla17-zZ*85Q4bNBRsZ#+@&a%BGCf%QX$Nkb^R~3ty_R<C zOL{-mmx#KS-yQR1xJYTVCkVtbO=xOv5#a<0E#D^ZkIED7-ynx}EmwG4=tq~{{)(rZ zOk!giq}Sg{HI!()9Kd)rP`h-(-fwAT%2z{uwfQX{Q|W1yctgyfEQIH8gGE1Q=l(QW zqE53)me<5&L``^eA95gDh5k)Dv9C(kxhGk7aJLNrr{uf{{SB6s1|h|7Rm30xCI14m z{$i~1<8<(mP`-WtfK(fTMEnov>d5BE{wLC={&Z;#Iwzp~<v(>(p-9A=z7~qSP+Xr$ z-v0&Cl0?Ef>*D0*UP39!GyXr@^>J{QrOQ5{33j}O1pV7TpYI=d7*%U10jGiEN_~v) z^}n(MeK7ul$&|E9KUXP^&R0IbVMzzZtF4YI@0DNAX4?+Z|05ge{sYqMe+i_bZ%X<9 zbVNe>_3{6x{BPVyNHNHNSNPw;f$`t${@)F!tbSWl{tuciYr7ZgU$_|((pgvE{~*r( z2Qv46jjj9tu^|WD>!Oi1m)pDu7T=s<EYiGbi*O-s1^fIFj19j>b1|u)1g(mofDRk| zmllwNDjg8y(|<bJ6g2;Zq!;k81gpZfI#ve0hw|-Ky!>_1_A}JJcem>WWzczPyyZag zw(yV_qM2rNKZWf+s+cW8p@&8{iFEj`l8D?u?hp2V%)-omA>F<(sT-DB7nr9!#Qoxp zdBIbD;XK%HI=R=9R)yB!(2JG2p5_F``>E{z*!}kDKh29+V^JcxXNPwQTNj_9Q_K6B z*vUqgOl1sFjVq<3Wh#+{Jce6ZwqBB~)K)}N`m1Lrq)d9aw8~8V2YQ@}YFU!1Z$#*G z%(RCGfz<lktI8@gyvIl+)%>@PnF+kHtGL)6#oYZrEt)0Cc5zi6{{j-7s?>HA3Ba~{ ziJo%&VvKx*1o!hO4^;j9_F)I!6Y~?Ro~H#^+MaO(oTT7l2BG+Shr8r&xtKWiX(>TI zNA)AVmsPXFnv*P9d@}({#esdVy~shkz_#tEJ%;Bip&YI+M_{e_zED-8F7J))#c(YK zuzH4G)&~Tz9e@1+8wwvO&JB=@dotfJHMLIzd+Ll+|F@?<Reia|{o;Fcp2r9F<?ykm zx}v;Lq(r56g@^`z<M0m}4DBO=y^g6Rw$v^$d1S5)c{`~q>*p46bOR>P1fh?9z%ujG zH7pgPxw|*r7@PPAZg$LE4Q`nK5ly=P<XQF&F>0Of_^j}~H{$92ba-6J>z+N6gyP$M z(;+82&>O6`1GzPzm{&u5fmN)elhiaI=|BdD<+84cpunlD0{-(H@(nF|tFOCNhyBr@ zP0yu!3FpyA3;Q&d=<>FM3^ST$ts$~UzOeuH12LxO@9QVjS~t}AfQD}0@xv(L14Net z=f0)0VI>cLQ#u}6^~V_!o<1}eL<za@^KHe^hMG8er=}o@-Kym-<R&E9=#IJ2+&|^r zhJcrVtcpMnO?U^Vw)bT9N`7ph-{i8JISlrIz!H4yOUEwW-Cm%4RtONt&U77eyUI>^ zKnM|YjjJqKo_ythtv>YVd1kfu$<^tq;EY9x40=71{$jQ@%+y4*H#K*Jv;k@(Z8uji zkK&7ixr}IW45Ac)!_M7JW8cnDHnCACvDG?<rh9A`4NX4zovi78U+MR*4Jjk6(wKbo zG9Z2OBJMa@tB~^0Nel9+;r?$aIWS}*<rW@qCrgBFZA&~s=0Mo>y}X*C5QKGH)N51T zh|`uX^MOk53eDxA^4w5qQFrj+v6XLsTb#%_-tDxymaL`)op`YmC24msCz5fxl%XOV zWQt#^QpaRABh^T^_#DYh3^mPoMNYwPXe|1{;gAoY0n6(Vao@QrNUtz?k*dGt$8PnO znrsPTm`41fF8**`HA@yE{43mBfCi*zQeDf?AOl-8JsvN6{(=#J62Q{SrFh|fSg_ft z>on`mA;O?W{@J&$sRoul-19&vSM@K-7wOk!+<figD(sG)VkxKmGNt1K>uH=X5&LNK zC=tGU<ILZ`qx;hqKh=bza&yyDwVn3oes@<Dm2`YKoPlb@iZI7FLbvx0rB4S3tZiR9 zt&6^N&;anC1B|R{W897wXPa^aIU`d2(Fl{URZxDI#|=;-7<`Kz0|#ts`o0bKV0+Xv zvk%u153siRxGWABxToxv5++q5ElEPgsxlg;o?E7|t-ZeH+%ey)P{N~wWQe9LI;DoZ zfpl-0HqTh9F3b$vm}>oX=KU7g`cj8{(w7q1aC7vGwZFWcZ0(R%Rx-?eL$&;`!9v>f z9ok%4O8l{U-!t=$HZQ8>baQXi`JkVFa?8sD{#GyX$C4wJF8QOMa0cNJ`AUw!?RCPQ zF8n#AA13(qb7!-tcrHD1*{PV5R)3!O*Em05z?>Rtl;4}vX^A8+wrQ`J_-_(e`R`@F z9S=Dy23&l4ut0UDSoIs^GZi*i^@aSIAPBA^!-R6@xw9hkKeqcfpSq`CvK>t(zyy7} z4opew!ow-V_r3NDGda2kySC?Vnd8&1AJ@OeR3fLXMYd~-WSTbgZ$GY9L<Ih~C7IM) z<Dj>xsaHPEa-`!q_-QjNqMcb;8vQfFrB`|mD<9Z3bsi)%O;6Y3cK?{u0iVL-kIXCT zRXaWS<W3zXxR|Xo^o80`@7=JxP+X%$Uv=P3T3`K~2w4-mg;qu&_nu{lth8L1+p4C4 z#R#u9k1{rHbi{Bo%TFmxZ|}1-L1^th!0&QQ+`?Goq+cX<r^Q*pzBv>tZ-^<U+5yt3 z>YRV0FnNZBg{Y2i9=_~XA}0xh0vfi<f|9s3>P~L02H?a0Eew)JvVD|N$}8K*$i+HZ z`jQMU$X^s$oV>n)mNc-G#a*e^cL0f1Mm^I_v+S4mP}`nND4S_ZpuO_<0w*5Fu(D5b zb&y4GnOJnf7kSS;j?GegsByZR%qE?w167AEa|=NZ-}-{1qgUVfcz0RfwjOh?TET_) z6?J8tj>fS7(rI5oqc%1%skyEfQ#V7B%oHsHq#Vl|TU)waH&tB&6#qLG(2A8<(9`U* z(y!g`cD0z5+JP%IP3V(3IS|WvQO#XNn5SQoGg$7g!Smnhe8YIhsB6a)l71=NytChY z3^xbe>}Rg^E4lV3UVnHsJ*Ql!4bV-L5sJw;73Fb)<EZ0hq{Y@yg?%@?!n0wudMMZl z=!pl>GTgi<>RfEoUYfDqzxZ%{5h`rq*5ow!)t12%?bha+$RYr8iR}Ak*Jj!85iKuR ze4t$6pui&FmX~l(%<V8cz1@EDYx36vJWpAjo)MQ9^{u~V7s$PYA7>|a_#yd=GX?PS z2C?al2@#)g+uvWr7Ch(wyYDBX*4m~_;}ja*+){x2mM!prfbIOaj`&J0;Pu;g&lj_q zN~e9`0N>m6)gVOkK6SxJO26&cJ5!D4*#Z@Xt#)z5nykJC$u^JAhs94s5<o__u@5zo zOzhW#ImiTNE@Ug-PK&qeT`T&DlBKGqK?%41*9RTL9g{&i1lGmi1j7z5r^PY#3Z}cV zLv9_qDq-c7W@ia+?EjRDRQQv~X3VkD<du<uOIdz4``dcbSX<r!@6+A4Szb^_tSc-? z83*zjG5yAJIBc!CxiY5h`2C&!P%8qUt`Z_;I{SP>Q(7{$9?cWHHY?61?8pu9fKNcl z<rfBmS`qgFjhR1hUZ5D`{x7FS%iP_8B9H@U3tMVl{y2I=p2b7I;!(qf<Rmn|UT^8H zOU7Jr`|B)Qq+%LK`QGbXe@httgl~K?`P(2#I355uHbrr!{ZFqxZj&xZ)d8i=b!twj znR>01qSrx5oy`tQ!yd#>^Yr__je~Oj`eVAPXMnxyMcD_TO@!&cJ-_Lc&#$BWuhft! zGJ{*uU;lALQbR@l@*nyC?InNvzjdeor{NDq&gQm|{-xu{<@R=rlEi=66N~aiTe8<f zPnVZx`(lxJKrHS4dlj0f_;~J$ihsD3zL;^&^z9`54ki;IGymWig~=VUMJD80S%W^` z;(p+DbCXI)`0tVb1tCaFUK^i?i;cHmEge6TKtcQDURbp8UEH~cIW6hnM{tSdZ6}PF z={4V1B!5W1E!(2q(&q2~t_u$`Y)s9MQQ<fkHXl@cecKg{Z%pr=p8o{B%x~o@Rw14J zi|op%u<a(g>;XQ1a?yI{8>f78j_`f-F-Lpdi7LQGHQe9-Zkqj{DSZl%efb&zH_bYZ z9KNRe$iA4&L;xR!QQg1lhJ;D*<8&lUsg(5agyZ4=x!k)Q&+7k#<%Q#N5*zaS$yE^b zUk*|o06<utxbDXIFT0c|gmYFaJy~r_Mux8;exG>bQ2pr!GdnYL$|%P3-*lKyn1xwU z6^teDs%*ShzCFY)u~>Y_**Qt86X4u`Rs87*VzFPHy>{Qz9Rx!JJwFXKG@3QA8U|e{ z6=5n6rLb`rJ0P=0I5EAX$oY-@B~&A_%;euA!4qR)t6x~|EpRL@iJJJFk=5{rJCuzB zd6O_KdY5vN+ePPJd;iO4D&cEZ>ub_dyZ0X7agqAC3qp}MnHT^54BdoPlT|3Jw*HOE zm#U<1PB+l}uZyf~va7o&r>TOTR}N6toGk(}*j}62N@55?!@jvZz5o5({WOux-{E!i zcpdiUfK6^eI9N7=DpHWZYxj$fvRiH<afCst$C1BAL0B`H{z}Ph^T83eM+bJF1(!i1 zwjr<O#oZ;RuV<xHQqnu$GkG87k?YD103!1r@gm0U)`ZU+Aw;H?_wd0ZC9WLW#-*HS z-Eal_NA+M4Rw^o$`TkH*jqSmUkv7+p%^np)sUO&~CgaRB<Ap5ZhMA_gzZo^vYBm5& zC4#A%ZVz>!I8#=@S%U~{8xb(1am}KT;kpG3=D#?O{CJ)EIRmf-EK;~g#gtT8^?py; zVD5P|M44-HW_wiWOg-q?$bBWi2;8|%C&=@k1!s#mj#D5ptoL-o&$ix-rl}D>X{f=R z0suEY0VnCORF=EF$sB!+irn`+`p-@V7k4+986hR9Wcn=**Usl$=4hFZw?XXkQSzhe zyQX08UBCo4Ih)<rG|3;&D%?x95ri{b0}Fo=*+YhQ7uSAe#?-4YBU0;qMXFGpt^6qd z)lV~1Wmgv7Hm~Ev%m5}N2o?|RpI6XG^}F_WC^fo1ix1yL&q@pI<}I+5fYQ)8=}pJ} z{Hp+F@00bSCimr*pHR8NtevSYWq|*4g8TAHdZHzaJ1{+dDu&pkT{4p@B~2H#0$%JZ z_}&s=zHotOYvrXLAol~Hq{kUuy%z^!6%OL81qB?LqUf?Tnx3yWE4=~-4Eb+9UeU~3 z&77otqQns9+#eu17NH!Co1~m8n*?ob&C)BE3kS1^@|p5%1#z2t<f9$#9K0v2P_IN! zlW3;t6Xc2|0Byri-}&leM6quk1tGV2rHat4exD2!kSY2+f)+hlYmME)QWr7tQ(G86 zXYXjZy=R$pqD;YZ0mLF>Lb0{bNv&d3ZjhQ%w1sh3O|raDtzqc|0F*xd_BMhW)Hyo4 z{q{(^AeM0y(1(UDuz4ko<^u%}E38Xc<oafSDZK6aZp?X$_xFNR1MaxmW8jbr{))hV z&L_neE<xjt>!a20`giJteh?R@vIePsE9n%k{m<{pf;Irw<D!%uurBG|wGP13H`m!g zPjjpO+?cLRdY}h%{y9`NyP3OyA=)pNoYBfE8y$U=n?1TZBVCCiuTeTvjVv7d>avBp zf3(IMmlHUny7<l#+?K+~%HQNp=-mXI+NCpA?v_iLwy-qPji0EsY9x*H-X(-~dN%K* zr&=<YbS4*Up5+(`d%ZRIS=qm*VT*3KG+k3Q#lw3uGG6FE>s~e{+HvE?rZmjAcUJRp z7FCIndk=lUIH%_04Av>5(;z)_sB!0ci$p5d!(fSnleMEu71JO2jtnXq{~;%G^6}_I zHmmgZi&5@T7NHBJN~Q9#&H$)RV8weiJ#Zm=vU#R^_Bd}OIzQX~yDItfR^yF4grR#` z8+AIrxBqh8bHA#W)hm9<iM-Vm{YrAS)1`;t?>*qsi={UA4bu+avTTl5#v6wNN?pmy z*~Znyy6_D~os8A)lSOB9h5~KZBLp)MkY4MJf9AQ-25jCh-4}tl9BKtK@lA!kiZ5M8 z*_?~H+6qQJRE0rK$h>Z<bKifAn%OP0Vva<4dMFGD><e9~7)h?*^bBGku*<513}kSZ zwYf)X;E}v*7FJMD!@tTvoJ&)B-GldjA0p?9=$~>OKb`9|tS&-hbw4K?^j*Ifcf=r% z{j=lk-0?G!m3ijuK{bQ3idwOvSQXQ-ffpQArmQl_o{T#rrn#?nh5PwFRf&lHQDP$G znBQrzx(8c&<Ak?YvilBFncyyyVg2gN-6ORm;l#K{)c8woo>4co*xX${r3C=t+(QX+ zGuClbqvU<yts3<ts{A=uUU(?R8T68Jls;M37RtR>bQlntxuPv(lH6rK+Oz8+gWw+) znPlW<0F3nNI80`p{g!4=%~hPz<md>qJO?>ecE3~Hh+3?mY}nWO3%@GkqXsTAwc^-H zzQ5vQSf8hAyzQ1a?wvtTv$SMyO-vIO8<Ql&$7{Zm1hBOrlH}oLeJeSl2J(w3N`2$` z3fKQ+kA99;q>Jwa9Qsp`Be_fG^j9tgWLXS<aYSMP`F{MG)uxH#I(yMh-f;%~p#NN9 zK~4jDGW~Ox0`mW=gdNlaq0gXMy1Q2Rn_04hFJ-15X*^#0*uYC$lBWiXgNEv?OnpxB zX7zbko{1CfG+Y<#61~_@kCYm9CKIFG^1OuecApQi<BB|Z)lzs}%&w|!kX(wh58u@L zA5fUvp8W1p7v0F42~R3h89Tp%S+k20y4@W?bnh4S%zf1_+COp^$oX9s+W)4)&5RQb zxNC$2cNRW$^Q}K9SZrQgJSvr`DKLlMjVL)0rYvWqXIdFQJfvofGUo{H$t?D&$_RsI z=40YQ>d52FZ;ovMmsbdPU|9F^`QTRY@SX?Dl3zsAVKClb1SWbU=QZTeo=(s!0sA&b zUP+z6nrxhkIb7{Am+S$tGG4qrBRoK0n%)_Bc{=7jrWs$V80`Rf76E2ms$~V1%8~_m z*9S;k*fLr#L6-Z-f=G!0H8DH)6TiS##;DWdFvxk<gZs(hdm{ARGG~R+6Ms+;5T0&K zTBaD8(SDYLo`k2#9p%#<6u3BkR|XSr*H$zzYC`>0T3YHTPr80c@2DbADmsh4Xn%5s zS@nako95z>=q<<0<a#Exor2HVtA!A?g)8@bjGbq_7Jiny;BzMup^5CfS%-#}L~JTa zI9<D%*))Ud3~wJH;*(r)A`SCImYNAdik(gUV1TkS+gM9Ael?||yY<Y1-N{V(EGnos zu^W6=$+=rPF<L6i{`fmwZAfwzgo7RTZrr`Cy3^?3<ZLrsP|gW^#Zytl?`O~Qfq=*# zedjw**@xy~yEH+DyN=Vw`|0mmxodVey?N`DwGrNw)1a+IZhz#EWUgzs7)cSZptyg| zge3WJAJYu&Q$x;`eydv-X$k>;o*It_j#j549j3KPhI%rP&i%GmpMbX{^^nz0>XrL& zT)T-iV{#9{BI8j}wfcJKO_i~OjK+@Thrb`a>)GT<lEJmBllpfBIBD+&!~H3eO=ytz z@0GJfl`fwo!Vf7??It6$9eeGB_zM2D8Api>WatL?lV9-Sd{)x(ZhS%n9UtOISK*)O z-B)_y-3n{_lSO1GX)mLo>2XPT3y)+Zg7))mbym4PzTlWfCrqzNvZdhkyb8rJTD3og zPt4BGrog4qvckAp5v_g5!^nv`bgv<Sk<h8mXUP=}2s}HjSW*sRmt6KF7q5<wwng=o zm9Nx&#udFS(V4kEC@$#>W63V+9?-R8>hU#5)Yx8(wZR8*RmNB8X9P^&fHX$PP&zvY zN5&rBkTCIRmYP)QxwP}#M9l?mKJ={#hFsm;ET(7jPQD=0gsjig@2<>?0Kt31X%{}- zVdv!{lKPQySjV%=KRouB7hs@N`r<N=fMe)Zt%pN4pr|qA{W<?`F9L>hJVv$GdoiXG zS-h8A8VIt~y!lx`iItafOQO)_dJ`Rl4I7zj^L{U+^1=9S*17658PEOTq`hh5@SwwU zaf{P_l0czk#!VCcZMh8f9(#99pmIEvNfC{N5TL23y#8d8VU=|LJ51%6`_Sy`rIWFc zp#r9s$2|VtVNegHyGbRNw=sXve){PIvojs>zSW}K^vR3*s_0uM)z)zPb!#ze#iq4> zfSR&8+l!_k><>}Z+f2v{|2q~?{O61&FXZ$h;{sH$%$Y&5aDCQ2d<8FYvkV0I=MIi5 zoX{yo6)@~Gaw}P8I%F6q>lNnG1VGG$R1`J33`D?H=GiH~;Uv7-A88L+{P@Kiyb@cM zW)qCqG-j*|DcI6~d&3xN1%?LjuCKQoQste8J=3a+?GDz|-g%t?OicOLmoEvl|0XPQ zD>&aBKb}IqdX7{p2wrrbHa<e;i*^5^IAprkP}bs3V#khXcFXdV3hz}K*gGf+J3q^5 z7|VqGcK1$pHtkdZPu3kwiJ2ffx_VU38E??fFxsKKvN+a{a&iJ3SsVQ|64nL9H@5)% zkx!F_b%OKb`h7pYsypGYGhF})AMTbJs{ZE0&SGV1@cp#~-&WFFyTw+TZ4Kt>rYf@% zuG()Z>$ECMP4y}ht!FHTGp4O4?YJAE;^V8+F&ypSC*${NU%~4}hxsz9Si^H8Ekkeb z^Bxz@RURf~;q~~b>&)y)>s=|;Z<MP0pH6BZ;vY3z+i#lChsMTclu4`Y#>SK*D?i@d zf|I->{(hiT{Gi}1@7hUKb*NBruye99Fwv-G|Ha78%FN$VUw+^FX}Ye0vF_oQ#1nI^ zSE<wq%Cg{{huN*u(@7m!e0Lu+NEWqu#lGMWX=&_-rfV9z<)CGX(&#DYp*V{@K~q<k zvZdYc+H@;BCo|cWlTkbEfr?eh;MP#Q+F$T!Mtmu73-EpmE^RIBN9erwW1g9kmYEsY z#GfMM9b{>_F+q`HmQ24~*;xMnaQ4<=QFV|0C<X#jN{4{bjpWcM64KJr(hbtgP>O`K z<j_b<$IwG}58d6}F!bC}-}igY`Q7Ka=efW64`%JXSM1q)txqko?QLsPLSjMHi$OlK z6Ki5j=4z$w@fwS3M2)k~NH=~qp<Cvt&l*^1t#k`$%68wpZB{bl&=%-*-i9ku=d$|o zqlQv_Wb)bJ_7SffnC~^sY>U^BL?q4m-OLfWh@h1*i9LtIx@3p9S3jy}{%bDx4&^YV zH%e3-)ZI$qV`_6ciVjo@1$o1@v$xCDF&BXnWVz6}E|26AJJIV=%fyR9TBJ}j=*g9G z3vrZZJ2?7EL|WiO@a{EHqMosS<6^SXUUYPnlK-JLS%Rp~#phY~d1kNn`?uAf1mzq? z-7*VOGekSd#;diGJ_K|os;UnXgk+fDf`59q8x%H<+o;_Ao<@&F^emtDaT%>TNzX+3 z;cK40&y^Y(jVzHT5Be<8(C+*3eksq%;8}i#>k~;W<d1A>z`&o2VZf!KVq=y&sqv&( z@ivR5xpz#pCASY0LAKTh7+0f^NSNlw<R>*t0paeLsi)!*Mq0J-6u~d^>z6htYEOTl zX3l^Y1szW{yuG%!KGarBIK>X^B?H$t?M&kM>$GD`B~rrpPDz(L>{A`kFefSZaSz4F zRP1Je@;ANs=)Ltqfbu>+@zGnIo+~rgs+a>vQ@@R0{YXep0^PpeK(HOXtXyA`D+cs! zd?IxAbMVuxw|dllLH9-_pO-xD0t9px7}?l*BND5YBOalSC;3s%d+lyE^5sU@yub%S z3P;I_yBzWxnHWrQ!z24-`!G!HvhgkAh7G;LXwJ<*=<b<`cT7XBRaKb*j|Xp7`sNY0 zUIG_8SHJpv4rUPp0SX<#QT>HB^Jh<cBC`$9?pX3jurxQaP5LnU`WMvYdrrSMK*3-r z_<L4oF5~wcMzM&R$D!Cq$Il_bCcQI{9Ae(Ej%4Y6hmCrF*z5=l@LRMh3?B!|Y|;{P zGmgT#x={~WFO25bHP(_}=%<|KnXFJxhz`9sclEevYu(z&Q~*cE#bw(3>?dB^KjV<2 z>K%qM=s23VeT<V+0=+D2^q=^(Q+V0!+&jSW`JjJNSZ;N@#kN6RR@ytb&JfrT`%})G ziOh?oxcXN_y;NoJ$IK}O#&>N4c~Z)f7rkqkvAFacF%q2{ym!lk*L@|qHe{xTlA%RE zdMa3cMG`U}qgx{HDvR%WB|*{gH~QpbnWv>3g8;HT#}{ZG(KM&@^_Opd<s)+CKy-r? z6O-mc@uag{12*Hs<c$kG3D@JTc`O^juo8WzkN2;S2N*oep37~G!bA}z3cF&uwHv2* zc=}DR8uk=`l<)K%<Lnk|?xPO@XnOqsUh7Zp6Jwo~4VJeCeNBrM*B0~?8#`3J+^a4` zQCm&kcl2wrgWm#W9E1Zx-Qw<#Pjl86?H35t=_~A013#(%)Pz(^2F+#2iEZWw3PD)= zxfBCEAr7O7A?tSmS3=uD78^k97cJn&clFyzMQA0@i-DNoD6=l0^{p;UI4;sWqNdZ; z;3eeo3~2RsxzbuWXk)$+eNSE~S31Ba@usKo3l4?9Fl1!x4g(PVeV(Lg@B-AUmG~ZJ zv3=9=qj+CIp*Fn1S4`WLdFV=pGR^EGn{wqlw8zd4@JoQO@ZKD0bQL|$L+Byc3n+9x zctSP7k$R(V4wy^~nqK+B<c$>BRKJerV#p?AsTNhux#U0Tz@PJVE7ULlMs1bdX?lrx zGW^IP)!UZ}&`|wd&N9ZihUQnphbnjEdfuLB|C6#6or8y^lo+Hwps#1?gooId_>P97 zSCy+?KXFqs;vRK}wVG@5=T0)kO+BAVJGB<bjA44*e>gfqkb~ac+GY_kQ0yIFYL{TJ zw<q%It-03rc}&_N?+*m5&iT-kP-$ueI+VyLOA2AJICW*$p)vMQ@)`JQD(RdbvjPP= zxT0}*9DbklOBlVgQn!zOngbz_O(D*MtXzj&|FTvy&Z`WWcPC}+dXl8dTjjCzQTg*w zl%&ulD@$tkv9cn?4x3nZp-xpYW|AmIbsC`UZtPkwadLqwvBFgE0&}tdAfzA@31R&V z{yaw|-8G)rdhG3UbKimi^jpjeDdj@Z2ZG*rAaH?4UGJ(mScN{D+69EtR2bt;cKFQ2 zCp`z}x#ES?o)w0;8PHam7e-zN#cR8e^JULAN9{#9hQ|^Xa6RL!+~Tv*uKp@X3S8&A zxD5_62D_^bHdFD~YOIesYYn!E0d;Ny!8(XNVNz4mu7XhZtMMQO|F5HQYPaLSu|f_s zqSz!Y2l*Zb3g)KDEn%BpF3_-PjxftR-G{d0E*EHe)`f}lefBWW(K2O&O7VhSY{|&F zyINeSu9liOYoS%iQrmgPWEPwS;wcv_%Mv^vm4$J`5D=9n4U;LFo9o@X%SBv^R;p=u zAEfyB=}5z5_j6N{N$w^qSgP@D**mWrklLfeXCQP<_DP{uMyS(4%7>exRkLiIG4K9H zpv_9QT0#nkCB+N@vDveCmqr8E;|AVW?;%5vT74))Yj#FEK#ik~1`H9ODIT-tZ?uP= z*|g$gP&H;tsm90Yx0k!+9o})q9&RrKE{cEl=DyJsUYt_M+~j>P*jJR~^bRr^t%O{U z<oBhX=H85B+c@{ltyNk$r(ZWJaB!{H3LH}<2al*`Tbb%V_gV;)+#X!W@*UTgeZ;cx zL79qIE5!(qhZV+~Yc(_ZQu*P5v0h=J&b^?{TbjNf%HpEx@sfvaFA8~qq-1uPESSg# z6zqq8n$LZQFZ|qFofSL}byJ#ZqybKt%+;DAm5!m>T2f=jkILM#+ikC7-m(uI-!IRw z3S6UUu!-r2QzigCq=h`9sFJic_a!zC_WcKG5i2Ray=WSoY!qmMr|<L5jPfg5gxnQo zg`O6zk!pruJ0;m42d}q3)4n(pw-srC4kONdq{|-TDAwWoyQC#aY6%4u$=p?332Vo+ zE}fu;aa1oPpZDt<i0Z<Wzg|;<vXV|SOC%dG$4`r(r5CjG%%-I~T6y$Dg@Y<P2FC%x z*S~sp2%s{i7KH-p#tx`!4%=}}iqnk6r7MP36F7uQ%9)qqe?%cgX$fIY@bue*I8ULQ zjR<e-qBBLa9SX0}=z}@={9`Tk<STn!dP`lP2&t&45;uySI*IP_5XwzgWA7QK8FHq1 zPSg2|x3FedLloJK!uSV^O_Npo5S~O?;&>Dh*;Y4a-npdcbb*&B-d|V_Cc(mu49OJ8 zk|<w(@SP#H-U70HZChT!aO#>LYipRQew8*Y&3yyWDqM-e`zu;Gk0vJ8DV;v|nw`k9 zP2#hCdU`4w9C9#kee9McDp>c!S$X^6Xb1g}?^cfZ(lDvb<EgiwJODzo4XY5hrr&i! zc@vTzk+4m(dEIFGI$?c%9dYL-CI;BNzuQAH6Im{qU9XTTDC>-{+2;|JjCFa>+?}6b ze(@3(>gw!x^;ZmK@ut7hT2wz}_hw=&#;A;~S1sF?7rwOxmeQ{7{kq6Uh|VK+9s2G+ z{6>0mvu}#(iLPFZV##yPXWvkpQz`8%wX~#LTLaLZ{1rtFIUHR&JZY<dBw0VQ+-mM@ z9g9K{E+(FzL)19>3rh#o)jfa2Ipy(%g+jn3KvZVD5`vt+dW6$?SO0Dk%FK=Tp_zGC zdj}`eX!-+pQdFK2*4jDyw$6+Zl@r}kE<i-GP}f)E_^wt`ev}+Nr8-g%npbkN5p}Ct zpZ}bo-FLvX6#vrQ^<Xc6{7E|95a&@aK0^dO-MVleE;G@`k611)dV;h=ABH(B!~bQe zeiF7qX8s)he<uq2Usx>U4fOQC^H?Nmzhz@a{DF$eR@UM!`*XD^4Gr1;sDIfH6nBCS zIyyH9#CUaerpl7ra|p$VKLG0sZVLavx;8esaQxqzq6jCn85%y7JU)8+;4e$0D;^aT z#FX)uyhC~Y{rT6gh`Y%RkP+9P!72mb)j1!?|8q1}RzWTUg$*8ZG?3~iC&$_8S$qpc zuG-s?SeCNmV}aV*ta1FQ8IT9?d}kr0ZyA~Sh5USxmJUqN5_p2?gGGeG5+f^op071L ze0T_Tg9de|GRT+WqrWd;80bkicB$*PSB(Ec@<$JdLiwm`q+BBX<r7~xiWv7+nStQ7 zm&4JLIrVk+@OqZq?n2pW$1ldeKaHab(tDBSA^ELgHP9Yd*t*W;pxzdKKx{2WQB<$1 z8k44~LJid{e)Z{vygU;oW-+1CCo9?Ke`=Tehg))R0GCKC4#Y9X$d2@uY84`Rzy?pR zhKBn0EOF6EH-({h6B6LsL5e=-v!n#qko#{LB=R`|4^}@T?nV5?6%kI(JHG%=>;@Vn zLVy}|G{2i|t|s@m8H)Sj1DFX~=jPe67%^bw@*qN7D>2F7Gn?pvYp}`f78sp}8(HIf zj&Fui&G8ks)~%o7EynU&h4YFUN(ZZp>cJMATj9Seny3z0d{lqad|!o__ei(5H%~X- z6)w(}TW58AF;W7*a~xP`LeLNO%gUP6pifvJp}<nRXGodc-|WDVZ_O_BRrmTC9Y2MB zcbSF806B_5t*e5%#!Ph}E|BPKcUmu<5y^Ej3?(1LT5q+yc_<aMaOPrn>s<v3BKmgl zJ6G?g0FOP&3*fqJ*9G;--qzM00gx^c<&7M0<3B~3ml6RegHf`!wl>|6cg?Hw1B3J^ zLn^7Y|GD(*nhh`XIPpBss)MaeIh~Bx3k*XKE9%XQ|I<{gVwZZfkI`Po{T-L}`9hDF zdK+&r8|HZduBqLJBA`+z{kl<kXUFWo0h0*7Ox&|7Zk|0)@zjJG#8jTKwDVF7-JY(u z)a`JgzV1m(H+~iS9Vk~qU8F^xq{d{upE5a6#u-y9?%DM|T^Br8q`?Rvl|p>pS7qFD z#wpg}aC(3k`C=zd1hk=(Vekq2DW$Z<;q5E`vvTMHPm{29fuqLBTE4btYXT~S$aBV) zwHyhPV{-iJc6g8jC=e`&v$vF$*}9;S^vbDU&n_Zz(Z0Mexhb=ozKQxz?izRP|0{P5 z%V4xOnTK?J>64z&=7ugcvbT9+1v$*%#eEuWxuINJ$Ik1|`>elG`!}|I*_hZ<`f7)l zeYQ7jEM2njU8zw95hA15!<3zC#gk1|84zP4ecO0w^OS<X@&tBp>7r8~T9Gx!-4_?| z>l!w9xe7Quht1bn3<T+BM9JCFuf;~@2CnkEPb6oDY|8ISsI8PyvFRTT_rK9pETYZV zmGRsc&v(SnFix{7BUfiuid@4_g@C_DJ1xT_CiZGi9_#4PC^beUt7N@85=M3tn)jvU zX3!p*j#`4lNqbMo^g!*`-F+vXs7&(BJ7-o=o#G+(rhEu!ZjRExP-Uv@^77QMjFeDr zvU@a&j!Ub<hj7iIXOwF+mUl9M4aeQOUJ25k(YyBzDsJqOpG6A(eJ?7gk~Fz>?C5RM zMiIO-h|~bKXZ+fH{j;_*{P`y`(^14Z?jk%NXqDbIJ6eIzJUOE0H&q152A9@Pgpy_R z7#Q2aOvj?jwBu6V@rxfAr&cib$;mKKuGV+x;%sx7EFU(gnZ2Nn9$M_SPZ4mI$iQ=l zQ|;MWOXc<}@B{g{6zsOw_5o8#q&{2B$C!JDa=l#a`ysU7q%X>i@htb!PuuO2tM|X{ zsau+h!zGEFgj{xKn=~aKvorxW_OAToNmYf7bm!x%o`kFB>MKgny}uNpEm2+6Uoe{~ zbhRsrRd;!{E?)-!{vd_2Sj}#)2co&1QFrmwGaWxr_$!qUCZ!lCE2ZcsD3}LezL?&2 zD4!%niQ91uGVGr{V0F2e=Fp@RjO40-0yI;=j_V*<v)bBBoy`O4{VMO*{&?s@aL3Ka zy#j{l?;+hhG?Mwt=w8mn#6trQQe_gYn-2JQPYynbn7<<5eR{T@V;(mn63{4`YgIRw zznOHX;0hV?F5D?}=4Gqy%I*ni__2R7B2m!Y-CZauo_oC_iA>k=H<poQiJY44)9cRg z!8zWfV>NGNm8Lmy*c=tnmM^4LmBE-3xof`-7LesO9+chy&jvSmwd+PP1VzT*Tz*!i zjwaFfwj(q?dMaa*r7|}eQgU}uN2_Xlu(Gkfs<wNL=dxe2wJwzdd90@4=qfo#9p(Az z7UT-r5qssI>h&aOy$n}&y{9iC8@#pIKG7rLu|XqB&nE)?j)%LqO4EM+d)_thjqPdt zsZ81~)Yrv8RvG>=qa~%%Mc>3E@8)N4$mP@ML4(`~rmdy}7Hi5U^;=yWHhFg*yCe@L zq}iqsExBT810F|O(T#5FfTmo6_S@RE_36d?B8m%T*;b$X#lVn_BHfGYz+L7pb=j!< z+r_{|?`qR|MBKgS%0{Ju2D|&+=%MJf^Kd8?OqOzY%s=7MKWL?3k^OFGbBNoa*7>Zj zFgexLwXfRH@oVNzyXGm_APQ`XRMhwwPz_cRd_KYP;q|ez&~UKn-hhxgfA=^YO6)MO zonugb9=m2T)ac$?I9SiRJaql8Bg4+f(Xua8GToAy8d_mExbr$;s4bRBz*A%7Y*>4~ zaEm!6b-qfJHKI*f8kH@vOrYvyzaT&KT?nx)wC8M#nw~+ELz{|)A!4^$%#B^I)^Th% z8sd}{tGXq&6_Mt3H*g4bjbzB|R)h`5mk%(xTu+y1+TPw-xf+7bN~{|ZbQ&6*rt`Sw zcJGU&Z%^7L4JZ|0J>fG~n;RWs6m>DqtA|C4J{5G-nVl$cb~ul*Udcp7(MFCXN4e`b za`VRW;P>WC)_~{BaqpaYva=?L1@*qt-)!Ngv56)Y=QPFW-5s>S@2gp~ghIHk!0QIy zxwa@E5H2-Tzo6^K_Ju&~iPCN9UeYh$e(pu3)bnW$nckhj{Pg?Wg*nPJL6(c;ep3rr z>bE(WHB=d|ca6wM6DT`lN{2A>_M!bZ*rvtSPHnyQO-`6JEm<%%Fp_*SFd3HlnALP` zVQGoPR(-7<(ZEqP7W|I7+J1(zWNfVq$tGpIB%l%UoLuLzEwU;fq`**m#R|F@Xp!a` z*w=HXrRQ<?7-{p?C^{u<nA?AwRAu1(!wK*C_x6WGUBd%?eW`L*G5HGXc)TDkW!inc zY8H)w=TMcX(PJ&E-!Jb0-<EaEnwW*tU6*h=vN^5X^(<Xc!Zd@jK8=k|UH&xc(fj$h zmI4QopLQ*G9tETiCJ2)6PN4)&bpZ-R!qSamgAnAZA;#i1L#aD)z~TP15q)VGUzV1^ zM^IUGuOXHrC@&PV$*1A)quX%-`^>BJF#@jbdBj!S+WsQTVg1oAVpqY`pf(i||IJ%| zxyIBw1;=)_{xoDgr2BhplMM+&4ueyJd7-{=Pi!8ZY3fJHBu5rQgTXJTI%dF8b3Q3& zYWw0w3%-tbu1i+hKF3!Dw`E^<PQkR%Bp3|p-jH@&Y25+AFdc|&YTQei!}V8Fpz|D0 zUEG`^QdPFnwVG=M3d24d5a+pxByss@4P8jCt6|jhc@l0Sin?Kaona<Jv{g0(l5~oV z<(uFTKp?&RsdqorR~iRs*cQje<TfIul<If{qc|hD#=Xvz^T;Je!=;XprV**#hq{lB zVC5w*U-Z6yE_1bMQXeoGBK=&ITw3F#mb*;Ubu$`Bqsi*7EN{ux3Shs77p|F#_opm+ z+Aqja=hNQ9BD@7W=$fPv`7dIbh<DaL8`oDiQG8`sXLqf2T<whA1=M-C=7{MBH7y_` zkPin_-a`y$&+nTsNYnN&@7$_!7rc)SvhaGQ>-1WDIrZoF;8Jt_?r%*}9C@{5o|YIW zDA(0y8H7!uGt_;Sqebj&i;R@$=S>?U^RaM`^xf^5Jo?<4APcuuMvp)0t<t_}xKnDV z72z}8Sts$oqyw;r?5uX-G(O{Sf{%d1mkIvQ;Ej;@a>=&Mek|v79wzD5kU4s9VSRbG z+PjAVquYeXer%MYPO?9LjPP$rKVewUD>yL~fFB#%G$qe;^$2kAsjTvLr1WB8Dy?5% zmTfgqP5S2wlQ5C)9;gp{SaWeq-dX*Mu-s2J)o7EfdQlt#1(T=g+qvX@r_Re4JU1Bu zckpqTLu$fTz#s>QNFJnxMpb$D_cNKd2YoW_k&+4G==GZs#~V1(IE1hIwV?ofQKv5Z za)sA0n^&N0nCZ%~W8zRrWhQel+4EFxT2>Gx$6b#hY=6-o=SY5DxBIX_vuKz1(@_=! zFd1*Os;kETOiZh_N`T6qf3NxkRj{gJO%3Y_ckK%~YUQ2~jL|YH=c)dY$b#U8L!vG9 z7mi%zqd+M{1brlx?_lanjB-WQ1}j|$Rwx0F`MDPL#b=|9p5P!^a!n;{ZC5^%(@Bmr zb!>y$?Sfb0kepa$0LyjFWN2GSsLcR>m9C?Daak5Z!?f2FsHqfSsu5E)JEb3iXFh3W z&cc7Tna0s@{Dk5R$Hx{=gpQ4-ZMtVWjbheXpWx1dnf)sz8;^OZJ3BIiq`NrAiiaRD z-8Mg_basiII+b2ari|almL+{k7pLJocAX&o<HQU;<5>?gWs)qLr#o(@lrjL#1Bsh4 z&I`E}hknJlDpBB;ZX#rark2zv-QMkp;W`6nD~*B|ia)I-9W;g`^h03>^xp?cceqF7 z9M4riqCpLLL}AXGR*s5W+ah&SyJsX3qVlsJ5XMu`SkoxgvS~?^$q~~T*$>@3xvQu3 zXDPiIu-4-FB#BFEX<3i1@|+A3g6b3Etl@6-^-ub_ec4DYZPWAM0i)+yK$jL$PgvvJ zlB$fBx6CnC8dG=-y}CwzceHLqNsUIJ<KfNGW2zKLYRNB<WAWKfwXCo5Q!C&Ng-`)! zlN*MXL~qrFw1ciL7%6h8vOdlqXJKM4qLw7<@j~LToRBPRVR$}nuBkfJ<BPz-x2}4< zRmvqPw8qL<i$#~)DN0<*7;%g}En2-B>G2ODO<gyq0_K?+?0T1X-?-<A27MraG+}1J zQr()-lT7uqPaG6yf{qE`HRgFWEJHw7@(&0=IvDy{4FC(sK7J%5=t{j(k6M6RJE*%k z6Lh9NaKq=vZE2cI_JA#gYs6*_Oimx9UiQ{rir#`4{FzC6+;##EWuQcOZZsAMyz^63 zV9rS`tN_1Z@JoS_Y>$1-k_?z%bM^?4#8p!c_fFa45}_YVks#*Xm*0}fF~v8wX_yh1 zqPkpl11*7ZytX5p&u-vUK2w;fChp_9FP-;KjvVS(rHMUU3p=x6-C*|<h%}7I@kP6x zc=^rVaMob71gbe~Zfq*T*@zw=*gO1-Anif&B|fq0_s_x+k|(=;Ur*hYx{!)flFRFX zB*Yy&4V*-7i7|Izyy(g4waOuMQWQ&80IGZVWPM`serT9m+1Gh=A=;zn$JQcE2EjHN ze0XreZ*6N|<fEWS=c+E5vJb*|sH=pSUQe-%sv4Ol)~n7KiZE9HXtVKV-Y>D=9V7Gb z4EO+ttY=|8%0`8y)nvNvG^Dk}{KEOOp{1iytMRYYyZniFiz-Zw=2S=_X!;G$ZDbq8 zmPm9Fw3}InXDj>7{@(F2r(d6UypxE*_+Y|Y>BE_yx`!ucCgLMeQ9(C;)7~`3zM6XP znNOBiwX~3LRwboXp<O;NfgKW=aH+ivnFyJl$uV^MeB4I_Y&AaiA~ekkZ>hB6SWedI z$Z)=8JhmI$L(lVN*`v;QN~i2W(|Z%5YxRWBKMLZ*;1tks1oE*T23sq3_Qf__MF>Cb zKc^Hr-rMBX#~Ds7|KL=g!%i~$Rpw-e9v>?Ka<;?ZWPn2_uTCm<R1o{s)z6Y>%g~k4 z)GsZQH2Q4c9x6I!Us|(a!7^Iel(u&YY;)zZ^jrRwB5`stKn{uTG0%Swu2oGE4$fy_ zsgs-*;8OCwyO`*odoi+|=|ZESM6U2J79g}{_cQk-upbz~zT@{yJlGVgDMBNRR;hEi zWqB!_f}@eIWvo3W+<W^+JPDpDGU364wCTRoXu0H!L4Oi)%12z+bZvKCB35`u8zZVm zy9YlAUBH>{an>0WxSn70a?T`b<TsXgV^<zadlV3g)i*jGTR|Jwm(@cPJ$XxLV6`fB z=7#D@04qqkD}myg&P2Shhv7RrX&{`pSgX6liET;p-G~D0B3g>ZH-%LoOgt8Lbw(WK z7@+Wi1;EFUn>r?VYTvj>kMwk12qDy4mqbc=Bggr%&Zf0>iGwO_%Wj-MzHDZWL}t#} zD{N8rmVb@ud%eSEpo_^UUuJX7XUBe|URE2raF%&AoYFm!{dICp4uMVH0}3jykkKAL z>Z{yuzP^m?*{|5!0=1A*Yb0bsDZuGtbITyW&7u=|rjSzu(-{qfZii%(&1+RV;a^c0 zG#SabOJ+%_i(%Jn7&gy+86lc&`eP{wIO<3|FKD_gA8bd0>y4i}D>}{N^zQ7&DLZ%5 zCOn^3XqO{04a&P<K3xn5SVUE9<JB?Ze8UPk$?!Sb#72GIcr_iky1!2R&IiHEoR%TW zeae(p1|h<6dL=XdI~cZIN#{P8X|puevDaf{Gune^U7^*w%4S^eIW?>yp#abM86TG) z4NM`LJH;>JMAtM{SRqR0g^++cEUPz6uABB&QO~ckSRsc+RjGw+FyAPj=iMOT%ml?o zta-a>eoDL1fw2LK-fYU5v#P49Aj!mCzG9C(gPv&_)!p!Ducu6-)F*rUw<5_=={&?5 z$zzP6cjuWDi2&mIos8kq{r&SMd3gYuJ6X(AAR63moQkR%JY=GK>2e$%s(<KxEe2Pa zFMi`;ZKvNp$>K_c9S`5<Cl27c>LMiPsJ&f;bJtlK*cbFnj(Tiub6UX#GInxkpdWWB zKg|!y+9OmA?9N9PDMX%xanZ0u9Hs0u;7c&|eNYj<UtM6#=$GpmMSt;PYAn<9{EK*8 zuGuj;2<=mfJWn|Rh%t%5ae0;W_nr?$@7jc{uZ=-f0W^-I({5tISQeiVid3B5WlLU0 z=tVRr-UL>r-su6xnqKx5QgM-bZ}`R2@1fbTq)}L?>Og>HG|Arg(IGP2FFGu3kDIac zpMDYZ$ZdLC!sWkl6#w9JCfWAWME;Oe%1pboQ}k4xYK>Wn-`H&h?V~5nunD>$Z7iaa z3G5sKO-8GiTZR}NY)%H$!U_|1G{lW^FP`6#L8R(@=?61%yME1X3O{Vy_+w30$_Fk! z>NhJR=v6t{`zdGL(#h)rdl}aNom%j1hHNJ6(rIJ;0V7eqSudc}q%`Q+c|QQUuB~k% zMIU?I$tXWx!E`x>jfVczg+%xCNwasOh+R%jq`9<XZ;F1S2s%nd%>O{<fR8>=ad1@m zuh;fzc0-orO=1Wq>ymOAzHXQp$<8<VM0`=s0Eo<^SSbSk58P0mTnyw)j=wWCjmfl4 zT6C%?R;T7X9iA!*`xGBRdX>e|;V)Ij#=pK!_E2hDA1I)+NxD?j^dC?#QC&_L^*R=; z9~=ZFBK4w^1Eovk(H4cGhOB=wpM*+B*y-y(PE6y4DJoM>{!*t$>IP>MpS{L8o8q9j zTR<E(@*2QYf9L@J(_EecA`o_z61ezG?4i<|Pu460J!SalzYPi8dMAmh@(N6b9+Rz} zOcYNR+dh;Izvj&TgF*B1jw7Dxp8cUNhUQWm;3(v5_8cWsmzT(81U)RK0AT05J6Ag> zA^5nw5`SjJ<ri|8kydu!#5>AQuVeJ*p$u}gU5aTrU1~j$FYF9@Gg&&-Ab4}M%^xg( zX`6cr{dH0YGdojRMd`~S;hGnNN%-`-p}{j%9&U<u?3U+`tE!NV(le`D`|CAX8tw@R zVVklsdWmFfC>~cKd4PE#SK1+)t@kG#E#J~=3;uDhQ{qyXjkM4sk$t^~`>WFeeC&Ua zWn}G0-v6a_g1(ZlqFkJPww{f9fr0rO#{T_L+FLqa#R{-A2WbAsF(crzAtqkudSPR3 z4=byn{aX+vLR??h6Cfl+&mJz5fP=jyjn&os;PpOeWMGOMLu(`%&)@f-lEP0h`DR-b zmsY21AxU4UxJ)nq8?)l^CnMm0y6FFlF!^8X_WxDzw?C+yOERzgELF<s&|Ba?WC>DC zs=%n~<P?`6kVF#m_0iJG3N=1{u>EfHYi{JmEdMrUWkDn?zpz3=UrmJ-Qo4rMW~r(h z{}wB&=A&6B4`f5=FTa8Eg+cX4Qnp5{)@LlDN922yOXXbIdH;=6K_}Ni!^e*<A5lS$ z5cF|fg<6mF*>9>91*KCqF-hmchYz|c850)hJOOW&k?tctK0c&`2AkGcN1Jd>W}tr{ z=oNBQ8gnOJ_xJzV-3H}LBohk@G&V{_zj%4}<7uG)vO(xP8w*~7-&tY3bxBF!$>ENU z!qm@EmS+0J%D?F|x*_-hcr{P+&t*UPMcK`BS-z9Nx!91W&se0-U<oc}R~8DavVA|Y zKYLEkoxSxTfdqZwA1Bw*C6etqm}enA0zRe;`C{)>QlDHbHFArEp#1dbguk}?{V|^_ zE7x9Kt!<aBAK!YMkqqVHS}IUhnrUxpudQ#M78Rij7xQId!xH5*GaKH#NhE#v=h!c1 z5?$_qC_E{g9hgTxr;4}XaP6;nBp>;}>pE&;nbRtVuozV#{R6r?`j<5H=$7um9-0AM z@S8`!FU&&&@`D&e9MtEEh`%TPX@Hv+qO6=X?jD+G>2LjZGzPrdxX2)pkT^2_KG6V4 z$L!R14=j3B^9d{H@xCR(FSi_j9+c5iOK|)uBn~xYG!^xO*?U@2u8}AL8R>~9fw_!H z@%4Fh{Xw_J>PJ^t!UVr@!)A-Mjm_Ooce-s&#KQ3|u{ED}!|GbcUJj+R=&L%C+AGw` zTqdWH@~3G_TWTpp_c}<^t}spmud4E|n5OF6Te<rCHft|yWw1CNQfAftLZM-FTa1q? zh@<d$Ji4$uv$lkpy*p~ImZqn_kHhQ8hCzLJzVKm(*9cy!yB1v5X$Ajqty-eS%AJkt zVPQ-zEkoL3ql<@1hrLcFQ*T^WR^J%*U^~%O7$`l092^d&gMQ=Ne{VI6{*f(OS1F2s zw~WKW3WL`Q=;{cn!~3&$(-+QYJ%m?RPwVfu%}u9-($#R$4M)}9C)m5cpnKY<Z8D&C zxrEtNP1)<KJ+VpqWD($gSy$N9%9rZS8}wh-xhxPBE2>yPSMZh!@w@=B_w`iltlMeG zK~85Hj+n<QJ;gvttB(1aHD|35Ka2=S;Z627pty5Wy(wgCL1e(IO(ecnvT{rWsrS|> zEHrL`3X3HygsAV5yQI=?Adz1gjP_LT15c@Q=di+GYMC0nM(lpMy=yy1L;KlYQWp;9 zeas`){S@={p2;<d74fP)vMkg}vC4z`3!)djms4xhPGu1hH5nZ&sw>9Dx#q;rl@~?5 z+2?jTCQ|g(L(!&pn3lhlkW}{WZP1v}0X~WgHc+#PatY`Rlo5A7yZH^ekqA}lCd&BX zcU%y$8ZC=#r~<GvSJ1WC<Qikd1rj$I%)6OVt=*I&UDVNYBRdU*!G$Ivy7j{Iez-Wf z<K;0dWlRa{F`06k2S${u%LGou)r0erb5gzOksSu;O{poV*Glix&C`hQk{Pf0`7}IY zcDD|;X(lKWSjwZeXlALMB9k8V#5mk~CcocjO2zf<-!V2LPHEeg&g^M&d^Kx(MtiXy z0xtK!lniUIjMWe6rVRk6>2z=rMs>f>uF3WCE^MFcf(0qWkREfEof(SJQBHM9|BPu@ zlP`&nr0fa}l>u{0*m>(Kj$e`5NIK^^7V9@;FS$`{Y&TEfNGz}nhKhkbx%)b2(-nJf z$RxMDxcML(F5^2Y{yDZOmY_LRrWilNo-DG#qB48lr;-94d_K=oEMpwCB2CACl?Bzl zt7gZhEoPU=3*Z7+n*!#m)KKJ)?>ezn5fBjzXws@Pe5Yrp;z)h>T~zIX{)?;hCOD<X z_P2<@kZD2#%W`8}%1A9>9mLusQwG<d($c=TyDK`$ZSOEMa+FwcSa+(DMWfBh!Fc^p zs19s8i1&SuFiLNhLtcj)${}PqnSUOMr6guPF#L%QzIv=)%d}#xkT%Kfv3fl-J;7{P zRAP2)WE~6a7s@1OUDlby>}$dta6UTY!i`WG)R+nR<4Ppq4DhNpyl4urnU7wXPr$M0 zn9Khtnc`!77|vK_j%cwfjKOcBPynr;P71SaZEq95%ZQn8*e9ICEow|g^kDf21&5Dj zOdy=2+%FDoIhraC;Vc~W=T%1=O0&gJ(av1Cb8~MaPgZnR=e4k7zhy)ucRbXHi%kN9 zMc_^K-C<$@&tZ+;p6gdXsFdI-oU?4e(YVtX^DnrBP4+kICWHI{@7{|gZPhrev5l;r zkhvG0)0{7x{b@olup31^H(~;a^~78*#vzW&aw2tQro=wAKTcMx?TD~pZ<Is#j`XrG zsL%vCd9*ge{mh@ZU#$mHLHpe@+gy4UT%c=Sf#(iv;N<XXs-Cct<U#s%Nf~KZ$V0T+ z$jhecWZ6W~uK8w`3_0Z=Omx1KcCo`81MMTjyQo5~uX&X<BZ}}8ltH)Iw4l<;eG&X{ zYjCl?C%rOZyFr61WGfY;7FMx4mm5L@+3L(IaAYBmrmoGCh8OQo6Al+JA6oUR2PF^n zFg7ALJ&26WYn*%kt#3rJ-ht^A)Gyq1w%k9Z?wUU;=KJ?cBk|zDJ%uIL{aMw*TCbh_ z)_It;NikF1yUib`ugku_duo>)tokvrAPY742A7`ez4fr8($}0zQT&{&#kmR=M$bBf zaURnh|5B3$xjxYs2jWPSw-F)CDqI^emAe5X3f>)MSW>Hzz<+HQOcnd0{H`wGu#Gzx zeKC?Q2rGhWBII@dd`>;0C8RJ5yN^!~p4zyF4R0+y6vL1kRJ(A=>?hsO)k$^#uw*TY z!#iVFv@Ky@HBMJ}Hy)en8;#p5Pf#w*+fh9+C#GYMQF8gwpxc?GdJkW)fs5XKni=EI z>?Kp`_*yBIuEx{qFhR_FQL3)#zNHn#Luwo_zmR;C!MEYaeRlxfa!)X~XE*$Q-6W>K zqyRRnt1XRKkXOE*b>Jn{<Y>MTmiiY9AObdC_wW-7ita6LN($OVz7R=j-JS>$#mVMp zcTLPxX5y&x;3E-Hc^KIp*XH5?cU-kr3HKweRk(GpcjfO@lF---3y+;wfhTj>*Wl~% z**jeYW5UZzI>%B~5|n@!>!*3TRANMu&x<jYfUi}!CgwvFD8F&7pQwk;k{|);;aoJ2 z-42rk9k`T~q1K?+Ey*lL?uCQnU`W`QyXCuyop*M@{|y*sk{oH9p1KgpRl>-^-M$IV zQ*YU{$HazG*Bw~Os{2{C74v4<w*I2{kr%>cq;eSIf#qCjB^QBf@LskIa#C7rFt`TA z**UPt>#4<yB>|g`wk6ML_5{D*Jec3NomMxl-dRbuA#BooQ|(G0yf>#34RT!Nt6{a1 zk?JopB3DA7h-P=ofxAsQ;F1UT_q1GmlV#>&kw_UuaK6*~peV+CyiHo^`8tQLd~=U| z!cj?PsNL83A{jHA8d^G~p(2Xt3Ou%JGFs~r@Wx|$4s7f}&dFySAm5x1#fGK)gbKk% zr!{9obs6c_e%oiV*vpoWw0h@;g_=8BzH$Z8u{M5@L=0+8=*AJIy+d?tl#(DJj-<+) zOAhnXZ*WR2=$?J`@gWS?Ip3zSG8bX+Wx?j0BQjS$pPk##>1syI1Uu$OZqBGv{={%^ zs^mX{uHfAT^Zs0~&Y1U##S3MK!c9a&^W+mLtT-?AFYPO3c?GduopdsY;sv*FhV~3U z*Un;sEy=><+cw7Qw_;LN@_(4O8<cqF7R&hnD1nd9_WT@Qm*~28EL(GVnaJ%GwDLab zOU^j3++y-gRAolg=ju8C&H(PPwqb-f8sPwidqeTdMqZSkLUw1Ap4Ogho!=gKk!d}D zvIyY-T&k(W8K?-inFk{&>UU48F0+C7#yAwy_cEn9i!pEp;m~XareYDOmE=||oBA`S zcg5dD>FMd$^xND97gRS)2xIfd9l(b}neFiLUX@WO9hnZxTMy_%OEXz1%e<XNDdobE z_~@z)-$6TSozp;Mq}YT8?fpnc-@e4YkPrr|!Xe>yU)bzWxtf}yMuMq_7+ScNj?u<e zr&Hnk0z{A&JlN@kCvIOzlkR|Vur4{W;F(rrl{~qFM;*#qYFINDZ*Ji&rD0WEu1WO~ z;WTSC(o|ey;V9R~LPj^)F`D|mHG-~ZjH~CWl(w<~3Tea_b|z=<2Q%@+WJ147GJ?N= z7eGwSVI!sP$pIzr8AEXwiD9HSEhBUf3fC~f1yqkUJwU<`W3TCQ=eViCJHv$NDxYc_ z;K#&_biA3m??5ZT28o=(5HZ(R1^%hWe}Vvt2q}iV8`cZ6R))rW+aUDoiTP+dRs`G1 z$I~+$PopzczK4Wd{8Ebs-aTpa8ELgFQ95%2zV__K`wsNiv^TN9W#=84ik4pGedv}> zNcTecdc4|zoO>p^>ouU-Lfml6fyO^TpSo`%#el#`1VY^wx?3io3k#>%Vk#q4H8}=H zJMm%_%cU^I0$&3Us9ECNhve`V6ZySkh{2Q&UiHeOt!p7n7aS!YPqyKNh{lwe+SfZ) zs}Zdkm#JP735Mx6w6|XuaCl3q)U)X5@ZlH`@cnDOWJdv+i=&o~b;+o{$<wVuUV&pU zx0Vq@&MQp`DtvmdbxxctA+Pu+^_GFPv$?=YG5l*G{Ho^6XlcN8n)#%+NWGx;hv32j zh73F6XWXKTGre<ub0wkDvx#FE8~qfZ%PUvnvP08O|C9)*PQF<66R@dOJcCXpZ#Hw^ zgvJf%m&SQTArZxc2_u29MA74Gz^p2ZD8_A0zdY_tjrX!>-F)zMf5P*ZV-0;NbSF;c zUgUMR14j=GytzyjXOMIBCMtQQO`sy&P#OD43|1WdX=|LnbY&jeAo}O5GexSs)pr{v zI|Ffe7kN+#Y4|qxC_p`Sx^a^gAbQvKtI1Cn{Dnz1Y67ijBBy#qJ&obd!idK7)dA<; z^&pj~m`p!YW#<^((zv(!qauP1?ugmy1B=x9(mC(jS>tv>Vxp(QdIUmzKF$HuF-+<1 zu`)#s2HvVCT|0#4L3vW|YD3fJX0>lmXhFTb5NCOv_qFu`1J%bF{|z*?ySHA=dG9;H zhRF~kWqTmvm7~N^eB<1)I1BL7Q<K#?=GP!tcJU}0jFHtXaX?0c4ivmx!Qj6B(e>f@ zlEn}P-lsINX1jo2yCJgA7Q`KB04v%dcXP5$5zYzw>K^=I{{xM6P=HW*8e4T_Qdj%9 zePHLHrmkxaKWTWP6A5}<IO8F()}gCZzPB<Hu8<c3!7FB%uQfkJ9Fe}Iw;^X?R!uk4 zuBx`t(TGWLhBy8&mI)^Gn{UkQ{#w)nEg1O$QnZO7%wA;vRLAc|G^3RoQXgGVcUa6& zy%Uz>rlk?br$M`~7&VrY^euslWm^`c1Gl5Rh(YF2z>zn@xtCNJfHG-Jk+qf%U^_Wn zA1iW}U(DCxPul)0OKY2A4jS2&+28k}-0wCZvGT%Bhp1^sz`eIV#wP5G-2;WE^sxe# z(@*0X<mPW(asWSy4mAJdULI+M=EQ!81jl7AL-|SeC_%p$zV7s_x3{OL+&SA<zM`oy zV4dmH{I&$Vo_qW7nFc^6+B^E(%X)3Ya4DvNK?dPS3ONRRI`!Q*+6f#zljwW<Y-beA zCDY|D=U{`0gpW7;8Vqg09p-*$sHUji!e!SOm36Aj;a^XA9tP4=W=|S$hYdrC&PTdc zXs4aWYqu}}JpMr0CkE#6@aLLqa~&Zmf_%p^)%m(TVZU?)WcOdPk9M|n^kp`%MgOYA z4Igpdo-d+M2LS}`tu!oWs}iGB@Pz>OVr8*14j>6NM`fLy9xo93jlPahn)k)@CiCcI zO|^zzGAVu%V*@OUC%?)y<pwBN8kG8b>Sr`p5+ASelg&`a2@PH>GNZ%U-LP^tr8@K6 zNv@=^L2$w2=ozb7QM>D4iih)BH5=%MSoECg5FM;)cX&Q_{*rD8qnh>(1fRGW-_I&= z#i9NK@BV>P-4%<jmjffeU7r>fkttlgM7gN8PPKk=iW!2FB6xc2aR_h;sE;3*Z5tQ% zkg@~?%b(4h1&K<SKTQ*^%9)f`h^QwTI8=VP^mGP*o2Sq}ecnjD)tLWLS(YR&4%a@V zK~qCl*Q~b+Ia6}52o4F}E-YdlP$26qS~iW$2aqQuaa+Y4=>sjNMFLW*nJhJ>Vb(t= z)%zYdvw346`EaD9l=Jv-cvW{S)?1_wK(?(l1^01F&U1lCHaT;GZupk#wyZqUm{e<+ zuE@ku<(~<t5y-psu3Ccz)I5?aE&OLx_5#qrvs~Nm6&JMl+;S}3BaFQxy*U`Qyw^pk zc!}|ekn@0uuwq4P-@jPgPg3ly(ak2ry^!;cjh(kB5M%=s7PQ0KnuSMh7@c<L-%8~O zS9SeL=+X(<kV;6jI*3)J|6W9yR`pOpC*z^R-&&(IuZAeSUn(7#W)(izJdS0I?Fd#W zQA|1Wwr-aDsdL3$_q%P#?SXkczP>E^AhqOhCw<??!J1JJ$s_i-`OkMK5fQLeP7*B3 zh0*%<y96oCl!OEUy|An+QdSf`OKZ@fe&*q?KVyjEzED$PZ@?J6*ch$P7;VXe9+D_g zG_0nPD6dcL>RQY(-QcJY2F4X*`ZoVa_hWq5>MUgH#qR|^a(`>jtlC;sR8*vv+N!-- zq4_7H9400?Ik}L|?2{-axiwUv3JZB#C@wiG3OkGHLX(@p(vrmA`k`RG+B`_9qNg|8 z2mp4*x3GWZ<2FXZ;UzBtA^(&RF1t;JA{F<GNF6!n68h)tC?l=eDte+g4J>Xa)lu^e z+wQ+5hh$^G)?=pG9Ve}b`x{v6$dBr2GJX_#r0-L%PnM9ZGLiBb{XZv-@TFAGCOP}b z+}GdVY_G{j7ZArP(*UC36apxvtNqp2BNf;!W@US}wqEsuKwu62H?B4G*CN7RTkT9b zl(0Sp`Goi(s=q;dD9xD`qd8$43k{d;I;dP%rEec?MIe>pL@1-n2g}jRsdf}EF8Th# z&L~SgCL3!Eaw-NdrWQYNmbRomq;R8&G#`-s2i!z?vdp;TAdp<sF7XGNHpY(@o)`S5 zPAHF#9q|pIkrG(#pB|L*Fn|rM#Q*I|zqbiyn;XATOVyv1FwaialE2?Hf4RRYkw7ZK zjY^+?`undh6IlPt_TxyY`ro2I$5rBD%li8hqh5;~oE&%W1_9fPzrlZe6@i{G%}XF< zU$gi?{D3;0ALbZR{{YabK5vEGY^N)wwX|nGP%G$P!dS$Fe%NgST!)1+AN8*SO0=79 zs#E~!+VnCp-Pjk-8*#q>RPD>=V#5vm9YVzW+Gq`-KqB_bt%T2ZW-EuO*2~Bzb<?-D z#AV$|Y$i0B=>RWHnG;1`x?Wk6mXu6(ss$pA=4}cx)t0YOd9jqPp!dW5RrvZoEXt1| z4o?g8%Jgt}UPpc;bG3Zj6M$gH39Y~U&J*{ew=pg|y(`Ycd5Nncb6~P0J6nzk{OZnA zf^}r>^~1lJT%TB<nv@?_-Bv9n*&og|6r5&DCB`WTo)I`vDV95&?CdnFZ0hMlTs7TA z`@*xa7Oz%&Rkf`4!RzI-&jVLZS?)DtjoJMc<6=$p7Vd9~R~=I+=rHsvK|P1WSp)@Z zw{fO{Bikk3H&<N)oU-JiL6>)3OZ=uXD`G5e`Wm10gnz}XI>Z9`I70u~NS*}jr&GO% zI4L+3z3Z;@y3sbU;31IY+@YV$GN}D9wO*NB@|_L+*zIN79bwXyxED~Bh%$4t5$`3n z7HTA3PqPDlos-;U)c4ixa@QEiKtm@6)L_Q2*DS71+Kn^cj3E8fy2ag=&Q$D@iB$g% zYG_DP1RQ4Ir3JXAi>Z916iFXxWhJlL`;>>QQrEbJy96tgRJWKnQge>TMpj*)LXT4~ zGcPf0z=*SAlnIll&sROtcjfh#{8mZLTz<*c=W4aKt=FJoT8{1Q{Zr99v$oiJB9aGE zDF61jZpv(l{@L<KEdsK6>e5tsjD9rkcj)~rBrZ$uBgC~YRU#}UUX#R0M34O3YGl>O zsmADmYSNdEbqnT4W~Q#~`?uQ9S=H~TT7Tuf4z9?OSm85082O1nZa~dI9Ay^xZ#HqN zkEiu6DgtolFWPV}dY}JPPIw&=U_VvrIAg$SEt`-kEv%o)S%5j|8=AyAU3YTI?KkpE z@Cy^szgR%+f)fHZ%f}*<RR8V^V)<N7RHQA3Mi09$T$n6)E%2`1g}cMZ;``;g;Y8MS z<?pDxKfHF@?V;`8=O+q`CyZ5u4f^37(zInQlAy&D&CDbuTA;WxYdP)5veWES7|3sJ z4r9>0d18jx=rU;hcqGdC_}_cs4K@MAprd5nsFto!TxvUo;O8EE*4-d8ABh^Yzm|~y zHEQ|4xS#(&BvEok#>0PFFvLvCg>ta85cc<(fkL9f$oWk7A@<)tr5^l$KWzT*@|Y{0 z?$lrUL?kQ4J^!*vNG5>1R<o+PY+@R<sQcRw*F*kkp{u!p-iW(!&*Of$IXv2aQt@^v z+QBGT@o3>1bML{S&s6}Jci6<w3(@m@qA(lcHBvbmvc=lJFVavi9ZtE>_f@iah<VpQ z2i86&yT)xe6GNQxk(YkNoraj*t1qROp9=2`w!U_&f}Jw5;5B4Dn(OXxzA>cKSOB`2 zQ2dahGoX;yQ-2G;z+9nwFpgh7xIbTc(|X>oBEEOyd3JxM@2TJ=2NOWt`RXNB$9eOA zS9-6%c49iqyDFB@B~(``71^nnz$Gisp&ae#eY4?Xq_QpF4=mPyP!fje+_$jsX?jU3 zbJSfoip=iSM)(59Kmy)Yr&qkBV|RIWnOz#2SDR!;_4W==*0N$`QlnTLqLd(JHik+R z{n-cH)57uYSeYbnQE{~Q`m>(i#gV{9Zc(_Kh*nv&FV#8#^@w20I*wM<<UIH@*UmS8 z)vc8GmpQQNT#vy^lSV4dYQV*^YMnrR&h)wd{-Ea%N6mRJA~8KUn)(eII?B<TN6;m* zuNdQ-K8y13vK*L!d5XdLz{JOcQhQO-L&latIB*E>!RJ_&o|d0KDW)%?#p16r@aL;` zywiYY9;G+ppxlqv`Aj8;mk!>v+f$L5i}a?jE0c|vqOKMLZ**WK=e#h{JJ=%horsqi z+*B-{zdKb@Ip$5fO0GZZzF@S`qIv6Qr3W%%R76$wH|W)~|vJuO~8?<LhXHFjAP zH00$QD>vP{(4sL{5g0fycv9^icdE(c#nDHZ)LgEr^lU(dN|Z6;#XKeT#r^9KKL{!7 zcg2e>nq!9bt0Wv5d7RasX$fYul(#^ns*llkWn>bd@&=c07f&#E8y9b8O<|^Aaj+V; zof88E6>?v#hg}~P`Fjyg=epWPtYIacM}fZ>+p9xL@<+S1GRnA#FJi}KVX<-*1_>B0 zO&O38p7c*BN@<FaGmvF3V~-nJ^`VQ`!1B<-?iuI0vzTbZyW#z?rmN32ZvbdA^MiA? zeg~p`(uXBaP~Jp5ac$vU)1RW;V@h4k6t1+^Wc8A5xc-7Kv}kUCiQ1evQa{>~#AP}c z2{Dhr-?e4Q(ReX^r3dwt7}c|u&t{B+Oy#Cc!C<479e`J8ZKF5g#N6m4A0<UR&hen! zQEkSw+md9h#aERAuwcmu1HJb-pur@O2I`HoQ>SHiy*qf>RK5NK&-oW7@EwDAzG`j! zF|D4+E7rr~j6lqBi?Q|Ij9bsjDf@$5ugXVu9^~4fZ!B~Z?fNyTDY;|9AN>>T)l61a zR|!8z)a;$@W5-~pinx&D_6Qglhy!J1qcUql?N}9hF5jLGE&h0{EBJn$7Gd1G6e7|v zS7Z(Q-JK81pg&!&(Q;AGJ+t+!R#6{WDC8&6$qBpmUapNp8}*dF-+h5~4%09v5;TZW z2QxZ54sAJaWW$}|T3+*KcUCAUXO^Fcb;EGvV>aDTF{5}8`ja0W<O%DhKW*Oppr_$7 zSuO69yg)A3sTK>yBQ&a}&_7$6iKgoY;NtC4m1g!+-0EX?&sW}6?=M5@&t7bEUa~KE zIx-qaRU;?nPo&e*`KgFhOxPpBVlb}V)v;A#H`5l+Gx=Pfh3Oz<()w*kmoEr}FY~x- zF2Y#{23Ttok6fHSf6_08hHBSh6HnChoPkW}`<!IMAQls%t7h2f3-#7_H_-2~eV9&W zPngMcK_V=%jT{!!K-q+ZBIg$yo82m%$WFa{jV~%P^Ygv^Pn)B?VN2mvid{MFR3Fey zMDDhjyri+DuGC$R6#&;Xr!ipcrAXKu%8*?;;m9QGw4xXN8P?5h=!nPEXR3YWH{ZlE zW&}R&I&a|gf4;k6MM3$0+I!EaroV1o6nh0!K%@%@kuK7^C`bp9-bJJnkQ#ba1f)iK z2c?A)2raZgBE9$COCWSY=!BAs?|<)mKkhsBIcJ>x;mxP~ej}@lIp<nyX0B&G^IATc z>R}plrC)CS+lvWfsn_9Ya9+z%_mHMojd&wVGtv0il_rqQa)V*mjX(4_YHvGUV!b0g zZ}F{}6zo{4O<n19sSO=a&XMyLpstIBcK(P1B&huoi^VEN-|3pJ?2F~V)UPV*+JMh8 zJIEbHvqfG#gvdWz6H{J%U+O$uwyDZyx%{D6=5jHRRC_oI+3=C}RC>_i-tXNGa#=lU zg`T%sw-}4o`r$%)h}Ec>_Q4RttbG1R1)Z~<?i(yomCtxF*t%y{6gfKb>`BTju=ebC zk1~|ydjHy=ygMCaj74PY)6C=dUU&B2#eX`5q+Ccn640JLYG4RJ^fs&sk}EsC*`*v~ zdc6T$(zw6%*pRut=Y5oi`KcvMSE{j$_p{awQYObsdz+!>d~0F{!YVD){uNwP=Cv42 zt--sHcUrApQD|a*dSn`$k+KDwTyDTEEH6cYiRGar(ZU+{N%I}J@|n<5)(zM$v|S<d z)-pH^&)9q2xdIsM2&jIWrJ1X|$d?0PAe=V4FG>MkeP5Wvz9J?n^ukUpieYC@at_&V zT_zW`^iA=ft+uMsFE^9uS?;3x>qSCN=|!ZoSd8;x0x}Ob9~act++mWtN|g}gVsAoJ z6%>nNwkLMrL@coHG(-x~e9vzW<zM6&g-kPaL}^h!^XHqe4hja?AM*`j9F%Jn&rnU! z`_Y;i*8{i0)&%At8;ckYQD>ItKeA|;R$+DY^z_LNGL{`by}NGqJ)LQNQ+-hJvs2|4 zJY{92v-3`Hx)5&q@RwF9zMRm05z)h*D!mzsg$+sSd(#byFkK%8m6xL4Ug&R(R?JcU z(aMGzaI&3NZue9$p+DVSnTIr_fNJ<sw)N&9?S_C`_V23%GFk@FeOF0dJXfCIYkkSr z_$yWj4r%SxOJL!W64G$7n>f^86mT+swLv~=xWlYJX|+F@`emPr*{E`F7OV!QjUQ7P zVpl;;M4f+ly=-}NQbCUF)MkHY6s&gQnnR!^Z=^ZC*sU?;>q*I+e&YQRBLY}eqU>|s z?B?)e$YbyBr4vL2s5{0n1kI|v;rNP#bjfT4{0&}$U%JywDR4hPj#aq@Y0al{Ol*A9 zL;J<5JIH+QB(nc7NG?EymL1ZbC;N0`!55Jra8uWLj@OW~$hLT?dP+!skxE9D=?3O` z!myUG=i@b7$x-!ff>C>b$}f)eqkU)TtB>U+<&gn;XcYjaT8(k8EoV1UE)Cl-I`*@I zT6PVsG4^>(Mr7^ssMj6=#wRD%H{9Bj(|UZfT5RANM`^}&+}%UUo{d7HLiTBxPa)@A zaGDoU^E$~d%NK4C*V4VBH!)NhuWv?zEjsC(ep<`@E7SV68O#7p=;FKc<chqhF7qWt zXV7tj{a@PCK8ZR(u6HmA(cE~;9IQQTGwnKzo!ca<yF;t&#Mc7@P`ZqcdC|rPkl-8l zR5UQQAMa^mE!%$}uA%Q_A=K;@NrhcVFhR4IE^YFsg_R8OzaWM@Vps3?-4UK^_|S%@ ztQw=cEWo*<a&xR9VrnTzi~EuwokGoEkKwlD*uG#od*y@2=#B&dp(19X@%Ag>blw)1 z@KjsifBc94^6<ashyLT^KDlxI&p+QjQ2u`<NnWr8mGA8J{l^He3O}`zc&eG3E;A9& zOG5tnIf*3yH8RBFBb$F7|F^L`{}-P8huyWzljaBN3dW(&wRA7eTXv$Zr!(8xHp?K7 zQbWRjMCKCZRqpmoxt9n4dNOxBnj>BpdI+lyR<M}FEX|3y&QHBiyZuTle@P^(kMW)a z9Zv^Wm77BwKO3Zw8`FK5Hn6-Ufv}Jn^xY3PHv>}@tS)U3uB$kCM<qKsBjV|3&3HZR z_4dXJJ4MgVlhXpXd}<6t#=b4kXt{{+bDkN?Rmi;+9rbv8e^*~Sa4a&NZ$1QQu=J41 zRKjyzIIF(BK<toKZ8}iOjz2klOY0ZPXKT7aU`*w>mnE#NXb3R({APZpVT)&psWyH@ zB_Co?X$5rXe<@gY95x`ved~(KoNjE6NiAFX<InEmFE2AMYqXM7t#7Hs+&amAW*iod zici#ft?$ct57@X9nI<h&qT%%M_NA?HqigJT)*H6niE8JZ^>Svr(e`u+-6@jR9xZ4u zrfrSq?2ueh=eY#g1<;5bn;6QOD0KBeQ>6*=y^T8h@jGtPxk`Yz%8Y((0ZV=e%enqe zv>r7u(lKhWVeeIXiNCTLQN)UmZ1Qj`bD}AzzH~GC?4gyOl867<VQShPOPvfdIQb-X z9MpT5MyXU1z^5_#{e&^@f<<U*(T)f7M$y+X&5{z@k}j3;6#SiSEdRM@dfpH@Z_>$V z>yGwKqqJ&bQw<ZmUblZK>7M#6;5b*l_|tbPS!6*i%ObVq{Yj+;Le=*!%g_kA_l<|I zbJd9-?35i5RVyeb@lPOLyiHH<o5%}|hGjBZw#M5#^uHE7DP35II-#vM<J{%LNw`Qs zdlNcgi6!$n8&$a4pT&8d&v3>&A}S!0-faE#4Fz4lz4Hb^(yzKOd5wv3WX}^J|7-<? z-X<lsJ9q^)Lf?w*-SWE}L_NT%k^QN05TZR=l2!#0wHMeNEgO1|0uz&N)pmaW!_<vB zale9Uydh_NE_M0VB}ZXCoVS{bO;U5&Ya$x?9+8u#))SG#8H?50=O&bCC0rK!+S}Yu z8yb0Rt1BOMmt~Z9;*rPH#t2z=kafPHvG?OrkILejx@hFuU6m-4G<I#{=Q#-<Lvr!H z99`j(sekCtmcPycLVC2L`$;8SwRc}O@)v^0;sjY^J~w+%8tEziXDlF2Dn>aj<^f&H z@LM;lgPi<ic8xBbJKrmHI_&yvMs25C&Dus3c{GqbD|Z8p{YdrVnLEtIYbQs?@(y~9 z3Khk}hLD+vdoZJ$jUXx)MT^d*8nN$~=KSZR<wFuv8mOUm7P|y9Flm$P?{|;VS<~?o z-|#FqY51Q#^#xbI{>^gWF@_K~N3h74xRm$Ss3U6iHTA2qip#0<1hU-6G+FJeexr#1 zZ;&<N@Sb<f8vaL%&HKuTE!=un9f!c^q|ipuStwuGv%A5wtU<?Ni3dD|9Cl+tsF?~z zwW1SoTK%KomOO;1Bd-vJP<0MtVd=sLj96AX%AeOn@bJb&>3aD_=YG3e?>LP>o7ljL zR^1KRLI~Rmsk_S`VJC#)OA@P>Y2wbxA8}SkcGhMCeE9-{PVGQ(iI)c=4?<sT*3};F zApAv@*doLkp-%&=_Bf&v&}h0SIXP(IR_)E>!G%(S<31iUM)~+7x!GKC?_)RelqXT` zhex<q^HBo;1wH>K1IvYM7E>w^aOI-s5hOwVQ8vyP;i8rLZPu9V>mE8~qV4_e)M+8h zE>&QQoZ9s_kJYGyZ{dFQKIeWIdF*&Hy&ZTeZA{lwuGOrPeG=F#_PO`2cYJWzb0z6I zbNuM4Z!P@84`{EEAfixBDzNnPgYq!8!ABoK3fA$9&2zJJ&uyJdJahUj2EKjyLCW<; z;PxAn#=VTwnL6g)@PZslt@}cyWQiyn&0mT|X>3gmEK<_-FU%}N><%-%u4L+ZEzn4^ z3jlxW@IULIgcwyP=DqZ|Fq>;B5F=iQcf=`mI^uj?8)-+TXJi+DnG+?tD&pokvxBMV zgZcE4&W~I-V)?r$?kM?|zxmM?J%fY}BvAq!&e5ohWu4p;E6gRY0}Eu5aFRL_>@+aM zeLibc<&h7XCDaNydY%w_$u8WF-aCynH8)!xe~Y8D_Ht#SL|pFWyIy-DW+SHaEbGLz zcy}!m3u@9cGY3f2Vntz#AKF-q#oRs3D+I+7H9j)ga$eEvzDL*9@hwXx()Ht`iJTEW zBF<rsiFjN<oo_UMrILJu>!^E?n1U87t-ViC6KsX9Yj8RE>dxvAvfp1Ps}hz#e=P5l z2<?<A#cmy#N3DHd5L)cwDQvBY{(b;Ql@9p~p}WLqhRbhz&A5-{FC5k5F+PzmR*QAL z1+u>XSiMMHpnuU|z#~=d>rqV{#9iH5#p$fI$rw=$Y`6zEfvrZ?zn{}A(2Y6PMC(WM z^Ys?Dr9!N|22OuB+8gP92S8Qq-G;>_$jEz2Ck5<4Qgp7lyeZ4}nowefj7-U!5$U{V zvVGgCAoQ;kn2U#ZjxJ9j;{9Ce;vFg|{FzApllqZc2UbR5+q{FX`}_@5-9`s^3mX`g zzf}c-&)@ruNfy2FeODyD4;SC&{Bhe9`I4JItaP&|j4{Sa0~9s|a{?1SP}lhud$fjj zhCC_RFZ>eP8P$LA6W^KAi1vr)k@g@EUG`hKJglgbosStfX|j2pXZ~P|-uvP-`A}@( zno(yq+mP48h$3X=6L`Uqq%mC2%nrN@KVt`Sq=goMxiB9v6IxGbDp*EO%ai;kbyGb) zwRlW?Cugr+Rq1$qtCuP!buZdtj7c@K`l{>6!4ix*Ej3S4NmaOX*NT}?l=JLxu%M4m zu3)l$cR7!yo)KUCvdMlT_yfPFaGFo)z20<#-Cc;lW0O$sO8g;qbn2C$+bwD^4<}&~ zv1)%OpK2^6PoS~NVcNH(zxQiYwe3I~&Pg)8rQ|u8(aA#kluHMmXW{NGa^{dJl;;c{ zX}5Jl`?q=YBGgN5p!`i#HPCD1#tP1%?xzM*^>y`(&f8Ni$Bhj|r#em65fn!Kbj{44 z2fogL)9#xtaZOM>g3dhSM6^^}8fk3L65@_0%OVCYn4F|)C2XP&5Ws~sw=>P?81<{v zTX!z@mJZow*cvu^6$`2JqUlZPS`$mLeK8UjlLF8;#~G)X^_uKF{YNMs)y};KnwL?$ zRA!Dl`V~GHWBB3^n@7h(PT=(EddmS4`^<X6`A+MLu#*&`crB)n)gJJ(so~ZTg7kG9 z>vBD6{ZAyCtb6arl&dJCo`94>w|K;{jUhC}4Dzt{VD)^x&hq!2m${#JnlHY6_Y0<2 z)y=P^spJb?6A^j`WANAR>N!J6y1NjA9)`!r$^w=aOT*AK8|@En_}6Yg;w&U|7z-(5 zE+z$KCk2hjtn)r4e0%JuxLWpE<+_x`FV3aC2!OV?_3$9Oso#kE0#2_tL$u^os;e+J zXVqJ09XnZQDcyyri``JlH<dzaJ<}&J^$Aerxc%a=W@p+#w^5a%P7Oc(EyzY+_zdX1 zguMFE7qP`C3JK?Z%Idc?pY8z2RF1XA7($LQVGlpvw4JxJx@38_*u(+)(hSAQHh0GE zfs_mQEaEqezNfpx(V6pJA7(7p3p0P{44I}Ads<7bZ{6s=FF||-L7G!XBTV3g-WCj~ zh1Gj9ad_+dh{v0i?u>yMi{Vsv30>VA9|{^@2ahF;ZRQ<pithL5=X(%kW+E7OVA72J zNQt2tzEzdn9OQe3wjTZJlKhr;!!bPhLJ7SW>X&O%WDl18)+4rFR2+#7%T5=sz>nQ@ zVXQYZBTRMPcq3@;eFPm5)9cAAqIcHS*Dg5hieB2e?Vj+e@0MOXC3I{?Z3Aix3*F~w zDJsFckD0(14%yXa-Yae93JKFYJYkCWwE5+OEnap)9nGd>oISB+d@&lPGSBQqn3G*g zd#pOUpW}L1sZt7Yu`t}TnSjRU=@djyyWGs${n-{P+j)1%?MTmwnKH0rV8HGkp*!>! z(*2h8)&byt7;g-<8r_=e;9eh0m66}C0IgQ~l>h8Y_R?}xMju}dmlArYrOBla@u}s3 z*;Tc-A!laklInT&j3MTlC*Vxxq1NwwhBfkn=fMxD#u$x2yHsXjqID8t3duRopR4m} zdp*caQ#cA;+VYbp6GP2xJ=u5cC@MsVL*?GAFt73tzwz;i*LQm&{>R%gvqDb4M)DwS z^@n!gK^$uXep2JTcomn}I<FAijWc{o@Mu|WpGlYTO>4IarA>n0t3K}DeVZ83`JUOy z@&Z*PZ*QQzjFG13r&oWf|1fSnSzlt+WHZV2(b>#N^gv}bpH{UfU2Fc%u_-PGWB82q zIu?(4;SEsWzAee;aLRjz>1-a4v2Hrtx_@FmFye;A84)z-7esx`Y&HcItlFhRei2hq zZd$SZz>1-9TUO-lBz25;kxa_ZbcI&;^(D)WaE#CX>PX0}U&rd%lNolk0!4v*^s2*B zPhDM#xjDYY%aWOH<@+NYXDf9pMg~JTel$r_#b034A$cbd-g@A&_yB4kb0JZvYtSQp zT|LFl>l?3~RVj^@-(G>-Y=~)Qo6m+YPfjopJ^4!l&MjM|NN%Bvys%Ih@13jBc<J%c z>NbOXAF11NJLZ5=|Kb~Pq~WR7G#U<6Y}X50Vg>Xji<JfQ)EJnJyp(KJzqiS<^k6D! z$Rp_dLDJJKg<lnvRR`QGF+RF;wF34!l#*so99iQTpUAd}jWt3xJ-Mp;!M(fkCRfkN zuQRC9zck~%6Y=yyK+2(ANGh$Qcj9xJ;N_zE?yd3~P&`3AO5C@L;5*pvbqo6h1sJfU z_bTHR&4<Dov^M%GTa5{$+Q!ysn0j$ajks4|dJrg<(+`PVyfT{o&|P<F;i5u(#jcK* zwU%l8VLE@a!Ba8Dw%dtA4d0&W`db=+MAUW<czbGRli^^F%(K;BZ^!C8xb*wt*9T+0 zoJ-7oKPla6dKTGa)$IbMoQU3|=G_d#rw=7SWan>b-wmw79bdUh*GQA$$!uh*E~;_V zki=8trqdu(PJWhhGH<b%FMnD=<n!J`sCl=2<@mV8??`}sMIbFNVF+6RPloTS2v0?u z@Y6LV|JJ2$#hKPfg>q1ey|^1DbRC0&1T}c<tu7Se7(WQ*=c#$~QI%rr8P<QZUiI}0 zlw3o6j;M^JxxdlLHJ<eSV}DAPOTfGPTPH?omcwGGQc91tt*M5xMLfloYvW|!Ce4gM zN%f!PBc`H9I=rnrJoRsW9J5|CL>}9Zkugr3ZPRpsG9m`lhmW3o3of@IzP0z7jqO;L zTb244ee}bb)el;S-|8*5{fc2%Tc><PSq7)rlPhLDk2A3h@BF0eF0C3wquH@<)t0Su zuIbmuc}Y|YY#8Pxf2}ABAi!03+qG?f?*;LMQXbzpq#9Bgcq~6fn?w)r4^XftglY$3 zq?}<Glpk|2k3gTmx$Y74R_*cuWp!xMi_Zs4NP^>3rPzLqd2sK|t&6h7CmK2jPhRL~ zWjN)O-&UD0xZ2wBe%O6j)!E0c!#X$%N4OPE`^VJxnRxHfYkG-!uF@NR+xZ-6C9{GN zo1*Let6(y<CS>_~%bJ>QOXi!IIF>*)?ze9yM*IIH>*5mJ1ppD-&o@5)%w@_Jsgx;t z-8NF+F<H_~a+zk}afKpq@~U<|G9~?w#e+CQlZmeWV3#=efo#q}17)TCtYYN$UE$iC z^}gG;==wNwGE+;qm;!SLY*I+%1(%iGPgp-`KCgLTv@K;_ok17&fR9OJZ<8tkPJMFY zHsjhI`#yol2*gt3fWW4IcWiw^`)aAiIpQIY*B!yO15C_44fc`qDq!g<e{V2<0a5E@ zZnrz}xz+qM)!s_)?fpOU?l<4LNPHUdZ(YjSD{30suF+K;KB0MUH5PT5pp6Gr_3~*8 z<}m54$T6tI>S8@M!-pG8jz4G#&`(Qo=g`Pak5t*~nLSjByew}S8qxPYm63|q-1A2G z`5DGwMkO=T^+$lutr0}i0G0eZrK~p;At~duJ(v@AslAgw(g9|sw6t+i58a=;nY}Kh z34YTSR{Yh8jNLp8J(<ADD3Y&r4)HGd!Lyu{WJ^;+z?c)pXZwtPf({fvmA;w|i75f% zgWlK{5UiGdoQXRNkP&^K%jQP6ZKWP9#=_OLneIK(@%|>)N`7XuoPdjP)6VCNY)lb% zs^^-g6&>Xgaa%}MNxiT@T$`En-KVOqZT$%1C9ho<Jm}&HQ~0rTXCUc2X0^Q=N$axu zesW8|2=o@K4bTNJ&Uy>0<tX2qsyry@9ML%Ya65r#xcrBA=bR^LQ+a`?^O3|JdLl(1 zzqInV{Pva74+3|ZCcxjzKb@8_JX(1LUh!tVy;TDge_SAQN;tggZT(4mpV(GNAU5Wu zH3TynD^4}f7&LXb=@o>uh<?U<WU0Wd)i$;-txU#rb@&*i0~&Lf$nJC^T*MB{%6v(v z_8E%0Uv&kwWhbo(+jcbUDXFwjrYPss<r8Az)saoE`?}7(%pb|+rdKk+GxA5*(Qr9W zW%u60+cH2}17FYa97(m7sc_!r=QX|bj|2LC@3K*m`|4V?8uw9q3tqyh{^1TBmTa_0 zr7<SUQPt=z_GEy)<PsChC=sR1oU>y~5(|vqoT3BuTu36ChD~Q9WE{(D5LjvvyWznf zabZ?Y1Hw<=BEKZ5m~Cl;KWcr2DBv$^u`*cMXq0JQHFn>zDS(TNKI)q-ndWl9@Ne9i zyK!i<0*^F(RF}UI?NxuxQzfCE7TCkB*&k3xrj2n|#f=Y}+s{sFD&oYrYt=8N$8rgR zB{^-0B1p$^L2&Ein;aa>j&NSuk#Vn|Z>`N=P(GC18!aeGvI`6(rqa9)V)JrWA4Ax$ zXv}<Sc$e_=@M}5NN82p)Mjrra>N?6D%V&DtQSyL5k(D(UK9Zp;ncq6<GpXpT_zpX- z#8)l+`SJI)aZvUVxf%GSO-UC_#si!UdUy@Xos?MPJLNzG?gK97Z)nw5Kw=A|Soh2q z;q1#ir*^8reM-v$g>{Ukr5$)$v3!NqF(<NFg%#FCfkg3pg97mHf~r0S?!*WH`5qu* zqXn4vhkQ^VsjMAtMs|{XfyDek4AqHw6y!5S{4hB_NSV3wFoYp#q@`^CbI(+NeU~q= z<uK!Ppk3YS+3#ICBfqdb>lu5UVZHkFi^vD}TLvwfH~)}!JytVq`#=$xJZ8C6XDj6? zcUm&-Y|<X=EY*54IV>U2R2v3Az-?gbYoQzA5bEQNJ~N!@uhH-;<P>9@L>kkW6`66% z7n35kpi%uYajq5{JAN#PUutH2@Sw<6$?e_D`EDk!_{QoGrl&As)97F`nPxpXkjV~O zwaa7<?yT5jV41!!c%P9`J3P5a@EBqRc*Bg(MzvM!@2Px6TuTx3x;A3cX+f_yjQ)1u zkk1ZW4&q$dPKv{AQ^WO7bg7N+ol|OZb46D;PegrqtZ7s2kFh$&uRRt19IMh_D<3v) z9NL1p)4dU4Mul&{6`|i{p9>srbs5nI^B73^AH)7^r3A#&XQSh6VKdiqHSl)(sH+yg zdljc^%6<DAnHiGaTxJC2R+r$f1m^x_aTW^niUSjmn%H->^HjF26hnT!k#OVckx$F; zm&jmtDYvf+<x20&^ApcZ$o)O`G{-8NVuP%AR887)pq-m7KP38NP+H1(q=7{|a!>&b zye2uMy;~73`H{=R`-y2smM&Uq;iqTUb{yZh@JmpVB&&ku#;2+I<*NnLDR+{ZOSO8& zs7GWhuEAxap=_Tzxm!%WL-uL26`=4x<kSzjI?Q)XB<?Ut{}$JC(Q;gylwC=Cdnbt# zRn1A^wOPjDl(BixHPA9^b-?>t<0M79X$QtxlVYj}vq$|2BAbl|Qgjm>RfB^>Y6>oE zdco-Y#C~p@{<@&ynpL%co<2%u-|nSV%(c!*HV)6_sn16$rn$qwtv5Y-)L>_T*^KoG zwZgkYILepw%|hjV-!T#5Y`#mF(8(5iH<b}1O|^V_cUe2;rU|mP{Gu<Hd<oD}?B0aX zYZC-Fm)6@msmG9m#E)v`x{M3r`sKF;ga3naI)qu$?8=J@_7NWgv;DoDiW~-lZ)=ds zTD0WY4e)02$Q^iw(Csz+h^q*Fr1CRX;jKy0?9!OKAF_up6V+G;DImdd;YPb5v%g!c z`vvOWj|*M<BhdNf%4d7jb=q{Gsy#%LReYXjzW;7sMS|5$@s|=O5x3>`?Voc}DNKX@ z{J2CwsK4I7ckn(TmePtU6zWeuPfV3VFD03BY+e?!`%erVNm0TTg)c_C9Dj);{(dw_ z|4C&gA^AUX>5<#~*|8_x;H2#muKXX!%U?qLpGc+~>goQK6LvXl_PdWytFP1kGoAmc z9{qm<B=uk1!Th_&5C4e%|AhW;|L-9~__qrGEysV1H0$5X_20|&U#9Tq`BRdAvL;CW zXBov8qBU+dpu89-^_4g*X|VVG+0yIV59XiQybAbcT=|u?(k{&Nr*ffw%Rw1boI%2R zQpyN)R8cM<B{5~2W{@^>2x;2SlTg<CRHyCG^#drcU>q1%XiWC$p4jW^mRE{bLjW4E z6{YG#i5l5PUo$v*KEcet?`W_741477k6yVW`{3_par@g}4EL)C*Z-Q!x2Jz0>kqD9 zxx{UJd3yWL<*Vzmf2H^I_SF}c5C2!2TxOwsfAv3QGXG<x^zVuNx2)IVCRg%*#sdB) z8vno5`rlIP|1VYE<RO8!VGaNrte0EdlV=@?Vk}Zpw9f(_f4TQ|nL*t5*=63u7g?8` z{)JED;Ws;e-I&iGi*=YNW&PLtGRroFF|``;%Lde6RFrv7&-$Fs@I`+~tZr_C(8MOH z%Y7H9q$H@wTL{k&zu9D+;Ws)xeb#m)UA?)>RgA_aJ6p7U3UB{$+2R-9y2dDzq?>0~ z?-s{pyRPXneQuAF<p5WlNB`BGOCX)U_%$412xa2a3GsdJw>zBwcOb$?IdAWVyt@1t zI10O7SZw1yVVNKwVBUYZYd^R`(v*n)OLFzUyvcu$wb}74vE9oBN)*`Jd6&DIdK`A! zj`bvYHN7GmY;xvJMhSb)2bC;^nK@X$kzO3wSq;9o5N)K^Wd2ev;dQysP^0-^64c}u zn=s7oI!RFTr6|dTffJ{FOmof~39jV|hgL)jA;9cZwOFWhhJfo^H2L7nWw-u<m)#aD zF>_Rw|7O73jPExEe(osBWzM)ikkh|uTKQSj4Qos77($vsd*#dTmo|`BPLq}Atp)<# z;QHukfg13x)Wui&Y$V25@Z4tS52;SaoA;M9lvo@nS^H;(Z=`85Ren#*RK0NC{}Yug zC-|&ry&=%(V-xf($%nlAlMM6g$;sQrddXxkAMANI5+ww5JKgFy?~_^>b@Rnx187X> zvwxqBjB!H!G4UPvynOGKFgTFQxa4>gkKc}TjdGuuU&$~)<W$xi$Z(vvCivn;Gas4| zJ(>ns-ScZM0Xcc_pzI~y;NzvQK>N9w>Jvo^y9Pwnw4tu<0|;*&Gd(_!@BH)bpV~MH zg*n8pXUMJe(^2VDb8P)ws!*-_wEZE><N{k+P+>5K0$X{TAWt3`&UyHd+2$Sh+;h_H zsli3%8>E59wE`$2pvgmnL*Av%B;rCy^S)%g+0MF?s2Yrxk?Hn0!3;jD<-PJ|iw?|x zT7!%PikIOh5;&=)Dwn2X=_cdcd;jznuJEh=RbXe!@grDn5G$1egzb^kX0&J>rk?b` z<RaU7E{YZUh-VFSA^_Kmn|<kY*7D=Zc|u-cLBveSe6Q1->roZXz*lc=)e$~O8(HjS zK7yTGeWZ(YJ-KIZpUdNWR(fljr^sk;y5OJ*mP#>Y=IwHDk(&-u*7mEg#D&NWgJ%xj zxL}#`Hg?uoLs$HK1}X-uSroP+K_cnN9!Hp?KJLiLzF_mYDrz;gliIoMX@ujJLYyCc zmR7KE5#cj2zgU=Ya?ttXzFon-a-Gd60y^uxTy;tt?OM2do}kCbQ|s=civxpq3<t~Q zdeVW=yHF~Vc8c{7o&aqXTwkHzz!;X)P>keHKTS7Z!#cVdfINCTcCK@XWO6&fU~_AM zU1s_wLMva}Rvu@+Z8GK#T~_Dis4woJa(Y?slot+B5i7&kxcUzq2!xiu&zjs~f=#(( zmz{z5GM@7wfrrOS2gX{!7HkpKt(2iq<>EAc>5YB9;=*uh%TXP+yVk0`42}qq5eBIz zRI^6LFIX5O5>dwrY$3y=VQaaQyQEa5&bDoxTpJ&2^!lDNfDa#QEv`l$;NnEGA$-Y( zhVMNOJ5L=JN}c7$dY?;cS*^@gzoV6z*jczZj#Xkk_2;^%*e3jz&&1D1k_-FtZ}YeV z^A(Zi-Y(5_hxNqMD$d9ZMcApDo0`(zVdWmP`wN1{4+)QV&mw{TM8%HG(?J<y^n#+n z!m;%I_k?CTT*ml8D}~Xh0=25$EKK2X9C05pf^qCJ-g9&q$g&JPPcx)`m+9En+nSkj z_ZFLysi#S*o{^CD_BbM|Y}W?1Ix05gop?w>0woP2nbt-)fdp-p)Hi2%!Nx|VE)&J$ zFx=U37pBjL!}ViVC_opdA;*Ca-&E3zusikyyT=NfA7<v1?`cu!Z0Hf4T`_TZtVWiS z2yc?#GB(a>c-F0tfnq4LHm$E{S^%?6M>mQp{msuaI-MxwHxNzdLU3P>RWvQbam*#9 zC)HsiEFpZ#rZGYc6BTD`CMT{de9t3*o(l;QD7*+nHDZPO@v=2z2VafH`f+!>v39-l z#NO0hCr&P<!l|7}IH%+329mukCDOs?pawFi(uqRN^VI@XgKM$sKRk$pd_;!&6A6|A zcz#b}rQ5>3rsfD?4E-GFUxCdRsE-{O6mxCMRXC3_d;yegDCf%Xsrg7iboA?_x?^9W z*T#$W%*GQ<#c}b`o-Ia1pPqCi_^fC!{yX@?d1nNvKrVIaWDTK%9CE%hAl5<myQ2|| zasX<J!(mSij(2;iE)GCYYxfB{lgi&~tF0?$b4iN!#g6od<5{>_#wcnqw%lac3KN~* zlVav&J92^jbYfAQq@#TjOzN`%A~s#n+)J31VPjoKZWEZ5pA~o%`wJD$R0AZedrq@d zl-nRb5bBU_ll+z(flvi&kyHl>rkk@>6Nqc;<9P@EV|>y!;ecc~YK@RgF02R5dG;s^ zJLh+r4Kef2Y&x@@Mth7*O=X4G>AM~@B=d(c)mc0EA*FFEc9#nTxUqg0Ep2b;y+yE| zg;#GGvCZ79lpCo%<RVNO3?NUTQZJ#{WnqC&gTKxArlf#t&DcrEoKb0kWrW~Tx8Y0l z{^rzeoq)m3&9yM5={>Z6C2T>3oVZnVV<imTK`kZ14D|<^dr<QjNO};v;Epx|*Wqc5 z$sAz`mBUjYwYE+X!N<Iu+aCKqrUo;WE0HH&MBD+NU58EaY}sP4kzsCU;zStZIxauA zYtL@5L#i;tcWu1xhATUOb9PdUfSS0WskaidO+MvJ`-x$$HdD<+K)`O2RBdC8S+fc> z(h15;*{*?_LGO`#uptd5NfoUFISH;E))m7aQG~Qw2-zm(Q!U@33uQlAw7pDizcJ_Y z!Fy8!?X`Gdvj!q!lUs{4&}Fv7;i+<ux^iX=7lel~C0P`85jTgD+D@l~T1%E&5_>4Z z>_HBNj37*VJ+3b89HFqSZrX$Mvt5>$0{FRFSq(EqxKl}h*rKU(N~iM;0t4}%%sJLt z_Lz_>@s0W)r=x&<x{UV8;{N-`jn4S)#^}KgFCNS7sp?`D*U5yzXc6(3VQ&GW4zOyV zi|y{Me9Q`%IAG*m9kAaq8tFbmF9gWEBf?kb{1JxF&@Ixh_Ze)|n49R&tpF!a52`?B zB>c27=_|U$a}ozBV@L2o{q4F_bPRD}3XVE0T<%j4h`$JKL;GSM`*DLN1CFLgUz)OU z5HU$=5_T$GWzmT4(~Blj1#)^R{r9mbEKyIen<gzTZ@ThAZY`S0zkHd(Zx1^=N|*}i zEcVXoTGB$I>%^g(rAR>A#(Iq3>>wSG3%J&pS9{({G$O9$R>03^z)6xX?an-p0C?Lf zJ;81C$=50P`q9d3BrZ6Lp{;)FzGF=!UuSoCXfevsSJKYLLEx{=m#dS)ER2Kz@7cL8 zKq6*wqn;l-_oMzI?0CUmT>uIf@$L+Hyt|V_JNo=qmAWEu`gB_6OgL&)^uf7_8YvZ% zqe?6{Z$~J!H!bdBny@<=O*7Myq<`VQrRS#UYq2YZs67-|y2sJ7M;#v#G2-OCoHK{x zF&(ch^8TDW!y*(v=smZ4wlc0!d4aDNrYOS?@fw!-EN+Dn{cKa1MI{8!fu=nu6X@Me zZ_kUGmADZTpRz-kCkn^|xV5HD;qGOsb|DeZ2!UE#J&rWfAKhiw{Afl!gi_doHk`+> zR~6Z-VkH=Ya)r+00a$JW&hx8(a@H2F&o8AMNu8A}pR91~l_P709)(fG`VoImCC{EM zmnFD>J&CY^m70|3M|~Z(HF+vs9bHNt%A*PK4<Uym?6h>!#eK<>1kTcM{*wW0L2Hy2 zRPFe~&|!cr!sn5|h22sJ;uM54!8ebNN-NPx{N{gXd5Gy~(T_E#n5R?eoHWY?Tyi7x z+zQLF&XrS0PKh3C+wxCW92QP5D4Z_c)87DxVdIu6i7nfLjN96Ju~Coum_`UeuMIB= zH`WW*ujy<lciGq0<s=w-w6}*zOYQZ%Jg4YNmIUD;SYGKGr>z)1n4unF%gD$~RLJzF zf9(W*dVjo9tB7KLDP1JP7pP~~uzIFHlP?g%V&KUlB~p}7KQNJ(Z&rUYPp2Vu(EEMO z%)oc(dlPv5-C9Q0s##>kR3dmrMr7%H>Q2#IX^gOQ=l7+VMw|KC&0u|Q#85T)eq#OB z)K?FO!dRGd4Ev|IP_k`fg!lPuvPO)Pg1i<jg&4=yiiYd(lZYh2f@Q?}GU$@^Cd6;j z{3)R(nU-^I(z}Aw8;`Ce8_2H>5|V)QBu)WS6ii1v<RKwd{v(=>NE(-Isft+GnPySd z2%KI(c&ol3S+2`(j7%$M4)As9^O?Vw*MbG4bqwa^=eH1G=_R$9V%!+vrG-xX38;kQ zrlSYqn2HRE=2Og~QiA8PqLmi*Gu4b4;B7BU_o4LG83wzFL92r1n=S=wggWs|&jrkp z!)Z@4bwYxIi1b;SLN3%@Xv?SHnKrdubl9pOBM-Lru6HW93Z<1Muz>9r)X=^5hi(1a zjd{7P!y}OV(28+FoT$FDV3bkvY>(wq(n`jjPhDsDa=ahrYk{>tVzX!f!uMnjNnVtb z%2j;O&*5b;WJm5lC}{cGF7x8EzdK7Y3CXP%B{^Ab9oO0Bici89gRw`tXBXrHaRN*o zN-C=K5|-0?j7SbIF2sd~Phpu0Z|ln8qmC2=4{7*{1~0u=#ZJ6@6=j=|baxG#vP!(y z?%A!?f?_N-s6eBDB4)@UBA8s*N==nd(Oz%T5+DMUcCR(c949GI4JtOPqeCApc(s(; z$XPjh>;^3)-J5RCzeWeD@c&qz>uVveUGa;};~ClZbe#AKYALNd*U#yqN{Xt;r*$vP z)qZwHM-2HZXQybRGP~ROg-~JF?CRUe*g*0)p>441vXd^v$HxCuwr+DsEyW(*4y?lW zqkYTPAW)ypeTMn7_)5|xiIQ4eu(ZU#j^=B)aaY<SaCN@<;ULlfP3!L?JxMcnEBlm_ znCJws<-r;+uz13!j$b)@iL}+OfWyp8^Ye;^Ayb^w2`-XK#spd9;&DVSm#t<~o@p_V zgd+eY-mr6vmD{@lM*xTrrL_*2e;&WR%i9fR3DvlZ^y_wAdMI7#?MPgA3T@ZiQI{op zN`H9hlA$F2^Tv0F3z(}4nKsUF%<DFxD8JIz6|-v8w|8fUQCAnVwN@5iuEj>{3m}ih zg%qQ3mwa<5_Y;jdK6sKew99tx{EE10!1#iP)_Lgk8QF4kt15csca09$tg+1U*AzHy z9q!RB65ix{kH1*p_pv~k(~fn07+xFOt-y#9MsA!-<M1ad!iyO6MSo+bB_A)}u{tDU zZM{;~!Ww*_0!No_wuHm5gyv>A53t^oMxaKp@-PkN1Bg&IZ3lonml1#qehXC?ikVml zQ4JxmMTu4dC$wZtCTQ>PTJxREe#<Ndg+oxqCEF_+=ley2fe;x<`~7_h+|*GaAVWWa zmd);?Jh`-FxgLDf(gc|$ciu`xT<K4YC9GE@k*oDEQ6ad19pg6d!k?!jK3sY&$N0(T zJfpFS9E9Cyx_g(-^>oD+{Jc+S#9gT821u%``XDB#^lUIwi|=e#20cYEw&6b7FP<2} z`MxkX))<xN2$6%W7N2&hJ8fDzr5+#GTaPk!cB+d_C(6j2c9zCW>WMSgsB|Whu}YwU zkeNES!8{IIC%ECs;H+5H+yK0d_VNrwt-fHoIbC&ab6j{Z8U(T*yEt5%DV<DCmZOdV zh}}VZ%yce8h|k!#01{lGYCm$q8IJAj&IyH$6QK04$cYx$TvUx+)7P>_N0}O0`|`}J zp*iC8E^{BBm7=oSHsAcPgK?<~*i_prP4n=b%QQ&A<kF5(uiJ?b9Y-SZ_rTN1NOnGb z-#5E31Y%XDXM4NVJ($hcb;J6LMCGTBk*wKm+uMZPT;H*fZ)KT`eOW681}0TdEuMhG z#Lv_#BM*7{2I$RByW#0-N?}3@@}O>wLt{I=ArxRMSJq{;B5j@H21A!ph^5t%8n>yW zV@sDkz}IOZ`@%n)hUCf1gpo1rw*d>d48jRHqhB><G{_>3l;gz9U`C4Pa9BnfxteVM zs2pWD>K!$8AnU!556w6|W%5Of_y~_Mz$2E!l{d_T3)Y|eIN;KKHbDV>Vg^3k5Bts+ zHLi%zPl33j%-+3N^o7s;UaE9Yn>^pJM5-{=a%3@grYFXXT4hMbjv7?82oLLy3PHHr zr9gc6C6tbgkpRI5(yLrl6t=uviEK->5%pfukCpIus~eJ~{TS%ARpl{9x7DdJ%p|VO z)WziEsE>qIG_@WIFgq_EL-DEMvy!9Zr~a$K>U-vY{HEF>BWjJ*BHCo6XHye0GG_Po z#ZY@|aZ|a|NH<gUE+TKQoj7q9C-_EP!u{->`>Bx2<WE6lmzEYXe&6MG?_8tBM!G4= z&tq(kY$P~t#LuVKCwa>vz$Z3u1~l&(D^^>*^$h1SnKANvMM6D5-JP_Ap5%i*O@+iC zuZf8^JM|?ih(nc7y3zP~0_@pQUsp#;&CcN-{gSU?WK7yjSDMykBq_~0_-uC0Y|SiS ziZ*KG0`*R^s+!ttf23wf-94w-wbdi9LL<k06j+LfIldK(Pa9h+*5j70?Lse2FU!`h zMMN&Qy=+x?073J+lqDP-<}6C1>ix$Nxbyy7Y1pPOr<HsRbO$BLgD&imVYNeUzAIgD zG)R<9Vpg9i#s4%sRbYy*(QbX@Us6!%TO+811ZPi}q$f`zvfG$fRqna2ND->`GEFNA zE?uqvR|1q>oV?qyOGvZuG#?a3a8-MZr0WVvP#l;4K8VOqdq3m+q#TI!fOK|5LQN)8 z&SUhLalmQct)*zZMce-A(;0@9%r%98TiXsDdJv|m108R>Ss_>G(tQG`Vjnd=3>L6+ z!LQcAxZpo-&UtmA`-499_tB*L`N$hy9EhkQD}U|O)__kEz)=I}8c+uhaVQ8}?laN2 z(0>`mt7a2kvL+|_&`vtZ+OLFjnyx#^Vi*w;s-F-$VLS`B)mrII<^LJ5|2Z`*7f#Ap z&CGqWNIf<26XGc-NmzJYy`($zIq_c+yxEG+iGL$_HX)>imk~T>{=~mGP$WS`U4KKe xx4-<iuc$7lNJxl8Nu@xp%YJVDJ<zX_I1D#ImqGW5mv2av<kjVhUw`=a{{Ym4X>kAm From 1e6979657a98f054360873a8f661e51faee16d7c Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 16:17:00 -0500 Subject: [PATCH 013/102] feat(web): add vertical orientation and fractional default to SplitPane MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The vertical mode stacks panes and sizes the bottom (secondary) pane — the reading-pane arrangement. An untouched pane follows initialFraction of the container; an explicit drag or arrow-key resize persists pixels to localStorage. A collapsed mode keeps the primary content mounted with no handle or secondary pane. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../lib/components/layout/SplitPane.svelte | 172 +++++++++++++++--- .../lib/components/layout/SplitPane.test.ts | 139 +++++++++++++- 2 files changed, 280 insertions(+), 31 deletions(-) diff --git a/web/src/lib/components/layout/SplitPane.svelte b/web/src/lib/components/layout/SplitPane.svelte index d878e1e0b..500488b97 100644 --- a/web/src/lib/components/layout/SplitPane.svelte +++ b/web/src/lib/components/layout/SplitPane.svelte @@ -1,13 +1,25 @@ <script lang="ts"> import { SplitResizeHandle, type SplitResizeEvent } from '@kenn-io/kit-ui'; - import { onMount, type Snippet } from 'svelte'; + import { onDestroy, onMount, type Snippet } from 'svelte'; interface Props { ariaLabel: string; storageKey: string; + /** Horizontal panes sit side by side and size the PRIMARY (left) pane; + * vertical panes stack and size the SECONDARY (bottom) pane — the + * reading-pane arrangement, where the list above flexes and the pane + * below keeps its height. */ + orientation?: 'horizontal' | 'vertical'; initialSize?: number; + /** Fraction of the container granted to the sized pane while the user + * has never resized it (no stored size). Once the user drags or keys the + * handle, the persisted pixel size wins. */ + initialFraction?: number; minPrimary?: number; minSecondary?: number; + /** Collapses the split to the primary pane only (no handle, no + * secondary) without unmounting the primary content. */ + collapsed?: boolean; primary?: Snippet; secondary?: Snippet; onSizeChange?: (size: number) => void; @@ -16,26 +28,31 @@ let { ariaLabel, storageKey, + orientation = 'horizontal', initialSize = 360, + initialFraction = undefined, minPrimary = 220, - minSecondary = 320, + minSecondary = orientation === 'vertical' ? 160 : 320, + collapsed = false, primary, secondary, onSizeChange }: Props = $props(); - const handleWidth = 4; + const keyboardStep = 24; + const vertical = orientation === 'vertical'; + const handleThickness = vertical ? 5 : 4; - function readSize(): number { + function readStoredSize(): number | undefined { try { const storage = globalThis.localStorage; - if (typeof storage === 'undefined') return initialSize; + if (typeof storage === 'undefined') return undefined; const stored = storage.getItem(storageKey); - if (stored === null) return initialSize; + if (stored === null) return undefined; const parsed = Number(stored); - return Number.isFinite(parsed) ? parsed : initialSize; + return Number.isFinite(parsed) ? parsed : undefined; } catch { - return initialSize; + return undefined; } } @@ -50,57 +67,122 @@ } let host: HTMLDivElement; - let primarySize = $state(readSize()); - let availableWidth = $state(0); + const storedSize = readStoredSize(); + let sizedSize = $state(storedSize ?? initialSize); + let userSized = storedSize !== undefined; + let available = $state(0); let resizeStartSize = 0; + let dragCleanup: (() => void) | undefined; + const minSized = $derived(vertical ? minSecondary : minPrimary); + const minOther = $derived(vertical ? minPrimary : minSecondary); const maximum = $derived( - availableWidth > 0 - ? Math.max(0, availableWidth - minSecondary - handleWidth) - : Math.max(minPrimary, primarySize) + available > 0 + ? Math.max(0, available - minOther - handleThickness) + : Math.max(minSized, sizedSize) ); - const minimum = $derived(Math.min(minPrimary, maximum)); + const minimum = $derived(Math.min(minSized, maximum)); function clamp(size: number): number { const atLeastMinimum = Math.max(minimum, size); - return availableWidth > 0 ? Math.min(maximum, atLeastMinimum) : atLeastMinimum; + return available > 0 ? Math.min(maximum, atLeastMinimum) : atLeastMinimum; } - function setSize(size: number): void { + function setSize(size: number, persist = true): void { const next = clamp(size); - primarySize = next; - persistSize(next); + sizedSize = next; + if (persist) { + userSized = true; + persistSize(next); + } onSizeChange?.(next); } function startResize(): void { - resizeStartSize = primarySize; + resizeStartSize = sizedSize; } function resize(event: SplitResizeEvent): void { setSize(resizeStartSize + event.deltaX); } + function startVerticalDrag(event: MouseEvent): void { + event.preventDefault(); + dragCleanup?.(); + const startY = event.clientY; + resizeStartSize = sizedSize; + const onMove = (moveEvent: MouseEvent): void => { + // The sized pane is below the handle: dragging up grows it. + setSize(resizeStartSize - (moveEvent.clientY - startY)); + }; + const onUp = (): void => { + dragCleanup?.(); + dragCleanup = undefined; + }; + window.addEventListener('mousemove', onMove); + window.addEventListener('mouseup', onUp); + dragCleanup = () => { + window.removeEventListener('mousemove', onMove); + window.removeEventListener('mouseup', onUp); + }; + } + + function handleVerticalKeydown(event: KeyboardEvent): void { + if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return; + event.preventDefault(); + setSize(sizedSize + (event.key === 'ArrowUp' ? keyboardStep : -keyboardStep)); + } + + function measure(entry: ResizeObserverEntry): number | undefined { + return vertical ? entry.contentRect.height : entry.contentRect.width; + } + onMount(() => { const observer = new ResizeObserver((entries) => { - const width = entries[0]?.contentRect.width; - if (width === undefined) return; - availableWidth = width; - setSize(primarySize); + const entry = entries[0]; + if (!entry) return; + const measured = measure(entry); + if (measured === undefined) return; + available = measured; + if (userSized) setSize(sizedSize); + else if (initialFraction !== undefined && available > 0) { + setSize(Math.round(available * initialFraction), false); + } else setSize(sizedSize, false); }); observer.observe(host); return () => observer.disconnect(); }); + + onDestroy(() => dragCleanup?.()); </script> -<div class="split-pane" data-split-pane bind:this={host}> - <section class="pane primary" data-pane="primary" style:flex-basis={`${primarySize}px`}> +<div class="split-pane" class:split-pane--vertical={vertical} data-split-pane bind:this={host}> + <section + class="pane primary" + data-pane="primary" + style:flex-basis={vertical ? undefined : `${sizedSize}px`} + > {#if primary}{@render primary()}{/if} </section> - <SplitResizeHandle {ariaLabel} onResizeStart={startResize} onResize={resize} /> - <section class="pane secondary" data-pane="secondary"> - {#if secondary}{@render secondary()}{/if} - </section> + {#if !collapsed} + {#if vertical} + <button + class="vertical-handle" + type="button" + aria-label={ariaLabel} + onkeydown={handleVerticalKeydown} + onmousedown={startVerticalDrag} + ></button> + <section class="pane secondary" data-pane="secondary" style:flex-basis={`${sizedSize}px`}> + {#if secondary}{@render secondary()}{/if} + </section> + {:else} + <SplitResizeHandle {ariaLabel} onResizeStart={startResize} onResize={resize} /> + <section class="pane secondary" data-pane="secondary"> + {#if secondary}{@render secondary()}{/if} + </section> + {/if} + {/if} </div> <style> @@ -111,8 +193,14 @@ height: 100%; } + .split-pane--vertical { + min-height: 0; + flex-direction: column; + } + .pane { min-width: 0; + min-height: 0; overflow: auto; } @@ -124,4 +212,30 @@ .secondary { flex: 1; } + + .split-pane--vertical > .primary { + flex: 1; + overflow: hidden; + } + + .split-pane--vertical > .secondary { + flex-grow: 0; + flex-shrink: 0; + overflow: hidden; + } + + .vertical-handle { + height: 5px; + flex: none; + padding: 0; + border: 0; + appearance: none; + background: var(--border-muted); + cursor: row-resize; + } + + .vertical-handle:hover, + .vertical-handle:focus-visible { + background: var(--accent-blue); + } </style> diff --git a/web/src/lib/components/layout/SplitPane.test.ts b/web/src/lib/components/layout/SplitPane.test.ts index e3229ad28..5d09aeb0d 100644 --- a/web/src/lib/components/layout/SplitPane.test.ts +++ b/web/src/lib/components/layout/SplitPane.test.ts @@ -28,15 +28,19 @@ afterEach(() => { observers.clear(); }); -function reportWidth(target: Element, width: number): void { +function reportSize(target: Element, size: { width?: number; height?: number }): void { const callback = observers.get(target); if (!callback) throw new Error('split pane was not observed'); callback( - [{ target, contentRect: { width } } as unknown as ResizeObserverEntry], + [{ target, contentRect: size } as unknown as ResizeObserverEntry], {} as ResizeObserver ); } +function reportWidth(target: Element, width: number): void { + reportSize(target, { width }); +} + describe('SplitPane', () => { it('remains usable when the storage accessor throws during reads and writes', async () => { const storageDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'localStorage'); @@ -106,4 +110,135 @@ describe('SplitPane', () => { await waitFor(() => expect(primary.style.flexBasis).toBe('176px')); expect(localStorage.getItem('archive:test-split')).toBe('176'); }); + + it('sizes an untouched vertical secondary pane as a fraction of the container without persisting', async () => { + const { container } = render(SplitPane, { + props: { + storageKey: 'archive:reading-pane', + ariaLabel: 'Resize reading pane', + orientation: 'vertical', + initialFraction: 0.55, + minPrimary: 120, + minSecondary: 160 + } + }); + const host = container.querySelector('[data-split-pane]')!; + const secondary = container.querySelector('[data-pane="secondary"]') as HTMLElement; + + reportSize(host, { height: 800 }); + await waitFor(() => expect(secondary.style.flexBasis).toBe('440px')); + expect(localStorage.getItem('archive:reading-pane')).toBeNull(); + + // The proportional default keeps following the container until the user + // resizes explicitly. + reportSize(host, { height: 1000 }); + await waitFor(() => expect(secondary.style.flexBasis).toBe('550px')); + expect(localStorage.getItem('archive:reading-pane')).toBeNull(); + }); + + it('persists an explicit vertical resize and restores it on the next mount', async () => { + const first = render(SplitPane, { + props: { + storageKey: 'archive:reading-pane', + ariaLabel: 'Resize reading pane', + orientation: 'vertical', + initialFraction: 0.55, + minPrimary: 120, + minSecondary: 160 + } + }); + const host = first.container.querySelector('[data-split-pane]')!; + const secondary = first.container.querySelector('[data-pane="secondary"]') as HTMLElement; + reportSize(host, { height: 800 }); + await waitFor(() => expect(secondary.style.flexBasis).toBe('440px')); + + await fireEvent.keyDown(screen.getByRole('button', { name: 'Resize reading pane' }), { + key: 'ArrowUp' + }); + expect(secondary.style.flexBasis).toBe('464px'); + expect(localStorage.getItem('archive:reading-pane')).toBe('464'); + + await fireEvent.keyDown(screen.getByRole('button', { name: 'Resize reading pane' }), { + key: 'ArrowDown' + }); + expect(secondary.style.flexBasis).toBe('440px'); + expect(localStorage.getItem('archive:reading-pane')).toBe('440'); + + first.unmount(); + const second = render(SplitPane, { + props: { + storageKey: 'archive:reading-pane', + ariaLabel: 'Resize reading pane', + orientation: 'vertical', + initialFraction: 0.55 + } + }); + const restored = second.container.querySelector('[data-pane="secondary"]') as HTMLElement; + expect(restored.style.flexBasis).toBe('440px'); + }); + + it('drags a vertical handle with the pointer, growing the pane as the handle moves up', async () => { + const { container } = render(SplitPane, { + props: { + storageKey: 'archive:reading-pane', + ariaLabel: 'Resize reading pane', + orientation: 'vertical', + initialSize: 300, + minPrimary: 120, + minSecondary: 160 + } + }); + const host = container.querySelector('[data-split-pane]')!; + const secondary = container.querySelector('[data-pane="secondary"]') as HTMLElement; + reportSize(host, { height: 800 }); + + const handle = screen.getByRole('button', { name: 'Resize reading pane' }); + await fireEvent.mouseDown(handle, { clientY: 500 }); + await fireEvent.mouseMove(window, { clientY: 420 }); + expect(secondary.style.flexBasis).toBe('380px'); + await fireEvent.mouseUp(window, { clientY: 420 }); + expect(localStorage.getItem('archive:reading-pane')).toBe('380'); + }); + + it('collapses to the primary pane only, without a handle', () => { + const { container } = render(SplitPane, { + props: { + storageKey: 'archive:reading-pane', + ariaLabel: 'Resize reading pane', + orientation: 'vertical', + collapsed: true + } + }); + + expect(container.querySelector('[data-pane="secondary"]')).toBeNull(); + expect(screen.queryByRole('button', { name: 'Resize reading pane' })).toBeNull(); + }); + + it('caps the vertical pane only at the opposite pane minimum, not an arbitrary maximum', async () => { + const { container } = render(SplitPane, { + props: { + storageKey: 'archive:reading-pane', + ariaLabel: 'Resize reading pane', + orientation: 'vertical', + initialSize: 300, + minPrimary: 120, + minSecondary: 160 + } + }); + const host = container.querySelector('[data-split-pane]')!; + const secondary = container.querySelector('[data-pane="secondary"]') as HTMLElement; + reportSize(host, { height: 800 }); + + const handle = screen.getByRole('button', { name: 'Resize reading pane' }); + for (let step = 0; step < 30; step += 1) { + await fireEvent.keyDown(handle, { key: 'ArrowUp' }); + } + // 800 - 120 (list minimum) - 5 (handle) = 675. + expect(secondary.style.flexBasis).toBe('675px'); + + for (let step = 0; step < 30; step += 1) { + await fireEvent.keyDown(handle, { key: 'ArrowDown' }); + } + expect(secondary.style.flexBasis).toBe('160px'); + }); }); From 6982bd1a1ff4ee11b1fcc58033eb32926557d0d9 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 16:21:02 -0500 Subject: [PATCH 014/102] feat(web): render archived content seamlessly without entry gating The archived-content frame drops the Enter/Exit toolbar, pointer shield, and inert gating: the sanitized document renders immediately, sized to its content by a new bridge height report, so the thread scrolls as one column. Remote-image consent collapses to one quiet inline notice. The frame is Tab-focusable; the bridge still forwards Escape (focus exits to the surrounding scroller) and scroll/navigation keys, and the sandbox, CSP, nonce, and CID resolution pipeline are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../reader/ContentFrame.browser.svelte.ts | 9 + .../reader/ContentFrame.browser.test.ts | 30 ++- .../lib/components/reader/ContentFrame.svelte | 248 ++++++++---------- .../components/reader/ContentFrame.test.ts | 114 ++++---- web/src/lib/content/frame-document.ts | 6 +- 5 files changed, 205 insertions(+), 202 deletions(-) diff --git a/web/src/lib/components/reader/ContentFrame.browser.svelte.ts b/web/src/lib/components/reader/ContentFrame.browser.svelte.ts index d679e029f..4634b60f0 100644 --- a/web/src/lib/components/reader/ContentFrame.browser.svelte.ts +++ b/web/src/lib/components/reader/ContentFrame.browser.svelte.ts @@ -1,5 +1,6 @@ import { ARCHIVED_CONTENT_CHANNEL, + MAX_ARCHIVED_FRAME_HEIGHT, MAX_ARCHIVED_SCROLL_DELTA } from '../../content/frame-document'; @@ -10,6 +11,7 @@ interface ArchivedContentHandlerOptions { nonce: () => string; onKey: (key: string) => void; onScroll: (deltaY: number) => void; + onHeight: (height: number) => void; } function exactKeys(value: Record<string, unknown>, expected: string[]): boolean { @@ -36,6 +38,13 @@ export function createArchivedContentMessageHandler( if (typeof data.deltaY !== 'number' || !Number.isFinite(data.deltaY) || Math.abs(data.deltaY) > MAX_ARCHIVED_SCROLL_DELTA) return; options.onScroll(data.deltaY); + return; + } + if (data.type === 'height') { + if (!exactKeys(data, ['channel', 'nonce', 'type', 'height'])) return; + if (typeof data.height !== 'number' || !Number.isFinite(data.height) || + data.height <= 0 || data.height > MAX_ARCHIVED_FRAME_HEIGHT) return; + options.onHeight(data.height); } }; } diff --git a/web/src/lib/components/reader/ContentFrame.browser.test.ts b/web/src/lib/components/reader/ContentFrame.browser.test.ts index 38657aea8..1e2c0da65 100644 --- a/web/src/lib/components/reader/ContentFrame.browser.test.ts +++ b/web/src/lib/components/reader/ContentFrame.browser.test.ts @@ -16,7 +16,8 @@ describe('archived content frame message boundary', () => { frameWindow: () => frameWindow, nonce: () => 'secret-nonce', onKey, - onScroll + onScroll, + onHeight: vi.fn() }); const valid = { channel: 'msgvault-archived-content', @@ -46,7 +47,8 @@ describe('archived content frame message boundary', () => { frameWindow: () => frameWindow, nonce: () => 'nonce', onKey: vi.fn(), - onScroll + onScroll, + onHeight: vi.fn() }); const base = { channel: 'msgvault-archived-content', nonce: 'nonce', type: 'scroll' }; @@ -60,4 +62,28 @@ describe('archived content frame message boundary', () => { expect(onScroll).toHaveBeenCalledTimes(1); expect(onScroll).toHaveBeenCalledWith(12.5); }); + + it('accepts bounded positive content heights only through the exact height schema', () => { + const frameWindow = {} as Window; + const onHeight = vi.fn(); + const handle = createArchivedContentMessageHandler({ + frameWindow: () => frameWindow, + nonce: () => 'nonce', + onKey: vi.fn(), + onScroll: vi.fn(), + onHeight + }); + const base = { channel: 'msgvault-archived-content', nonce: 'nonce', type: 'height' }; + + handle(event(frameWindow, { ...base, height: 480 })); + handle(event(frameWindow, { ...base, height: 0 })); + handle(event(frameWindow, { ...base, height: -20 })); + handle(event(frameWindow, { ...base, height: 65_537 })); + handle(event(frameWindow, { ...base, height: Number.POSITIVE_INFINITY })); + handle(event(frameWindow, { ...base, height: '480' })); + handle(event(frameWindow, { ...base, height: 480, key: 'Escape' })); + + expect(onHeight).toHaveBeenCalledTimes(1); + expect(onHeight).toHaveBeenCalledWith(480); + }); }); diff --git a/web/src/lib/components/reader/ContentFrame.svelte b/web/src/lib/components/reader/ContentFrame.svelte index e9b769feb..4b73cfe09 100644 --- a/web/src/lib/components/reader/ContentFrame.svelte +++ b/web/src/lib/components/reader/ContentFrame.svelte @@ -1,6 +1,5 @@ <script lang="ts"> - import { Button } from '@kenn-io/kit-ui'; - import { onDestroy, onMount, tick, untrack } from 'svelte'; + import { onDestroy, onMount, untrack } from 'svelte'; import type { APIClient } from '../../api/client'; import { buildFrameDocument } from '../../content/frame-document'; @@ -16,26 +15,25 @@ messageId, html, title, - onContentFocusChange = undefined, - onFrameKey = undefined + onEscapeContent = undefined }: { client?: APIClient; messageId: number; html: string; title: string; - onContentFocusChange?: (focused: boolean) => void; - onFrameKey?: (key: string) => void; + /** Called when Escape is pressed while focus is inside the archived + * frame — the keyboard path out of the content. Defaults to focusing + * the nearest scrollable ancestor. */ + onEscapeContent?: () => void; } = $props(); + let host = $state<HTMLElement>(); let frame = $state<HTMLIFrameElement>(); - let enterControl = $state<HTMLSpanElement>(); - let toolbar = $state<HTMLDivElement>(); - let scrollShell = $state<HTMLDivElement>(); let frameDocument = $state<{ generation: number; srcdoc: string }>(); let documentState = $state<'building' | 'loading' | 'ready' | 'failed'>('building'); let remoteImageCount = $state(0); let remoteImagesAllowed = $state(false); - let contentEntered = $state(false); + let frameHeight = $state(96); let nonce = $state(createFrameNonce()); let documentGeneration = 0; let messageIdentity = ''; @@ -44,11 +42,6 @@ function invalidateDocument(): void { documentGeneration += 1; inlineController?.abort(); - if (contentEntered) { - contentEntered = false; - onContentFocusChange?.(false); - toolbar?.focus({ preventScroll: true }); - } frameDocument = undefined; documentState = 'building'; } @@ -100,42 +93,55 @@ documentState = 'ready'; } - async function exitContent(): Promise<void> { - if (!contentEntered) return; - contentEntered = false; - onContentFocusChange?.(false); - await tick(); - enterControl?.querySelector('button')?.focus(); - } - - async function enterContent(): Promise<void> { - if (documentState !== 'ready' || !frame) return; - contentEntered = true; - onContentFocusChange?.(true); - await tick(); - frame?.focus(); - } - function loadRemoteImages(): void { if (remoteImagesAllowed || documentState !== 'ready') return; invalidateDocument(); remoteImagesAllowed = true; } - function handleFrameKey(key: string): void { - if (!contentEntered) return; - if (key === 'Escape') { - void exitContent(); + function nearestScroller(): HTMLElement | undefined { + let node = host?.parentElement ?? undefined; + while (node) { + const style = getComputedStyle(node); + if ((style.overflowY === 'auto' || style.overflowY === 'scroll') && + node.scrollHeight > node.clientHeight) { + return node; + } + node = node.parentElement ?? undefined; + } + return undefined; + } + + function scrollThread(deltaY: number): void { + nearestScroller()?.scrollBy({ top: deltaY }); + } + + function exitContent(): void { + if (onEscapeContent) { + onEscapeContent(); return; } - onFrameKey?.(key); + const scroller = nearestScroller(); + if (scroller && scroller.tabIndex >= 0) scroller.focus(); + } + + function handleFrameKey(key: string): void { + const scroller = nearestScroller(); + if (key === 'Escape') exitContent(); + else if (key === 'ArrowDown') scrollThread(48); + else if (key === 'ArrowUp') scrollThread(-48); + else if (key === 'PageDown') scrollThread((scroller?.clientHeight ?? 480) * 0.9); + else if (key === 'PageUp') scrollThread(-(scroller?.clientHeight ?? 480) * 0.9); + else if (key === 'Home') scroller?.scrollTo({ top: 0 }); + else if (key === 'End') scroller?.scrollTo({ top: scroller.scrollHeight }); } const messageHandler = createArchivedContentMessageHandler({ frameWindow: () => frame?.contentWindow ?? null, nonce: () => nonce, onKey: handleFrameKey, - onScroll: (deltaY) => scrollShell?.scrollBy({ top: deltaY }) + onScroll: scrollThread, + onHeight: (height) => { frameHeight = height; } }); onMount(() => window.addEventListener('message', messageHandler)); @@ -143,141 +149,97 @@ window.removeEventListener('message', messageHandler); inlineController?.abort(); documentGeneration += 1; - if (contentEntered) onContentFocusChange?.(false); }); </script> -<section - class="content-frame" - class:content-frame--entered={contentEntered} - aria-label={title} -> - <div - bind:this={toolbar} - class="content-toolbar" - aria-label="Archived content controls" - tabindex="-1" - > - {#if contentEntered} - <span class="content-state" role="status">Archived content active</span> - <Button - size="sm" - surface="outline" - label="Exit content" - ariaLabel="Exit archived content" - onclick={() => { void exitContent(); }} - /> - {:else} - <span bind:this={enterControl}> - <Button - size="sm" - surface="outline" - label="Enter content" - ariaLabel="Enter archived content" - disabled={documentState !== 'ready'} - onclick={() => { void enterContent(); }} - /> - </span> - {/if} - {#if documentState === 'building' || documentState === 'loading'} - <span class="content-state" role="status">Preparing archived content…</span> - {:else if documentState === 'failed'} - <span class="content-state" role="alert">Archived content unavailable</span> - {/if} - {#if remoteImageCount > 0 && !remoteImagesAllowed} - <Button - size="sm" - surface="soft" - label={`Load ${remoteImageCount} remote ${remoteImageCount === 1 ? 'image' : 'images'}`} - ariaLabel={`Load ${remoteImageCount} remote ${remoteImageCount === 1 ? 'image' : 'images'}`} +<section class="content-frame" aria-label={title} bind:this={host}> + {#if remoteImageCount > 0 && !remoteImagesAllowed} + <p class="remote-notice" role="status"> + <span>{remoteImageCount === 1 + ? '1 remote image is not loaded.' + : `${remoteImageCount} remote images are not loaded.`}</span> + <button + type="button" + aria-label={`Load ${remoteImageCount} remote ${remoteImageCount === 1 ? 'image' : 'images'}`} disabled={documentState !== 'ready'} onclick={loadRemoteImages} - /> - {/if} - </div> - <div bind:this={scrollShell} class="frame-scroll"> - {#if !contentEntered} - <div - class="content-entry-shield" - aria-hidden="true" - onpointerdown={(event) => event.preventDefault()} - ></div> - {/if} - {#if frameDocument} - {#key frameDocument.generation} - <iframe - bind:this={frame} - title={title} - sandbox="allow-scripts" - referrerpolicy="no-referrer" - tabindex="-1" - inert={!contentEntered} - srcdoc={frameDocument.srcdoc} - onload={() => handleFrameLoad(frameDocument!.generation)} - ></iframe> - {/key} - {/if} - </div> + >Load images</button> + </p> + {/if} + {#if documentState === 'failed'} + <p class="frame-state" role="alert">Archived content unavailable</p> + {:else if !frameDocument} + <p class="frame-state" role="status">Preparing message…</p> + {/if} + {#if frameDocument} + {#key frameDocument.generation} + <iframe + bind:this={frame} + title={title} + sandbox="allow-scripts" + referrerpolicy="no-referrer" + tabindex="0" + srcdoc={frameDocument.srcdoc} + style:height={`${frameHeight}px`} + onload={() => handleFrameLoad(frameDocument!.generation)} + ></iframe> + {/key} + {/if} </section> <style> .content-frame { display: flex; - min-height: 240px; + min-width: 0; flex-direction: column; - overflow: hidden; - border: 1px solid var(--border-default); - border-radius: var(--radius-md); - background: var(--bg-surface); - } - - .content-frame--entered { - outline: 2px solid var(--accent-blue); - outline-offset: 2px; + gap: var(--space-2); } - .content-toolbar { - position: sticky; - z-index: 1; - top: 0; + .remote-notice { display: flex; - min-height: 36px; align-items: center; - gap: var(--space-2); - padding: var(--space-2); - border-bottom: 1px solid var(--border-muted); - background: var(--bg-surface); - } - - .content-state { - color: var(--text-secondary); + gap: var(--space-3); + margin: 0; + color: var(--text-muted); font-size: var(--font-size-xs); } - .frame-scroll { - position: relative; - min-height: 0; - flex: 1; - overflow: auto; + .remote-notice button { + padding: 0; + border: 0; + background: none; + color: var(--accent-blue); + cursor: pointer; + font: inherit; } - .content-entry-shield { - position: absolute; - z-index: 1; - inset: 0; + .remote-notice button:disabled { + color: var(--text-muted); cursor: default; } + .remote-notice button:hover:not(:disabled) { + text-decoration: underline; + } + + .frame-state { + margin: 0; + color: var(--text-muted); + font-size: var(--font-size-xs); + } + + /* HTML mail assumes a white canvas; keep it white in both themes but + * seamless — rounded, borderless, sized to its content by the bridge. */ iframe { display: block; width: 100%; - min-height: 320px; border: 0; + border-radius: var(--radius-sm); background: white; } - iframe[inert] { - pointer-events: none; - user-select: none; + iframe:focus-visible { + outline: var(--focus-ring); + outline-offset: 1px; } </style> diff --git a/web/src/lib/components/reader/ContentFrame.test.ts b/web/src/lib/components/reader/ContentFrame.test.ts index 860200c57..9a6bffe24 100644 --- a/web/src/lib/components/reader/ContentFrame.test.ts +++ b/web/src/lib/components/reader/ContentFrame.test.ts @@ -23,7 +23,7 @@ function deferred<T>(): { } describe('ContentFrame', () => { - it('uses an opaque scripted sandbox without same-origin authority', async () => { + it('uses an opaque scripted sandbox that is focusable but never same-origin', async () => { const { container } = render(ContentFrame, { props: { messageId: 42, html: '<p>Archived content</p>', title: 'Archived message' } }); @@ -32,31 +32,28 @@ describe('ContentFrame', () => { const frame = container.querySelector('iframe'); expect(frame?.getAttribute('sandbox')).toBe('allow-scripts'); expect(frame?.getAttribute('sandbox')).not.toContain('allow-same-origin'); - expect(frame?.getAttribute('tabindex')).toBe('-1'); - expect((frame as HTMLIFrameElement & { inert: boolean }).inert).toBe(true); + // The frame is a first-class focus target: keyboard users Tab into it + // and the bridge forwards Escape back out. No entry gating remains. + expect(frame?.getAttribute('tabindex')).toBe('0'); + expect(frame?.hasAttribute('inert')).toBe(false); await waitFor(() => expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain('Archived content') ); }); - it('keeps focus in the shell until explicit content entry', async () => { - render(ContentFrame, { + it('renders archived content immediately, with no entry gating chrome', async () => { + const { container } = render(ContentFrame, { props: { messageId: 42, html: '<p>Archived content</p>', title: 'Archived message' } }); - const enter = screen.getByRole('button', { name: 'Enter archived content' }); - await waitFor(() => expect(document.querySelector('iframe')).not.toBeNull()); - await fireEvent.load(document.querySelector('iframe')!); - enter.focus(); - - expect(screen.queryByText('Archived content active')).toBeNull(); - await fireEvent.click(enter); - expect(screen.getByText('Archived content active')).toBeTruthy(); - expect(screen.getByRole('button', { name: 'Exit archived content' })).toBeTruthy(); - expect((document.querySelector('iframe') as HTMLIFrameElement & { inert: boolean }).inert).toBe(false); + await waitFor(() => expect(container.querySelector('iframe')).not.toBeNull()); + expect(screen.queryByRole('button', { name: /Enter archived content/ })).toBeNull(); + expect(screen.queryByRole('button', { name: /Exit archived content/ })).toBeNull(); + expect(screen.queryByText(/Archived content active/)).toBeNull(); + expect(container.querySelector('.content-entry-shield')).toBeNull(); }); - it('keeps entry unavailable until delayed CID resolution and the final iframe load', async () => { + it('keeps a quiet preparing state until delayed CID resolution completes', async () => { const response = deferred<Response>(); const fetchFn = vi.fn<typeof fetch>(async () => await response.promise); const { container } = render(ContentFrame, { @@ -69,47 +66,16 @@ describe('ContentFrame', () => { }); await waitFor(() => expect(fetchFn).toHaveBeenCalledOnce()); - const enter = screen.getByRole('button', { name: 'Enter archived content' }); - expect((enter as HTMLButtonElement).disabled).toBe(true); + expect(screen.getByRole('status').textContent).toContain('Preparing message'); expect(container.querySelector('iframe')).toBeNull(); response.resolve(new Response(onePixelPNG, { headers: { 'Content-Type': 'image/png' } })); await waitFor(() => expect(container.querySelector('iframe')).not.toBeNull()); - expect((enter as HTMLButtonElement).disabled).toBe(true); await fireEvent.load(container.querySelector('iframe')!); - expect((enter as HTMLButtonElement).disabled).toBe(false); + expect(screen.queryByText(/Preparing message/)).toBeNull(); }); - it('synchronously exits and focuses stable shell chrome before consent replaces an entered frame', async () => { - const onContentFocusChange = vi.fn(); - const { container } = render(ContentFrame, { - props: { - messageId: 42, - html: '<p>Words</p><img src="https://images.example/chart.png" alt="Chart">', - title: 'Archived message', - onContentFocusChange - } - }); - await waitFor(() => expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain('Words')); - await fireEvent.load(container.querySelector('iframe')!); - await fireEvent.click(screen.getByRole('button', { name: 'Enter archived content' })); - expect(screen.getByText('Archived content active')).toBeTruthy(); - - await fireEvent.click(screen.getByRole('button', { name: 'Load 1 remote image' })); - - expect(screen.queryByText('Archived content active')).toBeNull(); - expect(onContentFocusChange).toHaveBeenLastCalledWith(false); - expect(container.querySelector('iframe')).toBeNull(); - expect(document.activeElement).toBe(screen.getByLabelText('Archived content controls')); - const enter = screen.getByRole('button', { name: 'Enter archived content' }); - expect((enter as HTMLButtonElement).disabled).toBe(true); - await waitFor(() => expect(container.querySelector('iframe')).not.toBeNull()); - expect((enter as HTMLButtonElement).disabled).toBe(true); - await fireEvent.load(container.querySelector('iframe')!); - expect((enter as HTMLButtonElement).disabled).toBe(false); - }); - - it('publishes a failed CID placeholder but still requires the final iframe load before entry', async () => { + it('publishes a failed CID placeholder inside the rendered document', async () => { const { container } = render(ContentFrame, { props: { client: createAPIClient(vi.fn<typeof fetch>(async () => new Response('missing', { status: 404 }))), @@ -119,16 +85,11 @@ describe('ContentFrame', () => { } }); - const enter = screen.getByRole('button', { name: 'Enter archived content' }); - expect((enter as HTMLButtonElement).disabled).toBe(true); await waitFor(() => { expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain( 'Inline image unavailable: Missing' ); }); - expect((enter as HTMLButtonElement).disabled).toBe(true); - await fireEvent.load(container.querySelector('iframe')!); - expect((enter as HTMLButtonElement).disabled).toBe(false); }); it('aborts old message work and ignores its stale completion after a replacement is ready', async () => { @@ -238,7 +199,7 @@ describe('ContentFrame', () => { await Promise.resolve(); }); - it('keeps remote image consent in the shell and rebuilds only after consent', async () => { + it('keeps remote image consent as one quiet inline notice and rebuilds only after consent', async () => { const { container } = render(ContentFrame, { props: { messageId: 42, @@ -247,6 +208,7 @@ describe('ContentFrame', () => { } }); const consent = await screen.findByRole('button', { name: 'Load 1 remote image' }); + expect(screen.getByText('1 remote image is not loaded.')).toBeDefined(); await waitFor(() => { const srcdoc = container.querySelector('iframe')?.getAttribute('srcdoc'); expect(srcdoc).toContain('Remote image blocked: Chart'); @@ -261,5 +223,45 @@ describe('ContentFrame', () => { 'https://images.example/chart.png' ) ); + expect(screen.queryByRole('button', { name: /remote image/ })).toBeNull(); + }); + + it('detaches the consent-superseded frame before the newly capable document renders', async () => { + const { container } = render(ContentFrame, { + props: { + messageId: 42, + html: '<p>Words</p><img src="https://images.example/chart.png" alt="Chart">', + title: 'Archived message' + } + }); + await waitFor(() => expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain('Words')); + await fireEvent.load(container.querySelector('iframe')!); + const priorFrame = container.querySelector('iframe'); + + await fireEvent.click(screen.getByRole('button', { name: 'Load 1 remote image' })); + + expect(container.querySelector('iframe')).toBeNull(); + expect(priorFrame?.isConnected).toBe(false); + await waitFor(() => + expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain('images.example') + ); + }); + + it('applies bridge-reported content heights to the frame element', async () => { + const { container } = render(ContentFrame, { + props: { messageId: 42, html: '<p>Archived content</p>', title: 'Archived message' } + }); + await waitFor(() => expect(container.querySelector('iframe')).not.toBeNull()); + const frame = container.querySelector('iframe') as HTMLIFrameElement; + const nonce = /data-bridge-nonce="([^"]+)"/.exec(frame.getAttribute('srcdoc') ?? '')?.[1]; + expect(nonce).toBeTruthy(); + + window.dispatchEvent(new MessageEvent('message', { + source: frame.contentWindow, + origin: 'null', + data: { channel: 'msgvault-archived-content', nonce, type: 'height', height: 732 } + })); + + await waitFor(() => expect(frame.style.height).toBe('732px')); }); }); diff --git a/web/src/lib/content/frame-document.ts b/web/src/lib/content/frame-document.ts index 32eb3cb87..dbe889c11 100644 --- a/web/src/lib/content/frame-document.ts +++ b/web/src/lib/content/frame-document.ts @@ -1,10 +1,14 @@ export const ARCHIVED_CONTENT_CHANNEL = 'msgvault-archived-content'; export const MAX_ARCHIVED_SCROLL_DELTA = 10_000; +export const MAX_ARCHIVED_FRAME_HEIGHT = 65_536; // This generated script is the only executable content admitted by the frame // CSP. Its sole dynamic value is a validated, JSON-serialized shell origin. +// Besides key/scroll forwarding it reports the document's natural height so +// the shell can size the frame to its content (the thread scrolls as one +// column; the frame itself never scrolls internally). export function archivedContentBridge(targetOrigin: string): string { - return `(()=>{const n=document.documentElement.dataset.bridgeNonce;const c='${ARCHIVED_CONTENT_CHANNEL}';const o=${JSON.stringify(targetOrigin)};const keys=new Set(['Escape','PageUp','PageDown','Home','End','ArrowUp','ArrowDown']);addEventListener('keydown',e=>{if(keys.has(e.key))parent.postMessage({channel:c,nonce:n,type:'key',key:e.key},o)});addEventListener('wheel',e=>{if(Number.isFinite(e.deltaY)&&Math.abs(e.deltaY)<=${MAX_ARCHIVED_SCROLL_DELTA})parent.postMessage({channel:c,nonce:n,type:'scroll',deltaY:e.deltaY},o)},{passive:true})})()`; + return `(()=>{const n=document.documentElement.dataset.bridgeNonce;const c='${ARCHIVED_CONTENT_CHANNEL}';const o=${JSON.stringify(targetOrigin)};const keys=new Set(['Escape','PageUp','PageDown','Home','End','ArrowUp','ArrowDown']);addEventListener('keydown',e=>{if(keys.has(e.key))parent.postMessage({channel:c,nonce:n,type:'key',key:e.key},o)});addEventListener('wheel',e=>{if(Number.isFinite(e.deltaY)&&Math.abs(e.deltaY)<=${MAX_ARCHIVED_SCROLL_DELTA})parent.postMessage({channel:c,nonce:n,type:'scroll',deltaY:e.deltaY},o)},{passive:true});const h=()=>{const v=Math.min(Math.ceil(document.documentElement.scrollHeight),${MAX_ARCHIVED_FRAME_HEIGHT});if(v>0)parent.postMessage({channel:c,nonce:n,type:'height',height:v},o)};if(typeof ResizeObserver==='function')new ResizeObserver(h).observe(document.body);addEventListener('load',h);h()})()`; } export interface FrameDocumentOptions { From 6ec12f278254d17a068ae05b5d6767cda9a747b7 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 16:24:40 -0500 Subject: [PATCH 015/102] feat(web): rebuild the conversation view as a mail-client thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each message is a card: collapsed cards show sender, one-line snippet, and date, and expand inline on click without refetching; the anchor starts expanded and scrolled into view. Expanded cards render sender, recipients, date, and subject over the body directly — HTML by default with plain text behind a small per-message overflow control. The toolbar, one-line message buttons, and boxed selected-message frame are gone; bounded windows collapse to quiet inline notices. MessageCard absorbs MessageReader. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../lib/components/reader/ContentFrame.svelte | 3 + .../components/reader/ConversationView.svelte | Bin 7733 -> 7970 bytes .../reader/ConversationView.test.ts | 153 +++++---- .../lib/components/reader/MessageCard.svelte | 297 ++++++++++++++++++ .../lib/components/reader/MessageCard.test.ts | 172 ++++++++++ .../components/reader/MessageReader.svelte | 118 ------- .../components/reader/MessageReader.test.ts | 129 -------- 7 files changed, 565 insertions(+), 307 deletions(-) create mode 100644 web/src/lib/components/reader/MessageCard.svelte create mode 100644 web/src/lib/components/reader/MessageCard.test.ts delete mode 100644 web/src/lib/components/reader/MessageReader.svelte delete mode 100644 web/src/lib/components/reader/MessageReader.test.ts diff --git a/web/src/lib/components/reader/ContentFrame.svelte b/web/src/lib/components/reader/ContentFrame.svelte index 4b73cfe09..62d90a556 100644 --- a/web/src/lib/components/reader/ContentFrame.svelte +++ b/web/src/lib/components/reader/ContentFrame.svelte @@ -173,6 +173,9 @@ {/if} {#if frameDocument} {#key frameDocument.generation} + <!-- svelte-ignore a11y_no_noninteractive_tabindex -- the sandboxed + frame is the keyboard path into archived content; the bridge + forwards Escape back out to the surrounding scroller. --> <iframe bind:this={frame} title={title} diff --git a/web/src/lib/components/reader/ConversationView.svelte b/web/src/lib/components/reader/ConversationView.svelte index 2693bccd55a6543a684244ed4120f00482485e8d..dc82b68b161dd21e0103542035218c2251ab8f78 100644 GIT binary patch delta 2306 zcmZuy&u<$=6vhcnT9-7aO-vG?eX*k27TI-*f`nYJLusQ{r9qVvK@mdDcz0}%vfeQ} zV<(ntnKQrJK|)AephpfBGMu>43ljVR91#){2QCPSE4<lRZ{xO-59@hv-uvEr-}~nK z^4FD5Pv5?Jd`mKSZxz~s42io3eW-=J2|3ZBzNESHX#m!2@lZk!wxhFcDj)JHhLpIH zb(rjayO&NaTqL0vhp~%WR0vY1`b^zUy{~c;r`6s>|ITD_Sev|*IwRbW`#uf9ffXT% zq~$9+Th}&y+N6Q3<cD89nM>td9ta8aphW_YdgvO^{ReQ$LAzO{VI{BbAGvzjM|?gf z==UZIJih-L?c#-8REmj10=L1#Ymtvm>(P*PsF!aVsKOS!N!9w)+Mrm3O`TLQ9sm;< z*Cl=lNR;1%0ofo@+lY^C@Vdaz+oO0)V;I;%jI$qsiE9w(1udVr6whUYw<UzMMy1<e zLEVN6;IUebhCA2-K+GK)3L+81s)=Q++F0f7z!R&$Lm&bEbYXzu5(shHkm{CTT4^gX zb#GuAVT`6hyDPnrzT2ikQt#rSl>H|T8MY-sCTwhAJIBs911~<^)r>*T)F6K-y5o$G z<Oa^<?Agc@M%2sa3*b~>YzX<$Ob(lDh77=n3ET@&*@Z#@dg_&#j5;$tfAQh4BktJ5 z^YV|}G@t=P%61r-e|>d)dgFLf#3~H0PTx(POb$vQWQgNh9sYUrkIA{mQEqtusUxWY zjMpB~F^MwDoZxePO2T~Mk#mjPBa+x=$Tyjn_;fgSB%~cmeA9|C$4T@Y{y39LJr<(+ z;lzFQ)6v<%*nQ@ez${gv1lLgzn4o2yrUJcaYGj`<30gWvPz|G8z4{J}mU_cRMupvN z>Qh(paD26Q-`4e5X5a&AG0(}a^qAMrzOx(GrfIYs#k_S3mPb(wV{;mv+U8@2w-CS< zkqx^^4)SZOpzqtfRufceqXjQV(fetvq}C2lxp29sB(K+fsy>)q86>l45Ru33A+KVp zCKeAXl2jW<gBqhw<eoU|(IuX`BplZ=5;zh~8nZXaLDZF*6I0C7fz>aw&nYoGH~e?@ z*NMThWr5LUi`4@jQXuEfb#De7e*p_5MFmG0rZ*+2qT0{{zzbT3WJ60+G8I%SQxAbX z+O2Zzk7b%isy@Z$5)XL|q8-+>LZRkHQQoR?w=DoAppFu|DO}Q`CXT5cP29+SEP+~) zLX<SrR;^-E3sx;8O==^sO1dnN6xjPTs57VLuXh;T&tg0hGGh6pN_{7LScu{(w9BYI zC%dNq>S9Z>!GzNrk)fZisP(yTrVTz&zbz~ui@yQx%x6Jo>q#z&4;5I04cLX>-}?+i zgX3z}VMYCSYW~uS@#ybDNh_WmM<^FtmFTlqWVL?uRrXj1T%QQxWMc)hOxM+o`O-{z z>%l9SR<CAqs+oBqGa`u78-3ZxIQ8Me!cnv2PEUP4pHp9EURvbAPDD@#A!(w<0sCq7 zb7p;_SW(|CG$*krWiMvt%x&|)8GJ~kt;IKvL|E0`MdxIT3#OZ2!g2ffwsffacxh4n zv-tWf=m*4S0Yweq0aeb@Gj-jrQZ#?7!vxbbF*}jCc2Kn}%eZJ+j29!eTW0@CV7=Hn z(9Dju&mM0T%J(Nr3+mL;Qt7eg3Y6+TuM$6>T-3W=!nR8bdFFfi1^9buF14n<c>bfO b!C(?CWU;kgIIsSC_VTfEQOK@OE9w6MV}$$3 delta 2183 zcmb7FO>7%Q6qZdwNswqzLe)5tH8BC(QrC7u0pZ#UwEPH^fJ6o2nDOp-J@tCV%*;A* zH<s^k05n44TyDUniYg9B2sm)z1b4V|;=-*L-t6oqP6!oPvgCdH=FRuM?|aX8+<UE0 ziqBq;l;R9cP*?B)D!<exj5(Co`&2m*Enizihz^EaC=?^kULjHmKK|5MyF0#+TYraO zpNRBmt-ed7!~wBU{dw-gx$D^L>q7qN$2hyenIcTR#=S`DO8&DY^R9_1Rn&ITh*MwR z%RgT^op+;$SwLQdtgbmm{3hyf9umxse#_s?9lbwS%;`Jxua-T|q(YK}#8bpaZL|rv z=!p20fyE?iUCMl2d$^&mUieyn_sFJxYvIxnU-%-Y`N9)N?xR0Hq{|oY>o1q?>mMHf zRu5Jd=J=2>{qgeVa}-Lp+X#CKYQD_89v4+4h>8R=1R-F?MpMAG3iYekj>yU%`Kbg* z6d3kGEM>a{NJm6SY+}xclKm3Gf?_Ac9TK)nh9yiHWdWMaEV*z^t7qvxuaW{<I#?um zkZDyl(5CQ}^fqai<e-#bh380gu?Ha@sgN>~#ZwGMm@H(l?O13gY^6ShEUn3z7gDd^ zjyW@llO$Ys7kh@DR&U#d&W;;z)gh2%#81*LQe5E>xu`K|$suNLd@Nc>SfF|n#Jea? zvL>y%O{vqaxdSRCq_~Uvqsw3E_UeWXR!hqr9x)$`un(g1{aUL&boKA6#d)a!o6Z-S z^U1~fmqK~k!a`9mB(plidp9#LrC88rhN?#W$=<|fdyx=e>UK$qh?FvoGaowX*;Ez7 zg!!~~XXj)-;X%JqDC@9L*MAjW&^v22BO83q8wW$Nu}L;e)^nC{dkP^SKKp4nN;^CW z#!V7RGJ9smGUlYc-x_D&XKt`HkL@FQSDk)k9Te}Kkd>^J!$VYlm-QLnXE3Coj3`gg zkKfF2Dx=OV7!+hOD-lyuszTyd%fR$ZCn#av`Z3;n8^#lzE04A5IUu4lAkI`jHgRPs zSxaUCq+;p2h09Abw8qAO`fzP<<s7diFB9B}%{DSaO!TGo_w)zrZT-_a-kmH$N^N$S zoOFy)oE#o<NzI;UA_)%dkL|9x?SQGtK@)A;XT^t2)X?9rufB;x8o>5eWFVW!1A`D@ za{>%GBP=S81LNZn=b5T~F~q(P%jO?1H7c%5w}uBu@({M4J=);{q;q`I#Zjoz3LTiJ zK$v;UigW@ewMdfkfHr&EX{Q6qoPFx69&}=XbwjcYJ#N{j0TuxT9vb@3;>OhhELR}t z0fo(uxMJAN#EJE<#n-1Mk{ZTIRzZ5bPvtPg<0c9O_3e%So3Ud+r!R0(GYEMHhm~3% zOzm`%|J!PMaoY1%qw$oz_ErQ^cgGI6!VGv50S^ZTt57E`XLic=OJ<jU(Grj$_}>)% zln8Lm8BwukjN){{h-8XdnFaP~1U|Cb+b1Wb9_sx!*;D=#Ej+zb99^_+z#3QMkhqI~ E1N2?BQ2+n{ diff --git a/web/src/lib/components/reader/ConversationView.test.ts b/web/src/lib/components/reader/ConversationView.test.ts index 38d693692..53defdc08 100644 --- a/web/src/lib/components/reader/ConversationView.test.ts +++ b/web/src/lib/components/reader/ConversationView.test.ts @@ -8,7 +8,7 @@ function message(id: number, type = 'email') { return { id, conversation_id: 7, - subject: `Message ${id}`, + subject: `Message subject ${id}`, message_type: type, from: 'alice@example.com', to: ['bob@example.com'], @@ -24,7 +24,7 @@ function message(id: number, type = 'email') { } describe('ConversationView', () => { - it('loads the anchored chronological email thread and highlights the selected item', async () => { + it('loads the thread with the anchor expanded and the rest as one-line collapsed cards', async () => { const requests: Request[] = []; const fetchFn = vi.fn<typeof fetch>(async (input) => { const request = input instanceof Request ? input : new Request(input); @@ -36,12 +36,16 @@ describe('ConversationView', () => { }); render(ConversationView, { - props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack: vi.fn() } + props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2 } }); - expect(await screen.findByRole('heading', { name: 'Conversation' })).toBeTruthy(); - expect((await screen.findByRole('article', { name: 'Selected message 2' })).getAttribute('aria-current')).toBe('true'); - expect(screen.getByRole('button', { name: /Open message 1/ })).toBeTruthy(); + const anchorCard = await screen.findByRole('article', { name: 'Message 2' }); + expect(anchorCard.getAttribute('aria-current')).toBe('true'); + expect(anchorCard.textContent).toContain('alice@example.com'); + expect(anchorCard.textContent).toContain('to bob@example.com'); + expect(screen.getByRole('button', { name: /Expand message 1/ })).toBeTruthy(); + expect(screen.getByRole('button', { name: /Expand message 3/ })).toBeTruthy(); + expect(screen.queryByRole('article', { name: 'Message 1' })).toBeNull(); const url = new URL(requests[0]!.url); expect(url.pathname).toBe('/api/v1/conversations/7'); expect(url.searchParams.get('anchor')).toBe('2'); @@ -59,62 +63,92 @@ describe('ConversationView', () => { render(ConversationView, { props: { - client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack: vi.fn(), + client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, start: '2026-07-19T00:00:00.000Z', end: '2026-07-20T00:00:00.000Z' } }); - await screen.findByRole('article', { name: 'Selected message 2' }); + await screen.findByRole('article', { name: 'Message 2' }); const url = new URL(requests[0]!.url); expect(url.searchParams.get('start')).toBe('2026-07-19T00:00:00.000Z'); expect(url.searchParams.get('end')).toBe('2026-07-20T00:00:00.000Z'); }); - it('switches the selected conversation message to plain text', async () => { + it('switches an expanded message to plain text through its overflow control', async () => { const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ id: 7, anchor_id: 2, messages: [message(2)], has_before: false, has_after: false, total: 1 })); render(ConversationView, { - props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack: vi.fn() } + props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2 } }); - await screen.findByRole('article', { name: 'Selected message 2' }); - await fireEvent.click(screen.getByRole('button', { name: 'Text' })); - expect(screen.getByRole('button', { name: 'Text' }).getAttribute('aria-pressed')).toBe('true'); + await screen.findByRole('article', { name: 'Message 2' }); + await fireEvent.click(screen.getByText('⋯')); + await fireEvent.click(screen.getByRole('button', { name: 'Show plain text' })); expect(screen.getByText('Body 2')).toBeDefined(); }); - it('drills chat conversation rows to individual messages without losing conversation context', async () => { + it('expands additional messages inline without refetching and reports the anchor change', async () => { + const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ + id: 7, anchor_id: 2, messages: [message(1, 'imessage'), message(2, 'imessage')], + has_before: false, has_after: false, total: 2 + })); + const onAnchorChange = vi.fn(); + render(ConversationView, { + props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onAnchorChange } + }); + + await screen.findByRole('article', { name: 'Message 2' }); + await fireEvent.click(screen.getByRole('button', { name: /Expand message 1/ })); + + // Both stay expanded — a real thread, not a single-open stack. + expect(await screen.findByRole('article', { name: 'Message 1' })).toBeTruthy(); + expect(screen.getByRole('article', { name: 'Message 2' })).toBeTruthy(); + expect(onAnchorChange).toHaveBeenCalledWith(1); + expect(fetchFn).toHaveBeenCalledTimes(1); + }); + + it('collapses an expanded message back to its one-line card', async () => { + const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ + id: 7, anchor_id: 2, messages: [message(1), message(2)], + has_before: false, has_after: false, total: 2 + })); + render(ConversationView, { + props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2 } + }); + + await screen.findByRole('article', { name: 'Message 2' }); + await fireEvent.click(screen.getByRole('button', { name: /Collapse message 2/ })); + expect(screen.queryByRole('article', { name: 'Message 2' })).toBeNull(); + expect(screen.getByRole('button', { name: /Expand message 2/ })).toBeTruthy(); + expect(fetchFn).toHaveBeenCalledTimes(1); + }); + + it('refetches only when the requested anchor falls outside the loaded window', async () => { const requests: Request[] = []; const fetchFn = vi.fn<typeof fetch>(async (input) => { const request = input instanceof Request ? input : new Request(input); requests.push(request); const anchor = Number(new URL(request.url).searchParams.get('anchor')); return Response.json({ - id: 7, anchor_id: anchor, messages: [message(1, 'imessage'), message(2, 'imessage')], + id: 7, anchor_id: anchor, + messages: anchor > 90 ? [message(anchor % 10), { ...message(9), id: anchor }] : [message(1), message(2)], has_before: false, has_after: false, total: 2 }); }); const client = createAPIClient(fetchFn); - const onBack = vi.fn(); - let rendered: ReturnType<typeof render>; - const onAnchorChange = vi.fn((nextAnchor: number) => { - void rendered.rerender({ - client, conversationId: 7, anchorId: nextAnchor, onBack, onAnchorChange - }); - }); - rendered = render(ConversationView, { - props: { client, conversationId: 7, anchorId: 2, onBack, onAnchorChange } + const rendered = render(ConversationView, { + props: { client, conversationId: 7, anchorId: 2 } }); + await screen.findByRole('article', { name: 'Message 2' }); - await fireEvent.click(await screen.findByRole('button', { name: /Open message 1/ })); - - await waitFor(() => expect(onAnchorChange).toHaveBeenCalledWith(1)); + await rendered.rerender({ client, conversationId: 7, anchorId: 99 }); await waitFor(() => expect(requests).toHaveLength(2)); - expect(new URL(requests[1]!.url).searchParams.get('anchor')).toBe('1'); + expect(new URL(requests[1]!.url).searchParams.get('anchor')).toBe('99'); + expect(await screen.findByRole('article', { name: 'Message 99' })).toBeTruthy(); }); - it('aborts superseded anchors and ignores stale resolve or reject settlements', async () => { + it('aborts a superseded window load and ignores stale resolve or reject settlements', async () => { const pending = new Map<number, { request: Request; resolve: (response: Response) => void; @@ -132,31 +166,24 @@ describe('ConversationView', () => { }); }); const client = createAPIClient(fetchFn); - const onBack = vi.fn(); - let rendered: ReturnType<typeof render>; - const onAnchorChange = (nextAnchor: number): void => { - void rendered.rerender({ - client, conversationId: 7, anchorId: nextAnchor, onBack, onAnchorChange - }); - }; - rendered = render(ConversationView, { - props: { client, conversationId: 7, anchorId: 2, onBack, onAnchorChange } + const rendered = render(ConversationView, { + props: { client, conversationId: 7, anchorId: 2 } }); - await screen.findByRole('article', { name: 'Selected message 2' }); + await screen.findByRole('article', { name: 'Message 2' }); - await fireEvent.click(screen.getByRole('button', { name: /Open message 1/ })); - await waitFor(() => expect(pending.get(1)).toBeDefined()); - await fireEvent.click(screen.getByRole('button', { name: /Open message 3/ })); - await waitFor(() => expect(pending.get(3)).toBeDefined()); + await rendered.rerender({ client, conversationId: 7, anchorId: 98 }); + await waitFor(() => expect(pending.get(98)).toBeDefined()); + await rendered.rerender({ client, conversationId: 7, anchorId: 99 }); + await waitFor(() => expect(pending.get(99)).toBeDefined()); - expect(pending.get(1)?.request.signal.aborted).toBe(true); - pending.get(3)?.resolve(Response.json({ - id: 7, anchor_id: 3, messages: [message(1), message(2), message(3)], - has_before: false, has_after: false, total: 3 + expect(pending.get(98)?.request.signal.aborted).toBe(true); + pending.get(99)?.resolve(Response.json({ + id: 7, anchor_id: 99, messages: [message(1), { ...message(9), id: 99 }], + has_before: false, has_after: false, total: 2 })); - pending.get(1)?.reject(new TypeError('stale network failure')); + pending.get(98)?.reject(new TypeError('stale network failure')); - expect(await screen.findByRole('article', { name: 'Selected message 3' })).toBeTruthy(); + expect(await screen.findByRole('article', { name: 'Message 99' })).toBeTruthy(); expect(screen.queryByRole('alert')).toBeNull(); expect(fetchFn).toHaveBeenCalledTimes(3); }); @@ -166,9 +193,7 @@ describe('ConversationView', () => { throw new TypeError('connection refused'); }); render(ConversationView, { - props: { - client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack: vi.fn() - } + props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2 } }); expect((await screen.findByRole('alert')).textContent).toContain('Conversation network error'); @@ -179,26 +204,34 @@ describe('ConversationView', () => { throw new DOMException('superseded', 'AbortError'); }); render(ConversationView, { - props: { - client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack: vi.fn() - } + props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2 } }); await waitFor(() => expect(fetchFn).toHaveBeenCalledOnce()); expect(screen.queryByRole('alert')).toBeNull(); }); - it('keeps Back in the shell-owned sticky toolbar and names unavailable states', async () => { - const onBack = vi.fn(); + it('names unavailable conversation states', async () => { const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ error: 'conversation_unavailable', message: 'Conversation details are unavailable' }, { status: 503 })); render(ConversationView, { - props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2, onBack } + props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2 } }); expect((await screen.findByRole('alert')).textContent).toContain('Conversation details are unavailable'); - await fireEvent.click(screen.getByRole('button', { name: 'Back from conversation' })); - expect(onBack).toHaveBeenCalledOnce(); + }); + + it('notes bounded windows with quiet notices instead of chrome', async () => { + const fetchFn = vi.fn<typeof fetch>(async () => Response.json({ + id: 7, anchor_id: 2, messages: [message(2)], has_before: true, has_after: true, total: 40 + })); + render(ConversationView, { + props: { client: createAPIClient(fetchFn), conversationId: 7, anchorId: 2 } + }); + + await screen.findByRole('article', { name: 'Message 2' }); + expect(screen.getByText(/Earlier messages are outside this view — showing 1 of 40./)).toBeTruthy(); + expect(screen.getByText(/Later messages are outside this view./)).toBeTruthy(); }); }); diff --git a/web/src/lib/components/reader/MessageCard.svelte b/web/src/lib/components/reader/MessageCard.svelte new file mode 100644 index 000000000..c3aeb0b4e --- /dev/null +++ b/web/src/lib/components/reader/MessageCard.svelte @@ -0,0 +1,297 @@ +<script lang="ts"> + import type { APIClient } from '../../api/client'; + import type { ArchiveMessageDetail, MessageViewMode } from '../../archive/types'; + import ContentFrame from './ContentFrame.svelte'; + + interface Props { + message: ArchiveMessageDetail; + expanded: boolean; + /** The thread's current anchor — announced via aria-current and used by + * the thread to scroll the card into view. */ + anchor?: boolean; + viewMode?: MessageViewMode; + sanitizationFailed?: boolean; + onToggle?: (id: number) => void; + onViewModeChange?: (id: number, mode: MessageViewMode) => void; + client?: APIClient; + } + + let { + message, + expanded, + anchor = false, + viewMode = 'html', + sanitizationFailed = false, + onToggle, + onViewModeChange, + client = undefined + }: Props = $props(); + + let menuOpen = $state(false); + + const hasRenderableHTML = $derived( + typeof message.bodyHtml === 'string' && message.bodyHtml.trim() !== '' && !sanitizationFailed + ); + const renderAsHTML = $derived(hasRenderableHTML && viewMode === 'html'); + + function formatDate(value: string): string { + const parsed = new Date(value); + if (Number.isNaN(parsed.valueOf())) return value; + return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(parsed); + } + + function selectMode(mode: MessageViewMode): void { + menuOpen = false; + onViewModeChange?.(message.id, mode); + } +</script> + +{#if expanded} + <article + class="message-card message-card--expanded" + aria-label={`Message ${message.id}`} + aria-current={anchor ? 'true' : undefined} + data-message-id={message.id} + > + <div class="card-header"> + <button + type="button" + class="collapse-target" + aria-label={`Collapse message ${message.id} from ${message.sender || 'unknown sender'}`} + aria-expanded="true" + onclick={() => onToggle?.(message.id)} + > + <span class="header-line"> + <strong class="sender">{message.sender || 'Unknown sender'}</strong> + <time datetime={message.sentAt}>{formatDate(message.sentAt)}</time> + </span> + {#if message.recipients.length > 0} + <span class="recipients">to {message.recipients.join(', ')}</span> + {/if} + {#if message.subject} + <span class="subject">{message.subject}</span> + {/if} + </button> + {#if hasRenderableHTML} + <details class="card-menu" bind:open={menuOpen}> + <summary aria-label={`Message ${message.id} display options`}>⋯</summary> + <div class="menu-sheet kit-popover-card"> + {#if viewMode === 'html'} + <button type="button" onclick={() => selectMode('text')}>Show plain text</button> + {:else} + <button type="button" onclick={() => selectMode('html')}>Show formatted HTML</button> + {/if} + </div> + </details> + {/if} + </div> + + {#if sanitizationFailed} + <p class="sanitize-notice" role="alert">Could not render HTML formatting. Showing plain text.</p> + {/if} + + <section class="card-body" aria-label="Message body"> + {#if renderAsHTML} + <ContentFrame + {client} + messageId={message.id} + html={message.bodyHtml ?? ''} + title="Message body" + /> + {:else} + <pre>{message.body}</pre> + {/if} + </section> + </article> +{:else} + <button + type="button" + class="message-card message-card--collapsed" + aria-label={`Expand message ${message.id} from ${message.sender || 'unknown sender'}`} + aria-expanded="false" + data-message-id={message.id} + onclick={() => onToggle?.(message.id)} + > + <span class="collapsed-sender">{message.sender || 'Unknown sender'}</span> + <span class="collapsed-snippet">{message.snippet || message.subject}</span> + <time datetime={message.sentAt}>{formatDate(message.sentAt)}</time> + </button> +{/if} + +<style> + .message-card { + min-width: 0; + border-bottom: 1px solid var(--border-muted); + } + + .message-card--collapsed { + display: flex; + width: 100%; + align-items: baseline; + gap: var(--space-4); + padding: var(--space-3) var(--space-4); + border: 0; + border-bottom: 1px solid var(--border-muted); + background: none; + color: var(--text-secondary); + cursor: pointer; + font: inherit; + text-align: left; + } + + .message-card--collapsed:hover { + background: var(--bg-surface-hover); + } + + .collapsed-sender { + flex: none; + max-width: 220px; + overflow: hidden; + color: var(--text-primary); + font-size: var(--font-size-sm); + font-weight: 600; + text-overflow: ellipsis; + white-space: nowrap; + } + + .collapsed-snippet { + min-width: 0; + flex: 1; + overflow: hidden; + font-size: var(--font-size-sm); + text-overflow: ellipsis; + white-space: nowrap; + } + + .message-card time { + flex: none; + color: var(--text-muted); + font-size: var(--font-size-xs); + font-variant-numeric: tabular-nums; + white-space: nowrap; + } + + .card-header { + display: flex; + align-items: flex-start; + gap: var(--space-3); + padding: var(--space-3) var(--space-4) 0; + } + + .collapse-target { + display: grid; + min-width: 0; + flex: 1; + gap: 2px; + padding: 0; + border: 0; + background: none; + cursor: pointer; + font: inherit; + text-align: left; + } + + .header-line { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--space-4); + } + + .sender { + min-width: 0; + overflow: hidden; + color: var(--text-primary); + font-size: var(--font-size-sm); + text-overflow: ellipsis; + white-space: nowrap; + } + + .recipients, + .subject { + overflow: hidden; + color: var(--text-muted); + font-size: var(--font-size-xs); + text-overflow: ellipsis; + white-space: nowrap; + } + + .subject { + color: var(--text-secondary); + } + + .card-menu { + position: relative; + flex: none; + } + + .card-menu summary { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: var(--radius-sm); + color: var(--text-muted); + cursor: pointer; + list-style: none; + } + + .card-menu summary::-webkit-details-marker { + display: none; + } + + .card-menu summary:hover { + background: var(--bg-surface-hover); + color: var(--text-primary); + } + + .menu-sheet { + position: absolute; + z-index: var(--z-popover); + top: 26px; + right: 0; + min-width: 160px; + padding: var(--space-2); + } + + .menu-sheet button { + display: block; + width: 100%; + padding: var(--space-2) var(--space-3); + border: 0; + border-radius: var(--radius-sm); + background: none; + color: var(--text-primary); + cursor: pointer; + font: inherit; + font-size: var(--font-size-xs); + text-align: left; + white-space: nowrap; + } + + .menu-sheet button:hover { + background: var(--bg-surface-hover); + } + + .sanitize-notice { + margin: var(--space-2) var(--space-4) 0; + color: var(--text-secondary); + font-size: var(--font-size-xs); + } + + .card-body { + padding: var(--space-3) var(--space-4) var(--space-4); + } + + /* Plain text renders on the theme surface with theme text. */ + pre { + margin: 0; + overflow-wrap: anywhere; + color: var(--text-primary); + font-family: var(--font-mono); + font-size: var(--font-size-sm); + line-height: 1.55; + white-space: pre-wrap; + } +</style> diff --git a/web/src/lib/components/reader/MessageCard.test.ts b/web/src/lib/components/reader/MessageCard.test.ts new file mode 100644 index 000000000..2605b7dd4 --- /dev/null +++ b/web/src/lib/components/reader/MessageCard.test.ts @@ -0,0 +1,172 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; +import { describe, expect, it, vi } from 'vitest'; + +import type { ArchiveMessageDetail } from '../../archive/types'; +import MessageCard from './MessageCard.svelte'; + +function detail(overrides: Partial<ArchiveMessageDetail> = {}): ArchiveMessageDetail { + return { + id: 42, + conversationId: 7, + subject: 'Quarterly plan', + sender: 'Alice Example <alice@example.com>', + recipients: ['bob@example.com'], + sentAt: '2026-01-01T12:00:00Z', + snippet: 'Plan preview', + body: 'Plain text fallback.', + attachments: [], + ...overrides + }; +} + +describe('MessageCard', () => { + it('collapses to one line of sender, snippet, and date that expands on click', async () => { + const onToggle = vi.fn(); + render(MessageCard, { + props: { message: detail(), expanded: false, onToggle } + }); + + const collapsed = screen.getByRole('button', { + name: 'Expand message 42 from Alice Example <alice@example.com>' + }); + expect(collapsed.textContent).toContain('Alice Example'); + expect(collapsed.textContent).toContain('Plan preview'); + expect(collapsed.getAttribute('aria-expanded')).toBe('false'); + + await fireEvent.click(collapsed); + expect(onToggle).toHaveBeenCalledWith(42); + }); + + it('renders the expanded header directly: sender, recipients, date, subject, then the body', async () => { + const { container } = render(MessageCard, { + props: { message: detail({ bodyHtml: '<p>Formatted body</p>' }), expanded: true, anchor: true } + }); + + const card = screen.getByRole('article', { name: 'Message 42' }); + expect(card.getAttribute('aria-current')).toBe('true'); + expect(card.textContent).toContain('Alice Example <alice@example.com>'); + expect(card.textContent).toContain('to bob@example.com'); + expect(card.textContent).toContain('Quarterly plan'); + await waitFor(() => expect(container.querySelector('iframe')).not.toBeNull()); + // Body renders without any frame chrome to click through. + expect(screen.queryByRole('button', { name: /Enter archived content/ })).toBeNull(); + expect(screen.queryByRole('button', { name: /HTML/ })).toBeNull(); + }); + + it('collapses again from the expanded header', async () => { + const onToggle = vi.fn(); + render(MessageCard, { + props: { message: detail(), expanded: true, onToggle } + }); + + await fireEvent.click(screen.getByRole('button', { + name: 'Collapse message 42 from Alice Example <alice@example.com>' + })); + expect(onToggle).toHaveBeenCalledWith(42); + }); + + it('renders plain text on the theme surface when text mode is selected', () => { + const { container } = render(MessageCard, { + props: { message: detail({ bodyHtml: '<p>Formatted body</p>' }), expanded: true, viewMode: 'text' } + }); + + expect(container.querySelector('pre')?.textContent).toBe('Plain text fallback.'); + expect(container.querySelector('iframe')).toBeNull(); + }); + + it('offers plain text through a small overflow control, defaulting to HTML', async () => { + const onViewModeChange = vi.fn(); + render(MessageCard, { + props: { + message: detail({ bodyHtml: '<p>Formatted body</p>' }), + expanded: true, + onViewModeChange + } + }); + + await fireEvent.click(screen.getByText('⋯')); + await fireEvent.click(screen.getByRole('button', { name: 'Show plain text' })); + expect(onViewModeChange).toHaveBeenCalledWith(42, 'text'); + }); + + it('switches the overflow control back to HTML from text mode', async () => { + const onViewModeChange = vi.fn(); + render(MessageCard, { + props: { + message: detail({ bodyHtml: '<p>Formatted body</p>' }), + expanded: true, + viewMode: 'text', + onViewModeChange + } + }); + + await fireEvent.click(screen.getByText('⋯')); + await fireEvent.click(screen.getByRole('button', { name: 'Show formatted HTML' })); + expect(onViewModeChange).toHaveBeenCalledWith(42, 'html'); + }); + + it('sanitizes framed HTML and blocks sender-controlled network requests', async () => { + const { container } = render(MessageCard, { + props: { + message: detail({ + bodyHtml: + '<script>parent.postMessage("stolen", "*")</script>' + + '<img src="https://tracking.example/pixel.png">' + + '<a href="//tracking.example/click">Open</a>' + + '<p style="background:url(https://tracking.example/bg.png)">Safe text</p>' + }), + expanded: true + } + }); + + await waitFor(() => expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain('Safe text')); + const srcdoc = container.querySelector('iframe')?.getAttribute('srcdoc') ?? ''; + expect(srcdoc).toContain('Content-Security-Policy'); + expect(srcdoc).toContain("default-src 'none'"); + expect(srcdoc.match(/<script>/g)).toHaveLength(1); + expect(srcdoc).not.toContain('stolen'); + expect(srcdoc).not.toContain('tracking.example'); + }); + + it('removes SVG URL attributes that can bypass HTML URL filtering', async () => { + const { container } = render(MessageCard, { + props: { + message: detail({ + bodyHtml: + '<svg><a xlink:href="https://tracking.example/click"><text>Open</text></a></svg>' + }), + expanded: true + } + }); + + await waitFor(() => expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain('Open')); + const srcdoc = container.querySelector('iframe')?.getAttribute('srcdoc') ?? ''; + expect(srcdoc).toContain('Open'); + expect(srcdoc).not.toContain('tracking.example'); + }); + + it('falls back to plain text when HTML contains only whitespace', () => { + const { container } = render(MessageCard, { + props: { message: detail({ bodyHtml: ' \n\t ' }), expanded: true } + }); + + expect(container.querySelector('pre')?.textContent).toBe('Plain text fallback.'); + expect(container.querySelector('iframe')).toBeNull(); + expect(screen.queryByText('⋯')).toBeNull(); + }); + + it('falls back to plain text when HTML is rejected by sanitization', () => { + const { container } = render(MessageCard, { + props: { + message: detail({ bodyHtml: '<p>Unsafe body</p>' }), + expanded: true, + sanitizationFailed: true + } + }); + + expect(screen.getByRole('alert').textContent).toMatch(/could not render HTML/i); + expect(container.querySelector('pre')).not.toBeNull(); + expect(container.querySelector('iframe')).toBeNull(); + expect(screen.queryByText('⋯')).toBeNull(); + }); +}); diff --git a/web/src/lib/components/reader/MessageReader.svelte b/web/src/lib/components/reader/MessageReader.svelte deleted file mode 100644 index 37ff7c7be..000000000 --- a/web/src/lib/components/reader/MessageReader.svelte +++ /dev/null @@ -1,118 +0,0 @@ -<script lang="ts"> - import type { APIClient } from '../../api/client'; - import type { ArchiveMessageDetail, MessageViewMode } from '../../archive/types'; - import ContentFrame from './ContentFrame.svelte'; - - interface Props { - message: ArchiveMessageDetail | null; - loading?: boolean; - error?: string | null; - viewMode?: MessageViewMode; - sanitizationFailed?: boolean; - onViewModeChange?: (id: number, mode: MessageViewMode) => void; - onContentFocusChange?: (focused: boolean) => void; - client?: APIClient; - } - - let { - message, - loading = false, - error = null, - viewMode = 'html', - sanitizationFailed = false, - onViewModeChange, - onContentFocusChange, - client = undefined - }: Props = $props(); - - const hasRenderableHTML = $derived( - message !== null && - typeof message.bodyHtml === 'string' && - message.bodyHtml.trim() !== '' && - !sanitizationFailed - ); - const renderAsHTML = $derived(hasRenderableHTML && viewMode === 'html'); - - function selectMode(mode: MessageViewMode): void { - if (message === null) return; - onViewModeChange?.(message.id, mode); - } -</script> - -<article class="message-reader" aria-label="Message reader"> - {#if loading} - <p aria-label="Loading message">Loading message…</p> - {:else if error} - <p role="alert">{error}</p> - {:else if message === null} - <p>Select a message to read it.</p> - {:else} - <header> - <h1>{message.subject}</h1> - <p>{message.sender}</p> - {#if hasRenderableHTML} - <div class="view-mode" role="group" aria-label="Message view"> - <button - type="button" - aria-pressed={viewMode === 'html'} - onclick={() => selectMode('html')}>HTML</button - > - <button - type="button" - aria-pressed={viewMode === 'text'} - onclick={() => selectMode('text')}>Text</button - > - </div> - {/if} - </header> - - {#if sanitizationFailed} - <p role="alert">Could not render HTML formatting. Showing plain text.</p> - {/if} - - <section aria-label="Message body"> - {#if renderAsHTML} - <ContentFrame - {client} - messageId={message.id} - html={message.bodyHtml ?? ''} - title="Message body" - {onContentFocusChange} - /> - {:else} - <pre>{message.body}</pre> - {/if} - </section> - {/if} -</article> - -<style> - .message-reader { - min-width: 0; - } - - header { - position: relative; - } - - h1 { - margin: 0; - font-size: var(--font-size-lg); - } - - .view-mode { - display: flex; - gap: 4px; - } - - pre { - box-sizing: border-box; - width: 100%; - min-height: 240px; - } - - pre { - margin: 0; - white-space: pre-wrap; - } -</style> diff --git a/web/src/lib/components/reader/MessageReader.test.ts b/web/src/lib/components/reader/MessageReader.test.ts deleted file mode 100644 index 8bcc580cb..000000000 --- a/web/src/lib/components/reader/MessageReader.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { fireEvent, render, screen, waitFor } from '@testing-library/svelte'; -import { describe, expect, it, vi } from 'vitest'; - -import type { ArchiveMessageDetail } from '../../archive/types'; -import MessageReader from './MessageReader.svelte'; - -function detail(overrides: Partial<ArchiveMessageDetail> = {}): ArchiveMessageDetail { - return { - id: 42, - conversationId: 7, - subject: 'Quarterly plan', - sender: 'alice@example.com', - recipients: ['bob@example.com'], - sentAt: '2026-01-01T12:00:00Z', - snippet: 'Plan preview', - body: 'Plain text fallback.', - attachments: [], - ...overrides - }; -} - -describe('MessageReader', () => { - it('renders plain text when text mode is selected', () => { - const { container } = render(MessageReader, { - props: { message: detail({ bodyHtml: '<p>Formatted body</p>' }), viewMode: 'text' } - }); - - expect(container.querySelector('pre')?.textContent).toBe('Plain text fallback.'); - expect(container.querySelector('iframe')).toBeNull(); - }); - - it('renders non-empty HTML in an isolated frame when HTML mode is selected', async () => { - const { container } = render(MessageReader, { - props: { message: detail({ bodyHtml: '<p>Formatted body</p>' }), viewMode: 'html' } - }); - - await waitFor(() => expect(container.querySelector('iframe')).not.toBeNull()); - const frame = container.querySelector('iframe'); - expect(frame?.getAttribute('title')).toBe('Message body'); - expect(frame?.hasAttribute('sandbox')).toBe(true); - await waitFor(() => - expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain( - '<p>Formatted body</p>' - ) - ); - expect(container.querySelector('pre')).toBeNull(); - }); - - it('sanitizes framed HTML and blocks sender-controlled network requests', async () => { - const { container } = render(MessageReader, { - props: { - message: detail({ - bodyHtml: - '<script>parent.postMessage("stolen", "*")</script>' + - '<img src="https://tracking.example/pixel.png">' + - '<a href="//tracking.example/click">Open</a>' + - '<p style="background:url(https://tracking.example/bg.png)">Safe text</p>' - }), - viewMode: 'html' - } - }); - - await waitFor(() => expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain('Safe text')); - const srcdoc = container.querySelector('iframe')?.getAttribute('srcdoc') ?? ''; - expect(srcdoc).toContain('Content-Security-Policy'); - expect(srcdoc).toContain("default-src 'none'"); - expect(srcdoc).toContain('Safe text'); - expect(srcdoc.match(/<script>/g)).toHaveLength(1); - expect(srcdoc).not.toContain('stolen'); - expect(srcdoc).not.toContain('tracking.example'); - }); - - it('removes SVG URL attributes that can bypass HTML URL filtering', async () => { - const { container } = render(MessageReader, { - props: { - message: detail({ - bodyHtml: - '<svg><a xlink:href="https://tracking.example/click"><text>Open</text></a></svg>' - }), - viewMode: 'html' - } - }); - - await waitFor(() => expect(container.querySelector('iframe')?.getAttribute('srcdoc')).toContain('Open')); - const srcdoc = container.querySelector('iframe')?.getAttribute('srcdoc') ?? ''; - expect(srcdoc).toContain('Open'); - expect(srcdoc).not.toContain('tracking.example'); - }); - - it('falls back to plain text when HTML contains only whitespace', () => { - const { container } = render(MessageReader, { - props: { message: detail({ bodyHtml: ' \n\t ' }), viewMode: 'html' } - }); - - expect(container.querySelector('pre')?.textContent).toBe('Plain text fallback.'); - expect(container.querySelector('iframe')).toBeNull(); - expect(screen.queryByRole('button', { name: 'HTML' })).toBeNull(); - }); - - it('falls back to plain text when HTML is unavailable or rejected', () => { - const { container } = render(MessageReader, { - props: { - message: detail({ bodyHtml: '<p>Unsafe body</p>' }), - viewMode: 'html', - sanitizationFailed: true - } - }); - - expect(screen.getByRole('alert').textContent).toMatch(/could not render HTML/i); - expect(container.querySelector('pre')).not.toBeNull(); - expect(container.querySelector('iframe')).toBeNull(); - expect(screen.queryByRole('button', { name: 'HTML' })).toBeNull(); - }); - - it('reports explicit HTML and text selections to the owner', async () => { - const onViewModeChange = vi.fn(); - render(MessageReader, { - props: { - message: detail({ bodyHtml: '<p>Formatted body</p>' }), - viewMode: 'html', - onViewModeChange - } - }); - - await fireEvent.click(screen.getByRole('button', { name: 'Text' })); - - expect(onViewModeChange).toHaveBeenCalledWith(42, 'text'); - }); -}); From b42866897d996add73fd335995e7a9988164ac46 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 16:48:46 -0500 Subject: [PATCH 016/102] feat(web): open rows and files with a single click A single click now selects and opens: Everything table rows, Files grid rows (both the workspace and the files presentation), and hub timeline rows all open their content directly instead of requiring a double click. Keyboard behavior is unchanged (j/k moves, Enter opens). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../components/explore/EverythingTable.svelte | 4 +- .../explore/FilesPresentation.svelte | 6 +- .../components/files/FilesWorkspace.svelte | 8 +- .../lib/components/inspector/Inspector.svelte | 467 ------------------ .../components/inspector/Inspector.test.ts | 274 ---------- .../AttachmentRail.svelte | 0 .../relationships/RelationshipTimeline.svelte | 6 +- .../RelationshipTimeline.test.ts | 5 +- 8 files changed, 22 insertions(+), 748 deletions(-) delete mode 100644 web/src/lib/components/inspector/Inspector.svelte delete mode 100644 web/src/lib/components/inspector/Inspector.test.ts rename web/src/lib/components/{inspector => reader}/AttachmentRail.svelte (100%) diff --git a/web/src/lib/components/explore/EverythingTable.svelte b/web/src/lib/components/explore/EverythingTable.svelte index a3cdccbe1..6ecaed187 100644 --- a/web/src/lib/components/explore/EverythingTable.svelte +++ b/web/src/lib/components/explore/EverythingTable.svelte @@ -504,6 +504,8 @@ <div class="virtual-window" style:transform={`translateY(${slice.topPad}px)`}> {#each renderedRows as row, offset (row.key)} {@const index = slice.start + offset} + <!-- svelte-ignore a11y_click_events_have_key_events -- Enter on + the focused grid opens the same row via handleKeydown. --> <div class="data-row" class:data-row--active={index === activeIndex} @@ -522,7 +524,7 @@ onActiveKey?.(row.key); gridElement?.focus(); }} - ondblclick={() => onOpen?.(row)} + onclick={() => onOpen?.(row)} > {#each visibleColumns as column (column)} <span class={`cell cell--${column}`} role="gridcell"> diff --git a/web/src/lib/components/explore/FilesPresentation.svelte b/web/src/lib/components/explore/FilesPresentation.svelte index 09debf56d..aad16dd39 100644 --- a/web/src/lib/components/explore/FilesPresentation.svelte +++ b/web/src/lib/components/explore/FilesPresentation.svelte @@ -290,6 +290,8 @@ <div class="virtual-window" style:transform={`translateY(${slice.topPad}px)`}> {#each renderedFiles as file, offset (file.key)} {@const index = slice.start + offset} + <!-- svelte-ignore a11y_click_events_have_key_events -- Enter on + the focused grid opens the same file via handleKeydown. --> <div id={rowID(file.key)} class="file-row file-data" @@ -298,7 +300,9 @@ tabindex="-1" aria-rowindex={index + 2} onpointerdown={() => { activeKey = file.key; onActiveKey?.(file.key); grid?.focus(); }} - ondblclick={() => onOpenFile?.(file)} + onclick={(event) => { + if (!(event.target as Element).closest('button')) onOpenFile?.(file); + }} > <span role="gridcell">{formatTime(file.occurred_at)}</span> <span class="filename" role="gridcell">{file.filename || 'Unnamed file'}</span> diff --git a/web/src/lib/components/files/FilesWorkspace.svelte b/web/src/lib/components/files/FilesWorkspace.svelte index 9600d6367..8fb70c5c2 100644 --- a/web/src/lib/components/files/FilesWorkspace.svelte +++ b/web/src/lib/components/files/FilesWorkspace.svelte @@ -493,6 +493,8 @@ <div class="virtual-window" style:transform={`translateY(${slice.topPad}px)`}> {#each renderedRows as row, offset (row.key)} {@const index = slice.start + offset} + <!-- svelte-ignore a11y_click_events_have_key_events -- Enter on + the focused grid opens the same file via handleKeydown. --> <div id={rowID(row)} class="data-row" @@ -501,7 +503,11 @@ tabindex="-1" aria-rowindex={index + 2} onpointerdown={(event) => { activeKey = row.key; onActiveKey?.(row.key); grid?.focus(); viewerReturnFocus = event.currentTarget as HTMLElement; }} - ondblclick={(event) => open(row, event.currentTarget as HTMLElement)} + onclick={(event) => { + if (!(event.target as Element).closest('button')) { + open(row, event.currentTarget as HTMLElement); + } + }} > <span role="gridcell"><time datetime={row.occurred_at}>{formatDate(row.occurred_at)}</time></span> <span role="gridcell"><strong>{row.filename || '(unnamed)'}</strong></span> diff --git a/web/src/lib/components/inspector/Inspector.svelte b/web/src/lib/components/inspector/Inspector.svelte deleted file mode 100644 index e3d0970f4..000000000 --- a/web/src/lib/components/inspector/Inspector.svelte +++ /dev/null @@ -1,467 +0,0 @@ -<script module lang="ts"> - import type { - EntryRow, - ExploreGroupDimension, - ExplorePredicate - } from '../../explore/models'; - - export type InspectorSelection = - | { kind: 'entry'; row: EntryRow } - | { - kind: 'group'; - dimension: ExploreGroupDimension; - key: string; - label: string; - count?: number; - estimatedBytes?: number; - latestAt?: string; - }; - - export type InspectorStatus = 'ready' | 'loading' | 'missing' | 'error' | 'unavailable'; -</script> - -<script lang="ts"> - import { Button, SplitResizeHandle, type SplitResizeEvent } from '@kenn-io/kit-ui'; - import { onDestroy, tick, untrack } from 'svelte'; - - import type { APIClient } from '../../api/client'; - import { createExploreAPI } from '../../explore/api'; - import type { ExploreCacheUnavailable, ExploreFileFact, ExploreFilter } from '../../explore/models'; - import ConversationView from '../reader/ConversationView.svelte'; - import TaskLinks from '../tasks/TaskLinks.svelte'; - import AttachmentRail from './AttachmentRail.svelte'; - - let { - client, - selection = undefined, - targetKey = '', - status = 'ready', - statusMessage = '', - unavailable = undefined, - predicate, - width, - onClose = undefined, - onWidthChange = undefined, - onContentFocusChange = undefined, - conversationAnchorId = undefined, - conversationStart = undefined, - conversationEnd = undefined, - onViewConversation = undefined, - onBackConversation = undefined, - onConversationAnchorChange = undefined, - onOpenSettings = undefined, - onOpenRelationship = undefined - }: { - client: APIClient; - selection?: InspectorSelection; - targetKey?: string; - status?: InspectorStatus; - statusMessage?: string; - unavailable?: ExploreCacheUnavailable; - predicate: ExplorePredicate; - width: number; - onClose?: () => void; - onWidthChange?: (width: number) => void; - onContentFocusChange?: (focused: boolean) => void; - conversationAnchorId?: number; - /** Lower UTC bound (RFC3339, inclusive) restricting the conversation - * window opened for this selection — e.g. a chat burst's local day. */ - conversationStart?: string; - /** Upper UTC bound (RFC3339, exclusive) restricting the conversation window. */ - conversationEnd?: string; - onViewConversation?: (conversationId: number, anchorId: number) => void; - onBackConversation?: () => void; - onConversationAnchorChange?: (anchorId: number) => void; - onOpenSettings?: () => void; - /** Header action for entry selections that carry a participant, jumping - * to that participant's cluster in the Relationships hub. Omitted (and - * the button hidden) where the caller has no such destination — e.g. the - * Relationships hub's own reading pane, whose timeline rows don't carry - * participant IDs. */ - onOpenRelationship?: (participantID: number) => void; - } = $props(); - - const api = createExploreAPI(untrack(() => client)); - let files = $state<ExploreFileFact[]>([]); - let totalFiles = $state(0); - let filesLoading = $state(false); - let filesError = $state(''); - let requestGeneration = 0; - let requestController: AbortController | undefined; - let resizeStartWidth = 0; - let contentFocused = $state(false); - let contentElement: HTMLDivElement | undefined; - let contentFocusButton = $state<HTMLButtonElement>(); - const title = $derived(selection - ? selection.kind === 'entry' ? selection.row.title || '(untitled)' : selection.label - : targetKey || 'Selected result'); - const showFiles = $derived(selection?.kind === 'group' && - (selection.dimension === 'participant' || selection.dimension === 'domain')); - const conversationRow = $derived(selection?.kind === 'entry' && selection.row.conversation_id && - selection.row.anchor_message_id ? selection.row : undefined); - // counterpart_participant_id is server-computed (see EntryRow): the - // smallest non-owner participant on the entry, or absent when the owner - // set is unknown or every participant is the owner. Unlike - // participant_ids[0], it never resolves to the archive owner. - const counterpartParticipantId = $derived( - selection?.kind === 'entry' ? selection.row.counterpart_participant_id : undefined - ); - - const filesPredicateFingerprint = $derived(JSON.stringify(predicate)); - - $effect(() => { - const selected = selection; - void filesPredicateFingerprint; - const currentPredicate = untrack(() => predicate); - requestGeneration += 1; - requestController?.abort(); - requestController = undefined; - files = []; - totalFiles = 0; - filesError = ''; - if (!selected || selected.kind !== 'group' || - (selected.dimension !== 'participant' && selected.dimension !== 'domain')) { - filesLoading = false; - return; - } - const generation = requestGeneration; - const controller = new AbortController(); - requestController = controller; - filesLoading = true; - const filters: ExploreFilter[] = [ - ...(currentPredicate.filters ?? []).filter((filter) => filter.dimension !== selected.dimension), - { dimension: selected.dimension, values: [selected.key] } - ]; - const filePredicate: ExplorePredicate = { - ...currentPredicate, - cursor: undefined, - candidate_snapshot_id: undefined, - grouping: undefined, - filters, - presentation: 'table' - }; - void api.files(filePredicate, controller.signal) - .then((loaded) => { - if (generation !== requestGeneration || controller.signal.aborted) return; - if (loaded.status === 'unavailable') { - filesError = loaded.unavailable.message; - return; - } - files = loaded.result.files; - totalFiles = loaded.result.totalCount; - }) - .catch((cause: unknown) => { - if (generation !== requestGeneration || controller.signal.aborted) return; - filesError = cause instanceof Error ? cause.message : 'Could not load file facts.'; - }) - .finally(() => { - if (generation === requestGeneration) filesLoading = false; - }); - }); - - onDestroy(() => { - requestGeneration += 1; - requestController?.abort(); - if (contentFocused) onContentFocusChange?.(false); - }); - - async function focusContent(event: MouseEvent): Promise<void> { - contentFocusButton = event.currentTarget as HTMLButtonElement; - if (!contentFocused) { - contentFocused = true; - onContentFocusChange?.(true); - } - await tick(); - contentElement?.focus(); - } - - function exitContentFocus(): void { - if (contentFocused) { - contentFocused = false; - onContentFocusChange?.(false); - } - contentFocusButton?.focus(); - } - - function handleContentKeydown(event: KeyboardEvent): void { - if (event.key !== 'Escape' || !contentFocused) return; - event.preventDefault(); - event.stopPropagation(); - exitContentFocus(); - } - - function beginResize(): void { - resizeStartWidth = width; - } - - function resize(event: SplitResizeEvent): void { - onWidthChange?.(Math.max(320, Math.min(720, resizeStartWidth - event.deltaX))); - } - - function handleOpenRelationship(): void { - if (counterpartParticipantId !== undefined) onOpenRelationship?.(counterpartParticipantId); - } - - function formatDate(value: string): string { - const parsed = new Date(value); - return Number.isNaN(parsed.valueOf()) ? value : parsed.toLocaleString(); - } - - function formatBytes(value: number): string { - if (value < 1024) return `${value} B`; - if (value < 1024 * 1024) return `${Math.round(value / 1024)} KB`; - return `${(value / (1024 * 1024)).toFixed(1)} MB`; - } -</script> - -{#snippet header()} - <div class="inspector-header"> - <div><span class="eyebrow">Inspector</span><strong>{title}</strong></div> - <div class="inspector-actions"> - {#if onOpenRelationship && counterpartParticipantId !== undefined} - <Button - size="sm" - surface="outline" - label="Open relationship" - ariaLabel="Open relationship" - onclick={handleOpenRelationship} - /> - {/if} - <Button - size="sm" - surface="outline" - label="Focus content" - ariaLabel="Focus inspector content" - onclick={(event) => { void focusContent(event); }} - /> - <Button size="sm" surface="outline" label="Close" ariaLabel="Close inspector" onclick={() => onClose?.()} /> - </div> - </div> -{/snippet} - -{#snippet contents()} - <!-- svelte-ignore a11y_no_noninteractive_element_interactions --> - <div - bind:this={contentElement} - class="inspector-content" - class:inspector-content--focused={contentFocused} - role="region" - aria-label="Inspector content" - tabindex="-1" - onkeydown={handleContentKeydown} - > - {#if conversationAnchorId !== undefined && conversationRow} - <ConversationView - {client} - conversationId={conversationRow.conversation_id!} - anchorId={conversationAnchorId} - start={conversationStart} - end={conversationEnd} - onBack={() => onBackConversation?.()} - onAnchorChange={(anchorId) => onConversationAnchorChange?.(anchorId)} - {onContentFocusChange} - /> - {:else if !selection} - <section class="summary" aria-label="Inspector status"> - {#if status === 'loading'} - <p role="status">Restoring selected detail…</p> - {:else if status === 'unavailable' && unavailable} - <p role="alert"> - {unavailable.message} Rebuild the cache with <code>{unavailable.recovery_action}</code>. - </p> - {:else} - <p role="alert">{statusMessage || 'The selected result is no longer available in this context.'}</p> - {/if} - </section> - {:else if selection.kind === 'entry'} - <section class="summary" aria-label="Entry details"> - <p class="preview">{selection.row.preview || 'No preview is available.'}</p> - <dl> - <div><dt>Kind</dt><dd>{selection.row.message_type}</dd></div> - <div><dt>Source</dt><dd>{selection.row.source_identifier}</dd></div> - <div><dt>Occurred</dt><dd>{formatDate(selection.row.occurred_at)}</dd></div> - <div><dt>Items</dt><dd>{selection.row.message_count.toLocaleString()}</dd></div> - <div><dt>Files</dt><dd>{selection.row.attachment_count.toLocaleString()}</dd></div> - </dl> - {#if conversationRow} - <span data-conversation-return> - <Button - size="sm" - surface="soft" - label="View conversation" - ariaLabel="View conversation" - onclick={() => onViewConversation?.( - conversationRow.conversation_id!, conversationRow.anchor_message_id! - )} - /> - </span> - {/if} - </section> - {#if selection.row.message_type === 'email' && selection.row.anchor_message_id} - <TaskLinks - {client} - messageId={selection.row.anchor_message_id} - title={selection.row.title || '(untitled)'} - sourceType={selection.row.source_type} - sourceIdentifier={selection.row.source_identifier} - onsettings={onOpenSettings} - /> - {/if} - {:else} - <section class="summary" aria-label="Group details"> - <p class="group-kind">Grouped by {selection.dimension}</p> - <dl> - {#if selection.count !== undefined}<div><dt>Items</dt><dd>{selection.count.toLocaleString()} items</dd></div>{/if} - {#if selection.estimatedBytes !== undefined}<div><dt>Estimated</dt><dd>{formatBytes(selection.estimatedBytes)}</dd></div>{/if} - {#if selection.latestAt}<div><dt>Latest</dt><dd>{formatDate(selection.latestAt)}</dd></div>{/if} - </dl> - </section> - {/if} - {#if showFiles} - <AttachmentRail files={files} totalCount={totalFiles} loading={filesLoading} error={filesError} /> - {/if} - </div> -{/snippet} - -<div class="pinned-inspector"> - <SplitResizeHandle ariaLabel="Resize inspector" onResizeStart={beginResize} onResize={resize} /> - <aside aria-label={`Inspect ${title}`} style:width={`${width}px`}> - {@render header()} - <div class="pinned-body">{@render contents()}</div> - </aside> -</div> - -<style> - .pinned-inspector { - display: flex; - min-height: 0; - height: 100%; - flex: none; - } - - aside { - display: flex; - min-width: 320px; - max-width: 720px; - flex-direction: column; - overflow: hidden; - border: 1px solid var(--border-default); - border-left: 0; - border-radius: 0 var(--radius-md) var(--radius-md) 0; - background: var(--bg-surface); - } - - .inspector-header { - display: flex; - width: 100%; - min-width: 0; - align-items: center; - justify-content: space-between; - gap: var(--space-4); - padding: var(--space-4) var(--space-5); - border-bottom: 1px solid var(--border-muted); - } - - .inspector-header > div:first-child { - display: grid; - min-width: 0; - gap: var(--space-1); - } - - .inspector-header strong { - overflow: hidden; - color: var(--text-primary); - font-size: var(--font-size-sm); - text-overflow: ellipsis; - white-space: nowrap; - } - - .eyebrow, - .group-kind { - color: var(--text-muted); - font-size: var(--font-size-2xs); - letter-spacing: 0.06em; - text-transform: uppercase; - } - - .inspector-actions { - display: flex; - flex: none; - gap: var(--space-2); - } - - .pinned-body { - min-height: 0; - flex: 1; - overflow: auto; - } - - .inspector-content { - display: flex; - min-height: 100%; - flex-direction: column; - } - - .inspector-content--focused:focus { - outline: 2px solid var(--accent-blue); - outline-offset: -2px; - } - - .summary { - display: grid; - gap: var(--space-4); - padding: var(--space-5); - } - - .summary p, - dl { - margin: 0; - } - - .preview { - color: var(--text-secondary); - font-size: var(--font-size-sm); - line-height: 1.5; - } - - dl { - display: grid; - gap: var(--space-2); - } - - dl div { - display: grid; - grid-template-columns: 88px minmax(0, 1fr); - gap: var(--space-3); - font-size: var(--font-size-xs); - } - - dt { - color: var(--text-muted); - } - - dd { - min-width: 0; - margin: 0; - overflow-wrap: anywhere; - color: var(--text-primary); - } - - @media (max-width: 760px) { - .pinned-inspector { - width: 100%; - background: var(--bg-surface); - } - - .pinned-inspector > :global(.kit-split-resize-handle) { - display: none; - } - - aside { - width: 100% !important; - max-width: none; - border: 0; - border-radius: 0; - } - } -</style> diff --git a/web/src/lib/components/inspector/Inspector.test.ts b/web/src/lib/components/inspector/Inspector.test.ts deleted file mode 100644 index 4443fe659..000000000 --- a/web/src/lib/components/inspector/Inspector.test.ts +++ /dev/null @@ -1,274 +0,0 @@ -import { fireEvent, render, screen, waitFor, within } from '@testing-library/svelte'; -import { describe, expect, it, vi } from 'vitest'; - -import { createAPIClient } from '../../api/client'; -import type { EntryRow } from '../../explore/models'; -import Inspector from './Inspector.svelte'; - -function entry(key = 'message:1'): EntryRow { - return { - key, - kind: 'message', - message_type: 'email', - conversation_type: 'email', - title: 'Synthetic subject', - preview: 'Synthetic excerpt', - occurred_at: '2026-07-18T12:00:00Z', - source_id: 1, - source_identifier: 'archive@example.com', - source_type: 'synthetic', - participant_labels: ['Example Person'], - participant_ids: [42], - counterpart_participant_id: 99, - attachment_count: 1, - attachment_size: 42, - has_attachments: true, - deleted_from_source: false, - message_count: 1, - anchor_message_id: 42, - conversation_id: 7, - match: {} - }; -} - -describe('Inspector', () => { - it('shows a concrete row in the pinned side panel and exposes close', async () => { - const onClose = vi.fn(); - render(Inspector, { - client: createAPIClient(vi.fn()), - selection: { kind: 'entry', row: entry() }, - predicate: { filters: [], presentation: 'table' }, - width: 380, - onClose - }); - - const aside = screen.getByRole('complementary', { name: 'Inspect Synthetic subject' }); - expect(within(aside).getByText('Synthetic excerpt')).toBeDefined(); - expect(document.querySelector('.kit-detail-drawer-overlay')).toBeNull(); - await fireEvent.click(within(aside).getByRole('button', { name: 'Close inspector' })); - expect(onClose).toHaveBeenCalledOnce(); - }); - - it('opens the entry\'s server-computed counterpart into the Relationships hub from the header action', async () => { - const onOpenRelationship = vi.fn(); - render(Inspector, { - client: createAPIClient(vi.fn()), - selection: { kind: 'entry', row: entry() }, - predicate: { filters: [], presentation: 'table' }, - width: 380, - onOpenRelationship - }); - - await fireEvent.click(screen.getByRole('button', { name: 'Open relationship' })); - // counterpart_participant_id (99) is asserted, not participant_ids[0] - // (42) — the entry fixture deliberately sets them to different values so - // this test fails if the component regresses to the old - // participant_ids[0] heuristic, which frequently resolved to the - // archive owner rather than the other side of the conversation. - expect(onOpenRelationship).toHaveBeenCalledWith(99); - }); - - it('hides the Open relationship action when the caller has no destination for it, or the entry carries no counterpart', async () => { - render(Inspector, { - client: createAPIClient(vi.fn()), - selection: { kind: 'entry', row: entry() }, - predicate: { filters: [], presentation: 'table' }, - width: 380 - }); - expect(screen.queryByRole('button', { name: 'Open relationship' })).toBeNull(); - - const onOpenRelationship = vi.fn(); - render(Inspector, { - client: createAPIClient(vi.fn()), - selection: { kind: 'entry', row: { ...entry(), counterpart_participant_id: undefined } }, - predicate: { filters: [], presentation: 'table' }, - width: 380, - onOpenRelationship - }); - expect(screen.queryByRole('button', { name: 'Open relationship' })).toBeNull(); - }); - - it('shows selected group details and chronological files from one bounded request', async () => { - const requests: Request[] = []; - const fetchFn = vi.fn<typeof fetch>(async (input) => { - const request = input instanceof Request ? input : new Request(input); - requests.push(request); - return Response.json({ - files: [ - { - key: 'file:new', entry_key: 'message:2', message_id: 2, - occurred_at: '2026-07-18T12:00:00Z', source_id: 1, - source_identifier: 'archive@example.com', title: 'Newer entry', - filename: 'new.txt', size: 20 - }, - { - key: 'file:old', entry_key: 'message:1', message_id: 1, - occurred_at: '2026-07-17T12:00:00Z', source_id: 1, - source_identifier: 'archive@example.com', title: 'Older entry', - filename: 'old.txt', size: 10 - } - ], - total_count: 3, cache_revision: 'cache-1', search_provenance: {} - }); - }); - render(Inspector, { - client: createAPIClient(fetchFn), - selection: { - kind: 'group', dimension: 'participant', key: '42', label: 'Example Person', - count: 12, estimatedBytes: 30, latestAt: '2026-07-18T12:00:00Z' - }, - predicate: { query: 'pasta', search_mode: 'semantic', filters: [], presentation: 'table' }, - width: 380 - }); - - await screen.findByText('new.txt'); - const facts = screen.getAllByRole('listitem').map((item) => item.textContent); - expect(facts[0]).toContain('old.txt'); - expect(facts[1]).toContain('new.txt'); - expect(screen.getByText('Showing a bounded sample of 2 of 3 files.')).toBeDefined(); - expect(screen.queryByText(/Open Files/)).toBeNull(); - expect(screen.getByText('12 items')).toBeDefined(); - expect(requests).toHaveLength(1); - await expect(requests[0]!.clone().json()).resolves.toMatchObject({ - limit: 100, - predicate: { - query: 'pasta', search_mode: 'semantic', - filters: [{ dimension: 'participant', values: ['42'] }], - presentation: 'table' - } - }); - expect((await requests[0]!.clone().json()).predicate).not.toHaveProperty('grouping'); - }); - - it('aborts superseded file facts and ignores a stale response that still settles', async () => { - const signals: AbortSignal[] = []; - const resolvers: Array<(response: Response) => void> = []; - const fetchFn = vi.fn<typeof fetch>((input) => { - const request = input instanceof Request ? input : new Request(input); - signals.push(request.signal); - return new Promise<Response>((resolve) => resolvers.push(resolve)); - }); - const rendered = render(Inspector, { - client: createAPIClient(fetchFn), - selection: { kind: 'group', dimension: 'participant', key: '1', label: 'First person' }, - predicate: { filters: [], presentation: 'table' }, - width: 380 - }); - await waitFor(() => expect(signals).toHaveLength(1)); - - await rendered.rerender({ - client: createAPIClient(fetchFn), - selection: { kind: 'group', dimension: 'domain', key: 'example.com', label: 'example.com' }, - predicate: { filters: [], presentation: 'table' }, - width: 380 - }); - await waitFor(() => expect(signals).toHaveLength(2)); - expect(signals[0]!.aborted).toBe(true); - - resolvers[1]!(Response.json({ - files: [{ - key: 'file:second', entry_key: 'message:2', message_id: 2, - occurred_at: '2026-07-18T12:00:00Z', source_id: 1, - source_identifier: 'archive@example.com', title: 'Second entry', - filename: 'second.txt', size: 20 - }], - total_count: 1, cache_revision: 'cache-2', search_provenance: {} - })); - expect(await screen.findByText('second.txt')).toBeDefined(); - - resolvers[0]!(Response.json({ - files: [{ - key: 'file:first', entry_key: 'message:1', message_id: 1, - occurred_at: '2026-07-17T12:00:00Z', source_id: 1, - source_identifier: 'archive@example.com', title: 'First entry', - filename: 'stale.txt', size: 10 - }], - total_count: 1, cache_revision: 'cache-1', search_provenance: {} - })); - await Promise.resolve(); - expect(screen.queryByText('stale.txt')).toBeNull(); - }); - - it('resizes a pinned inspector through the kit handle', async () => { - const onWidthChange = vi.fn(); - render(Inspector, { - client: createAPIClient(vi.fn()), - selection: { kind: 'entry', row: entry() }, - predicate: { filters: [], presentation: 'table' }, - width: 400, - onWidthChange - }); - - await fireEvent.keyDown(screen.getByRole('button', { name: 'Resize inspector' }), { - key: 'ArrowLeft' - }); - expect(onWidthChange).toHaveBeenCalledWith(424); - }); - - it('enters and exits an explicit content-focus mode', async () => { - const onContentFocusChange = vi.fn(); - render(Inspector, { - client: createAPIClient(vi.fn()), - selection: { kind: 'entry', row: entry() }, - predicate: { filters: [], presentation: 'table' }, - width: 400, - onContentFocusChange - }); - - await fireEvent.click(screen.getByRole('button', { name: 'Focus inspector content' })); - const content = screen.getByRole('region', { name: 'Inspector content' }); - expect(document.activeElement).toBe(content); - expect(onContentFocusChange).toHaveBeenCalledWith(true); - - await fireEvent.keyDown(content, { key: 'Escape' }); - expect(onContentFocusChange).toHaveBeenLastCalledWith(false); - expect(document.activeElement).toBe(screen.getByRole('button', { name: 'Focus inspector content' })); - }); - - it('opens the containing conversation from an email or chat row', async () => { - const onViewConversation = vi.fn(); - const rendered = render(Inspector, { - client: createAPIClient(vi.fn()), - selection: { kind: 'entry', row: entry() }, - predicate: { filters: [], presentation: 'table' }, - width: 400, - onViewConversation - }); - - await fireEvent.click(screen.getByRole('button', { name: 'View conversation' })); - expect(onViewConversation).toHaveBeenCalledWith(7, 42); - - await rendered.rerender({ - client: createAPIClient(vi.fn()), - selection: { kind: 'entry', row: { ...entry(), kind: 'conversation', message_type: 'imessage' } }, - predicate: { filters: [], presentation: 'table' }, - width: 400, - onViewConversation - }); - await fireEvent.click(screen.getByRole('button', { name: 'View conversation' })); - expect(onViewConversation).toHaveBeenLastCalledWith(7, 42); - }); - - it('threads optional conversation start/end bounds to the conversation fetch', async () => { - const requests: Request[] = []; - const fetchFn = vi.fn<typeof fetch>(async (input) => { - const request = input instanceof Request ? input : new Request(input); - requests.push(request); - return Response.json({ id: 7, anchor_id: 42, messages: [], has_before: false, has_after: false, total: 0 }); - }); - render(Inspector, { - client: createAPIClient(fetchFn), - selection: { kind: 'entry', row: entry() }, - predicate: { filters: [], presentation: 'table' }, - width: 400, - conversationAnchorId: 42, - conversationStart: '2026-07-19T00:00:00.000Z', - conversationEnd: '2026-07-20T00:00:00.000Z' - }); - - await waitFor(() => expect(requests).toHaveLength(1)); - const url = new URL(requests[0]!.url); - expect(url.searchParams.get('start')).toBe('2026-07-19T00:00:00.000Z'); - expect(url.searchParams.get('end')).toBe('2026-07-20T00:00:00.000Z'); - }); -}); diff --git a/web/src/lib/components/inspector/AttachmentRail.svelte b/web/src/lib/components/reader/AttachmentRail.svelte similarity index 100% rename from web/src/lib/components/inspector/AttachmentRail.svelte rename to web/src/lib/components/reader/AttachmentRail.svelte diff --git a/web/src/lib/components/relationships/RelationshipTimeline.svelte b/web/src/lib/components/relationships/RelationshipTimeline.svelte index 4512cbdb7..121d2e29f 100644 --- a/web/src/lib/components/relationships/RelationshipTimeline.svelte +++ b/web/src/lib/components/relationships/RelationshipTimeline.svelte @@ -197,6 +197,8 @@ <span role="gridcell" aria-label={`Month: ${item.label}`}>{item.label}</span> </div> {:else} + <!-- svelte-ignore a11y_click_events_have_key_events -- Enter + on the focused grid opens the same row via handleKeydown. --> <div class="timeline-row" class:active={item.key === activeKey} @@ -206,8 +208,8 @@ data-row-key={item.key} aria-selected={item.key === selectedKey} style:height={`${rowHeight}px`} - onpointerdown={() => selectRow(item.row)} - ondblclick={() => selectRow(item.row)} + onpointerdown={() => { activeKey = item.key; gridElement?.focus(); }} + onclick={() => selectRow(item.row)} > <div role="gridcell"> <span class="row-top"> diff --git a/web/src/lib/components/relationships/RelationshipTimeline.test.ts b/web/src/lib/components/relationships/RelationshipTimeline.test.ts index 761a1251e..8a2f3641e 100644 --- a/web/src/lib/components/relationships/RelationshipTimeline.test.ts +++ b/web/src/lib/components/relationships/RelationshipTimeline.test.ts @@ -66,14 +66,15 @@ describe('RelationshipTimeline', () => { expect(onRowOpen).toHaveBeenCalledWith(expect.objectContaining({ key: 'message:2' })); }); - it('opens a row by clicking it', async () => { + it('opens a row with a single click', async () => { const onRowOpen = vi.fn(); render(RelationshipTimeline, { rows: [emailRow('message:1', '2026-07-18T12:00:00Z', 'Click target')], onRowOpen }); - await fireEvent.pointerDown(screen.getByText('Click target').closest('[role="row"]')!); + await fireEvent.click(screen.getByText('Click target').closest('[role="row"]')!); + expect(onRowOpen).toHaveBeenCalledOnce(); expect(onRowOpen).toHaveBeenCalledWith(expect.objectContaining({ key: 'message:1' })); }); From 8370cfee9cfd20d58b2bbaa2ce28891f52d21d30 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 16:49:04 -0500 Subject: [PATCH 017/102] feat(web): replace the Inspector with a bottom reading pane Selecting a result now opens a reading pane under the results as a draggable vertical split (default ~55% of the content area, size persisted locally) that renders the conversation thread directly. Metadata collapses into a compact strip in the pane header, with Tasks behind a disclosure. The same pane serves the Everything workspace and the Relationships hub; hub timeline rows open their thread immediately, chat bursts bounded to the burst's local day. The in-thread anchor is committed to the URL by replacement so Back/Forward restore the exact message. The hub's stacked layout tier is gone: the reading pane no longer needs the horizontal room that tier freed up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../lib/components/layout/SplitPane.svelte | 13 +- .../lib/components/reader/ReadingPane.svelte | 402 ++++++++++++++++++ .../RelationshipsWorkspace.svelte | 226 ++++------ .../RelationshipsWorkspace.test.ts | 44 +- .../relationships/timeline-support.ts | 14 +- web/src/lib/components/shell/AppShell.svelte | 72 +--- web/src/lib/components/shell/AppShell.test.ts | 2 +- .../shell/EverythingSessionState.svelte.ts | 14 +- .../shell/EverythingWorkspace.svelte | 208 ++++----- .../shell/EverythingWorkspace.test.ts | 118 ++--- web/src/lib/explore/models.ts | 1 - web/src/lib/explore/state.svelte.ts | 7 - 12 files changed, 733 insertions(+), 388 deletions(-) create mode 100644 web/src/lib/components/reader/ReadingPane.svelte diff --git a/web/src/lib/components/layout/SplitPane.svelte b/web/src/lib/components/layout/SplitPane.svelte index 500488b97..17713a474 100644 --- a/web/src/lib/components/layout/SplitPane.svelte +++ b/web/src/lib/components/layout/SplitPane.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import { SplitResizeHandle, type SplitResizeEvent } from '@kenn-io/kit-ui'; - import { onDestroy, onMount, type Snippet } from 'svelte'; + import { onDestroy, onMount, untrack, type Snippet } from 'svelte'; interface Props { ariaLabel: string; @@ -16,6 +16,7 @@ * handle, the persisted pixel size wins. */ initialFraction?: number; minPrimary?: number; + /** Defaults to 160 for vertical panes and 320 for horizontal ones. */ minSecondary?: number; /** Collapses the split to the primary pane only (no handle, no * secondary) without unmounting the primary content. */ @@ -32,7 +33,7 @@ initialSize = 360, initialFraction = undefined, minPrimary = 220, - minSecondary = orientation === 'vertical' ? 160 : 320, + minSecondary = undefined, collapsed = false, primary, secondary, @@ -40,8 +41,10 @@ }: Props = $props(); const keyboardStep = 24; - const vertical = orientation === 'vertical'; + const vertical = untrack(() => orientation) === 'vertical'; const handleThickness = vertical ? 5 : 4; + const minSized = $derived(vertical ? minSecondary ?? 160 : minPrimary); + const minOther = $derived(vertical ? minPrimary : minSecondary ?? 320); function readStoredSize(): number | undefined { try { @@ -68,14 +71,12 @@ let host: HTMLDivElement; const storedSize = readStoredSize(); - let sizedSize = $state(storedSize ?? initialSize); + let sizedSize = $state(untrack(() => storedSize ?? initialSize)); let userSized = storedSize !== undefined; let available = $state(0); let resizeStartSize = 0; let dragCleanup: (() => void) | undefined; - const minSized = $derived(vertical ? minSecondary : minPrimary); - const minOther = $derived(vertical ? minPrimary : minSecondary); const maximum = $derived( available > 0 ? Math.max(0, available - minOther - handleThickness) diff --git a/web/src/lib/components/reader/ReadingPane.svelte b/web/src/lib/components/reader/ReadingPane.svelte new file mode 100644 index 000000000..44de424b6 --- /dev/null +++ b/web/src/lib/components/reader/ReadingPane.svelte @@ -0,0 +1,402 @@ +<script module lang="ts"> + import type { + EntryRow, + ExploreGroupDimension, + ExplorePredicate + } from '../../explore/models'; + + export type ReadingPaneSelection = + | { kind: 'entry'; row: EntryRow } + | { + kind: 'group'; + dimension: ExploreGroupDimension; + key: string; + label: string; + count?: number; + estimatedBytes?: number; + latestAt?: string; + }; + + export type ReadingPaneStatus = 'ready' | 'loading' | 'missing' | 'error' | 'unavailable'; +</script> + +<script lang="ts"> + import { Button } from '@kenn-io/kit-ui'; + import { onDestroy, untrack } from 'svelte'; + + import type { APIClient } from '../../api/client'; + import { createExploreAPI } from '../../explore/api'; + import type { ExploreCacheUnavailable, ExploreFileFact, ExploreFilter } from '../../explore/models'; + import TaskLinks from '../tasks/TaskLinks.svelte'; + import AttachmentRail from './AttachmentRail.svelte'; + import ConversationView from './ConversationView.svelte'; + + let { + client, + selection = undefined, + targetKey = '', + status = 'ready', + statusMessage = '', + unavailable = undefined, + predicate, + onClose = undefined, + conversationAnchorId = undefined, + conversationStart = undefined, + conversationEnd = undefined, + onConversationAnchorChange = undefined, + onOpenSettings = undefined, + onOpenRelationship = undefined + }: { + client: APIClient; + selection?: ReadingPaneSelection; + targetKey?: string; + status?: ReadingPaneStatus; + statusMessage?: string; + unavailable?: ExploreCacheUnavailable; + predicate: ExplorePredicate; + onClose?: () => void; + /** Overrides the entry's own anchor when the reader navigated within the + * thread (URL-carried, so Back/Forward restore the same message). */ + conversationAnchorId?: number; + /** Lower UTC bound (RFC3339, inclusive) restricting the conversation + * window opened for this selection — e.g. a chat burst's local day. */ + conversationStart?: string; + /** Upper UTC bound (RFC3339, exclusive) restricting the conversation window. */ + conversationEnd?: string; + onConversationAnchorChange?: (anchorId: number) => void; + onOpenSettings?: () => void; + /** Header action for entry selections that carry a participant, jumping + * to that participant's cluster in the Relationships hub. Omitted (and + * the button hidden) where the caller has no such destination — e.g. the + * Relationships hub's own reading pane, whose timeline rows don't carry + * participant IDs. */ + onOpenRelationship?: (participantID: number) => void; + } = $props(); + + const api = createExploreAPI(untrack(() => client)); + let files = $state<ExploreFileFact[]>([]); + let totalFiles = $state(0); + let filesLoading = $state(false); + let filesError = $state(''); + let tasksOpen = $state(false); + let requestGeneration = 0; + let requestController: AbortController | undefined; + const title = $derived(selection + ? selection.kind === 'entry' ? selection.row.title || '(untitled)' : selection.label + : targetKey || 'Selected result'); + const showFiles = $derived(selection?.kind === 'group' && + (selection.dimension === 'participant' || selection.dimension === 'domain')); + const conversationRow = $derived(selection?.kind === 'entry' && selection.row.conversation_id && + selection.row.anchor_message_id ? selection.row : undefined); + // The thread opens immediately at the entry's own anchor; an explicit + // anchor (in-thread navigation restored from the URL) overrides it. + const threadAnchorId = $derived(conversationAnchorId ?? conversationRow?.anchor_message_id); + // counterpart_participant_id is server-computed (see EntryRow): the + // smallest non-owner participant on the entry, or absent when the owner + // set is unknown or every participant is the owner. Unlike + // participant_ids[0], it never resolves to the archive owner. + const counterpartParticipantId = $derived( + selection?.kind === 'entry' ? selection.row.counterpart_participant_id : undefined + ); + const showTasks = $derived(selection?.kind === 'entry' && + selection.row.message_type === 'email' && selection.row.anchor_message_id !== undefined); + + const metaStrip = $derived.by((): string => { + if (!selection) return ''; + if (selection.kind === 'entry') { + const row = selection.row; + const parts = [row.message_type, row.source_identifier, formatDate(row.occurred_at)]; + if (row.message_count > 1) parts.push(`${row.message_count.toLocaleString()} items`); + if (row.attachment_count > 0) { + parts.push(`${row.attachment_count.toLocaleString()} ${row.attachment_count === 1 ? 'file' : 'files'}`); + } + return parts.filter(Boolean).join(' · '); + } + const parts = [`Grouped by ${selection.dimension}`]; + if (selection.count !== undefined) parts.push(`${selection.count.toLocaleString()} items`); + if (selection.estimatedBytes !== undefined) parts.push(formatBytes(selection.estimatedBytes)); + if (selection.latestAt) parts.push(`latest ${formatDate(selection.latestAt)}`); + return parts.join(' · '); + }); + + const filesPredicateFingerprint = $derived(JSON.stringify(predicate)); + + $effect(() => { + const selected = selection; + void filesPredicateFingerprint; + const currentPredicate = untrack(() => predicate); + requestGeneration += 1; + requestController?.abort(); + requestController = undefined; + files = []; + totalFiles = 0; + filesError = ''; + if (!selected || selected.kind !== 'group' || + (selected.dimension !== 'participant' && selected.dimension !== 'domain')) { + filesLoading = false; + return; + } + const generation = requestGeneration; + const controller = new AbortController(); + requestController = controller; + filesLoading = true; + const filters: ExploreFilter[] = [ + ...(currentPredicate.filters ?? []).filter((filter) => filter.dimension !== selected.dimension), + { dimension: selected.dimension, values: [selected.key] } + ]; + const filePredicate: ExplorePredicate = { + ...currentPredicate, + cursor: undefined, + candidate_snapshot_id: undefined, + grouping: undefined, + filters, + presentation: 'table' + }; + void api.files(filePredicate, controller.signal) + .then((loaded) => { + if (generation !== requestGeneration || controller.signal.aborted) return; + if (loaded.status === 'unavailable') { + filesError = loaded.unavailable.message; + return; + } + files = loaded.result.files; + totalFiles = loaded.result.totalCount; + }) + .catch((cause: unknown) => { + if (generation !== requestGeneration || controller.signal.aborted) return; + filesError = cause instanceof Error ? cause.message : 'Could not load file facts.'; + }) + .finally(() => { + if (generation === requestGeneration) filesLoading = false; + }); + }); + + // A new selection collapses the previous entry's Tasks disclosure. + $effect(() => { + void targetKey; + void selection; + tasksOpen = false; + }); + + onDestroy(() => { + requestGeneration += 1; + requestController?.abort(); + }); + + function handleOpenRelationship(): void { + if (counterpartParticipantId !== undefined) onOpenRelationship?.(counterpartParticipantId); + } + + function formatDate(value: string): string { + const parsed = new Date(value); + return Number.isNaN(parsed.valueOf()) + ? value + : new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(parsed); + } + + function formatBytes(value: number): string { + if (value < 1024) return `${value} B`; + if (value < 1024 * 1024) return `${Math.round(value / 1024)} KB`; + return `${(value / (1024 * 1024)).toFixed(1)} MB`; + } +</script> + +<aside class="reading-pane" aria-label={`Reading pane: ${title}`}> + <header class="pane-header"> + <div class="pane-heading"> + <strong class="pane-title">{title}</strong> + {#if metaStrip}<span class="pane-meta">{metaStrip}</span>{/if} + </div> + <div class="pane-actions"> + {#if showTasks} + <details class="tasks-disclosure" bind:open={tasksOpen}> + <summary aria-label="Tasks for this message">Tasks</summary> + </details> + {/if} + {#if onOpenRelationship && counterpartParticipantId !== undefined} + <Button + size="sm" + surface="outline" + label="Open relationship" + ariaLabel="Open relationship" + onclick={handleOpenRelationship} + /> + {/if} + <Button size="sm" surface="outline" label="Close" ariaLabel="Close reading pane" onclick={() => onClose?.()} /> + </div> + </header> + + {#if showTasks && tasksOpen && selection?.kind === 'entry'} + <div class="tasks-sheet"> + <TaskLinks + {client} + messageId={selection.row.anchor_message_id!} + title={selection.row.title || '(untitled)'} + sourceType={selection.row.source_type} + sourceIdentifier={selection.row.source_identifier} + onsettings={onOpenSettings} + /> + </div> + {/if} + + <div class="pane-body"> + {#if conversationRow && threadAnchorId !== undefined} + <ConversationView + {client} + conversationId={conversationRow.conversation_id!} + anchorId={threadAnchorId} + start={conversationStart} + end={conversationEnd} + onAnchorChange={(anchorId) => onConversationAnchorChange?.(anchorId)} + /> + {:else if !selection} + <section class="pane-status" aria-label="Reading pane status"> + {#if status === 'loading'} + <p role="status">Restoring selected detail…</p> + {:else if status === 'unavailable' && unavailable} + <p role="alert"> + {unavailable.message} Rebuild the cache with <code>{unavailable.recovery_action}</code>. + </p> + {:else} + <p role="alert">{statusMessage || 'The selected result is no longer available in this context.'}</p> + {/if} + </section> + {:else if selection.kind === 'entry'} + <section class="pane-status" aria-label="Entry details"> + <p class="preview">{selection.row.preview || 'No preview is available.'}</p> + </section> + {:else} + <div class="group-scroll" aria-label="Group details"> + {#if showFiles} + <AttachmentRail files={files} totalCount={totalFiles} loading={filesLoading} error={filesError} /> + {:else} + <section class="pane-status"> + <p>No further detail is available for this group.</p> + </section> + {/if} + </div> + {/if} + </div> +</aside> + +<style> + .reading-pane { + display: flex; + min-width: 0; + min-height: 0; + height: 100%; + flex-direction: column; + overflow: hidden; + background: var(--bg-surface); + } + + .pane-header { + display: flex; + min-height: 40px; + flex: none; + align-items: center; + justify-content: space-between; + gap: var(--space-4); + padding: var(--space-2) var(--space-4); + border-bottom: 1px solid var(--border-muted); + } + + .pane-heading { + display: flex; + min-width: 0; + align-items: baseline; + gap: var(--space-4); + } + + .pane-title { + overflow: hidden; + color: var(--text-primary); + font-size: var(--font-size-sm); + text-overflow: ellipsis; + white-space: nowrap; + } + + .pane-meta { + flex: none; + overflow: hidden; + color: var(--text-muted); + font-size: var(--font-size-2xs); + text-overflow: ellipsis; + white-space: nowrap; + } + + .pane-actions { + display: flex; + flex: none; + align-items: center; + gap: var(--space-2); + } + + .tasks-disclosure summary { + display: inline-flex; + align-items: center; + padding: 3px 10px; + border: 1px solid var(--control-border); + border-radius: var(--radius-md); + color: var(--text-secondary); + cursor: pointer; + font-size: var(--font-size-xs); + list-style: none; + } + + .tasks-disclosure summary::-webkit-details-marker { + display: none; + } + + .tasks-disclosure[open] summary, + .tasks-disclosure summary:hover { + background: var(--bg-surface-hover); + color: var(--text-primary); + } + + .tasks-sheet { + flex: none; + max-height: 40%; + overflow: auto; + padding: 0 var(--space-4); + border-bottom: 1px solid var(--border-muted); + } + + .pane-body { + display: flex; + min-height: 0; + flex: 1; + flex-direction: column; + } + + .group-scroll { + min-height: 0; + flex: 1; + overflow: auto; + } + + .pane-status { + display: grid; + gap: var(--space-3); + padding: var(--space-4); + } + + .pane-status p { + margin: 0; + color: var(--text-secondary); + font-size: var(--font-size-sm); + line-height: 1.5; + } + + .preview { + max-width: 70ch; + } + + code { + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-sm); + background: var(--bg-inset); + color: var(--text-primary); + font-family: var(--font-mono); + } +</style> diff --git a/web/src/lib/components/relationships/RelationshipsWorkspace.svelte b/web/src/lib/components/relationships/RelationshipsWorkspace.svelte index a4b89886f..0edae2aa6 100644 --- a/web/src/lib/components/relationships/RelationshipsWorkspace.svelte +++ b/web/src/lib/components/relationships/RelationshipsWorkspace.svelte @@ -1,14 +1,13 @@ <script module lang="ts"> - export type HubLayout = 'wide' | 'stacked' | 'narrow'; + export type HubLayout = 'wide' | 'narrow'; /** Pure container-width → layout classification, kept outside the * component so it's unit-testable without a real ResizeObserver (jsdom - * doesn't fire one). Below 1100px the reading pane stacks over the - * timeline; below 720px the list additionally becomes a slide-in drawer. */ + * doesn't fire one). Below 720px the list becomes a slide-in drawer; the + * reading pane always stacks under the timeline, so no intermediate + * breakpoint remains. */ export function computeHubLayout(width: number): HubLayout { - if (width < 720) return 'narrow'; - if (width < 1100) return 'stacked'; - return 'wide'; + return width < 720 ? 'narrow' : 'wide'; } </script> @@ -22,7 +21,8 @@ import type { RelationshipFacet, RelationshipTimelineRow } from '../../relationships/models'; import { debounce } from '../../util/debounce'; import FilesWorkspace from '../files/FilesWorkspace.svelte'; - import Inspector, { type InspectorSelection } from '../inspector/Inspector.svelte'; + import SplitPane from '../layout/SplitPane.svelte'; + import ReadingPane, { type ReadingPaneSelection } from '../reader/ReadingPane.svelte'; import RelationshipHeader from './RelationshipHeader.svelte'; import RelationshipList from './RelationshipList.svelte'; import RelationshipTimeline from './RelationshipTimeline.svelte'; @@ -74,10 +74,9 @@ onOpenFileConversation = undefined }: Props = $props(); - let selection = $state<InspectorSelection | undefined>(); + let selection = $state<ReadingPaneSelection | undefined>(); let conversationAnchorId = $state<number | undefined>(); let conversationBounds = $state<{ start: string; end: string } | undefined>(); - let inspectorWidth = $state(380); let mobileListOpen = $state(false); let fileSort = $state<FileSearchSort>({ field: 'occurred_at', direction: 'desc' }); let fileFilenameQuery = $state(''); @@ -220,7 +219,7 @@ } // Closing the reading pane leaves the hub's own keydown handler as the - // next-highest focus target (AppShell.closeInspector follows the same + // next-highest focus target (AppShell.closeReadingPane follows the same // shape): only re-focus the timeline when a pane was actually open, so // this stays a no-op when called defensively (e.g. from selectListRow). async function closeReadingPane(): Promise<void> { @@ -298,28 +297,16 @@ } }); - // chat_burst rows open the conversation directly, bounded to the burst's - // local day; other rows show the entry summary (its own "View conversation" - // opens the full, unbounded window via viewFullConversation below). + // Every row opens its conversation thread directly in the reading pane. + // chat_burst rows bound the window to the burst's local day; other rows + // open the full window anchored at the row's own message. function openTimelineRow(row: RelationshipTimelineRow): void { selection = timelineRowToSelection(row); - if (row.kind === 'chat_burst' && row.conversation_id !== undefined && row.anchor_message_id !== undefined) { - conversationAnchorId = row.anchor_message_id; - conversationBounds = localDayBoundsUTC(row.first_at ?? row.occurred_at); - } else { - conversationAnchorId = undefined; - conversationBounds = undefined; - } - } - - function viewFullConversation(_conversationId: number, anchorId: number): void { - conversationAnchorId = anchorId; - conversationBounds = undefined; - } - - function backFromConversation(): void { - conversationAnchorId = undefined; - conversationBounds = undefined; + conversationAnchorId = row.anchor_message_id; + conversationBounds = row.kind === 'chat_burst' && row.conversation_id !== undefined && + row.anchor_message_id !== undefined + ? localDayBoundsUTC(row.first_at ?? row.occurred_at) + : undefined; } function editableTarget(value: EventTarget | null): boolean { @@ -356,7 +343,6 @@ <main class="relationships-hub" aria-label="Relationships" - class:layout-stacked={layout !== 'wide'} class:layout-narrow={layout === 'narrow'} bind:this={rootElement} onkeydown={handleEscape} @@ -397,74 +383,84 @@ /> </div> - <div class="pane-center-and-reading" class:stacked={layout !== 'wide'}> - <div class="pane-center"> - {#if target === null && controller.target === null} - <div class="hub-empty" role="status"> - <p class="hub-empty-title">Select a person or domain</p> - <p class="hub-empty-hint"> - Choose someone from the list to see your shared history across mail, chat, and files. - </p> + <div class="pane-center-and-reading"> + <SplitPane + ariaLabel="Resize reading pane" + storageKey="msgvault.reading-pane.size" + orientation="vertical" + initialFraction={0.55} + minPrimary={120} + minSecondary={160} + collapsed={selection === undefined} + > + {#snippet primary()} + <div class="pane-center"> + {#if target === null && controller.target === null} + <div class="hub-empty" role="status"> + <p class="hub-empty-title">Select a person or domain</p> + <p class="hub-empty-hint"> + Choose someone from the list to see your shared history across mail, chat, and files. + </p> + </div> + {:else} + <div class="pane-center-column"> + <RelationshipHeader + detail={controller.detail} + loading={controller.timelineLoading} + {filesOpen} + {onFilesToggle} + {client} + onLinkParticipants={(a, b) => controller.linkParticipants(a, b)} + onUnlinkParticipants={(a, b) => controller.unlinkParticipants(a, b)} + /> + {#if filesOpen && filesReady} + <FilesWorkspace + {client} + predicate={contextPredicate(predicate)} + identityScope={identityScopeFor(target)} + sort={fileSort} + filenameQuery={fileFilenameQuery} + mimeFamilies={fileMIMEFamilies} + onSortChange={(value) => (fileSort = value)} + onFilenameQueryChange={(value) => (fileFilenameQuery = value)} + onMIMEFamiliesChange={(value) => (fileMIMEFamilies = value)} + onOpenItem={onOpenFileItem} + onOpenConversation={onOpenFileConversation} + /> + {:else} + <RelationshipTimeline + rows={controller.timelineRows} + loading={controller.timelineLoading} + loadingMore={controller.timelineLoadingMore} + hasMore={Boolean(controller.timelineCursor)} + error={controller.timelineError} + restartNotice={controller.timelineRestartNotice} + selectedKey={selectedRowKey} + onRowOpen={openTimelineRow} + onLoadMore={() => { void controller.loadMoreTimeline(); }} + /> + {/if} + </div> + {/if} </div> - {:else} - <div class="pane-center-column"> - <RelationshipHeader - detail={controller.detail} - loading={controller.timelineLoading} - {filesOpen} - {onFilesToggle} - {client} - onLinkParticipants={(a, b) => controller.linkParticipants(a, b)} - onUnlinkParticipants={(a, b) => controller.unlinkParticipants(a, b)} - /> - {#if filesOpen && filesReady} - <FilesWorkspace + {/snippet} + {#snippet secondary()} + {#if selection !== undefined} + <div class="pane-reading"> + <ReadingPane {client} + {selection} predicate={contextPredicate(predicate)} - identityScope={identityScopeFor(target)} - sort={fileSort} - filenameQuery={fileFilenameQuery} - mimeFamilies={fileMIMEFamilies} - onSortChange={(value) => (fileSort = value)} - onFilenameQueryChange={(value) => (fileFilenameQuery = value)} - onMIMEFamiliesChange={(value) => (fileMIMEFamilies = value)} - onOpenItem={onOpenFileItem} - onOpenConversation={onOpenFileConversation} - /> - {:else} - <RelationshipTimeline - rows={controller.timelineRows} - loading={controller.timelineLoading} - loadingMore={controller.timelineLoadingMore} - hasMore={Boolean(controller.timelineCursor)} - error={controller.timelineError} - restartNotice={controller.timelineRestartNotice} - selectedKey={selectedRowKey} - onRowOpen={openTimelineRow} - onLoadMore={() => { void controller.loadMoreTimeline(); }} + onClose={() => { void closeReadingPane(); }} + {conversationAnchorId} + conversationStart={conversationBounds?.start} + conversationEnd={conversationBounds?.end} + onConversationAnchorChange={(anchorId) => (conversationAnchorId = anchorId)} /> - {/if} - </div> - {/if} - </div> - {#if selection !== undefined} - <div class="pane-reading"> - <Inspector - {client} - {selection} - predicate={contextPredicate(predicate)} - width={inspectorWidth} - onClose={() => { void closeReadingPane(); }} - onWidthChange={(width) => (inspectorWidth = width)} - {conversationAnchorId} - conversationStart={conversationBounds?.start} - conversationEnd={conversationBounds?.end} - onViewConversation={viewFullConversation} - onBackConversation={backFromConversation} - onConversationAnchorChange={(anchorId) => (conversationAnchorId = anchorId)} - /> - </div> - {/if} + </div> + {/if} + {/snippet} + </SplitPane> </div> </main> @@ -509,14 +505,10 @@ overflow: hidden; } - .pane-center-and-reading.stacked { - flex-direction: column; - } - .pane-center { display: flex; min-width: 0; - flex: 1; + height: 100%; flex-direction: column; overflow: hidden; } @@ -562,39 +554,7 @@ } .pane-reading { - flex: none; - border-left: 1px solid var(--border-muted); + height: 100%; background: var(--bg-surface); } - - /* The reading pane provides the boundary; the embedded Inspector card - * chrome would double it up. */ - .pane-reading :global(.pinned-inspector aside) { - border: 0; - border-radius: 0; - } - - .pane-center-and-reading.stacked .pane-reading { - flex: 1; - min-height: 0; - border-left: 0; - border-top: 1px solid var(--border-muted); - } - - /* Stacked below the timeline, the reading pane spans the hub: the drag - * handle is meaningless there and the inline resize width (an inline - * style, hence the !important) must give way to the full row. */ - .pane-center-and-reading.stacked .pane-reading :global(.pinned-inspector) { - width: 100%; - } - - .pane-center-and-reading.stacked .pane-reading :global(.pinned-inspector aside) { - width: auto !important; - max-width: none; - flex: 1; - } - - .pane-center-and-reading.stacked .pane-reading :global(.kit-split-resize-handle) { - display: none; - } </style> diff --git a/web/src/lib/components/relationships/RelationshipsWorkspace.test.ts b/web/src/lib/components/relationships/RelationshipsWorkspace.test.ts index 52c04afc0..89373c71f 100644 --- a/web/src/lib/components/relationships/RelationshipsWorkspace.test.ts +++ b/web/src/lib/components/relationships/RelationshipsWorkspace.test.ts @@ -85,7 +85,7 @@ describe('RelationshipsWorkspace', () => { expect(await screen.findByText('Alice Example')).toBeDefined(); expect(screen.getByText('Select a person or domain')).toBeDefined(); - expect(screen.queryByRole('complementary', { name: /Inspect/ })).toBeNull(); + expect(screen.queryByRole('complementary', { name: /Reading pane/ })).toBeNull(); }); it('selecting a list row calls onTargetChange and opens it through the controller', async () => { @@ -108,22 +108,31 @@ describe('RelationshipsWorkspace', () => { expect(await screen.findByText('Subject')).toBeDefined(); }); - it('selecting a timeline row shows the Inspector reading pane', async () => { + it('single-clicking a timeline row opens the conversation thread in the reading pane', async () => { const { fetchFn } = fetchHandler({ '/api/v1/people/1': async () => Response.json(person(1, 'Alice Example')), '/api/v1/relationships/1/timeline': async () => Response.json({ canonical_id: 1, identity_revision: 1, cache_revision: 'cache-rel', rows: [{ key: 'message:1', kind: 'email', occurred_at: when, preview: 'Preview text', source_id: 1, title: 'Subject line', has_attachments: false, message_count: 1, anchor_message_id: 9, conversation_id: 70 }], total_count: 1 + }), + '/api/v1/conversations/70': async () => Response.json({ + id: 70, anchor_id: 9, has_before: false, has_after: false, total: 1, + messages: [{ + id: 9, conversation_id: 70, subject: 'Subject line', from: 'alice@example.com', + to: ['me@example.com'], sent_at: when, snippet: 'Preview text', body: 'Full message body text' + }] }) }); const props = { ...baseProps(fetchFn), target: 'cluster:1' }; render(RelationshipsWorkspace, { props }); await props.controller.openTarget('cluster:1', props.predicate); - await fireEvent.pointerDown((await screen.findByText('Subject line')).closest('[role="row"]')!); - const reading = await screen.findByRole('complementary', { name: /Inspect/ }); - expect(within(reading).getByText('Preview text')).toBeDefined(); + await fireEvent.click((await screen.findByText('Subject line')).closest('[role="row"]')!); + const reading = await screen.findByRole('complementary', { name: /Reading pane/ }); + // The anchor message renders expanded immediately — actual content, not metadata. + expect(await within(reading).findByText('Full message body text')).toBeDefined(); + expect(within(reading).getByRole('button', { name: 'Collapse message 9 from alice@example.com' })).toBeDefined(); }); it('opens a chat_burst directly into the conversation window bounded to the local day', async () => { @@ -148,7 +157,7 @@ describe('RelationshipsWorkspace', () => { render(RelationshipsWorkspace, { props }); await props.controller.openTarget('cluster:1', props.predicate); - await fireEvent.pointerDown((await screen.findByText('6 messages in Team Chat')).closest('[role="row"]')!); + await fireEvent.click((await screen.findByText('6 messages in Team Chat')).closest('[role="row"]')!); await waitFor(() => expect(requestedConversations).toHaveLength(1)); const url = new URL(requestedConversations[0]!.url); expect(url.searchParams.get('anchor')).toBe('500'); @@ -255,14 +264,14 @@ describe('RelationshipsWorkspace', () => { const { rerender } = render(RelationshipsWorkspace, { props }); await props.controller.openTarget('cluster:1', props.predicate); - await fireEvent.pointerDown((await screen.findByText('Subject')).closest('[role="row"]')!); - await screen.findByRole('complementary', { name: /Inspect/ }); + await fireEvent.click((await screen.findByText('Subject')).closest('[role="row"]')!); + await screen.findByRole('complementary', { name: /Reading pane/ }); // AppShell's own hydration effect clears the target directly on a // Back/Forward popstate — never through this component's handleEscape. await rerender({ ...props, target: null }); - expect(screen.queryByRole('complementary', { name: /Inspect/ })).toBeNull(); + expect(screen.queryByRole('complementary', { name: /Reading pane/ })).toBeNull(); }); it('does not scope the embedded FilesWorkspace to the previous cluster while a fast target switch is still resolving', async () => { @@ -317,7 +326,7 @@ describe('RelationshipsWorkspace', () => { await props.controller.openTarget('cluster:1', props.predicate); const row = (await screen.findByText('report.pdf')).closest('[role="row"]')!; - await fireEvent.dblClick(row); + await fireEvent.click(row); await fireEvent.click(await screen.findByRole('button', { name: 'Open containing item' })); expect(onOpenFileItem).toHaveBeenCalledWith('message:42'); @@ -419,11 +428,11 @@ describe('RelationshipsWorkspace', () => { render(RelationshipsWorkspace, { props }); await props.controller.openTarget('cluster:1', props.predicate); - await fireEvent.pointerDown((await screen.findByText('Subject')).closest('[role="row"]')!); - const reading = await screen.findByRole('complementary', { name: /Inspect/ }); + await fireEvent.click((await screen.findByText('Subject')).closest('[role="row"]')!); + const reading = await screen.findByRole('complementary', { name: /Reading pane/ }); await fireEvent.keyDown(reading, { key: 'Escape' }); - await waitFor(() => expect(screen.queryByRole('complementary', { name: /Inspect/ })).toBeNull()); + await waitFor(() => expect(screen.queryByRole('complementary', { name: /Reading pane/ })).toBeNull()); await fireEvent.keyDown(screen.getByRole('grid', { name: 'Relationship activity' }), { key: 'Escape' }); expect(props.onTargetChange).toHaveBeenCalledWith(null); @@ -442,8 +451,8 @@ describe('RelationshipsWorkspace', () => { render(RelationshipsWorkspace, { props }); await props.controller.openTarget('cluster:1', props.predicate); - await fireEvent.pointerDown((await screen.findByText('Subject')).closest('[role="row"]')!); - const reading = await screen.findByRole('complementary', { name: /Inspect/ }); + await fireEvent.click((await screen.findByText('Subject')).closest('[role="row"]')!); + const reading = await screen.findByRole('complementary', { name: /Reading pane/ }); await fireEvent.keyDown(reading, { key: 'Escape' }); const timelineGrid = screen.getByRole('grid', { name: 'Relationship activity' }); @@ -537,11 +546,10 @@ describe('forceNarrowContainer test helper', () => { }); describe('computeHubLayout', () => { - it('classifies wide, stacked, and narrow by container width', () => { + it('classifies wide and narrow by container width', () => { expect(computeHubLayout(1300)).toBe('wide'); expect(computeHubLayout(1100)).toBe('wide'); - expect(computeHubLayout(1099)).toBe('stacked'); - expect(computeHubLayout(720)).toBe('stacked'); + expect(computeHubLayout(720)).toBe('wide'); expect(computeHubLayout(719)).toBe('narrow'); expect(computeHubLayout(400)).toBe('narrow'); }); diff --git a/web/src/lib/components/relationships/timeline-support.ts b/web/src/lib/components/relationships/timeline-support.ts index e3c3ab6c4..59b1fd003 100644 --- a/web/src/lib/components/relationships/timeline-support.ts +++ b/web/src/lib/components/relationships/timeline-support.ts @@ -1,11 +1,11 @@ -import type { InspectorSelection } from '../inspector/Inspector.svelte'; +import type { ReadingPaneSelection } from '../reader/ReadingPane.svelte'; import type { RelationshipTimelineRow } from '../../relationships/models'; /** * Pure helpers shared by RelationshipTimeline and RelationshipsWorkspace: - * turning a TimelineRow into display text, an Inspector entry selection, and - * (for chat bursts) the UTC bounds of the burst's local calendar day. Kept - * dependency-free so they're testable without mounting Svelte. + * turning a TimelineRow into display text, a reading-pane entry selection, + * and (for chat bursts) the UTC bounds of the burst's local calendar day. + * Kept dependency-free so they're testable without mounting Svelte. */ /** "N messages in <title>" for chat_burst rows; the row's own title otherwise. */ @@ -15,15 +15,15 @@ export function timelineRowTitle(row: RelationshipTimelineRow): string { } /** - * Adapts a TimelineRow into the EntryRow shape Inspector's `{kind: 'entry'}` - * selection expects. TimelineRow is a leaner shape (no message_type, + * Adapts a TimelineRow into the EntryRow shape the reading pane's + * `{kind: 'entry'}` selection expects. TimelineRow is a leaner shape (no message_type, * source_type/identifier, attachment counts, participants) than the * Everything-table EntryRow it's borrowing the reading pane from — fields * absent from the timeline response fall back to values derived from what * IS present (source_id as a string identifier, has_attachments as a 0/1 * attachment count) rather than being fabricated. */ -export function timelineRowToSelection(row: RelationshipTimelineRow): InspectorSelection { +export function timelineRowToSelection(row: RelationshipTimelineRow): ReadingPaneSelection { const isBurst = row.kind === 'chat_burst'; return { kind: 'entry', diff --git a/web/src/lib/components/shell/AppShell.svelte b/web/src/lib/components/shell/AppShell.svelte index 043774d07..2feac5ed5 100644 --- a/web/src/lib/components/shell/AppShell.svelte +++ b/web/src/lib/components/shell/AppShell.svelte @@ -199,7 +199,7 @@ onRestorationFocus: () => focusGrid() }); // Owned here (not by EverythingWorkspace) so coverage-poll backoff, the - // exact lexical match-count cache, and loaded inspector group detail + // exact lexical match-count cache, and loaded reading-pane group detail // survive a workspace round-trip: AppShell renders EverythingWorkspace // behind an {#if}, so it is destroyed and recreated on every switch away // from and back to 'everything'. @@ -209,9 +209,6 @@ let keyboardHelpScopeCleanup: (() => void) | undefined; let sortNotice = $state(DEFAULT_SORT_NOTICE); let editableScopeCleanup: (() => void) | undefined; - let inspectorContentScopeCleanup: (() => void) | undefined; - let previousConversationAnchor = untrack(() => exploreState.current.conversationAnchor); - let conversationFocusGeneration = 0; let previousSortNoticeWorkspace = untrack(() => exploreState.current.workspace); $effect(() => { @@ -238,7 +235,7 @@ }); const selectedAttachmentID = $derived(parseAttachmentSelection(exploreState.current.selectedRow)); - const inspectorTargetKey = $derived( + const readingTargetKey = $derived( selectedAttachmentID === undefined ? exploreState.current.selectedRow : null ); @@ -368,23 +365,6 @@ return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined; }); - $effect(() => { - const currentAnchor = exploreState.current.conversationAnchor; - const priorAnchor = previousConversationAnchor; - previousConversationAnchor = currentAnchor; - if (priorAnchor === null || currentAnchor !== null) return; - conversationFocusGeneration += 1; - const generation = conversationFocusGeneration; - void tick().then(() => { - if (generation !== conversationFocusGeneration || - exploreState.current.conversationAnchor !== null) return; - const returnControl = document.querySelector<HTMLButtonElement>( - '[data-conversation-return] button' - ); - (returnControl ?? currentGrid())?.focus(); - }); - }); - function editableTarget(target: EventTarget | null): boolean { if (!(target instanceof Element)) return false; const element = target as HTMLElement; @@ -448,18 +428,12 @@ grid.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: false, cancelable: true })); } - async function closeInspector(): Promise<void> { - setInspectorContentFocus(false); + async function closeReadingPane(): Promise<void> { commitNavigation({ selectedRow: null }); await tick(); focusGrid(); } - function openContainingConversation(_conversationId: number, anchorId: number): void { - setInspectorContentFocus(false); - commitNavigation({ conversationAnchor: String(anchorId) }); - } - function openFileItem(entryKey: string): void { if (selectedAttachmentID !== undefined) { replaceCommittedRestorableNavigation({ @@ -514,22 +488,10 @@ (contextualViewerReturnFocus ?? currentGrid())?.focus(); } - function backFromConversation(): void { - setInspectorContentFocus(false); - window.history.back(); - } - function changeConversationAnchor(anchorId: number): void { replaceCommittedNavigation({ conversationAnchor: String(anchorId) }); } - function setInspectorContentFocus(focused: boolean): void { - inspectorContentScopeCleanup?.(); - inspectorContentScopeCleanup = focused - ? appShortcuts.pushScope('everything-inspector-content') - : undefined; - } - // The Relationships hub owns its own Esc layering (reading pane → timeline // → list) and only lets an Esc it didn't consume bubble here once it has // nothing left to close. This function's branches read/write state that @@ -541,13 +503,12 @@ function handleEscape(event: KeyboardEvent): void { if (editableTarget(event.target)) return; if (exploreState.current.workspace === 'relationships') return; - if (exploreState.current.conversationAnchor !== null) { - backFromConversation(); - return; - } else if (selectedAttachmentID !== undefined) { + if (selectedAttachmentID !== undefined) { void closeContextualViewer(); } else if (exploreState.current.selectedRow !== null) { - void closeInspector(); + // Closing the reading pane clears any in-thread anchor with it — + // the thread is the pane's default content, not a separate layer. + void closeReadingPane(); } else if (exploreState.current.groupingChain.length > 0) { commitUngroup(); } @@ -570,7 +531,7 @@ document.querySelector<HTMLButtonElement>('button[aria-label="Sort: newest first"]')?.focus(); } - function navigateInspector(delta: number): void { + function navigateReader(delta: number): void { if (!exploreState.current.selectedRow || loader.rows.length === 0) return; const index = loader.rows.findIndex((row) => row.key === exploreState.current.selectedRow); if (index < 0) return; @@ -596,8 +557,8 @@ const commandHandlers: CommandHandlers = { 'move-next': (event) => relay(event, 'j'), 'move-previous': (event) => relay(event, 'k'), - 'reader-previous': () => navigateInspector(-1), - 'reader-next': () => navigateInspector(1), + 'reader-previous': () => navigateReader(-1), + 'reader-next': () => navigateReader(1), 'page-up': (event) => relay(event, 'PageUp'), 'page-down': (event) => relay(event, 'PageDown'), 'first-row': (event) => relay(event, 'Home'), @@ -677,6 +638,9 @@ } function openRow(row: EntryRow): void { + // Single-click selects AND opens; re-opening the already-open row must + // not push a duplicate history entry. + if (exploreState.current.selectedRow === row.key) return; commitNavigation({ selectedRow: row.key }); } @@ -830,7 +794,6 @@ editableScopeCleanup = undefined; keyboardHelpScopeCleanup?.(); keyboardHelpScopeCleanup = undefined; - setInspectorContentFocus(false); appearance.destroy(); relationshipsController.destroy(); if (ownsState) exploreState.destroy(); @@ -956,7 +919,7 @@ unavailable={loader.unavailable} drillable={groupingByDimension(exploreState.current.groupingChain[0]!).requestable} focusedKey={exploreState.current.activeRow} - inspectedKey={inspectorTargetKey} + inspectedKey={readingTargetKey} scrollAnchor={exploreState.current.scrollAnchor} restoring={loader.restoring} onDrill={drillGroup} @@ -1006,7 +969,7 @@ session={everythingSession} {selection} {enabled} - {inspectorTargetKey} + {readingTargetKey} {conversationAnchorId} {sortNotice} bind:searchInput @@ -1022,11 +985,8 @@ {drillGroup} {openFileItem} {openContextualFile} - closeInspector={() => void closeInspector()} - {setInspectorContentFocus} + closeReadingPane={() => void closeReadingPane()} {openRelationship} - {openContainingConversation} - {backFromConversation} {changeConversationAnchor} /> {/if} diff --git a/web/src/lib/components/shell/AppShell.test.ts b/web/src/lib/components/shell/AppShell.test.ts index 6975a78d7..b75e3c203 100644 --- a/web/src/lib/components/shell/AppShell.test.ts +++ b/web/src/lib/components/shell/AppShell.test.ts @@ -546,7 +546,7 @@ describe('AppShell', () => { const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); const row = (await screen.findByText('report.pdf')).closest('[role="row"]')!; - await fireEvent.dblClick(row); + await fireEvent.click(row); await fireEvent.click(await screen.findByRole('button', { name: 'Open containing item' })); await waitFor(() => expect(state.current.workspace).toBe('everything')); diff --git a/web/src/lib/components/shell/EverythingSessionState.svelte.ts b/web/src/lib/components/shell/EverythingSessionState.svelte.ts index 38b3b9374..a54230be2 100644 --- a/web/src/lib/components/shell/EverythingSessionState.svelte.ts +++ b/web/src/lib/components/shell/EverythingSessionState.svelte.ts @@ -1,4 +1,4 @@ -import type { InspectorSelection } from '../inspector/Inspector.svelte'; +import type { ReadingPaneSelection } from '../reader/ReadingPane.svelte'; import { VisibleLexicalCountCache, type SearchCoverageValue } from '../../search/modes'; /** @@ -27,10 +27,10 @@ export class EverythingSessionState { readonly lexicalCountCache = new VisibleLexicalCountCache(128); /** Maps a (lexicalRevision, predicateFingerprint) pair to its last-seen canonical query hash. */ readonly canonicalQueryHashes = new Map<string, string>(); - /** Loaded detail for the currently inspected group row, if any. */ - inspectorGroupDetail = $state<InspectorSelection>(); - /** Generation counter guarding the inspector-detail fetch against stale/aborted responses. */ - inspectorDetailGeneration = 0; - /** `predicateFingerprint|group:dimension:key` that `inspectorGroupDetail` was loaded under. */ - inspectorDetailFingerprint = ''; + /** Loaded detail for the group row currently open in the reading pane, if any. */ + readingGroupDetail = $state<ReadingPaneSelection>(); + /** Generation counter guarding the group-detail fetch against stale/aborted responses. */ + readingDetailGeneration = 0; + /** `predicateFingerprint|group:dimension:key` that `readingGroupDetail` was loaded under. */ + readingDetailFingerprint = ''; } diff --git a/web/src/lib/components/shell/EverythingWorkspace.svelte b/web/src/lib/components/shell/EverythingWorkspace.svelte index 70386e515..42c138046 100644 --- a/web/src/lib/components/shell/EverythingWorkspace.svelte +++ b/web/src/lib/components/shell/EverythingWorkspace.svelte @@ -27,13 +27,14 @@ import FilesPresentation from '../explore/FilesPresentation.svelte'; import GroupTable from '../explore/GroupTable.svelte'; import SelectionBar from '../explore/SelectionBar.svelte'; + import SplitPane from '../layout/SplitPane.svelte'; import PersonTimeline from '../people/PersonTimeline.svelte'; import SearchCoverage from '../search/SearchCoverage.svelte'; import SearchModeControl from '../search/SearchModeControl.svelte'; - import Inspector, { - type InspectorSelection, - type InspectorStatus - } from '../inspector/Inspector.svelte'; + import ReadingPane, { + type ReadingPaneSelection, + type ReadingPaneStatus + } from '../reader/ReadingPane.svelte'; import type { SearchCoverageAction } from '../../search/modes'; import type { EverythingSessionState } from './EverythingSessionState.svelte'; @@ -46,7 +47,7 @@ session: EverythingSessionState; selection: ExploreSelectionState; enabled: boolean; - inspectorTargetKey: string | null; + readingTargetKey: string | null; conversationAnchorId: number | undefined; sortNotice: string; searchInput?: HTMLInputElement; @@ -62,11 +63,8 @@ drillGroup: (row: ExploreGroupRow) => void; openFileItem: (entryKey: string) => void; openContextualFile: (file: ExploreFileFact) => void; - closeInspector: () => void; - setInspectorContentFocus: (focused: boolean) => void; + closeReadingPane: () => void; openRelationship: (participantID: number) => void; - openContainingConversation: (conversationId: number, anchorId: number) => void; - backFromConversation: () => void; changeConversationAnchor: (anchorId: number) => void; } @@ -77,7 +75,7 @@ session, selection, enabled, - inspectorTargetKey, + readingTargetKey, conversationAnchorId, sortNotice, searchInput = $bindable(undefined), @@ -93,19 +91,16 @@ drillGroup, openFileItem, openContextualFile, - closeInspector, - setInspectorContentFocus, + closeReadingPane, openRelationship, - openContainingConversation, - backFromConversation, changeConversationAnchor }: Props = $props(); const api = createExploreAPI(untrack(() => client)); // coverage, coveragePollAttempts/coveragePollKey, visibleLexicalRowKeys, - // lexicalCountCache/canonicalQueryHashes, and inspectorGroupDetail/ - // inspectorDetailGeneration/inspectorDetailFingerprint live on `session` + // lexicalCountCache/canonicalQueryHashes, and readingGroupDetail/ + // readingDetailGeneration/readingDetailFingerprint live on `session` // (owned by AppShell) so they survive this component being destroyed and // recreated on a workspace round-trip. Timer handles, AbortControllers, // and other in-flight-request bookkeeping below stay local: they are @@ -117,10 +112,10 @@ let lexicalCountController: AbortController | undefined; let lexicalCountTimer: ReturnType<typeof setTimeout> | undefined; let lexicalCountRequestKey = ''; - let inspectorDetailLoading = $state(false); - let inspectorDetailError = $state(''); - let inspectorDetailUnavailable = $state<ExploreCacheUnavailable>(); - let inspectorDetailController: AbortController | undefined; + let readingDetailLoading = $state(false); + let readingDetailError = $state(''); + let readingDetailUnavailable = $state<ExploreCacheUnavailable>(); + let readingDetailController: AbortController | undefined; const allMatchingSelection = $derived.by((): AllMatchingExploreSelection | undefined => { if (!loader.result || loader.loading || loader.loadingMore) return undefined; @@ -134,37 +129,37 @@ return createAllMatchingSelection(predicate, loader.result, loader.resultGeneration); }); - const inspectedSelection = $derived.by((): InspectorSelection | undefined => { - const selected = inspectorTargetKey; + const readingSelection = $derived.by((): ReadingPaneSelection | undefined => { + const selected = readingTargetKey; if (!selected) return undefined; const entry = loader.rows.find((row) => row.key === selected); if (entry) return { kind: 'entry', row: entry }; const group = parseGroupSelection(selected); - return group && session.inspectorGroupDetail?.kind === 'group' && - session.inspectorGroupDetail.dimension === group.dimension && session.inspectorGroupDetail.key === group.key - ? session.inspectorGroupDetail + return group && session.readingGroupDetail?.kind === 'group' && + session.readingGroupDetail.dimension === group.dimension && session.readingGroupDetail.key === group.key + ? session.readingGroupDetail : undefined; }); - const inspectorState = $derived.by((): { - status: InspectorStatus; + const readingState = $derived.by((): { + status: ReadingPaneStatus; message: string; unavailable?: ExploreCacheUnavailable; } => { - const selected = inspectorTargetKey; - if (!selected || inspectedSelection) return { status: 'ready', message: '' }; + const selected = readingTargetKey; + if (!selected || readingSelection) return { status: 'ready', message: '' }; if (parseGroupSelection(selected)) { - if (inspectorDetailUnavailable) { + if (readingDetailUnavailable) { return { status: 'unavailable', - message: inspectorDetailUnavailable.message, - unavailable: inspectorDetailUnavailable + message: readingDetailUnavailable.message, + unavailable: readingDetailUnavailable }; } - if (inspectorDetailLoading || !inspectorDetailError) return { status: 'loading', message: '' }; + if (readingDetailLoading || !readingDetailError) return { status: 'loading', message: '' }; return { status: 'missing', - message: inspectorDetailError + message: readingDetailError }; } if (loader.unavailable) { @@ -177,11 +172,11 @@ }; }); - const inspectorPredicateFingerprint = $derived(predicateFingerprint(exploreState.predicate())); + const readingPredicateFingerprint = $derived(predicateFingerprint(exploreState.predicate())); $effect(() => { const target = parseGroupSelection(exploreState.current.selectedRow); - const fingerprint = inspectorPredicateFingerprint; + const fingerprint = readingPredicateFingerprint; // Tracked, not untracked like `predicate` below: a cache rebuild // invalidates a stale detail whether it happened before this mount // (`session` persisted the fingerprint across a prior workspace @@ -195,9 +190,9 @@ const cacheRevision = loader.result?.cacheRevision; const predicate = untrack(() => exploreState.predicate()); if (target && cacheRevision === undefined) { - inspectorDetailLoading = true; - inspectorDetailError = ''; - inspectorDetailUnavailable = undefined; + readingDetailLoading = true; + readingDetailError = ''; + readingDetailUnavailable = undefined; return; } const loadedKey = target ? `${fingerprint}|${cacheRevision}|group:${target.dimension}:${target.key}` : ''; @@ -208,39 +203,39 @@ // which survives the round-trip — reuse it instead. Read through // `untrack` so this effect's dependencies stay limited to the selection // and predicate (matching the pre-persistence behavior); this effect - // also writes `session.inspectorGroupDetail`/`inspectorDetailFingerprint` + // also writes `session.readingGroupDetail`/`readingDetailFingerprint` // below, and a tracked read of the same fields here would make the // effect re-trigger itself on every write. const canReuse = untrack(() => Boolean( - target && loadedKey === session.inspectorDetailFingerprint && session.inspectorGroupDetail + target && loadedKey === session.readingDetailFingerprint && session.readingGroupDetail )); if (canReuse) { - inspectorDetailLoading = false; - inspectorDetailError = ''; - inspectorDetailUnavailable = undefined; + readingDetailLoading = false; + readingDetailError = ''; + readingDetailUnavailable = undefined; return; } - session.inspectorDetailGeneration += 1; - inspectorDetailController?.abort(); - inspectorDetailController = undefined; - session.inspectorGroupDetail = undefined; - session.inspectorDetailFingerprint = ''; - inspectorDetailError = ''; - inspectorDetailUnavailable = undefined; + session.readingDetailGeneration += 1; + readingDetailController?.abort(); + readingDetailController = undefined; + session.readingGroupDetail = undefined; + session.readingDetailFingerprint = ''; + readingDetailError = ''; + readingDetailUnavailable = undefined; if (!target) { - inspectorDetailLoading = false; + readingDetailLoading = false; return; } const filters = filtersForGroup(predicate.filters ?? [], target.dimension, target.key); if (!filters) { - inspectorDetailLoading = false; - inspectorDetailError = 'The selected group is not available in this context.'; + readingDetailLoading = false; + readingDetailError = 'The selected group is not available in this context.'; return; } - const generation = session.inspectorDetailGeneration; + const generation = session.readingDetailGeneration; const controller = new AbortController(); - inspectorDetailController = controller; - inspectorDetailLoading = true; + readingDetailController = controller; + readingDetailLoading = true; const detailPredicate = { ...predicate, cursor: undefined, @@ -252,17 +247,17 @@ }; void api.groups(detailPredicate, target.dimension, controller.signal) .then((loaded) => { - if (generation !== session.inspectorDetailGeneration || controller.signal.aborted) return; + if (generation !== session.readingDetailGeneration || controller.signal.aborted) return; if (loaded.status === 'unavailable') { - inspectorDetailUnavailable = loaded.unavailable; + readingDetailUnavailable = loaded.unavailable; return; } const row = loaded.result.rows.find((candidate) => candidate.key === target.key); if (!row) { - inspectorDetailError = 'The selected group is no longer available in this context.'; + readingDetailError = 'The selected group is no longer available in this context.'; return; } - session.inspectorGroupDetail = { + session.readingGroupDetail = { kind: 'group', dimension: target.dimension, key: target.key, @@ -271,14 +266,14 @@ estimatedBytes: row.estimated_bytes, latestAt: row.latest_at }; - session.inspectorDetailFingerprint = loadedKey; + session.readingDetailFingerprint = loadedKey; }) .catch((cause: unknown) => { - if (generation !== session.inspectorDetailGeneration || controller.signal.aborted) return; - inspectorDetailError = cause instanceof Error ? cause.message : 'Could not restore the selected group.'; + if (generation !== session.readingDetailGeneration || controller.signal.aborted) return; + readingDetailError = cause instanceof Error ? cause.message : 'Could not restore the selected group.'; }) .finally(() => { - if (generation === session.inspectorDetailGeneration) inspectorDetailLoading = false; + if (generation === session.readingDetailGeneration) readingDetailLoading = false; }); }); @@ -473,8 +468,8 @@ if (coveragePollTimer !== undefined) clearTimeout(coveragePollTimer); if (lexicalCountTimer !== undefined) clearTimeout(lexicalCountTimer); lexicalCountController?.abort(); - session.inspectorDetailGeneration += 1; - inspectorDetailController?.abort(); + session.readingDetailGeneration += 1; + readingDetailController?.abort(); }); </script> @@ -547,10 +542,17 @@ /> <span class="kit-sr-only" role="status" aria-label="Sort status" aria-live="polite">{sortNotice}</span> - <div - class="results-and-inspector" - class:results-and-inspector--pinned={Boolean(inspectorTargetKey)} - > + <div class="results-split"> + <SplitPane + ariaLabel="Resize reading pane" + storageKey="msgvault.reading-pane.size" + orientation="vertical" + initialFraction={0.55} + minPrimary={120} + minSecondary={160} + collapsed={!readingTargetKey} + > + {#snippet primary()} <div class="results-primary"> {#if exploreState.current.groupingChain.length > 0} <GroupTable @@ -565,7 +567,7 @@ unavailable={loader.unavailable} drillable={groupingByDimension(exploreState.current.groupingChain[0]!).requestable} focusedKey={exploreState.current.activeRow} - inspectedKey={inspectorTargetKey} + inspectedKey={readingTargetKey} scrollAnchor={exploreState.current.scrollAnchor} restoring={loader.restoring} onDrill={drillGroup} @@ -616,7 +618,7 @@ unavailable={loader.unavailable} error={loader.error} focusedKey={exploreState.current.activeRow} - inspectedKey={inspectorTargetKey} + inspectedKey={readingTargetKey} scrollAnchor={exploreState.current.scrollAnchor} restoring={loader.restoring} onOpen={openRow} @@ -634,7 +636,7 @@ columns={exploreState.current.columns} columnWidths={exploreState.current.columnWidths} focusedKey={exploreState.current.activeRow} - inspectedKey={inspectorTargetKey} + inspectedKey={readingTargetKey} scrollAnchor={exploreState.current.scrollAnchor} restoring={loader.restoring} loading={loader.loading} @@ -656,27 +658,26 @@ {/if} {/if} </div> - {#if inspectorTargetKey} - <Inspector - {client} - selection={inspectedSelection} - targetKey={inspectorTargetKey} - status={inspectorState.status} - statusMessage={inspectorState.message} - unavailable={inspectorState.unavailable} - predicate={exploreState.predicate()} - width={exploreState.current.inspectorWidth} - onClose={closeInspector} - onWidthChange={(inspectorWidth) => exploreState.replaceTransient({ inspectorWidth })} - onContentFocusChange={setInspectorContentFocus} - onOpenSettings={() => commitWorkspace('settings')} - onOpenRelationship={openRelationship} - {conversationAnchorId} - onViewConversation={openContainingConversation} - onBackConversation={backFromConversation} - onConversationAnchorChange={changeConversationAnchor} - /> - {/if} + {/snippet} + {#snippet secondary()} + {#if readingTargetKey} + <ReadingPane + {client} + selection={readingSelection} + targetKey={readingTargetKey} + status={readingState.status} + statusMessage={readingState.message} + unavailable={readingState.unavailable} + predicate={exploreState.predicate()} + onClose={closeReadingPane} + onOpenSettings={() => commitWorkspace('settings')} + onOpenRelationship={openRelationship} + {conversationAnchorId} + onConversationAnchorChange={changeConversationAnchor} + /> + {/if} + {/snippet} + </SplitPane> </div> <footer class="keyboard-help" aria-label="Keyboard shortcuts"> @@ -737,7 +738,7 @@ flex: 1; } - .results-and-inspector { + .results-split { display: flex; min-width: 0; min-height: 200px; @@ -748,11 +749,18 @@ .results-primary { display: flex; min-width: 0; - min-height: 0; - flex: 1; + height: 100%; flex-direction: column; } + /* The reading pane provides its own surface; the split's secondary pane + * only frames it with the hairline above the drag handle. */ + .results-split :global([data-pane='secondary']) { + border: 1px solid var(--border-default); + border-top: 0; + border-radius: 0 0 var(--radius-md) var(--radius-md); + } + .keyboard-help { display: flex; min-height: 22px; @@ -776,10 +784,6 @@ min-width: 100%; } - .results-and-inspector--pinned .results-primary { - display: none; - } - .keyboard-help { overflow-x: auto; } diff --git a/web/src/lib/components/shell/EverythingWorkspace.test.ts b/web/src/lib/components/shell/EverythingWorkspace.test.ts index a07b951bf..44c798867 100644 --- a/web/src/lib/components/shell/EverythingWorkspace.test.ts +++ b/web/src/lib/components/shell/EverythingWorkspace.test.ts @@ -545,7 +545,7 @@ describe('EverythingWorkspace', () => { } }); - it('refetches a persisted group inspector detail after the analytical cache rebuilds', async () => { + it('refetches a persisted group reading-pane detail after the analytical cache rebuilds', async () => { window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); let cacheRevision = 'cache-1'; let count = 12; @@ -565,8 +565,8 @@ describe('EverythingWorkspace', () => { await screen.findByText('Group Seven v1'); await fireEvent.click(screen.getByRole('button', { name: 'Drill into Group Seven v1' })); - expect(await screen.findByRole('complementary', { name: 'Inspect Group Seven v1' })).toBeDefined(); - expect(await screen.findByText('12 items')).toBeDefined(); + expect(await screen.findByRole('complementary', { name: 'Reading pane: Group Seven v1' })).toBeDefined(); + expect(await screen.findByText(/12 items/)).toBeDefined(); // Leave Everything via a workspace switch (destroying EverythingWorkspace, // but not the session that persists the loaded detail — commitWorkspace @@ -587,9 +587,9 @@ describe('EverythingWorkspace', () => { // Must reflect the rebuilt data, not the detail persisted from before // the rebuild under the same group key and predicate. - expect(await screen.findByRole('complementary', { name: 'Inspect Group Seven v2' })).toBeDefined(); - expect(await screen.findByText('99 items')).toBeDefined(); - expect(screen.queryByText('12 items')).toBeNull(); + expect(await screen.findByRole('complementary', { name: 'Reading pane: Group Seven v2' })).toBeDefined(); + expect(await screen.findByText(/99 items/)).toBeDefined(); + expect(screen.queryByText(/12 items/)).toBeNull(); rendered.unmount(); state.destroy(); }); @@ -868,7 +868,7 @@ describe('EverythingWorkspace', () => { const state = new ExploreState(window); const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); - expect(await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeDefined(); + expect(await screen.findByRole('complementary', { name: 'Reading pane: Synthetic subject 1200' })).toBeDefined(); expect(requests).toHaveLength(3); expect(state.current.selectedRow).toBe('message:1200'); rendered.unmount(); @@ -900,7 +900,7 @@ describe('EverythingWorkspace', () => { const state = new ExploreState(window); const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); - expect(await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeDefined(); + expect(await screen.findByRole('complementary', { name: 'Reading pane: Synthetic subject 1200' })).toBeDefined(); const grid = screen.getByRole('grid', { name: 'Everything results' }) as HTMLDivElement; await waitFor(() => expect(grid.getAttribute('aria-activedescendant')).toBe('everything-row-message-3a-1')); await waitFor(() => expect(grid.scrollTop).toBe(7)); @@ -967,13 +967,13 @@ describe('EverythingWorkspace', () => { }); const state = new ExploreState(window); const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); - await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1200' }); + await screen.findByRole('complementary', { name: 'Reading pane: Synthetic subject 1200' }); await fireEvent.click(screen.getByRole('button', { name: 'Settings' })); - expect(screen.queryByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeNull(); + expect(screen.queryByRole('complementary', { name: 'Reading pane: Synthetic subject 1200' })).toBeNull(); window.history.back(); await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); - expect(await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeDefined(); + expect(await screen.findByRole('complementary', { name: 'Reading pane: Synthetic subject 1200' })).toBeDefined(); const grid = screen.getByRole('grid', { name: 'Everything results' }) as HTMLDivElement; await waitFor(() => expect(requests).toHaveLength(6)); await waitFor(() => expect(grid.getAttribute('aria-activedescendant')).toBe('everything-row-message-3a-1')); @@ -982,7 +982,7 @@ describe('EverythingWorkspace', () => { window.history.forward(); await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); await waitFor(() => expect(screen.getByRole('button', { name: 'Settings' }).getAttribute('aria-current')).toBe('page')); - expect(screen.queryByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeNull(); + expect(screen.queryByRole('complementary', { name: 'Reading pane: Synthetic subject 1200' })).toBeNull(); rendered.unmount(); state.destroy(); }); @@ -1016,8 +1016,8 @@ describe('EverythingWorkspace', () => { const state = new ExploreState(window); const rendered = render(AppShell, { client: createAPIClient(fetchFn), state }); - expect(await screen.findByRole('complementary', { name: 'Inspect Alice Example' })).toBeDefined(); - expect(screen.getByRole('region', { name: 'Group details' }).textContent).toContain('3 items'); + const reading = await screen.findByRole('complementary', { name: 'Reading pane: Alice Example' }); + expect(within(reading).getByText(/3 items/)).toBeDefined(); expect(groupRequests).toHaveLength(1); await expect(groupRequests[0]!.clone().json()).resolves.toEqual({ filters: [ @@ -1115,7 +1115,7 @@ describe('EverythingWorkspace', () => { rows: [{ key: 'second.example', label: 'Second domain', count: 2 }], total_count: 1, cache_revision: 'cache-1', search_provenance: {} })); - expect(await screen.findByRole('complementary', { name: 'Inspect Second domain' })).toBeDefined(); + expect(await screen.findByRole('complementary', { name: 'Reading pane: Second domain' })).toBeDefined(); pending[0]!.resolve(Response.json({ rows: [{ key: 'first.example', label: 'Stale first domain', count: 9 }], @@ -1645,7 +1645,7 @@ describe('EverythingWorkspace', () => { await screen.findByText('Example source group'); await fireEvent.click(screen.getByRole('button', { name: 'Drill into Example source group' })); - expect(await screen.findByRole('complementary', { name: 'Inspect Restored source detail' })).toBeDefined(); + expect(await screen.findByRole('complementary', { name: 'Reading pane: Restored source detail' })).toBeDefined(); expect(screen.queryByRole('grid', { name: 'Everything grouped by source' })).toBeNull(); expect(state.current.selectedRow).toBe('group:source:7'); @@ -1656,7 +1656,7 @@ describe('EverythingWorkspace', () => { window.history.forward(); await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); - expect(await screen.findByRole('complementary', { name: 'Inspect Restored source detail' })).toBeDefined(); + expect(await screen.findByRole('complementary', { name: 'Reading pane: Restored source detail' })).toBeDefined(); expect(state.current.selectedRow).toBe('group:source:7'); rendered.unmount(); state.destroy(); @@ -1827,8 +1827,9 @@ describe('EverythingWorkspace', () => { }); - it('opens, resizes, and closes the pinned inspector with URL and focus restoration', async () => { + it('opens, resizes, and closes the bottom reading pane with size persistence and focus restoration', async () => { window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); + window.localStorage.removeItem('msgvault.reading-pane.size'); const fetchFn = vi.fn<typeof fetch>(async () => Response.json(exploreResponse({ rows: [entry(1), entry(2)], total_count: 2 }))); @@ -1839,29 +1840,32 @@ describe('EverythingWorkspace', () => { grid.focus(); await fireEvent.keyDown(grid, { key: 'Enter' }); - expect(await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1' })).toBeDefined(); + expect(await screen.findByRole('complementary', { name: 'Reading pane: Synthetic subject 1' })).toBeDefined(); expect(state.current.selectedRow).toBe('message:1'); - expect(state.current.inspectorPinned).toBe(true); - expect(screen.queryByRole('dialog', { name: 'Inspect Synthetic subject 1' })).toBeNull(); + // A bottom split, never a modal drawer over the results. + expect(screen.queryByRole('dialog')).toBeNull(); expect(document.querySelector('.kit-detail-drawer-overlay')).toBeNull(); - await fireEvent.keyDown(screen.getByRole('button', { name: 'Resize inspector' }), { key: 'ArrowLeft' }); - expect(state.current.inspectorWidth).toBe(404); - expect(parseExploreURLState(window.location.search).inspectorWidth).toBe(404); - await fireEvent.click(screen.getByRole('button', { name: 'Close inspector' })); + // Keyboard resize on the split handle persists the size locally (not in the URL). + await fireEvent.keyDown(screen.getByRole('button', { name: 'Resize reading pane' }), { key: 'ArrowUp' }); + const persisted = window.localStorage.getItem('msgvault.reading-pane.size'); + expect(persisted).not.toBeNull(); + expect(parseExploreURLState(window.location.search)).not.toHaveProperty('inspectorWidth'); + await fireEvent.click(screen.getByRole('button', { name: 'Close reading pane' })); await waitFor(() => expect(document.activeElement).toBe(grid)); expect(state.current.selectedRow).toBeNull(); window.history.back(); await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); - expect(await screen.findByRole('complementary', { name: 'Inspect Synthetic subject 1' })).toBeDefined(); - expect(state.current.inspectorWidth).toBe(404); + expect(await screen.findByRole('complementary', { name: 'Reading pane: Synthetic subject 1' })).toBeDefined(); + expect(window.localStorage.getItem('msgvault.reading-pane.size')).toBe(persisted); + window.localStorage.removeItem('msgvault.reading-pane.size'); rendered.unmount(); state.destroy(); }); - it('replaces peer anchors so one Back restores the complete inspector URL and focus', async () => { + it('carries the in-thread anchor in the URL by replacement so Back/Forward restore the same message', async () => { window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); const row = { ...entry(1), anchor_message_id: 1, conversation_id: 7 }; const fetchFn = vi.fn<typeof fetch>(async (input) => { @@ -1888,23 +1892,36 @@ describe('EverythingWorkspace', () => { const grid = await screen.findByRole('grid', { name: 'Everything results' }); await screen.findByText(row.title); grid.focus(); - await fireEvent.keyDown(grid, { key: 'Enter' }); const priorSearch = window.location.search; + await fireEvent.keyDown(grid, { key: 'Enter' }); - await fireEvent.click(await screen.findByRole('button', { name: 'View conversation' })); + // The thread renders directly in the reading pane: the anchor message is + // expanded, its sibling collapsed. + const reading = await screen.findByRole('complementary', { name: `Reading pane: ${row.title}` }); + expect(await within(reading).findByRole('button', { name: 'Collapse message 1 from alice@example.com' })).toBeDefined(); - expect(state.current.conversationAnchor).toBe('1'); - expect(await screen.findByRole('heading', { name: 'Conversation' })).toBeDefined(); - await fireEvent.click(await screen.findByRole('button', { name: /Open message 2/ })); + // Expanding another message moves the in-thread anchor. The anchor is + // committed by replacement onto the pane's own history entry, so a + // single Back closes the pane and restores the pre-open URL exactly. + await fireEvent.click(within(reading).getByRole('button', { name: 'Expand message 2 from alice@example.com' })); await waitFor(() => expect(state.current.conversationAnchor).toBe('2')); + expect(within(reading).getByRole('button', { name: 'Collapse message 2 from alice@example.com' })).toBeDefined(); + window.history.back(); await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); - await waitFor(() => expect(state.current.conversationAnchor).toBeNull()); + await waitFor(() => expect(state.current.selectedRow).toBeNull()); + expect(state.current.conversationAnchor).toBeNull(); expect(window.location.search).toBe(priorSearch); - const viewConversation = screen.getByRole('button', { name: 'View conversation' }); - expect(viewConversation).toBeDefined(); - await waitFor(() => expect(document.activeElement).toBe(viewConversation)); - await fireEvent.keyDown(viewConversation, { key: 'Escape' }); + await waitFor(() => expect(document.activeElement).toBe(grid)); + + // Forward restores the pane at the exact in-thread message it left off. + window.history.forward(); + await new Promise((resolve) => window.addEventListener('popstate', resolve, { once: true })); + await waitFor(() => expect(state.current.conversationAnchor).toBe('2')); + const restored = await screen.findByRole('complementary', { name: `Reading pane: ${row.title}` }); + expect(await within(restored).findByRole('button', { name: 'Collapse message 2 from alice@example.com' })).toBeDefined(); + + await fireEvent.keyDown(restored, { key: 'Escape' }); await waitFor(() => expect(state.current.selectedRow).toBeNull()); await waitFor(() => expect(document.activeElement).toBe(grid)); rendered.unmount(); @@ -1912,7 +1929,7 @@ describe('EverythingWorkspace', () => { }); - it('suspends application row shortcuts in explicit inspector content focus', async () => { + it('suspends application shortcuts inside editable reading-pane content and keeps reader navigation live outside it', async () => { window.history.replaceState(null, '', `/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); const fetchFn = vi.fn<typeof fetch>(async () => Response.json(exploreResponse({ rows: [entry(1), entry(2)], total_count: 2 @@ -1923,12 +1940,11 @@ describe('EverythingWorkspace', () => { await screen.findByText('Synthetic subject 2'); grid.focus(); await fireEvent.keyDown(grid, { key: 'Enter' }); - await fireEvent.click(await screen.findByRole('button', { name: 'Focus inspector content' })); - const content = screen.getByRole('region', { name: 'Inspector content' }); - expect(appShortcuts.activeScope()).toBe('everything-inspector-content'); + const reading = await screen.findByRole('complementary', { name: 'Reading pane: Synthetic subject 1' }); + // Editable content inside the pane suspends application shortcuts. const editor = document.createElement('textarea'); - content.append(editor); + reading.append(editor); editor.focus(); await waitFor(() => expect(appShortcuts.activeScope()).toBe('everything-editable')); const modified = new KeyboardEvent('keydown', { @@ -1937,14 +1953,16 @@ describe('EverythingWorkspace', () => { editor.dispatchEvent(modified); expect(modified.defaultPrevented).toBe(false); editor.remove(); - await waitFor(() => expect(appShortcuts.activeScope()).toBe('everything-inspector-content')); - content.focus(); + await waitFor(() => expect(appShortcuts.activeScope()).toBe('root')); - await fireEvent.keyDown(content, { key: 'j' }); - expect(state.current.activeRow).toBe('message:1'); - expect(state.current.selectedRow).toBe('message:1'); - await fireEvent.keyDown(content, { key: 'Escape' }); - expect(screen.getByRole('complementary', { name: 'Inspect Synthetic subject 1' })).toBeDefined(); + // Outside editable content, reader navigation stays live: 'l' advances + // the open item without the grid holding focus. + await fireEvent.keyDown(reading, { key: 'l' }); + await waitFor(() => expect(state.current.selectedRow).toBe('message:2')); + expect(screen.getByRole('complementary', { name: 'Reading pane: Synthetic subject 2' })).toBeDefined(); + + await fireEvent.keyDown(reading, { key: 'Escape' }); + await waitFor(() => expect(state.current.selectedRow).toBeNull()); expect(appShortcuts.activeScope()).toBe('root'); rendered.unmount(); state.destroy(); diff --git a/web/src/lib/explore/models.ts b/web/src/lib/explore/models.ts index 66e95ce8e..a66571d38 100644 --- a/web/src/lib/explore/models.ts +++ b/web/src/lib/explore/models.ts @@ -100,7 +100,6 @@ export interface ExploreURLState { activeRow: string | null; selectedRow: string | null; inspectorPinned: boolean; - inspectorWidth: number; conversationAnchor: string | null; scrollAnchor: ExploreScrollAnchor | null; selection?: never; diff --git a/web/src/lib/explore/state.svelte.ts b/web/src/lib/explore/state.svelte.ts index 7bb76325b..3968bb6a6 100644 --- a/web/src/lib/explore/state.svelte.ts +++ b/web/src/lib/explore/state.svelte.ts @@ -42,7 +42,6 @@ const COLUMNS = new Set(['kind', 'people', 'title', 'excerpt', 'time', 'attachme const TRANSIENT_HISTORY_FIELDS = [ 'columns', 'columnWidths', - 'inspectorWidth', 'activeRow', 'scrollAnchor' ] as const satisfies ReadonlyArray<keyof ExploreURLState>; @@ -93,7 +92,6 @@ export const defaultExploreURLState: ExploreURLState = { activeRow: null, selectedRow: null, inspectorPinned: true, - inspectorWidth: 380, conversationAnchor: null, scrollAnchor: null }; @@ -295,11 +293,6 @@ function normalize(value: unknown): ExploreURLState { : null, selectedRow: selectedRow(value.selectedRow), inspectorPinned: true, - inspectorWidth: - typeof value.inspectorWidth === 'number' && Number.isFinite(value.inspectorWidth) && - value.inspectorWidth >= 320 && value.inspectorWidth <= 720 - ? value.inspectorWidth - : defaultExploreURLState.inspectorWidth, conversationAnchor: typeof value.conversationAnchor === 'string' || value.conversationAnchor === null ? value.conversationAnchor From ea237b3d3fe05b21b56037fde06911b1583745b0 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 17:01:30 -0500 Subject: [PATCH 018/102] fix(web): always hand Escape focus back out of the archived frame The bridge's Escape exit only focused an ancestor that was actually overflowing, so short threads left Escape dead inside the frame. Walk up to the nearest focusable ancestor (the thread scroller) instead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- web/src/lib/components/reader/ContentFrame.svelte | 12 ++++++++++-- ...ector.spec.ts => everything-reading-pane.spec.ts} | 0 2 files changed, 10 insertions(+), 2 deletions(-) rename web/tests/{everything-inspector.spec.ts => everything-reading-pane.spec.ts} (100%) diff --git a/web/src/lib/components/reader/ContentFrame.svelte b/web/src/lib/components/reader/ContentFrame.svelte index 62d90a556..3f99f718b 100644 --- a/web/src/lib/components/reader/ContentFrame.svelte +++ b/web/src/lib/components/reader/ContentFrame.svelte @@ -121,8 +121,16 @@ onEscapeContent(); return; } - const scroller = nearestScroller(); - if (scroller && scroller.tabIndex >= 0) scroller.focus(); + // Focus the nearest focusable ancestor (the thread scroller) whether or + // not it currently overflows — Escape must always lead back out. + let node = host?.parentElement ?? undefined; + while (node) { + if (node.tabIndex >= 0) { + node.focus(); + return; + } + node = node.parentElement ?? undefined; + } } function handleFrameKey(key: string): void { diff --git a/web/tests/everything-inspector.spec.ts b/web/tests/everything-reading-pane.spec.ts similarity index 100% rename from web/tests/everything-inspector.spec.ts rename to web/tests/everything-reading-pane.spec.ts From a6ba33abaa8556ea1bdb3c0e82b931ee1067bb3c Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 17:01:45 -0500 Subject: [PATCH 019/102] test(web): update e2e specs to the bottom reading-pane flow Rename everything-inspector.spec.ts to everything-reading-pane.spec.ts and rewrite the flow tests: single click opens the pane, the split handle resizes it with local persistence, the thread renders directly with the anchor expanded, bridge forgery/spoof/oversize messages are ignored while the genuine Escape hands focus to the thread, and remote-image consent stays behind the quiet inline notice. Drop stale inspectorWidth fixture fields. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- web/tests/conversation-content-frame.spec.ts | 118 +++++++------------ web/tests/density-restoration.spec.ts | 2 +- web/tests/e2e/accessibility.spec.ts | 6 +- web/tests/e2e/keyboard.spec.ts | 17 ++- web/tests/e2e/security.spec.ts | 26 ++-- web/tests/everything-reading-pane.spec.ts | 52 ++++---- web/tests/files-viewer.spec.ts | 5 +- web/tests/files-workspace.spec.ts | 1 - web/tests/presentations.spec.ts | 6 +- web/tests/relationships.spec.ts | 9 +- 10 files changed, 115 insertions(+), 127 deletions(-) diff --git a/web/tests/conversation-content-frame.spec.ts b/web/tests/conversation-content-frame.spec.ts index c1b7f84f8..98a4f99c3 100644 --- a/web/tests/conversation-content-frame.spec.ts +++ b/web/tests/conversation-content-frame.spec.ts @@ -111,19 +111,19 @@ test('archived content has an opaque capability boundary and durable conversatio const grid = page.getByRole('grid', { name: 'Everything results' }); await expect(grid.getByText(row.title)).toBeVisible(); await grid.focus(); - await page.keyboard.press('Enter'); const priorURL = page.url(); - await page.getByRole('button', { name: 'View conversation' }).click(); - await expect(page.getByRole('heading', { name: 'Conversation' })).toBeVisible(); + await page.keyboard.press('Enter'); - const frame = page.locator('iframe[title="Message body"]'); - const enter = page.getByRole('button', { name: 'Enter archived content' }); - await expect(page.getByText('Preparing archived content…')).toBeVisible(); - await expect(enter).toBeDisabled(); + // The reading pane opens straight into the thread: the anchor message is + // expanded and its archived body renders directly, with no entry gating. + const reading = page.getByRole('complementary', { name: `Reading pane: ${row.title}` }); + await expect(reading).toBeVisible(); + const anchorCard = page.locator('[data-message-id="42"]'); + const frame = anchorCard.locator('iframe[title="Message body"]'); + await expect(anchorCard.getByText('Preparing message…')).toBeVisible(); await expect(frame).toHaveCount(0); releaseInitialInline(); await expect(frame).toHaveCount(1); - await expect(enter).toBeEnabled(); await expect(frame).toHaveAttribute('sandbox', 'allow-scripts'); await expect(frame).not.toHaveAttribute('sandbox', /allow-same-origin/); const frameHandle = await frame.elementHandle(); @@ -166,29 +166,13 @@ test('archived content has an opaque capability boundary and durable conversatio }); await expect.poll(() => outerFrame.locator('body').getAttribute('data-received')).toBe('no'); - await enter.focus(); - await expect(frame).toHaveJSProperty('inert', true); - const archivedWords = frame.contentFrame().getByText('Safe archived words'); - const archivedWordsBox = await archivedWords.boundingBox(); - expect(archivedWordsBox).not.toBeNull(); - await page.mouse.click( - archivedWordsBox!.x + archivedWordsBox!.width / 2, - archivedWordsBox!.y + archivedWordsBox!.height / 2 - ); - await expect(enter).toBeFocused(); - const box = await frame.boundingBox(); - expect(box).not.toBeNull(); - await page.mouse.move(box!.x + box!.width / 2, box!.y + box!.height / 2); - await page.mouse.wheel(0, 100); - await expect(enter).toBeFocused(); - - await enter.click(); - await expect(page.getByText('Archived content active')).toBeVisible(); - await expect(frame).toHaveJSProperty('inert', false); + const thread = page.getByRole('region', { name: 'Conversation thread' }); const nonce = await frame.contentFrame().locator('html').getAttribute('data-bridge-nonce'); expect(nonce).toBeTruthy(); - // Wrong source, wrong nonce, and an extra field must not exit content mode. + // Wrong source, wrong nonce, an extra field, and an oversized scroll delta + // must all be ignored by the bridge. + const priorScroll = await thread.evaluate((element) => element.scrollTop); await page.evaluate((frameNonce) => { postMessage({ channel: 'msgvault-archived-content', nonce: frameNonce, @@ -202,71 +186,57 @@ test('archived content has an opaque capability boundary and durable conversatio channel: 'msgvault-archived-content', nonce: frameNonce, type: 'key', key: 'Escape', extra: true }, '*'), nonce); - await expect(page.getByText('Archived content active')).toBeVisible(); - - const shellScroll = page.locator('.frame-scroll'); - const priorScroll = await shellScroll.evaluate((element) => element.scrollTop); await contentFrame!.evaluate((frameNonce) => parent.postMessage({ channel: 'msgvault-archived-content', nonce: frameNonce, type: 'scroll', deltaY: 10_001 }, '*'), nonce); - expect(await shellScroll.evaluate((element) => element.scrollTop)).toBe(priorScroll); + await expect(thread).not.toBeFocused(); + expect(await thread.evaluate((element) => element.scrollTop)).toBe(priorScroll); + + // The exact expected frame message hands focus back out to the thread. + await contentFrame!.evaluate((frameNonce) => parent.postMessage({ + channel: 'msgvault-archived-content', nonce: frameNonce, + type: 'key', key: 'Escape' + }, '*'), nonce); + await expect(thread).toBeFocused(); - // Consent while entered synchronously exits shell content scope and detaches - // the old frame before the newly-capable document can be entered. + // Remote-image consent rebuilds the document: the old incapable frame is + // detached before the newly-capable one loads, and the remote fetch goes + // out without referrer or credentials leaking. await page.getByRole('button', { name: 'Load 1 remote image' }).click(); - await expect(page.getByText('Archived content active')).toHaveCount(0); - await expect(page.getByLabel('Archived content controls')).toBeFocused(); - await expect(enter).toBeDisabled(); + await expect(page.getByText('1 remote image is not loaded.')).toHaveCount(0); expect(await frameHandle!.evaluate((element) => element.isConnected)).toBe(false); await expect.poll(() => networkRequests.length).toBe(1); expect(networkRequests[0]).toBe('https://images.example/chart.png?token=synthetic'); expect(remoteReferrers).toEqual([undefined]); expect(unintendedRequests).toEqual([]); releaseRemoteImage(); - await expect(enter).toBeEnabled(); - - await enter.click(); - await expect(page.getByText('Archived content active')).toBeVisible(); - const consentedHandle = await frame.elementHandle(); - const consentedContentFrame = await consentedHandle?.contentFrame(); const consentedNonce = await frame.contentFrame().locator('html').getAttribute('data-bridge-nonce'); expect(consentedNonce).toBeTruthy(); + expect(consentedNonce).not.toBe(nonce); - // The exact expected replacement-frame message exits and returns focus to the shell. - await consentedContentFrame!.evaluate((frameNonce) => parent.postMessage({ - channel: 'msgvault-archived-content', nonce: frameNonce, - type: 'key', key: 'Escape' - }, '*'), consentedNonce); - await expect(page.getByText('Archived content active')).toHaveCount(0); - await expect(page.getByRole('button', { name: 'Enter archived content' })).toBeFocused(); - + // Browser Back closes the reading pane, restores the pre-open URL, and + // returns focus to the grid. await page.evaluate(() => history.back()); - const restoredAfterBrowserBack = page.getByRole('button', { name: 'View conversation' }); - await expect(restoredAfterBrowserBack).toBeVisible(); - await expect(restoredAfterBrowserBack).toBeFocused(); + await expect(reading).toBeHidden(); + await expect(grid).toBeFocused(); expect(page.url()).toBe(priorURL); - const reopenAtPeer = async (): Promise<void> => { - await page.getByRole('button', { name: 'View conversation' }).click(); - await expect(page.getByRole('heading', { name: 'Conversation' })).toBeVisible(); - await page.getByRole('button', { name: /Open message 43/ }).click(); - await expect(page.getByRole('article', { name: 'Selected message 43' })).toBeVisible(); - await expect(frame.contentFrame().getByText('Inline image unavailable: Inline logo')).toBeVisible(); - await expect(page.getByRole('button', { name: 'Enter archived content' })).toBeEnabled(); - }; - - await reopenAtPeer(); - await page.getByRole('button', { name: 'Back from conversation' }).click(); - const restoredAfterToolbarBack = page.getByRole('button', { name: 'View conversation' }); - await expect(restoredAfterToolbarBack).toBeVisible(); - await expect(restoredAfterToolbarBack).toBeFocused(); - expect(page.url()).toBe(priorURL); + // Reopening and expanding the peer message keeps the anchor expanded and + // renders the peer's own frame, whose missing inline image degrades to a + // visible placeholder. + await page.keyboard.press('Enter'); + await expect(reading).toBeVisible(); + await reading.getByRole('button', { name: 'Expand message 43 from alice@example.com' }).click(); + const peerCard = page.locator('[data-message-id="43"]'); + await expect(peerCard).toHaveAttribute('aria-current', 'true'); + const peerFrame = peerCard.locator('iframe[title="Message body"]'); + await expect(peerFrame.contentFrame().getByText('Inline image unavailable: Inline logo')).toBeVisible(); + await expect(frame).toHaveCount(1); - await reopenAtPeer(); + // Escape closes the pane from anywhere in it, restoring URL and grid focus. await page.keyboard.press('Escape'); - const restoredAfterEscape = page.getByRole('button', { name: 'View conversation' }); - await expect(restoredAfterEscape).toBeVisible(); - await expect(restoredAfterEscape).toBeFocused(); + await expect(reading).toBeHidden(); + await expect(grid).toBeFocused(); expect(page.url()).toBe(priorURL); }); diff --git a/web/tests/density-restoration.spec.ts b/web/tests/density-restoration.spec.ts index 40890f476..33705e404 100644 --- a/web/tests/density-restoration.spec.ts +++ b/web/tests/density-restoration.spec.ts @@ -113,7 +113,7 @@ function exploreState(overrides: Record<string, unknown>) { groupingChain: [], presentation: 'table', sort: [{ field: 'occurred_at', direction: 'desc' }], fileSort: { field: 'occurred_at', direction: 'desc' }, fileFilenameQuery: '', fileMIMEFamilies: [], columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, - activeRow: null, selectedRow: null, inspectorPinned: false, inspectorWidth: 380, + activeRow: null, selectedRow: null, inspectorPinned: false, conversationAnchor: null, scrollAnchor: null, ...overrides }; } diff --git a/web/tests/e2e/accessibility.spec.ts b/web/tests/e2e/accessibility.spec.ts index 0c94418e1..615b0392e 100644 --- a/web/tests/e2e/accessibility.spec.ts +++ b/web/tests/e2e/accessibility.spec.ts @@ -31,7 +31,7 @@ for (const theme of ['light', 'dark'] as const) { await assertNoViolations(page, `Relationships timeline ${theme}/${density}`); await relationshipTimeline.focus(); await page.keyboard.press('Enter'); - await expect(page.getByRole('complementary', { name: /Inspect/ })).toBeVisible(); + await expect(page.getByRole('complementary', { name: /Reading pane/ })).toBeVisible(); await assertNoViolations(page, `Relationships reading pane ${theme}/${density}`); await page.keyboard.press('Escape'); @@ -41,8 +41,8 @@ for (const theme of ['light', 'dark'] as const) { await assertNoViolations(page, `Everything ${theme}/${density}`); await grid.focus(); await page.keyboard.press('Enter'); - await expect(page.getByRole('complementary', { name: /Inspect/ })).toBeVisible(); - await assertNoViolations(page, `inspector ${theme}/${density}`); + await expect(page.getByRole('complementary', { name: /Reading pane/ })).toBeVisible(); + await assertNoViolations(page, `reading pane ${theme}/${density}`); await page.keyboard.press('Escape'); await page.keyboard.press('Shift+/'); diff --git a/web/tests/e2e/keyboard.spec.ts b/web/tests/e2e/keyboard.spec.ts index fb826e343..0d63290b0 100644 --- a/web/tests/e2e/keyboard.spec.ts +++ b/web/tests/e2e/keyboard.spec.ts @@ -107,7 +107,7 @@ test('pointer-free archive journey preserves focus, announcements, and history', const relationshipTimeline = page.getByRole('grid', { name: 'Relationship activity' }); await expect(relationshipTimeline).toBeFocused(); await page.keyboard.press('Enter'); - const relationshipReading = page.getByRole('complementary', { name: /Inspect Synthetic conversation/ }); + const relationshipReading = page.getByRole('complementary', { name: /Reading pane: Synthetic conversation/ }); await expect(relationshipReading).toBeVisible(); await page.keyboard.press('Escape'); @@ -143,7 +143,7 @@ test('pointer-free archive journey preserves focus, announcements, and history', await expect(grouped).toBeFocused(); await tabTo(page, 'Drill into Synthetic chat'); await page.keyboard.press('Enter'); - await expect(page.getByRole('complementary', { name: /Inspect/ })).toBeVisible(); + await expect(page.getByRole('complementary', { name: /Reading pane/ })).toBeVisible(); await page.keyboard.press('Escape'); const grid = page.getByRole('grid', { name: 'Everything results' }); @@ -159,15 +159,14 @@ test('pointer-free archive journey preserves focus, announcements, and history', }).toBe(terminalServedRow.key); await expect(grid.locator(`[data-row-key="${terminalServedRow.key}"]`)).toBeVisible(); await page.keyboard.press('Enter'); - await expect(page.getByRole('complementary', { name: /Inspect/ })).toBeVisible(); - await tabTo(page, 'View conversation'); - await page.keyboard.press('Enter'); - await expect(page.getByRole('heading', { name: 'Conversation', exact: true })).toBeVisible(); - await page.keyboard.press('Escape'); - await expect(page.getByRole('heading', { name: 'Conversation', exact: true })).toBeHidden(); - await expect(page.getByRole('complementary', { name: /Inspect/ })).toBeVisible(); + const everythingReading = page.getByRole('complementary', { name: /Reading pane/ }); + await expect(everythingReading).toBeVisible(); + // The conversation thread renders directly in the pane: the anchor + // message arrives expanded, with no intermediate metadata step. + await expect(everythingReading.getByRole('button', { name: /Collapse message/ })).toBeVisible(); await expect(grid).toHaveAttribute('aria-busy', 'false'); await page.keyboard.press('Escape'); + await expect(everythingReading).toBeHidden(); await expect(grid).toBeFocused(); await tabTo(page, 'Files'); diff --git a/web/tests/e2e/security.spec.ts b/web/tests/e2e/security.spec.ts index 157c84f43..1f963414d 100644 --- a/web/tests/e2e/security.spec.ts +++ b/web/tests/e2e/security.spec.ts @@ -34,7 +34,9 @@ test('sanitized archived HTML requires remote-image consent and rejects forged f const grid = page.getByRole('grid', { name: 'Everything results' }); await grid.focus(); await page.keyboard.press('Enter'); - await page.getByRole('button', { name: 'View conversation' }).click(); + + // The reading pane renders the sanitized message frame directly — no + // intermediate step, no entry gating. const frame = page.locator('iframe[title="Message body"]'); await expect(frame).toHaveAttribute('sandbox', 'allow-scripts'); const frameHandle = await frame.elementHandle(); @@ -48,20 +50,28 @@ test('sanitized archived HTML requires remote-image consent and rejects forged f expect(csp).toContain("object-src 'none'"); expect(remoteRequests).toEqual([]); - const enter = page.getByRole('button', { name: 'Enter archived content' }); - await enter.click(); + // A forged bridge message (wrong nonce) and a spoofed one (right nonce, + // wrong source window) are both ignored; only the frame's own message + // with the real nonce drives the Escape hand-off to the thread. const nonce = await frame.contentFrame().locator('html').getAttribute('data-bridge-nonce'); - await page.evaluate((frameNonce) => postMessage({ - channel: 'msgvault-archived-content', nonce: frameNonce, type: 'key', key: 'Escape' - }, '*'), nonce); + const thread = page.getByRole('region', { name: 'Conversation thread' }); await contentFrame!.evaluate(() => parent.postMessage({ channel: 'msgvault-archived-content', nonce: 'forged', type: 'key', key: 'Escape' }, '*')); - await expect(page.getByText('Archived content active')).toBeVisible(); + await page.evaluate((frameNonce) => postMessage({ + channel: 'msgvault-archived-content', nonce: frameNonce, type: 'key', key: 'Escape' + }, '*'), nonce); + await expect(thread).not.toBeFocused(); + await contentFrame!.evaluate((frameNonce) => parent.postMessage({ + channel: 'msgvault-archived-content', nonce: frameNonce, type: 'key', key: 'Escape' + }, '*'), nonce); + await expect(thread).toBeFocused(); + // Remote images stay blocked behind one quiet inline notice. + await expect(page.getByText('1 remote image is not loaded.')).toBeVisible(); await page.getByRole('button', { name: 'Load 1 remote image' }).click(); await expect.poll(() => remoteRequests).toHaveLength(1); - await expect(page.getByLabel('Archived content controls')).toBeFocused(); + await expect(page.getByText('1 remote image is not loaded.')).toBeHidden(); }); test('activating a replacement API key rejects the old browser authority and requires login', async ({ page }) => { diff --git a/web/tests/everything-reading-pane.spec.ts b/web/tests/everything-reading-pane.spec.ts index 565446173..fc8379c41 100644 --- a/web/tests/everything-reading-pane.spec.ts +++ b/web/tests/everything-reading-pane.spec.ts @@ -44,7 +44,7 @@ function exploreURLState(overrides: Record<string, unknown> = {}) { }; } -test('pinned inspector width and visibility restore through browser history', async ({ page }) => { +test('the bottom reading pane opens on a single click, resizes, and persists its height', async ({ page }) => { await page.route('**/api/session', (route) => route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) ); @@ -52,7 +52,7 @@ test('pinned inspector width and visibility restore through browser history', as json: { rows: [entry(1), entry(2)], total_count: 2, - cache_revision: 'cache-inspector', + cache_revision: 'cache-reading-pane', search_provenance: {} } })); @@ -60,31 +60,39 @@ test('pinned inspector width and visibility restore through browser history', as await page.goto(`/?explore=${encodeURIComponent(JSON.stringify({ workspace: 'everything' }))}`); const grid = page.getByRole('grid', { name: 'Everything results' }); await expect(grid.getByText('Synthetic subject 1')).toBeVisible(); - await grid.focus(); - await page.keyboard.press('Enter'); - const inspector = page.getByRole('complementary', { name: 'Inspect Synthetic subject 1' }); - await expect(inspector).toBeVisible(); + // A single click opens the reading pane as a bottom split, never a drawer. + await grid.getByText('Synthetic subject 1').click(); + const reading = page.getByRole('complementary', { name: 'Reading pane: Synthetic subject 1' }); + await expect(reading).toBeVisible(); await expect(page.locator('.kit-detail-drawer-overlay')).toHaveCount(0); - const resize = page.getByRole('button', { name: 'Resize inspector' }); - await resize.press('ArrowLeft'); - await resize.press('ArrowLeft'); - await expect(inspector).toHaveCSS('width', '428px'); - - await page.getByRole('button', { name: 'Close inspector' }).click(); - await expect(inspector).toHaveCount(0); + const paneBox = await page.locator('[data-pane="secondary"]').boundingBox(); + const gridBox = await grid.boundingBox(); + expect(paneBox!.y).toBeGreaterThan(gridBox!.y); + + const resize = page.getByRole('button', { name: 'Resize reading pane' }); + const beforeResize = paneBox!.height; + await resize.press('ArrowUp'); + await resize.press('ArrowUp'); + await expect.poll(async () => (await page.locator('[data-pane="secondary"]').boundingBox())!.height) + .toBe(beforeResize + 48); + + await page.getByRole('button', { name: 'Close reading pane' }).click(); + await expect(reading).toHaveCount(0); await expect(grid).toBeFocused(); await page.goBack(); - const restored = page.getByRole('complementary', { name: 'Inspect Synthetic subject 1' }); + const restored = page.getByRole('complementary', { name: 'Reading pane: Synthetic subject 1' }); await expect(restored).toBeVisible(); - await expect(restored).toHaveCSS('width', '428px'); + // The dragged height persists locally across close and reopen. + await expect.poll(async () => (await page.locator('[data-pane="secondary"]').boundingBox())!.height) + .toBe(beforeResize + 48); await page.goForward(); await expect(restored).toHaveCount(0); }); -test('a direct multi-target inspector URL restores through refresh, Back, and Forward', async ({ page }) => { +test('a direct multi-target reading-pane URL restores through refresh, Back, and Forward', async ({ page }) => { const requests: Array<{ cursor?: string; limit?: number }> = []; await page.route('**/api/session', (route) => route.fulfill({ json: { auth_mode: 'loopback', https: false, plain_http_warning: false } }) @@ -109,24 +117,24 @@ test('a direct multi-target inspector URL restores through refresh, Back, and Fo await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); const grid = page.getByRole('grid', { name: 'Everything results' }); - await expect(page.getByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeVisible(); + await expect(page.getByRole('complementary', { name: 'Reading pane: Synthetic subject 1200' })).toBeVisible(); await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-1/); expect(requests).toHaveLength(3); expect(requests.every(({ limit }) => (limit ?? 0) <= 500)).toBe(true); await page.reload(); - await expect(page.getByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeVisible(); + await expect(page.getByRole('complementary', { name: 'Reading pane: Synthetic subject 1200' })).toBeVisible(); await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-1/); expect(requests).toHaveLength(6); await page.getByRole('button', { name: 'Settings' }).evaluate((button: HTMLButtonElement) => button.click()); await page.goBack(); - await expect(page.getByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toBeVisible(); + await expect(page.getByRole('complementary', { name: 'Reading pane: Synthetic subject 1200' })).toBeVisible(); await expect(grid).toHaveAttribute('aria-activedescendant', /message-3a-1/); await expect.poll(() => requests.length).toBe(9); await page.goForward(); await expect(page.getByRole('button', { name: 'Settings', exact: true })).toHaveAttribute('aria-current', 'page'); - await expect(page.getByRole('complementary', { name: 'Inspect Synthetic subject 1200' })).toHaveCount(0); + await expect(page.getByRole('complementary', { name: 'Reading pane: Synthetic subject 1200' })).toHaveCount(0); }); test('a drilled aggregate restores after grouped rows clear and through Back and Forward', async ({ page }) => { @@ -156,13 +164,13 @@ test('a drilled aggregate restores after grouped rows clear and through Back and await expect(grouped.getByText('Example source group')).toBeVisible(); await page.getByRole('button', { name: 'Drill into Example source group' }).click(); - await expect(page.getByRole('complementary', { name: 'Inspect Restored source detail' })).toBeVisible(); + await expect(page.getByRole('complementary', { name: 'Reading pane: Restored source detail' })).toBeVisible(); await expect(grouped).toHaveCount(0); await page.goBack(); await expect(page.getByRole('grid', { name: 'Everything grouped by source' })).toBeVisible(); await page.goForward(); - await expect(page.getByRole('complementary', { name: 'Inspect Restored source detail' })).toBeVisible(); + await expect(page.getByRole('complementary', { name: 'Reading pane: Restored source detail' })).toBeVisible(); }); test('global command and Escape shortcuts stay suspended in editable controls', async ({ page }) => { diff --git a/web/tests/files-viewer.spec.ts b/web/tests/files-viewer.spec.ts index 5c24b9dcf..6cb24e340 100644 --- a/web/tests/files-viewer.spec.ts +++ b/web/tests/files-viewer.spec.ts @@ -18,7 +18,6 @@ function exploreURLState() { activeRow: null, selectedRow: null, inspectorPinned: false, - inspectorWidth: 380, conversationAnchor: null, scrollAnchor: null }; @@ -190,7 +189,9 @@ test('opening a containing conversation and one Back restores the exact Files UR const filesURL = page.url(); await page.getByRole('button', { name: 'Open containing conversation' }).click(); - await expect(page.getByRole('region', { name: 'Containing conversation' })).toBeVisible(); + const reading = page.getByRole('complementary', { name: 'Reading pane: Containing item' }); + await expect(reading).toBeVisible(); + await expect(reading.getByRole('region', { name: 'Conversation thread' })).toBeVisible(); await page.goBack(); await expect(page).toHaveURL(filesURL); diff --git a/web/tests/files-workspace.spec.ts b/web/tests/files-workspace.spec.ts index 5008d232e..b74b736d3 100644 --- a/web/tests/files-workspace.spec.ts +++ b/web/tests/files-workspace.spec.ts @@ -68,7 +68,6 @@ test('Files restores a deep URL through bounded 500-row pages with bounded DOM', activeRow: `file:${target}`, selectedRow: `file:${target}`, inspectorPinned: false, - inspectorWidth: 380, conversationAnchor: null, scrollAnchor: null }; diff --git a/web/tests/presentations.spec.ts b/web/tests/presentations.spec.ts index 8a301407f..476d0c523 100644 --- a/web/tests/presentations.spec.ts +++ b/web/tests/presentations.spec.ts @@ -98,7 +98,7 @@ test('Show as preserves analytical meaning, keyboard focus, history, and Saved V await page.keyboard.press('Enter'); await expect(viewer).toBeVisible(); await viewer.getByRole('button', { name: 'Open containing item' }).click(); - await expect(page.getByRole('complementary', { name: 'Inspect Presentation message 1' })).toBeVisible(); + await expect(page.getByRole('complementary', { name: 'Reading pane: Presentation message 1' })).toBeVisible(); const tableState = JSON.parse(new URL(page.url()).searchParams.get('explore') ?? '{}'); expect(tableState).toMatchObject({ presentation: 'table', activeRow: 'message:1', scrollAnchor: null }); expect(tableState.activeRow).not.toContain(':file:'); @@ -158,7 +158,7 @@ test('Files presentation restores a deep paged anchor with bounded DOM and visib fileSort: { field: 'occurred_at', direction: 'desc' }, fileFilenameQuery: '', fileMIMEFamilies: [], columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, activeRow: 'message:550:file:550', selectedRow: null, inspectorPinned: false, - inspectorWidth: 380, conversationAnchor: null, + conversationAnchor: null, scrollAnchor: { key: 'message:540:file:540', offset: 5 } }; await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); @@ -213,7 +213,7 @@ test('attachment deep links restore with one bounded metadata lookup', async ({ fileSort: { field: 'occurred_at', direction: 'desc' }, fileFilenameQuery: '', fileMIMEFamilies: [], columns: ['kind', 'people', 'title', 'excerpt', 'time', 'attachments'], columnWidths: {}, activeRow: null, selectedRow: 'attachment:640', inspectorPinned: false, - inspectorWidth: 380, conversationAnchor: null, scrollAnchor: null + conversationAnchor: null, scrollAnchor: null }; await page.goto(`/?explore=${encodeURIComponent(JSON.stringify(state))}`); diff --git a/web/tests/relationships.spec.ts b/web/tests/relationships.spec.ts index 8f3a3814b..260bca4f6 100644 --- a/web/tests/relationships.spec.ts +++ b/web/tests/relationships.spec.ts @@ -107,12 +107,13 @@ test('legacy People URL lands on the Relationships hub and walks list, timeline, await expect(timeline.getByText('Subject line')).toBeVisible(); // A chat-burst row opens straight into the bounded conversation window in - // the reading pane rather than the plain entry summary. + // the reading pane rather than the plain entry summary: the anchor message + // renders expanded as a card in the thread. await timeline.getByText('6 messages in Team Chat').click(); - const reading = page.getByRole('complementary', { name: /Inspect 6 messages in Team Chat/ }); + const reading = page.getByRole('complementary', { name: /Reading pane: 6 messages in Team Chat/ }); await expect(reading).toBeVisible(); - await expect(reading.getByText('Bob Example')).toBeVisible(); - await expect(reading.getByRole('heading', { level: 2, name: 'Conversation' })).toBeVisible(); + await expect(reading.getByRole('button', { name: 'Collapse message 500 from Bob Example' })).toBeVisible(); + await expect(reading.getByText('Latest chat message')).toBeVisible(); // Toggling the facet switches the ranked list to Domains without losing // the open person detail underneath. From f73f6f7cd1b485b097c09d41cfe16a1850b86979 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 17:06:36 -0500 Subject: [PATCH 020/102] fix(web): pin the archived frame canvas to a light color-scheme With color-scheme:light dark, a dark OS preference gave the archived document a dark canvas and light default text, breaking the intended white mail surface (and clashing with the forced-white iframe background). Pin the embedded document to light so HTML mail keeps its white body surface in dark mode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- web/src/lib/content/frame-document.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/src/lib/content/frame-document.ts b/web/src/lib/content/frame-document.ts index dbe889c11..c4d3a3f00 100644 --- a/web/src/lib/content/frame-document.ts +++ b/web/src/lib/content/frame-document.ts @@ -95,6 +95,8 @@ export async function buildFrameDocument(options: FrameDocumentOptions): Promise '<meta charset="utf-8">' + `<meta http-equiv="Content-Security-Policy" content="${escapeAttribute(csp)}">` + '<meta name="referrer" content="no-referrer">' + - '<style>html{color-scheme:light dark}body{margin:0;padding:12px;overflow-wrap:anywhere;font:14px/1.5 system-ui,sans-serif}img{max-width:100%;height:auto}[data-archived-remote-image]{display:inline-block;padding:8px;border:1px dashed currentColor}</style>' + + // color-scheme is pinned to light: HTML mail assumes a white canvas, and + // the shell keeps that white surface (seamlessly styled) in dark mode too. + '<style>html{color-scheme:light}body{margin:0;padding:12px;overflow-wrap:anywhere;font:14px/1.5 system-ui,sans-serif}img{max-width:100%;height:auto}[data-archived-remote-image]{display:inline-block;padding:8px;border:1px dashed currentColor}</style>' + `</head><body>${encodedBody(options.html)}<script>${bridge}</script></body></html>`; } From 3e45509796d13bcb94921582a1d283b0fddcfbb7 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 17:56:41 -0500 Subject: [PATCH 021/102] perf: make relationship ranking and person detail fast on large archives Three measured fixes against a 2.5M-message archive: - Cast the relationship decay 'now' parameter to TIMESTAMP. The Go DuckDB driver binds time.Time as TIMESTAMP WITH TIME ZONE, which forced a per-row ICU session-timezone conversion of occurred_at across every fanned-out row (3.4s -> 1.1s cold) and made decay day counts depend on the server's timezone; day boundaries now count in UTC deterministically. - Memoize ranked relationship candidate lists per (cache revision, filters, show_all, UTC decay date) with singleflight, bounded to 4 entries. The revision folds the identity revision, so a stale list can never be served after identities or the archive change; cursor pages slice the cached list. Warm ranked-list loads drop to under a millisecond. - Resolve exact-person membership (person detail endpoint) with semi-joins against message_recipients / messages / conversation_ participants instead of unnesting logical_entries.participant_ids, which forced the analytical_entries view to assemble per-message participant lists for the whole archive (860ms -> 180ms). Filtered person lookups keep the fan-out shape, now pruned to one person before aggregation and projecting only consumed columns. Verified equivalent output for a 19-participant sample (top-activity, chat-heavy, and random) on the real archive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- internal/query/duckdb.go | 10 ++ internal/query/explore.go | 20 ++- internal/query/people.go | 68 ++++++++++- internal/query/relationships.go | 74 ++++++++---- internal/query/relationships_memo.go | 99 +++++++++++++++ internal/query/relationships_memo_test.go | 141 ++++++++++++++++++++++ internal/query/relationships_test.go | 70 +++++++++++ 7 files changed, 452 insertions(+), 30 deletions(-) create mode 100644 internal/query/relationships_memo.go create mode 100644 internal/query/relationships_memo_test.go diff --git a/internal/query/duckdb.go b/internal/query/duckdb.go index 49f03ab9f..8e0936fb1 100644 --- a/internal/query/duckdb.go +++ b/internal/query/duckdb.go @@ -84,6 +84,16 @@ type DuckDBEngine struct { searchCacheTable string // temp table name (e.g. "_search_matches_42") searchCacheCount int64 // cached COUNT(*) from materialization searchCacheStats *TotalStats // cached stats from Phase 4 + + // relMemo caches ranked relationship candidate lists keyed by committed + // cache revision plus request facets; see relationshipsMemo. The zero + // value is ready to use. + relMemo relationshipsMemo + + // relationshipsQueryRuns counts full relationship-ranking query + // executions (not memo hits). Test hook only: memo tests assert cache + // hits and misses through it. + relationshipsQueryRuns atomic.Uint64 } // DuckDBOptions configures optional DuckDB engine behavior. diff --git a/internal/query/explore.go b/internal/query/explore.go index 798c6dcef..c6463fe13 100644 --- a/internal/query/explore.go +++ b/internal/query/explore.go @@ -392,6 +392,20 @@ func buildExploreLogicalSQL(conditions string) string { return buildExploreLogicalSQLWithCandidateRank(conditions, "NULL::BIGINT") } +// sqlIsChatPredicate renders the shared chat-classification predicate for a +// message row. messageType and conversationType are SQL expressions that +// must never be NULL (analytical_entries and the base views COALESCE them). +// Any query that re-derives chat membership outside the classified CTE +// (e.g. the exact-person fast path in people.go) must use this so +// classifications cannot drift. +func sqlIsChatPredicate(messageType, conversationType string) string { + return "lower(" + messageType + ") IN (" + TextMessageTypeSQLList + `) + OR ( + lower(` + messageType + `) IN ('', 'chat', 'text') + AND lower(` + conversationType + `) IN ('direct_chat', 'group_chat', 'channel', 'chat') + )` +} + // buildExploreFilteredClassifiedCTE builds the "filtered" and "classified" // CTEs shared by every query that projects analytical_entries into // modality-neutral rows: buildExploreLogicalSQLWithCandidateRank's @@ -408,11 +422,7 @@ WITH filtered AS ( ), classified AS ( SELECT *, ` + candidateRankExpression + ` AS candidate_rank, - lower(message_type) IN (` + TextMessageTypeSQLList + `) - OR ( - lower(message_type) IN ('', 'chat', 'text') - AND lower(conversation_type) IN ('direct_chat', 'group_chat', 'channel', 'chat') - ) AS is_chat, + ` + sqlIsChatPredicate("message_type", "conversation_type") + ` AS is_chat, CASE WHEN lower(message_type) = 'email' OR message_type = '' THEN 'email' WHEN lower(message_type) = '` + messageTypeCalendar + `' THEN 'event' diff --git a/internal/query/people.go b/internal/query/people.go index a580a542a..5607435c5 100644 --- a/internal/query/people.go +++ b/internal/query/people.go @@ -162,6 +162,8 @@ func (e *DuckDBEngine) searchPeople( return nil, fmt.Errorf("read committed cache state: %w", err) } conditions, args := buildExploreConditions(request.Explore) + entriesCTE, entryArgs := personEntriesCTE(exactID, conditions) + args = append(args, entryArgs...) personWhere := []string{"true"} if exactID != nil { personWhere = append(personWhere, "person_id = ?") @@ -195,10 +197,7 @@ func (e *DuckDBEngine) searchPeople( limit = defaultExploreLimit } args = append(args, limit, request.Page.Offset) - queryText := buildExploreLogicalSQL(conditions) + ` -), person_entries AS ( - SELECT grouped.person_id, logical_entries.* - FROM logical_entries CROSS JOIN UNNEST(participant_ids) AS grouped(person_id) + queryText := buildExploreLogicalSQL(conditions) + entriesCTE + ` ), person_population AS ( SELECT p.id AS person_id, COALESCE(p.display_name, '') AS display_name, COALESCE(p.email_address, '') AS email_address, COALESCE(p.phone_number, '') AS phone_number, @@ -259,6 +258,67 @@ FROM counted ORDER BY ` + order + ` LIMIT ? OFFSET ?` return response, nil } +// personEntriesCTE returns the person_entries CTE (appended directly after +// buildExploreLogicalSQL, so it opens by closing the logical_entries CTE) +// plus the parameter values it binds. person_entries always projects exactly +// the columns the aggregates in searchPeople consume: it is referenced more +// than once, so DuckDB materializes it, and carrying logical_entries.* would +// materialize every wide list/text column per row. +// +// Three shapes, cheapest applicable first: +// +// 1. Exact-ID lookup with no analytical conditions (the person-detail +// endpoint): membership is resolved with semi-joins against the raw +// link tables, never touching logical_entries.participant_ids — +// unnesting that column forces the analytical_entries view to assemble +// per-message participant lists for the entire archive, which dominated +// person-detail latency. Equivalence with the fan-out shape: a non-chat +// entry is one message whose participants are its recipients plus its +// sender (the view's message_participant_links); a conversation entry's +// participants are the message-level participants of the group's chat +// messages plus the conversation's own participant rows. conversation_id +// is globally unique and NOT NULL, so matching it alone reproduces the +// (source_id, conversation_id) grouping exactly. +// 2. Exact-ID lookup with conditions: the fan-out filtered to one person +// before aggregation. The semi-join shape cannot be used because a +// conversation entry's participant list must only reflect messages +// inside the filtered context. +// 3. Listing/search: the full fan-out across every participant. +func personEntriesCTE(exactID *int64, conditions string) (string, []any) { + const fanOut = ` +), person_entries AS ( + SELECT grouped.person_id, occurred_at, attachment_count, source_type + FROM logical_entries CROSS JOIN UNNEST(participant_ids) AS grouped(person_id)` + if exactID == nil { + return fanOut, nil + } + if conditions != "true" { + return fanOut + ` + WHERE grouped.person_id = ?`, []any{*exactID} + } + return ` +), person_message_ids AS ( + SELECT mr.message_id FROM message_recipients mr WHERE mr.participant_id = ? + UNION + SELECT m.id AS message_id FROM messages m WHERE m.sender_id = ? +), person_chat_conversation_ids AS ( + SELECT cp.conversation_id FROM conversation_participants cp WHERE cp.participant_id = ? + UNION + SELECT m.conversation_id + FROM person_message_ids pm + JOIN messages m ON m.id = pm.message_id + LEFT JOIN conversations c ON c.id = m.conversation_id + WHERE ` + sqlIsChatPredicate("m.message_type", "COALESCE(c.conversation_type, '')") + ` +), person_entries AS ( + SELECT ?::BIGINT AS person_id, occurred_at, attachment_count, source_type + FROM logical_entries le + WHERE (le.entry_kind <> 'conversation' + AND le.anchor_message_id IN (SELECT message_id FROM person_message_ids)) + OR (le.entry_kind = 'conversation' + AND le.conversation_id IN (SELECT conversation_id FROM person_chat_conversation_ids))`, + []any{*exactID, *exactID, *exactID, *exactID} +} + func (e *DuckDBEngine) SearchDomains(ctx context.Context, request DomainSearchRequest) (*DomainSearchResponse, error) { return e.searchDomains(ctx, request, "") } diff --git a/internal/query/relationships.go b/internal/query/relationships.go index aacd757c9..089c55e25 100644 --- a/internal/query/relationships.go +++ b/internal/query/relationships.go @@ -115,6 +115,11 @@ type RelationshipAnalyzer interface { // clusters are excluded (you don't rank yourself); by default only // counterparts with at least one sent message or one shared meeting are // returned (the reciprocity gate), filtering out inbound-only newsletters. +// +// Ranked candidate lists are memoized per (cache revision, filters, +// show_all, decay date) so repeat hub loads and cursor pages within one +// committed cache snapshot skip the full-archive scan entirely; see +// relationshipsMemo for the coherence argument. func (e *DuckDBEngine) Relationships(ctx context.Context, request RelationshipsRequest) (*RelationshipsResponse, error) { if e.analyticsDir == "" { return nil, &CacheUnavailableError{Readiness: CacheAbsent} @@ -137,16 +142,48 @@ func (e *DuckDBEngine) Relationships(ctx context.Context, request RelationshipsR now = time.Now().UTC() } conditions, args := buildExploreConditions(ExploreRequest{Context: request.Context}) + key := relationshipsMemoKey(state.Revision(), conditions, args, request.ShowAll, now) + candidates, err := e.relMemo.rows(key, func() ([]RelationshipRow, error) { + return e.queryRelationshipCandidates(ctx, conditions, args, request.ShowAll, now) + }) + if err != nil { + return nil, err + } + + limit := request.Limit + if limit == 0 { + limit = defaultRelationshipsLimit + } + response := &RelationshipsResponse{ + Rows: make([]RelationshipRow, 0), + TotalCount: int64(len(candidates)), + CacheRevision: state.Revision(), + IdentityRevision: state.IdentityRevision, + } + if request.Offset < len(candidates) { + end := min(request.Offset+limit, len(candidates)) + response.Rows = candidates[request.Offset:end] + } + return response, nil +} + +// queryRelationshipCandidates runs the full ranking query and returns the +// gated, scored, sorted candidate list. The result is stored in the memo and +// shared across paginated responses, so callers must treat it as immutable. +func (e *DuckDBEngine) queryRelationshipCandidates( + ctx context.Context, conditions string, args []any, showAll bool, now time.Time, +) ([]RelationshipRow, error) { + e.relationshipsQueryRuns.Add(1) queryText := buildRelationshipsSQL(conditions, e.parquetPath(datasetParticipantClusters), e.parquetPath(datasetOwnerParticipants)) - args = append(args, math.Ln2/relationshipHalfLifeDays, now.UTC()) + queryArgs := append(append([]any{}, args...), math.Ln2/relationshipHalfLifeDays, now.UTC()) - rows, err := e.db.QueryContext(ctx, queryText, args...) + rows, err := e.db.QueryContext(ctx, queryText, queryArgs...) if err != nil { return nil, fmt.Errorf("query analytical relationships: %w", err) } defer func() { _ = rows.Close() }() - var candidates []RelationshipRow + candidates := make([]RelationshipRow, 0) for rows.Next() { var row RelationshipRow var memberIDsJSON string @@ -163,7 +200,7 @@ func (e *DuckDBEngine) Relationships(ctx context.Context, request RelationshipsR } row.Signals.LastInteractionAt = row.LastAt row.Score = RelationshipScore(row.Signals) - if !request.ShowAll && row.Signals.SentCount < 1 && row.Signals.MeetingCount < 1 { + if !showAll && row.Signals.SentCount < 1 && row.Signals.MeetingCount < 1 { continue } candidates = append(candidates, row) @@ -181,22 +218,7 @@ func (e *DuckDBEngine) Relationships(ctx context.Context, request RelationshipsR } return candidates[i].DisplayLabel < candidates[j].DisplayLabel }) - - limit := request.Limit - if limit == 0 { - limit = defaultRelationshipsLimit - } - response := &RelationshipsResponse{ - Rows: make([]RelationshipRow, 0), - TotalCount: int64(len(candidates)), - CacheRevision: state.Revision(), - IdentityRevision: state.IdentityRevision, - } - if request.Offset < len(candidates) { - end := min(request.Offset+limit, len(candidates)) - response.Rows = candidates[request.Offset:end] - } - return response, nil + return candidates, nil } func validateRelationshipsRequest(request RelationshipsRequest) error { @@ -214,6 +236,16 @@ func validateRelationshipsRequest(request RelationshipsRequest) error { // gate, score, and sort order are applied in Go so RelationshipScore stays // the single source of truth for the weights. // +// The now parameter is wrapped in CAST(? AS TIMESTAMP) because the Go DuckDB +// driver binds time.Time as TIMESTAMP WITH TIME ZONE. Left uncast, date_diff +// would coerce the naive-UTC occurred_at column to TIMESTAMPTZ on every +// fanned-out row — a per-row ICU session-timezone conversion that tripled +// whole-query time on a 2.5M-message archive — and would count day +// boundaries in the server's session timezone, making decay depend on the +// machine's locale. The cast pins the parameter to its UTC wall clock, so +// decay counts UTC day boundaries deterministically and depends only on the +// UTC date of now (which relationshipsMemoKey relies on). +// // Owners may themselves be clustered, so "owner_canon" resolves every owner // participant to its cluster canonical ID and interactions exclude any // canonical identity that appears there — you never rank yourself. @@ -282,7 +314,7 @@ func buildRelationshipsSQL(conditions, clustersGlob, ownersGlob string) string { EXISTS (SELECT 1 FROM author_links al WHERE al.message_id = le.anchor_message_id AND al.canonical_id = cn.canonical_id) AS is_author, - exp(-? * date_diff('day', le.occurred_at, ?)) AS decay + exp(-? * date_diff('day', le.occurred_at, CAST(? AS TIMESTAMP))) AS decay FROM le_with_owner le CROSS JOIN UNNEST(le.participant_ids) AS pid(participant_id) JOIN canon cn ON cn.participant_id = pid.participant_id diff --git a/internal/query/relationships_memo.go b/internal/query/relationships_memo.go new file mode 100644 index 000000000..edcc72fe5 --- /dev/null +++ b/internal/query/relationships_memo.go @@ -0,0 +1,99 @@ +package query + +import ( + "fmt" + "strings" + "sync" + "time" + + "golang.org/x/sync/singleflight" +) + +// relationshipsMemoMaxEntries bounds the memo to a handful of distinct +// (revision, filters, show_all, decay-date) result sets. The hub landing page +// always reuses one key; a few more cover facet variations without letting a +// scripted filter sweep grow the cache without bound. +const relationshipsMemoMaxEntries = 4 + +// relationshipsMemo caches fully ranked relationship candidate lists per +// memo key (see relationshipsMemoKey). Staleness is structurally impossible: +// the committed cache revision — which folds the identity revision — is part +// of every key, so a revision bump stops hitting old entries, and they age +// out FIFO. Concurrent identical requests share one computation via +// singleflight; errors are never cached. +// +// Cached slices are shared across callers and must be treated as immutable. +type relationshipsMemo struct { + group singleflight.Group + mu sync.Mutex + entries map[string][]RelationshipRow + order []string +} + +// relationshipsMemoKey identifies one cacheable ranked candidate list. +// Pagination (limit/offset) is deliberately absent: pages are slices of one +// cached list. Now contributes only its UTC date because decay is computed +// with date_diff('day', occurred_at, now) against a naive-UTC timestamp, so +// every instant within one UTC day yields identical results. +func relationshipsMemoKey(revision, conditions string, args []any, showAll bool, now time.Time) string { + var key strings.Builder + fmt.Fprintf(&key, "%s|%t|%s|%s", revision, showAll, now.UTC().Format(time.DateOnly), conditions) + for _, arg := range args { + fmt.Fprintf(&key, "\x1f%v", arg) + } + return key.String() +} + +// rows returns the cached candidate list for key, computing and storing it on +// a miss. compute runs at most once per in-flight key; its error is returned +// to every concurrent caller and not cached. +func (m *relationshipsMemo) rows(key string, compute func() ([]RelationshipRow, error)) ([]RelationshipRow, error) { + if rows, ok := m.get(key); ok { + return rows, nil + } + value, err, _ := m.group.Do(key, func() (any, error) { + if rows, ok := m.get(key); ok { + return rows, nil + } + rows, err := compute() + if err != nil { + return nil, err + } + m.put(key, rows) + return rows, nil + }) + if err != nil { + // The only error singleflight can surface here is compute's own, + // which is already wrapped with query context at its source. + return nil, err //nolint:wrapcheck + } + rows, ok := value.([]RelationshipRow) + if !ok { + return nil, fmt.Errorf("relationships memo returned unexpected type %T", value) + } + return rows, nil +} + +func (m *relationshipsMemo) get(key string) ([]RelationshipRow, bool) { + m.mu.Lock() + defer m.mu.Unlock() + rows, ok := m.entries[key] + return rows, ok +} + +func (m *relationshipsMemo) put(key string, rows []RelationshipRow) { + m.mu.Lock() + defer m.mu.Unlock() + if m.entries == nil { + m.entries = make(map[string][]RelationshipRow, relationshipsMemoMaxEntries) + } + if _, exists := m.entries[key]; !exists { + m.order = append(m.order, key) + } + m.entries[key] = rows + for len(m.order) > relationshipsMemoMaxEntries { + oldest := m.order[0] + m.order = m.order[1:] + delete(m.entries, oldest) + } +} diff --git a/internal/query/relationships_memo_test.go b/internal/query/relationships_memo_test.go new file mode 100644 index 000000000..b139bc45e --- /dev/null +++ b/internal/query/relationships_memo_test.go @@ -0,0 +1,141 @@ +package query + +import ( + "errors" + "fmt" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRelationshipsMemoKey(t *testing.T) { + base := time.Date(2026, 7, 20, 9, 30, 0, 0, time.UTC) + key := relationshipsMemoKey("rev-a", "true", nil, false, base) + + t.Run("same UTC date yields the same key regardless of time of day", func(t *testing.T) { + later := time.Date(2026, 7, 20, 23, 59, 59, 0, time.UTC) + assert.Equal(t, key, relationshipsMemoKey("rev-a", "true", nil, false, later)) + }) + + t.Run("each input distinguishes keys", func(t *testing.T) { + assert := assert.New(t) + assert.NotEqual(key, relationshipsMemoKey("rev-b", "true", nil, false, base)) + assert.NotEqual(key, relationshipsMemoKey("rev-a", "source_id = ?", nil, false, base)) + assert.NotEqual(key, relationshipsMemoKey("rev-a", "true", []any{int64(7)}, false, base)) + assert.NotEqual(key, relationshipsMemoKey("rev-a", "true", nil, true, base)) + assert.NotEqual(key, relationshipsMemoKey("rev-a", "true", nil, false, base.AddDate(0, 0, 1))) + }) + + t.Run("argument values distinguish keys", func(t *testing.T) { + withSeven := relationshipsMemoKey("rev-a", "source_id = ?", []any{int64(7)}, false, base) + withEight := relationshipsMemoKey("rev-a", "source_id = ?", []any{int64(8)}, false, base) + assert.NotEqual(t, withSeven, withEight) + }) +} + +func TestRelationshipsMemoRowsCachesPerKey(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + var memo relationshipsMemo + var computeCalls atomic.Int64 + compute := func(canonicalID int64) func() ([]RelationshipRow, error) { + return func() ([]RelationshipRow, error) { + computeCalls.Add(1) + return []RelationshipRow{{CanonicalID: canonicalID}}, nil + } + } + + first, err := memo.rows("key-1", compute(11)) + require.NoError(err) + second, err := memo.rows("key-1", compute(99)) + require.NoError(err) + + assert.Equal(int64(1), computeCalls.Load(), "second same-key call must be a cache hit") + require.Len(second, 1) + assert.Equal(first[0].CanonicalID, second[0].CanonicalID) + + other, err := memo.rows("key-2", compute(22)) + require.NoError(err) + assert.Equal(int64(2), computeCalls.Load(), "a different key must compute separately") + require.Len(other, 1) + assert.Equal(int64(22), other[0].CanonicalID) +} + +func TestRelationshipsMemoDoesNotCacheErrors(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + var memo relationshipsMemo + boom := errors.New("query failed") + _, err := memo.rows("key", func() ([]RelationshipRow, error) { return nil, boom }) + require.ErrorIs(err, boom) + + rows, err := memo.rows("key", func() ([]RelationshipRow, error) { + return []RelationshipRow{{CanonicalID: 5}}, nil + }) + require.NoError(err) + require.Len(rows, 1) + assert.Equal(int64(5), rows[0].CanonicalID) +} + +func TestRelationshipsMemoEvictsOldestBeyondCapacity(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + var memo relationshipsMemo + var computeCalls atomic.Int64 + compute := func() ([]RelationshipRow, error) { + computeCalls.Add(1) + return []RelationshipRow{}, nil + } + for i := 0; i <= relationshipsMemoMaxEntries; i++ { + _, err := memo.rows(fmt.Sprintf("key-%d", i), compute) + require.NoError(err) + } + baseline := computeCalls.Load() + + _, err := memo.rows("key-0", compute) + require.NoError(err) + assert.Equal(baseline+1, computeCalls.Load(), "oldest key must have been evicted and recomputed") + + _, err = memo.rows(fmt.Sprintf("key-%d", relationshipsMemoMaxEntries), compute) + require.NoError(err) + assert.Equal(baseline+1, computeCalls.Load(), "newest key must still be cached") +} + +func TestRelationshipsMemoConcurrentCallersShareOneComputation(t *testing.T) { + var memo relationshipsMemo + var computeCalls atomic.Int64 + release := make(chan struct{}) + // The always-nil error is imposed by relationshipsMemo.rows' compute + // signature; this test only exercises the success path. + compute := func() ([]RelationshipRow, error) { //nolint:unparam + computeCalls.Add(1) + <-release + return []RelationshipRow{{CanonicalID: 1}}, nil + } + + const callers = 8 + var started, done sync.WaitGroup + started.Add(callers) + done.Add(callers) + for range callers { + go func() { + started.Done() + rows, err := memo.rows("shared", compute) + assert.NoError(t, err) + assert.Len(t, rows, 1) + done.Done() + }() + } + started.Wait() + close(release) + done.Wait() + + assert.Equal(t, int64(1), computeCalls.Load(), "concurrent identical requests must share one computation") +} diff --git a/internal/query/relationships_test.go b/internal/query/relationships_test.go index ffde08bbb..41b7a7dba 100644 --- a/internal/query/relationships_test.go +++ b/internal/query/relationships_test.go @@ -2,6 +2,8 @@ package query import ( "context" + "encoding/json" + "os" "testing" "time" @@ -115,6 +117,74 @@ func TestRelationshipsRanksByReciprocityAndGatesNewsletters(t *testing.T) { }) } +// TestRelationshipsMemoizesRankedListPerRevision verifies the engine-level +// memoization contract: within one committed cache revision, repeated calls +// (including later same-UTC-day Now values and offset pagination) reuse one +// ranking query; ShowAll keys separately; and any revision change — here an +// identity-revision bump rewritten into the commit marker — forces a +// recompute, so a stale list can never be served after identities change. +func TestRelationshipsMemoizesRankedListPerRevision(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + b := NewTestDataBuilder(t) + srcID := b.AddSource("owner@example.com") + ownerID := b.AddParticipant("owner@example.com", "example.com", "Owner") + b.AddOwnerParticipant(srcID, ownerID) + bobID := b.AddParticipant("bob@example.com", "example.com", "Bob") + carolID := b.AddParticipant("carol@example.com", "example.com", "Carol") + + now := time.Date(2026, 1, 10, 3, 0, 0, 0, time.UTC) + for _, recipient := range []struct { + id int64 + name string + }{{bobID, "Bob"}, {carolID, "Carol"}} { + msgID := b.AddMessage(MessageOpt{SourceID: srcID, IsFromMe: true, SentAt: now.AddDate(0, 0, -1)}) + b.AddFrom(msgID, ownerID, "Owner") + b.AddTo(msgID, recipient.id, recipient.name) + } + + engine := b.BuildEngine() + ctx := context.Background() + + first, err := engine.Relationships(ctx, RelationshipsRequest{Now: now, Limit: 10}) + require.NoError(err) + require.Len(first.Rows, 2) + require.Equal(uint64(1), engine.relationshipsQueryRuns.Load()) + + second, err := engine.Relationships(ctx, RelationshipsRequest{Now: now.Add(6 * time.Hour), Limit: 10}) + require.NoError(err) + assert.Equal(uint64(1), engine.relationshipsQueryRuns.Load(), + "a same-revision, same-UTC-day repeat must not re-run the ranking query") + require.Len(second.Rows, 2) + assert.Equal(first.Rows[0].CanonicalID, second.Rows[0].CanonicalID) + + page, err := engine.Relationships(ctx, RelationshipsRequest{Now: now, Limit: 1, Offset: 1}) + require.NoError(err) + assert.Equal(uint64(1), engine.relationshipsQueryRuns.Load(), + "offset pages must slice the cached list, not re-query") + require.Len(page.Rows, 1) + assert.Equal(first.Rows[1].CanonicalID, page.Rows[0].CanonicalID) + assert.Equal(first.TotalCount, page.TotalCount) + + _, err = engine.Relationships(ctx, RelationshipsRequest{Now: now, Limit: 10, ShowAll: true}) + require.NoError(err) + assert.Equal(uint64(2), engine.relationshipsQueryRuns.Load(), "show_all must key separately") + + state, err := ReadCacheSyncState(engine.analyticsDir) + require.NoError(err) + state.IdentityRevision++ + stateData, err := json.Marshal(state) + require.NoError(err) + require.NoError(os.WriteFile(CacheStatePath(engine.analyticsDir), stateData, 0o600)) + + third, err := engine.Relationships(ctx, RelationshipsRequest{Now: now, Limit: 10}) + require.NoError(err) + assert.Equal(uint64(3), engine.relationshipsQueryRuns.Load(), + "a revision bump must miss the memo and recompute") + assert.Equal(state.IdentityRevision, third.IdentityRevision) +} + // TestRelationshipsExcludesClusteredOwners verifies that when the owner's own // participant identity is itself linked into a cluster with another // participant, the whole cluster is excluded from ranking (you never rank From 985720df143a7fc1ff2ecb81fd022266d1b9db4a Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 18:12:43 -0500 Subject: [PATCH 022/102] fix(web): allow blob: and data: images in the shell CSP The FileViewer loads verified archive bytes into an object URL, but the shell Content-Security-Policy (default-src 'self' with no img-src) made the browser block every blob: image, so previews always failed with a decode error. Sanitized data: images inherit the same policy inside the reader's srcdoc frame and were blocked the same way. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- internal/web/handler.go | 7 +++++-- internal/web/handler_test.go | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/web/handler.go b/internal/web/handler.go index 1956fe976..318be8a8c 100644 --- a/internal/web/handler.go +++ b/internal/web/handler.go @@ -13,8 +13,11 @@ import ( const ( shellCacheControl = "no-store, no-cache, must-revalidate, max-age=0" immutableCache = "public, max-age=31536000, immutable" - shellCSP = "default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'" - viteManifestPath = ".vite/manifest.json" + // img-src allows blob: for FileViewer image previews (object URLs over + // verified archive bytes) and data: for sanitized inline images in the + // reader's srcdoc frame, which inherits this policy. + shellCSP = "default-src 'self'; img-src 'self' data: blob:; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'" + viteManifestPath = ".vite/manifest.json" ) type viteManifestEntry struct { diff --git a/internal/web/handler_test.go b/internal/web/handler_test.go index a0252e1cf..3c532107a 100644 --- a/internal/web/handler_test.go +++ b/internal/web/handler_test.go @@ -138,7 +138,7 @@ func TestWebHandlerServesShellForSafeNavigation(t *testing.T) { assert.Equal("0", recorder.Header().Get("Expires")) assert.Equal("nosniff", recorder.Header().Get("X-Content-Type-Options")) assert.Equal( - "default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'", + "default-src 'self'; img-src 'self' data: blob:; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'", recorder.Header().Get("Content-Security-Policy"), ) }) From 74bc52ba382e1aad496e27159af51a6bc59a1b05 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 18:16:53 -0500 Subject: [PATCH 023/102] feat(cli): print the Web UI URL when the daemon starts in the background --- cmd/msgvault/cmd/serve_lifecycle.go | 18 +++++++ cmd/msgvault/cmd/serve_lifecycle_test.go | 62 ++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/cmd/msgvault/cmd/serve_lifecycle.go b/cmd/msgvault/cmd/serve_lifecycle.go index 76a97f529..43d43ee71 100644 --- a/cmd/msgvault/cmd/serve_lifecycle.go +++ b/cmd/msgvault/cmd/serve_lifecycle.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "net" "net/http" "os" "os/exec" @@ -306,10 +307,27 @@ func runServeStartWithOptions(cmd *cobra.Command, c *config.Config, opts backgro } _, _ = fmt.Fprintf(cmd.OutOrStdout(), "msgvault starting in background (pid %d)\n", proc.PID) + _, _ = fmt.Fprint(cmd.OutOrStdout(), webUIStartupHint(c)) _, _ = fmt.Fprintf(cmd.OutOrStdout(), "Logs: %s\n", proc.LogPath) return nil } +// webUIStartupHint tells the user where the web application will be reachable +// when a background daemon has not finished starting within the ready wait. +// With a fixed api_port the URL is known up front; with an auto-selected port +// it is only discoverable from the runtime record once the daemon binds. +func webUIStartupHint(c *config.Config) string { + if c.Server.APIPort == 0 { + return "Web UI: run `msgvault daemon status` for the URL once ready\n" + } + host := c.Server.BindAddr + switch host { + case "", "0.0.0.0", "::": + host = defaultDaemonBindAddr + } + return "Web UI: http://" + net.JoinHostPort(host, strconv.Itoa(c.Server.APIPort)) + "\n" +} + func runServeStopWithAPIKey(cmd *cobra.Command, dataDir string, apiKey string) error { return stopLiveDaemonsWithAPIKey(cmd, dataDir, apiKey, false) } diff --git a/cmd/msgvault/cmd/serve_lifecycle_test.go b/cmd/msgvault/cmd/serve_lifecycle_test.go index f4af47947..b92c70c16 100644 --- a/cmd/msgvault/cmd/serve_lifecycle_test.go +++ b/cmd/msgvault/cmd/serve_lifecycle_test.go @@ -757,6 +757,68 @@ func TestRunServeRestartStartsWhenNoDaemonIsRunning(t *testing.T) { assert.Empty(t, stderr.String()) } +func TestRunServeStartNotReadyPrintsWebUIURLForFixedPort(t *testing.T) { + dataDir := t.TempDir() + waitCh := make(chan error) + stubStartServeBackgroundProcess(t, func(*config.Config, backgroundServeStartOptions) (*backgroundServeProcess, error) { + return &backgroundServeProcess{ + PID: 777, + LogPath: "/tmp/msgvault-serve.log", + Wait: waitCh, + }, nil + }) + stubWaitForBackgroundServeReady(t, func( + context.Context, + string, + <-chan error, + time.Duration, + ) (*DaemonRuntime, bool, error) { + return nil, false, nil + }) + cmd, stdout, stderr := lifecycleTestCommand() + + require.NoError(t, runServeStart(cmd, lifecycleTestConfig(dataDir))) + + assert.Equal(t, + "msgvault starting in background (pid 777)\n"+ + "Web UI: http://127.0.0.1:8080\n"+ + "Logs: /tmp/msgvault-serve.log\n", + stdout.String()) + assert.Empty(t, stderr.String()) +} + +func TestRunServeStartNotReadyPointsAtStatusForAutoPort(t *testing.T) { + dataDir := t.TempDir() + waitCh := make(chan error) + stubStartServeBackgroundProcess(t, func(*config.Config, backgroundServeStartOptions) (*backgroundServeProcess, error) { + return &backgroundServeProcess{ + PID: 776, + LogPath: "/tmp/msgvault-serve.log", + Wait: waitCh, + }, nil + }) + stubWaitForBackgroundServeReady(t, func( + context.Context, + string, + <-chan error, + time.Duration, + ) (*DaemonRuntime, bool, error) { + return nil, false, nil + }) + cfg := lifecycleTestConfig(dataDir) + cfg.Server.APIPort = 0 + cmd, stdout, stderr := lifecycleTestCommand() + + require.NoError(t, runServeStart(cmd, cfg)) + + assert.Equal(t, + "msgvault starting in background (pid 776)\n"+ + "Web UI: run `msgvault daemon status` for the URL once ready\n"+ + "Logs: /tmp/msgvault-serve.log\n", + stdout.String()) + assert.Empty(t, stderr.String()) +} + func TestServeStopGraceTimeoutCoversDaemonShutdownBudget(t *testing.T) { assert.GreaterOrEqual(t, serveStopGraceTimeout, From d5c700e9f1575ec31a66c99a49a2897311014f4c Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 18:43:20 -0500 Subject: [PATCH 024/102] =?UTF-8?q?feat(web):=20design-language=20foundati?= =?UTF-8?q?ons=20=E2=80=94=20dual-voice=20type,=20avatars,=20empty=20state?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - data-mono / data-section-label voices in tokens.css: mono data values at 0.92em with tabular-nums, small-caps letterspaced section labels - hairline-sheen token (theme-scoped) for machined dual-hairline edges - thin muted data-scroll scrollbars that darken on hover - ::selection tinted from the accent - IdentityAvatar: deterministic FNV-1a hue, muted saturation per theme, squarer tile for domains; presentation-only - EmptyState: geometric hairline-stroke SVG glyphs with the label voice - command palette highlight aligned to the 2px accent inset bar Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- web/src/app.css | 2 +- .../lib/components/common/EmptyState.svelte | 82 +++++++++++++++++++ .../components/common/IdentityAvatar.svelte | 60 ++++++++++++++ web/src/lib/util/identity.test.ts | 49 +++++++++++ web/src/lib/util/identity.ts | 32 ++++++++ web/src/styles/theme-dark.css | 1 + web/src/styles/theme-light.css | 1 + web/src/styles/tokens.css | 32 +++++++- 8 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 web/src/lib/components/common/EmptyState.svelte create mode 100644 web/src/lib/components/common/IdentityAvatar.svelte create mode 100644 web/src/lib/util/identity.test.ts create mode 100644 web/src/lib/util/identity.ts diff --git a/web/src/app.css b/web/src/app.css index 1fae817b2..9878ecf0d 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -28,5 +28,5 @@ textarea { } .kit-command-palette__option.highlighted { - box-shadow: inset 3px 0 var(--accent-blue); + box-shadow: inset 2px 0 0 var(--accent-blue); } diff --git a/web/src/lib/components/common/EmptyState.svelte b/web/src/lib/components/common/EmptyState.svelte new file mode 100644 index 000000000..6a7753c68 --- /dev/null +++ b/web/src/lib/components/common/EmptyState.svelte @@ -0,0 +1,82 @@ +<script lang="ts"> + interface Props { + /** Hairline-stroke glyph matching the pane's subject matter. */ + glyph: 'conversations' | 'people' | 'envelope' | 'pulse' | 'search'; + label: string; + hint?: string; + /** Live-region role announced to assistive tech; panes that report a + * transient state keep `status`, hard failures use `alert`. */ + role?: 'status' | 'alert' | 'presentation'; + } + + let { glyph, label, hint = undefined, role = 'status' }: Props = $props(); +</script> + +<div class="empty-state" role={role === 'presentation' ? undefined : role}> + <svg + class="empty-glyph" + width="44" + height="44" + viewBox="0 0 48 48" + fill="none" + stroke="currentColor" + stroke-width="1.25" + stroke-linecap="round" + stroke-linejoin="round" + aria-hidden="true" + > + {#if glyph === 'conversations'} + <path d="M10 7h15a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4h-8l-5 5v-5h-2a4 4 0 0 1-4-4v-8a4 4 0 0 1 4-4Z" /> + <path d="M33 18h1a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4h-2v5l-5-5h-8a4 4 0 0 1-4-4v-1" /> + {:else if glyph === 'people'} + <circle cx="18" cy="16" r="6" /> + <path d="M7 38a11 11 0 0 1 22 0" /> + <path d="M31 11.5a6 6 0 0 1 0 9" /> + <path d="M34 27.5a11 11 0 0 1 7 10.5" /> + {:else if glyph === 'envelope'} + <path d="M8 21 24 9l16 12" /> + <path d="M8 21v17h32V21" /> + <path d="m8 21 16 11 16-11" /> + {:else if glyph === 'pulse'} + <path d="M5 29h9l4-11 6 20 5-15 3 6h11" /> + <circle cx="24" cy="10" r="1.5" /> + {:else if glyph === 'search'} + <circle cx="21" cy="21" r="11" /> + <path d="m29.5 29.5 11 11" /> + <path d="M15 21a6 6 0 0 1 6-6" /> + {/if} + </svg> + <p class="empty-label" data-section-label>{label}</p> + {#if hint}<p class="empty-hint">{hint}</p>{/if} +</div> + +<style> + .empty-state { + display: flex; + flex: 1; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--space-3); + padding: var(--space-8) var(--space-5); + text-align: center; + } + + .empty-glyph { + margin-bottom: var(--space-2); + color: var(--text-muted); + opacity: 0.6; + } + + .empty-label { + margin: 0; + } + + .empty-hint { + margin: 0; + max-width: 340px; + color: var(--text-muted); + font-size: var(--font-size-sm); + line-height: 1.5; + } +</style> diff --git a/web/src/lib/components/common/IdentityAvatar.svelte b/web/src/lib/components/common/IdentityAvatar.svelte new file mode 100644 index 000000000..133f1a325 --- /dev/null +++ b/web/src/lib/components/common/IdentityAvatar.svelte @@ -0,0 +1,60 @@ +<script lang="ts"> + import { identityHue, initialsFor } from '../../util/identity'; + + interface Props { + /** Visible display label the initials derive from. */ + label: string; + /** Stable identity key the hue derives from; falls back to the label. */ + seed?: string; + /** Domains render a single letter in a squarer tile so people and + * domains read differently at a glance. */ + shape?: 'person' | 'domain'; + size?: number; + } + + let { label, seed = undefined, shape = 'person', size = 28 }: Props = $props(); + + const hue = $derived(identityHue(seed ?? label)); + const glyph = $derived( + shape === 'domain' ? (label.trim()[0] ?? '?').toUpperCase() : initialsFor(label) + ); +</script> + +<span + class="identity-avatar" + class:identity-avatar--domain={shape === 'domain'} + style:--avatar-hue={hue} + style:width={`${size}px`} + style:height={`${size}px`} + style:font-size={`${Math.round(size * 0.39)}px`} + aria-hidden="true" +>{glyph}</span> + +<style> + /* Deterministic muted hue per identity: fixed low saturation and + * theme-appropriate lightness so the tiles sit quietly in both themes; + * the glyph is a deeper shade of the same hue. Presentation-only. */ + .identity-avatar { + display: inline-flex; + flex: none; + align-items: center; + justify-content: center; + border-radius: 27%; + background: hsl(var(--avatar-hue, 210) 30% 87%); + color: hsl(var(--avatar-hue, 210) 45% 29%); + font-family: var(--font-sans); + font-weight: 600; + letter-spacing: 0.02em; + line-height: 1; + user-select: none; + } + + .identity-avatar--domain { + border-radius: 12%; + } + + :global([data-theme='dark']) .identity-avatar { + background: hsl(var(--avatar-hue, 210) 22% 27%); + color: hsl(var(--avatar-hue, 210) 52% 80%); + } +</style> diff --git a/web/src/lib/util/identity.test.ts b/web/src/lib/util/identity.test.ts new file mode 100644 index 000000000..13e43602f --- /dev/null +++ b/web/src/lib/util/identity.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from 'vitest'; + +import { identityHue, initialsFor } from './identity'; + +describe('identityHue', () => { + it('is deterministic for the same seed', () => { + expect(identityHue('cluster:42')).toBe(identityHue('cluster:42')); + }); + + it('stays within [0, 360)', () => { + for (const seed of ['', 'a', 'cluster:1', 'domain:example.com', '🙂 unicode']) { + const hue = identityHue(seed); + expect(hue).toBeGreaterThanOrEqual(0); + expect(hue).toBeLessThan(360); + expect(Number.isInteger(hue)).toBe(true); + } + }); + + it('spreads nearby seeds to different hues', () => { + const hues = new Set([1, 2, 3, 4, 5, 6, 7, 8].map((n) => identityHue(`cluster:${n}`))); + expect(hues.size).toBeGreaterThan(4); + }); +}); + +describe('initialsFor', () => { + it('takes the first letters of the first two words', () => { + expect(initialsFor('Alice Example')).toBe('AE'); + expect(initialsFor(' thing document ')).toBe('TD'); + }); + + it('uses a single initial for one-word names', () => { + expect(initialsFor('Alice')).toBe('A'); + }); + + it('derives initials from the local part of an email address', () => { + expect(initialsFor('jane.doe@example.com')).toBe('JD'); + expect(initialsFor('person19@mail.test')).toBe('P'); + }); + + it('skips leading symbols when deriving initials', () => { + expect(initialsFor('@example.com')).toBe('EC'); + }); + + it('falls back for empty and symbol-only labels', () => { + expect(initialsFor('')).toBe('?'); + expect(initialsFor(' ')).toBe('?'); + expect(initialsFor('++')).toBe('+'); + }); +}); diff --git a/web/src/lib/util/identity.ts b/web/src/lib/util/identity.ts new file mode 100644 index 000000000..7a58bc4fe --- /dev/null +++ b/web/src/lib/util/identity.ts @@ -0,0 +1,32 @@ +/** Deterministic identity presentation helpers: stable hash-derived hues and + * display initials for avatar glyphs. Presentation-only — never serialized + * into exports or APIs. */ + +/** FNV-1a 32-bit hash of a seed string, folded to a hue in [0, 360). */ +export function identityHue(seed: string): number { + let hash = 0x811c9dc5; + for (let index = 0; index < seed.length; index += 1) { + hash ^= seed.charCodeAt(index); + hash = Math.imul(hash, 0x01000193); + } + return (hash >>> 0) % 360; +} + +/** 1-2 uppercase initials for a display label. + * + * Email-like labels use the local part ("jane.doe@example.com" → "JD"); + * multi-word names use the first character of the first two words; anything + * else falls back to its first character. + */ +export function initialsFor(label: string): string { + const trimmed = label.trim(); + if (trimmed === '') return '?'; + const at = trimmed.indexOf('@'); + const base = at > 0 ? trimmed.slice(0, at) : trimmed; + const initials = base + .split(/[\s._-]+/) + .map((word) => word.match(/[\p{L}\p{N}]/u)?.[0]) + .filter((char): char is string => char !== undefined); + if (initials.length === 0) return [...trimmed][0]!.toUpperCase(); + return initials.slice(0, 2).join('').toUpperCase(); +} diff --git a/web/src/styles/theme-dark.css b/web/src/styles/theme-dark.css index a4c6398e4..d7bff1d08 100644 --- a/web/src/styles/theme-dark.css +++ b/web/src/styles/theme-dark.css @@ -9,6 +9,7 @@ --border-default: #74777d; --border-muted: #2c2e32; --border-strong: #a0a3a9; + --hairline-sheen: rgb(255 255 255 / 5%); --control-bg: #1a1b1e; --control-border: #7e8187; --text-primary: #f2f3f5; diff --git a/web/src/styles/theme-light.css b/web/src/styles/theme-light.css index b1fbc8edf..00a365f64 100644 --- a/web/src/styles/theme-light.css +++ b/web/src/styles/theme-light.css @@ -9,6 +9,7 @@ --border-default: #6f737a; --border-muted: #d8dade; --border-strong: #545860; + --hairline-sheen: rgb(255 255 255 / 75%); --control-bg: #ffffff; --control-border: #6f737a; --text-primary: #191b1f; diff --git a/web/src/styles/tokens.css b/web/src/styles/tokens.css index f7350540a..16db9780a 100644 --- a/web/src/styles/tokens.css +++ b/web/src/styles/tokens.css @@ -24,6 +24,36 @@ font-family: var(--font-mono); } +/* Dual-voice typography: UI text speaks the native sans stack; data values + * (timestamps, counts, scores, sizes, identifiers) speak mono at a slightly + * smaller optical size so both voices sit on the same baseline weight. */ +:where([data-mono]) { + font-family: var(--font-mono); + font-size: 0.92em; + font-variant-numeric: tabular-nums; +} + +/* Third voice: small-caps letterspaced section labels for pane headers, + * month markers, and group headings — never bold-text-as-header. */ +:where([data-section-label]) { + color: var(--text-muted); + font-family: var(--font-sans); + font-size: var(--font-size-xs); + font-weight: 600; + letter-spacing: 0.07em; + text-transform: uppercase; +} + +/* Inner-pane scrollbars: thin, muted, darkens on hover. */ +:where([data-scroll]) { + scrollbar-width: thin; + scrollbar-color: color-mix(in srgb, var(--border-default) 55%, transparent) transparent; +} + +:where([data-scroll]:hover) { + scrollbar-color: var(--border-default) transparent; +} + :where(a, [data-link]) { color: var(--link-ink); } @@ -37,7 +67,7 @@ } ::selection { - background: var(--selected-bg); + background: color-mix(in srgb, var(--accent-blue) 24%, transparent); color: var(--text-primary); } From 1cf4045312db4331bbcfebc7c096222a6ab1e001 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 18:43:39 -0500 Subject: [PATCH 025/102] feat(web): elevate relationships hub to the precision-instrument voice - list rows gain identity avatars, right-edge mono dates, mono count with pill modality badge, staggered fade-up reveal on load - activity bars retuned to low-opacity accent with 2px caps - timeline month markers speak the section-label voice with a trailing hairline rule; times go mono - header: mono counts line, small-caps Identity evidence label, mono identifier values, 4px-grid spacing - 2px accent inset bar is the one selection language in list + timeline - designed empty states (people / pulse / conversations glyphs) - machined list-pane boundary via hairline sheen Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../relationships/RelationshipHeader.svelte | 15 ++- .../relationships/RelationshipList.svelte | 118 ++++++++++++------ .../relationships/RelationshipTimeline.svelte | 40 ++++-- .../RelationshipTimeline.test.ts | 2 +- .../RelationshipsWorkspace.svelte | 35 ++---- 5 files changed, 130 insertions(+), 80 deletions(-) diff --git a/web/src/lib/components/relationships/RelationshipHeader.svelte b/web/src/lib/components/relationships/RelationshipHeader.svelte index 045697bfb..5f5d37fbb 100644 --- a/web/src/lib/components/relationships/RelationshipHeader.svelte +++ b/web/src/lib/components/relationships/RelationshipHeader.svelte @@ -211,7 +211,7 @@ <Button label="Retry" surface="outline" size="sm" disabled={retrying} onclick={() => void retryRefresh()} /> </section> {/if} - <p class="counts"> + <p class="counts" data-mono> {detail.activity_count.toLocaleString()} items · {detail.file_count.toLocaleString()} files · {formatDate(detail.first_at)} – {formatDate(detail.last_at)} {#if !isPersonDetail(detail)} @@ -219,6 +219,7 @@ {/if} </p> {#if isPersonDetail(detail)} + <span class="identity-label" data-section-label>Identity evidence</span> <div class="identifiers" aria-label="Archive-wide identity evidence"> {#each detail.identifiers ?? [] as identifier (`${identifier.participant_id}:${identifier.type}:${identifier.value}`)} {@const isOtherMember = !!detail.cluster && identifier.participant_id !== detail.id} @@ -226,7 +227,7 @@ {#if identifier.display_value?.trim() && identifier.display_value.trim() !== identifier.value} <span class="chip-display">{identifier.display_value}</span> {/if} - <strong>{identifier.value}</strong> + <strong data-mono>{identifier.value}</strong> <small> {identifier.type} · {identifier.is_primary ? 'Primary' : 'Secondary'} · {identifier.provenance} {#if detail.cluster} @@ -305,17 +306,23 @@ </header> <style> + /* Spacing sits on the 4px grid: 8px between header lines, 16px of air + * before the hairline that closes the header. */ .relationship-header { display: flex; flex-direction: column; - gap: var(--space-3); + gap: var(--space-4); } .relationship-header.has-detail { - padding-bottom: var(--space-4); + padding-bottom: var(--space-6); border-bottom: 1px solid var(--border-muted); } + .identity-label { + margin-top: var(--space-2); + } + .header-empty { margin: 0; color: var(--text-muted); diff --git a/web/src/lib/components/relationships/RelationshipList.svelte b/web/src/lib/components/relationships/RelationshipList.svelte index 775b86d29..8267a7c7d 100644 --- a/web/src/lib/components/relationships/RelationshipList.svelte +++ b/web/src/lib/components/relationships/RelationshipList.svelte @@ -3,6 +3,8 @@ import type { DomainSummary, PersonSummary } from '../../explore/models'; import type { RelationshipFacet, RelationshipRow } from '../../relationships/models'; + import EmptyState from '../common/EmptyState.svelte'; + import IdentityAvatar from '../common/IdentityAvatar.svelte'; interface Props { rows: RelationshipRow[] | PersonSummary[] | DomainSummary[]; @@ -177,16 +179,18 @@ aria-label="Relationship results" aria-busy={loading} tabindex="0" + data-scroll bind:this={gridElement} onkeydown={handleKeydown} > {#if loading && views.length === 0} <p class="list-empty" role="status">Loading relationships…</p> {:else if views.length === 0} - <div class="list-empty" role="status"> - <p class="list-empty-title">No relationships found</p> - <p class="list-empty-hint">Try a different search, or switch between People and Domains.</p> - </div> + <EmptyState + glyph="people" + label="No relationships found" + hint="Try a different search, or switch between People and Domains." + /> {:else} {#each views as view, index (view.key)} <div @@ -197,24 +201,33 @@ tabindex="-1" data-row-key={view.key} aria-selected={view.target === activeTarget} + style:--reveal-index={index} onpointerdown={() => selectRow(view)} > <div role="gridcell"> - <div class="row-main"> - <span class="label">{view.label}</span> - {#if view.modalities !== null} - <span class="modality-badge" aria-label={`${view.modalities} modalities`}>{view.modalities}</span> - {/if} + <IdentityAvatar + label={view.label} + seed={view.key} + shape={view.key.startsWith('domain:') ? 'domain' : 'person'} + size={28} + /> + <div class="row-body"> + <div class="row-main"> + <span class="label">{view.label}</span> + <span class="last-at" data-mono>{formatDate(view.lastAt)}</span> + </div> + <div class="row-meta"> + <span class="item-count" data-mono>{view.itemCount.toLocaleString()} {view.itemUnit}</span> + {#if view.modalities !== null} + <span class="modality-badge" data-mono aria-label={`${view.modalities} modalities`}>{view.modalities}</span> + {/if} + {#if view.score !== null} + <span class="activity-track" aria-label={`Activity score ${view.score.toFixed(2)}`}> + <span class="activity-bar" style:width={`${Math.min(100, (view.score / maxScore) * 100)}%`}></span> + </span> + {/if} + </div> </div> - <div class="row-meta"> - <span class="last-at">{formatDate(view.lastAt)}</span> - <span class="item-count">{view.itemCount.toLocaleString()} {view.itemUnit}</span> - </div> - {#if view.score !== null} - <span class="activity-track" aria-label={`Activity score ${view.score.toFixed(2)}`}> - <span class="activity-bar" style:width={`${Math.min(100, (view.score / maxScore) * 100)}%`}></span> - </span> - {/if} </div> </div> {/each} @@ -259,17 +272,6 @@ text-align: center; } - .list-empty-title { - margin: 0; - color: var(--text-secondary); - font-weight: 600; - } - - .list-empty-hint { - margin: 0; - line-height: 1.5; - } - .named-state { display: flex; flex-direction: column; @@ -298,16 +300,24 @@ } .result-row { - border: 1px solid transparent; border-radius: var(--radius-sm); cursor: default; + transition: background-color 80ms ease-out; } .result-row [role='gridcell'] { display: flex; + align-items: center; + gap: var(--space-4); + padding: var(--space-2) var(--space-3); + } + + .row-body { + display: flex; + min-width: 0; + flex: 1; flex-direction: column; gap: 2px; - padding: var(--space-2) var(--space-3); } .result-row:hover { @@ -315,17 +325,19 @@ } .result-row.active { - box-shadow: inset 3px 0 var(--accent-teal); + box-shadow: inset 2px 0 0 var(--accent-blue); } .result-row.selected { background: var(--selected-bg); + box-shadow: inset 2px 0 0 var(--accent-blue); } .row-main { display: flex; - align-items: center; - gap: var(--space-2); + align-items: baseline; + justify-content: space-between; + gap: var(--space-3); } .label { @@ -337,20 +349,29 @@ white-space: nowrap; } + .last-at { + flex: none; + color: var(--text-muted); + font-size: var(--font-size-2xs); + } + .modality-badge { display: inline-flex; min-width: 16px; + flex: none; align-items: center; justify-content: center; - border-radius: var(--radius-sm); - background: var(--bg-inset); + border: 1px solid var(--border-muted); + border-radius: 999px; color: var(--text-muted); font-size: var(--font-size-2xs); - padding: 0 4px; + line-height: 1.4; + padding: 0 5px; } .row-meta { display: flex; + align-items: center; gap: var(--space-3); color: var(--text-muted); font-size: var(--font-size-2xs); @@ -358,15 +379,32 @@ .activity-track { display: block; + min-width: 0; height: 3px; - border-radius: var(--radius-sm); + flex: 1; + align-self: center; + border-radius: 2px; background: var(--bg-inset); } .activity-bar { display: block; height: 100%; - border-radius: var(--radius-sm); - background: var(--accent-teal); + border-radius: 2px; + background: color-mix(in srgb, var(--accent-blue) 45%, transparent); + } + + @media (prefers-reduced-motion: no-preference) { + .result-row { + animation: row-reveal 150ms ease-out backwards; + animation-delay: calc(min(var(--reveal-index, 0), 15) * 14ms); + } + + @keyframes row-reveal { + from { + opacity: 0; + transform: translateY(4px); + } + } } </style> diff --git a/web/src/lib/components/relationships/RelationshipTimeline.svelte b/web/src/lib/components/relationships/RelationshipTimeline.svelte index 121d2e29f..87953ca67 100644 --- a/web/src/lib/components/relationships/RelationshipTimeline.svelte +++ b/web/src/lib/components/relationships/RelationshipTimeline.svelte @@ -4,6 +4,7 @@ import type { RelationshipTimelineRow } from '../../relationships/models'; import { RowGeometry, tableViewportHeight } from '../../theme/preferences.svelte'; + import EmptyState from '../common/EmptyState.svelte'; import { monthGroupKey, monthGroupLabel, timelineRowTitle } from './timeline-support'; interface Props { @@ -177,6 +178,7 @@ aria-label="Relationship activity" aria-busy={loading || loadingMore} tabindex="0" + data-scroll bind:this={gridElement} onkeydown={handleKeydown} onscroll={handleScroll} @@ -185,7 +187,9 @@ {#if loading && rows.length === 0} <div role="row"><div role="gridcell"><p class="timeline-empty" role="status">Loading activity…</p></div></div> {:else if rows.length === 0} - <div role="row"><div role="gridcell"><p class="timeline-empty" role="status">No activity yet.</p></div></div> + <div role="row"><div role="gridcell"> + <EmptyState glyph="pulse" label="No activity yet" hint="Interactions will appear here as they land in the archive." /> + </div></div> {:else if !slice || rowHeight === undefined} <div role="row"><div role="gridcell"><p class="timeline-empty" role="status">Preparing timeline layout…</p></div></div> {:else} @@ -194,7 +198,7 @@ {#each renderedItems as item (item.key)} {#if item.type === 'header'} <div class="month-header" role="row" style:height={`${MONTH_HEADER_HEIGHT}px`}> - <span role="gridcell" aria-label={`Month: ${item.label}`}>{item.label}</span> + <span role="gridcell" data-section-label aria-label={`Month: ${item.label}`}>{item.label}</span> </div> {:else} <!-- svelte-ignore a11y_click_events_have_key_events -- Enter @@ -214,7 +218,7 @@ <div role="gridcell"> <span class="row-top"> <strong>{timelineRowTitle(item.row)}</strong> - <time datetime={item.row.occurred_at}>{formatTime(item.row.occurred_at)}</time> + <time datetime={item.row.occurred_at} data-mono>{formatTime(item.row.occurred_at)}</time> </span> <span class="preview">{item.row.preview}</span> </div> @@ -286,15 +290,27 @@ inset: 0 0 auto; } + /* Month markers speak the shared small-caps label voice (data-section-label) + * with a trailing hairline that rules the month across the pane. */ .month-header { display: flex; align-items: center; padding: 0 var(--space-3); - color: var(--text-muted); - font-size: var(--font-size-2xs); - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; + } + + .month-header [role='gridcell'] { + display: flex; + min-width: 0; + flex: 1; + align-items: center; + gap: var(--space-4); + } + + .month-header [role='gridcell']::after { + content: ''; + height: 1px; + flex: 1; + background: var(--border-muted); } .timeline-row { @@ -330,17 +346,21 @@ flex: none; } + .timeline-row { + transition: background-color 80ms ease-out; + } + .timeline-row:hover { background: var(--bg-surface-hover); } .timeline-row.active { - box-shadow: inset 3px 0 var(--accent-teal); + box-shadow: inset 2px 0 0 var(--accent-blue); } .timeline-row.selected { background: var(--selected-bg); - box-shadow: inset 3px 0 var(--accent-teal); + box-shadow: inset 2px 0 0 var(--accent-blue); } .timeline-empty { diff --git a/web/src/lib/components/relationships/RelationshipTimeline.test.ts b/web/src/lib/components/relationships/RelationshipTimeline.test.ts index 8a2f3641e..cc8710efa 100644 --- a/web/src/lib/components/relationships/RelationshipTimeline.test.ts +++ b/web/src/lib/components/relationships/RelationshipTimeline.test.ts @@ -123,6 +123,6 @@ describe('RelationshipTimeline', () => { it('shows an empty state when there is no activity', () => { render(RelationshipTimeline, { rows: [], onRowOpen: vi.fn() }); - expect(screen.getByText('No activity yet.')).toBeDefined(); + expect(screen.getByText('No activity yet')).toBeDefined(); }); }); diff --git a/web/src/lib/components/relationships/RelationshipsWorkspace.svelte b/web/src/lib/components/relationships/RelationshipsWorkspace.svelte index 0edae2aa6..2f488166d 100644 --- a/web/src/lib/components/relationships/RelationshipsWorkspace.svelte +++ b/web/src/lib/components/relationships/RelationshipsWorkspace.svelte @@ -20,6 +20,7 @@ import type { RelationshipsController } from '../../relationships/controller.svelte'; import type { RelationshipFacet, RelationshipTimelineRow } from '../../relationships/models'; import { debounce } from '../../util/debounce'; + import EmptyState from '../common/EmptyState.svelte'; import FilesWorkspace from '../files/FilesWorkspace.svelte'; import SplitPane from '../layout/SplitPane.svelte'; import ReadingPane, { type ReadingPaneSelection } from '../reader/ReadingPane.svelte'; @@ -396,11 +397,12 @@ {#snippet primary()} <div class="pane-center"> {#if target === null && controller.target === null} - <div class="hub-empty" role="status"> - <p class="hub-empty-title">Select a person or domain</p> - <p class="hub-empty-hint"> - Choose someone from the list to see your shared history across mail, chat, and files. - </p> + <div class="hub-empty"> + <EmptyState + glyph="conversations" + label="Select a person or domain" + hint="Choose someone from the list to see your shared history across mail, chat, and files." + /> </div> {:else} <div class="pane-center-column"> @@ -473,6 +475,8 @@ background: var(--bg-canvas); } + /* Machined pane boundary: the darker hairline carries the edge, an + * ultra-faint sheen line beside it catches the light. */ .pane-list { display: flex; flex: 0 0 clamp(280px, 19vw, 340px); @@ -480,6 +484,7 @@ overflow: hidden; background: var(--bg-primary); border-right: 1px solid var(--border-muted); + box-shadow: 1px 0 0 var(--hairline-sheen); } .pane-list.drawer { @@ -531,26 +536,6 @@ display: flex; flex: 1; flex-direction: column; - align-items: center; - justify-content: center; - gap: var(--space-2); - padding: var(--space-8); - text-align: center; - } - - .hub-empty-title { - margin: 0; - color: var(--text-secondary); - font-size: var(--font-size-lg); - font-weight: 600; - } - - .hub-empty-hint { - margin: 0; - max-width: 380px; - color: var(--text-muted); - font-size: var(--font-size-sm); - line-height: 1.5; } .pane-reading { From 4cde1dcaf77bc1afc458e5932203eec2e2fad314 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 18:43:41 -0500 Subject: [PATCH 026/102] feat(web): reading pane + thread cards join the design language - pane header machined hairline, mono meta strip, 160ms slide-up - thread cards: sender avatars (24px), mono timestamps and recipients, 180ms height+opacity body expand, 2px accent bar on the anchor card - envelope-glyph empty state for missing selections - thin data-scroll scrollbars on thread and group scrollers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../components/reader/AttachmentRail.svelte | 2 +- .../components/reader/ConversationView.svelte | Bin 7970 -> 7984 bytes .../lib/components/reader/MessageCard.svelte | 72 ++++++++++++++---- .../lib/components/reader/ReadingPane.svelte | 28 ++++++- 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/web/src/lib/components/reader/AttachmentRail.svelte b/web/src/lib/components/reader/AttachmentRail.svelte index afc256980..c61a55731 100644 --- a/web/src/lib/components/reader/AttachmentRail.svelte +++ b/web/src/lib/components/reader/AttachmentRail.svelte @@ -45,7 +45,7 @@ {#each chronological as file (file.key)} <li> <div><strong>{file.filename || '(unnamed file)'}</strong><span>{bytes(file.size)}</span></div> - <div><time datetime={file.occurred_at}>{date(file.occurred_at)}</time><span>{file.title || file.source_identifier}</span></div> + <div><time datetime={file.occurred_at} data-mono>{date(file.occurred_at)}</time><span>{file.title || file.source_identifier}</span></div> </li> {/each} </ol> diff --git a/web/src/lib/components/reader/ConversationView.svelte b/web/src/lib/components/reader/ConversationView.svelte index dc82b68b161dd21e0103542035218c2251ab8f78..697dd1bca133f3bd6bc9126c507f28ac773a8508 100644 GIT binary patch delta 24 fcmZ2vx4~{hsRVaQVo9QIadJ_9PR`~ci4AN3ck~Hh delta 12 TcmdmBx5#cosl?`HiM4D1Bklza diff --git a/web/src/lib/components/reader/MessageCard.svelte b/web/src/lib/components/reader/MessageCard.svelte index c3aeb0b4e..70ace24f8 100644 --- a/web/src/lib/components/reader/MessageCard.svelte +++ b/web/src/lib/components/reader/MessageCard.svelte @@ -1,6 +1,7 @@ <script lang="ts"> import type { APIClient } from '../../api/client'; import type { ArchiveMessageDetail, MessageViewMode } from '../../archive/types'; + import IdentityAvatar from '../common/IdentityAvatar.svelte'; import ContentFrame from './ContentFrame.svelte'; interface Props { @@ -54,6 +55,7 @@ data-message-id={message.id} > <div class="card-header"> + <IdentityAvatar label={message.sender || '?'} size={24} /> <button type="button" class="collapse-target" @@ -63,10 +65,10 @@ > <span class="header-line"> <strong class="sender">{message.sender || 'Unknown sender'}</strong> - <time datetime={message.sentAt}>{formatDate(message.sentAt)}</time> + <time datetime={message.sentAt} data-mono>{formatDate(message.sentAt)}</time> </span> {#if message.recipients.length > 0} - <span class="recipients">to {message.recipients.join(', ')}</span> + <span class="recipients" data-mono>to {message.recipients.join(', ')}</span> {/if} {#if message.subject} <span class="subject">{message.subject}</span> @@ -90,18 +92,20 @@ <p class="sanitize-notice" role="alert">Could not render HTML formatting. Showing plain text.</p> {/if} - <section class="card-body" aria-label="Message body"> - {#if renderAsHTML} - <ContentFrame - {client} - messageId={message.id} - html={message.bodyHtml ?? ''} - title="Message body" - /> - {:else} - <pre>{message.body}</pre> - {/if} - </section> + <div class="body-reveal"> + <section class="card-body" aria-label="Message body"> + {#if renderAsHTML} + <ContentFrame + {client} + messageId={message.id} + html={message.bodyHtml ?? ''} + title="Message body" + /> + {:else} + <pre>{message.body}</pre> + {/if} + </section> + </div> </article> {:else} <button @@ -112,9 +116,10 @@ data-message-id={message.id} onclick={() => onToggle?.(message.id)} > + <IdentityAvatar label={message.sender || '?'} size={24} /> <span class="collapsed-sender">{message.sender || 'Unknown sender'}</span> <span class="collapsed-snippet">{message.snippet || message.subject}</span> - <time datetime={message.sentAt}>{formatDate(message.sentAt)}</time> + <time datetime={message.sentAt} data-mono>{formatDate(message.sentAt)}</time> </button> {/if} @@ -127,7 +132,7 @@ .message-card--collapsed { display: flex; width: 100%; - align-items: baseline; + align-items: center; gap: var(--space-4); padding: var(--space-3) var(--space-4); border: 0; @@ -137,12 +142,47 @@ cursor: pointer; font: inherit; text-align: left; + transition: background-color 80ms ease-out; } .message-card--collapsed:hover { background: var(--bg-surface-hover); } + /* The thread anchor carries the same 2px accent inset bar as every other + * selected row in the app. */ + .message-card--expanded[aria-current='true'] { + box-shadow: inset 2px 0 0 var(--accent-blue); + } + + .body-reveal { + display: grid; + grid-template-rows: 1fr; + } + + .body-reveal > .card-body { + min-height: 0; + overflow: hidden; + } + + @media (prefers-reduced-motion: no-preference) { + .body-reveal { + animation: body-expand 180ms ease-out; + } + + @keyframes body-expand { + from { + grid-template-rows: 0fr; + opacity: 0; + } + + to { + grid-template-rows: 1fr; + opacity: 1; + } + } + } + .collapsed-sender { flex: none; max-width: 220px; diff --git a/web/src/lib/components/reader/ReadingPane.svelte b/web/src/lib/components/reader/ReadingPane.svelte index 44de424b6..0e26aaf44 100644 --- a/web/src/lib/components/reader/ReadingPane.svelte +++ b/web/src/lib/components/reader/ReadingPane.svelte @@ -27,6 +27,7 @@ import type { APIClient } from '../../api/client'; import { createExploreAPI } from '../../explore/api'; import type { ExploreCacheUnavailable, ExploreFileFact, ExploreFilter } from '../../explore/models'; + import EmptyState from '../common/EmptyState.svelte'; import TaskLinks from '../tasks/TaskLinks.svelte'; import AttachmentRail from './AttachmentRail.svelte'; import ConversationView from './ConversationView.svelte'; @@ -205,7 +206,7 @@ <header class="pane-header"> <div class="pane-heading"> <strong class="pane-title">{title}</strong> - {#if metaStrip}<span class="pane-meta">{metaStrip}</span>{/if} + {#if metaStrip}<span class="pane-meta" data-mono>{metaStrip}</span>{/if} </div> <div class="pane-actions"> {#if showTasks} @@ -258,7 +259,12 @@ {unavailable.message} Rebuild the cache with <code>{unavailable.recovery_action}</code>. </p> {:else} - <p role="alert">{statusMessage || 'The selected result is no longer available in this context.'}</p> + <EmptyState + glyph="envelope" + label="Nothing to read here" + hint={statusMessage || 'The selected result is no longer available in this context.'} + role="alert" + /> {/if} </section> {:else if selection.kind === 'entry'} @@ -266,7 +272,7 @@ <p class="preview">{selection.row.preview || 'No preview is available.'}</p> </section> {:else} - <div class="group-scroll" aria-label="Group details"> + <div class="group-scroll" aria-label="Group details" data-scroll> {#if showFiles} <AttachmentRail files={files} totalCount={totalFiles} loading={filesLoading} error={filesError} /> {:else} @@ -290,6 +296,21 @@ background: var(--bg-surface); } + @media (prefers-reduced-motion: no-preference) { + .reading-pane { + animation: pane-rise 160ms ease-out; + } + + @keyframes pane-rise { + from { + opacity: 0; + transform: translateY(10px); + } + } + } + + /* Machined hairline under the pane header: darker line plus a faint + * sheen below it. */ .pane-header { display: flex; min-height: 40px; @@ -299,6 +320,7 @@ gap: var(--space-4); padding: var(--space-2) var(--space-4); border-bottom: 1px solid var(--border-muted); + box-shadow: 0 1px 0 var(--hairline-sheen); } .pane-heading { From a7896a76305a640e7bee6c8675782bd45d605e7c Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 18:43:58 -0500 Subject: [PATCH 027/102] feat(web): explore + files tables adopt the instrument voice - time/size/count columns go mono and right-aligned; headers speak the 0.06em small-caps voice under a machined sticky hairline - 2px accent inset bar unifies active/selected rows across Everything, groups, and files tables; 80ms hover transitions - search-glyph empty state for Everything; mono result counts in the workspace header and context bar; app bar gains the hairline sheen Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../lib/components/explore/ContextBar.svelte | 2 +- .../components/explore/EverythingTable.svelte | 37 +++++++++++++++---- .../explore/EverythingTable.test.ts | 6 +-- .../explore/FilesPresentation.svelte | 6 +-- .../lib/components/explore/GroupTable.svelte | 24 ++++++++---- .../components/files/FilesWorkspace.svelte | 8 ++-- web/src/lib/components/shell/AppShell.svelte | 5 +++ .../shell/EverythingWorkspace.svelte | 2 +- web/tests/e2e/accessibility.spec.ts | 2 +- 9 files changed, 65 insertions(+), 27 deletions(-) diff --git a/web/src/lib/components/explore/ContextBar.svelte b/web/src/lib/components/explore/ContextBar.svelte index d1167f5c6..e89ed7fd1 100644 --- a/web/src/lib/components/explore/ContextBar.svelte +++ b/web/src/lib/components/explore/ContextBar.svelte @@ -103,7 +103,7 @@ {/if} </div> - <span class="context-count">{totalCount === undefined ? 'Count pending' : `${totalCount.toLocaleString()} results`}</span> + <span class="context-count" data-mono>{totalCount === undefined ? 'Count pending' : `${totalCount.toLocaleString()} results`}</span> {#if filtersOpen} <div class="filter-panel"> diff --git a/web/src/lib/components/explore/EverythingTable.svelte b/web/src/lib/components/explore/EverythingTable.svelte index 6ecaed187..5eeb3aa8f 100644 --- a/web/src/lib/components/explore/EverythingTable.svelte +++ b/web/src/lib/components/explore/EverythingTable.svelte @@ -11,6 +11,7 @@ import { DEFAULT_EXPLORE_COLUMNS } from '../../explore/models'; import type { ExploreSelectionState } from '../../explore/state.svelte'; import { rebaseVirtualScroll, RowGeometry, tableViewportHeight } from '../../theme/preferences.svelte'; + import EmptyState from '../common/EmptyState.svelte'; import RowKind from './RowKind.svelte'; interface Props { @@ -439,6 +440,7 @@ class="table-grid" bind:this={gridElement} role="grid" + data-scroll aria-label="Everything results" aria-rowcount={accessibilityRowCount} aria-colcount={visibleColumns.length} @@ -450,7 +452,11 @@ > <div class="table-header" bind:this={headerElement} role="row" style:grid-template-columns={template}> {#each visibleColumns as column (column)} - <span role="columnheader" aria-label={column === 'attachments' ? 'Attachments' : undefined}> + <span + role="columnheader" + class={`header-cell header-cell--${column}`} + aria-label={column === 'attachments' ? 'Attachments' : undefined} + > {column === 'attachments' ? '⌕' : ALL_COLUMNS.find((entry) => entry.id === column)?.label} </span> {/each} @@ -496,7 +502,9 @@ {/each} </div> {:else if rows.length === 0} - <div role="row"><div class="empty" role="gridcell" aria-colspan={visibleColumns.length}>No items match this view.</div></div> + <div role="row"><div class="empty" role="gridcell" aria-colspan={visibleColumns.length}> + <EmptyState glyph="search" label="No items match this view" hint="Adjust the search or clear filters to widen the view." role="presentation" /> + </div></div> {:else if !slice || rowHeight === undefined} <div role="row"><div role="gridcell" aria-colspan={visibleColumns.length}><p class="empty" role="status">Preparing table layout…</p></div></div> {:else} @@ -543,7 +551,7 @@ <span class="match-count">{row.match.lexical_match_count} lexical matches</span> {/if} {:else if column === 'time'} - <time datetime={row.occurred_at}>{formatTime(row.occurred_at)}</time> + <time datetime={row.occurred_at} data-mono>{formatTime(row.occurred_at)}</time> {:else if column === 'attachments'} {#if row.has_attachments} <span class="attachment" aria-label={`${row.attachment_count} attachments`}>⌕</span> @@ -551,7 +559,7 @@ <span aria-label="No attachments">—</span> {/if} {:else if column === 'size'} - {formatBytes(row.attachment_size)} + <span data-mono>{formatBytes(row.attachment_size)}</span> {/if} </span> {/each} @@ -641,6 +649,8 @@ outline-offset: -2px; } + /* Column headers speak the small-caps label voice; the sheen under the + * header hairline gives the sticky edge its machined depth. */ .table-header { position: sticky; z-index: 1; @@ -649,13 +659,19 @@ min-height: 30px; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); + box-shadow: 0 1px 0 var(--hairline-sheen); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 600; - letter-spacing: 0.04em; + letter-spacing: 0.06em; text-transform: uppercase; } + .header-cell--time, + .header-cell--size { + text-align: right; + } + .table-header span, .cell { min-width: 0; @@ -692,6 +708,7 @@ font-size: var(--font-size-sm); font-variant-numeric: tabular-nums; cursor: default; + transition: background-color 80ms ease-out; } .data-row:nth-child(even) { @@ -703,12 +720,18 @@ } .data-row--active { - box-shadow: inset 3px 0 var(--accent-teal); + box-shadow: inset 2px 0 0 var(--accent-blue); } .data-row--selected { background: color-mix(in srgb, var(--accent-teal) 12%, var(--bg-surface)); - box-shadow: inset 0 0 0 1px var(--selected-border); + box-shadow: inset 2px 0 0 var(--accent-blue), inset 0 0 0 1px var(--selected-border); + } + + /* Tabular data columns sit on the right edge, mono-aligned. */ + .cell--time, + .cell--size { + text-align: right; } .data-row--inspected { diff --git a/web/src/lib/components/explore/EverythingTable.test.ts b/web/src/lib/components/explore/EverythingTable.test.ts index 37c5e9302..b86f0a9b2 100644 --- a/web/src/lib/components/explore/EverythingTable.test.ts +++ b/web/src/lib/components/explore/EverythingTable.test.ts @@ -331,7 +331,7 @@ describe('EverythingTable', () => { expect(skeleton.children).toHaveLength(6); void rerender({ rows: [], selection, loading: false }); - expect(screen.getByText('No items match this view.')).toBeDefined(); + expect(screen.getByText('No items match this view')).toBeDefined(); expect(grid.getAttribute('aria-rowcount')).toBe('2'); void rerender({ @@ -350,7 +350,7 @@ describe('EverythingTable', () => { expect(grid.getAttribute('aria-rowcount')).toBeNull(); await fireEvent.click(screen.getByRole('button', { name: 'Retry cache check' })); expect(onRetry).toHaveBeenCalledOnce(); - expect(screen.queryByText('No items match this view.')).toBeNull(); + expect(screen.queryByText('No items match this view')).toBeNull(); }); it('renders request errors as an exclusive state rather than an empty result', () => { @@ -361,7 +361,7 @@ describe('EverythingTable', () => { }); expect(screen.getByRole('alert').textContent).toContain('The query could not be completed.'); - expect(screen.queryByText('No items match this view.')).toBeNull(); + expect(screen.queryByText('No items match this view')).toBeNull(); expect(screen.getByRole('grid', { name: 'Everything results' }).getAttribute('aria-rowcount')).toBeNull(); }); }); diff --git a/web/src/lib/components/explore/FilesPresentation.svelte b/web/src/lib/components/explore/FilesPresentation.svelte index aad16dd39..f1e1abd19 100644 --- a/web/src/lib/components/explore/FilesPresentation.svelte +++ b/web/src/lib/components/explore/FilesPresentation.svelte @@ -304,11 +304,11 @@ if (!(event.target as Element).closest('button')) onOpenFile?.(file); }} > - <span role="gridcell">{formatTime(file.occurred_at)}</span> + <span role="gridcell" data-mono>{formatTime(file.occurred_at)}</span> <span class="filename" role="gridcell">{file.filename || 'Unnamed file'}</span> <span role="gridcell"><button type="button" aria-label={`Open containing item ${file.message_id}`} onclick={(event) => { event.stopPropagation(); onOpenItem?.(file.entry_key); }}>{file.title || 'Untitled item'}</button></span> <span role="gridcell">{file.source_identifier}</span> - <span role="gridcell">{formatBytes(file.size)}</span> + <span role="gridcell" data-mono>{formatBytes(file.size)}</span> </div> {/each} </div> @@ -331,7 +331,7 @@ .file-grid { display: flex; min-height: 0; flex: 1; flex-direction: column; overflow: auto; border: 1px solid var(--border-default); border-radius: var(--radius-md); background: var(--bg-surface); outline: none; } .file-grid:focus-visible { box-shadow: inset 0 0 0 2px var(--focus-color); } .file-row { display: grid; grid-template-columns: 110px minmax(160px,1fr) minmax(180px,1.4fr) minmax(150px,1fr) 90px; align-items: center; gap: var(--space-3); min-height: var(--row-height); padding: 0 var(--space-3); } - .file-header { position: sticky; z-index: 1; top: 0; min-height: var(--table-header-height); flex: 0 0 auto; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 600; text-transform: uppercase; } + .file-header { position: sticky; z-index: 1; top: 0; min-height: var(--table-header-height); flex: 0 0 auto; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); box-shadow: 0 1px 0 var(--hairline-sheen); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; } .file-body { position: relative; min-height: 220px; flex: 0 0 auto; } .virtual-spacer { position: relative; } .virtual-window { position: absolute; inset: 0 0 auto; } diff --git a/web/src/lib/components/explore/GroupTable.svelte b/web/src/lib/components/explore/GroupTable.svelte index 1d400767c..a78fd1486 100644 --- a/web/src/lib/components/explore/GroupTable.svelte +++ b/web/src/lib/components/explore/GroupTable.svelte @@ -278,6 +278,7 @@ class="group-grid" bind:this={gridElement} role="grid" + data-scroll aria-label={`${workspaceLabel} grouped by ${dimension}`} aria-rowcount={accessibilityRowCount} aria-colcount="5" @@ -289,8 +290,8 @@ > <div class="group-header" bind:this={headerElement} role="row" style:grid-template-columns={TEMPLATE}> <span role="columnheader">Group</span> - <span role="columnheader">Items</span> - <span role="columnheader">Estimated</span> + <span role="columnheader" class="numeric">Items</span> + <span role="columnheader" class="numeric">Estimated</span> <span role="columnheader">Latest</span> <span role="columnheader">Action</span> </div> @@ -342,9 +343,9 @@ ondblclick={() => { if (drillable) onDrill(row); }} > <div role="gridcell"><strong>{row.label}</strong></div> - <span role="gridcell">{row.count.toLocaleString()}</span> - <span role="gridcell">{bytes(row.estimated_bytes)}</span> - <div role="gridcell"><time datetime={row.latest_at}>{date(row.latest_at)}</time></div> + <span role="gridcell" class="numeric" data-mono>{row.count.toLocaleString()}</span> + <span role="gridcell" class="numeric" data-mono>{bytes(row.estimated_bytes)}</span> + <div role="gridcell"><time datetime={row.latest_at} data-mono>{date(row.latest_at)}</time></div> <span role="gridcell"> {#if drillable} <Button @@ -408,13 +409,18 @@ min-height: 30px; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); + box-shadow: 0 1px 0 var(--hairline-sheen); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 600; - letter-spacing: 0.04em; + letter-spacing: 0.06em; text-transform: uppercase; } + .numeric { + text-align: right; + } + .group-header span, .group-row > :global(*) { min-width: 0; @@ -447,6 +453,10 @@ font-size: var(--font-size-sm); } + .group-row { + transition: background-color 80ms ease-out; + } + .group-row:hover { background: var(--bg-surface-hover); } @@ -456,7 +466,7 @@ } .group-row--active { - box-shadow: inset 3px 0 var(--accent-teal); + box-shadow: inset 2px 0 0 var(--accent-blue); } .group-row--inspected { diff --git a/web/src/lib/components/files/FilesWorkspace.svelte b/web/src/lib/components/files/FilesWorkspace.svelte index 8fb70c5c2..5f4febf26 100644 --- a/web/src/lib/components/files/FilesWorkspace.svelte +++ b/web/src/lib/components/files/FilesWorkspace.svelte @@ -509,10 +509,10 @@ } }} > - <span role="gridcell"><time datetime={row.occurred_at}>{formatDate(row.occurred_at)}</time></span> + <span role="gridcell"><time datetime={row.occurred_at} data-mono>{formatDate(row.occurred_at)}</time></span> <span role="gridcell"><strong>{row.filename || '(unnamed)'}</strong></span> <span role="gridcell">{row.mime_type || row.mime_family}</span> - <span role="gridcell">{formatBytes(row.size_bytes)}</span> + <span role="gridcell" data-mono>{formatBytes(row.size_bytes)}</span> <span role="gridcell">{people(row)}</span> <span role="gridcell">{row.source_identifier}</span> <span role="gridcell">{row.containing_title || row.entry_key}</span> @@ -580,14 +580,14 @@ .table-header, .data-row { display: grid; grid-template-columns: 112px minmax(150px, 1.5fr) minmax(120px, 1fr) 82px minmax(140px, 1.2fr) minmax(130px, 1fr) minmax(160px, 1.3fr) 105px; align-items: center; } .files-grid { display: flex; min-height: 0; flex: 1; flex-direction: column; overflow: auto; outline: none; } .files-grid:focus-visible { box-shadow: inset 0 0 0 2px var(--accent-blue); } - .table-header { position: sticky; z-index: 1; top: 0; min-height: var(--table-header-height); flex: 0 0 auto; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 600; text-transform: uppercase; } + .table-header { position: sticky; z-index: 1; top: 0; min-height: var(--table-header-height); flex: 0 0 auto; border-bottom: 1px solid var(--border-default); background: var(--bg-surface); box-shadow: 0 1px 0 var(--hairline-sheen); color: var(--text-muted); font-size: var(--font-size-2xs); font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; } .table-header span, .data-row span { min-width: 0; padding: 0 var(--space-3); overflow: hidden; text-align: left; text-overflow: ellipsis; white-space: nowrap; } .table-header button { width: 100%; height: var(--table-header-height); padding: 0; border: 0; background: transparent; color: inherit; cursor: pointer; font: inherit; text-align: left; text-transform: inherit; } .table-body { position: relative; min-height: 220px; flex: 0 0 auto; outline: none; } .virtual-spacer { position: relative; } .virtual-window { position: absolute; inset: 0 0 auto; } .data-row { height: var(--row-height); border-bottom: 1px solid var(--border-muted); color: var(--text-secondary); font-size: var(--font-size-xs); cursor: default; } - .data-row--active { background: color-mix(in srgb, var(--accent-blue) 12%, var(--bg-surface)); box-shadow: inset 3px 0 var(--accent-blue); } + .data-row--active { background: color-mix(in srgb, var(--accent-blue) 12%, var(--bg-surface)); box-shadow: inset 2px 0 0 var(--accent-blue); } .notice { display: flex; min-height: 180px; align-items: center; justify-content: center; gap: var(--space-3); flex-direction: column; color: var(--text-secondary); } .page-error { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); padding: var(--space-2) var(--space-3); color: var(--text-danger); } .progress { position: sticky; bottom: 0; padding: var(--space-2); background: var(--bg-inset); text-align: center; } diff --git a/web/src/lib/components/shell/AppShell.svelte b/web/src/lib/components/shell/AppShell.svelte index 2feac5ed5..2264a2835 100644 --- a/web/src/lib/components/shell/AppShell.svelte +++ b/web/src/lib/components/shell/AppShell.svelte @@ -1045,6 +1045,11 @@ font-size: var(--font-size-sm); } + /* Machined app-bar boundary: darker hairline plus a faint sheen line. */ + .app-shell :global(.kit-top-bar) { + box-shadow: 0 1px 0 var(--hairline-sheen); + } + /* Integrated app-bar tabs: quiet text buttons with a soft active pill * instead of kit-ui's detached inset track. */ .app-shell :global(.kit-top-bar__tabs) { diff --git a/web/src/lib/components/shell/EverythingWorkspace.svelte b/web/src/lib/components/shell/EverythingWorkspace.svelte index 42c138046..9cc85e30e 100644 --- a/web/src/lib/components/shell/EverythingWorkspace.svelte +++ b/web/src/lib/components/shell/EverythingWorkspace.svelte @@ -478,7 +478,7 @@ <div> <h1>Everything</h1> </div> - <p class="result-count" aria-live="polite"> + <p class="result-count" aria-live="polite" data-mono> {#if loader.result?.totalCount !== undefined} {loader.result.totalCount.toLocaleString()} items {:else if loader.result?.candidatePoolSaturated} diff --git a/web/tests/e2e/accessibility.spec.ts b/web/tests/e2e/accessibility.spec.ts index 615b0392e..cf56879c5 100644 --- a/web/tests/e2e/accessibility.spec.ts +++ b/web/tests/e2e/accessibility.spec.ts @@ -119,7 +119,7 @@ for (const theme of ['light', 'dark'] as const) { exploreMode = 'empty'; await page.reload(); - await expect(page.getByText('No items match this view.')).toBeVisible(); + await expect(page.getByText('No items match this view')).toBeVisible(); await assertNoViolations(page, `empty ${theme}/${density}`); exploreMode = 'error'; From 88e0a99d152e0e1722017e2bf9c8cdc7193f8a53 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 18:48:53 -0500 Subject: [PATCH 028/102] refine(web): iteration polish from screenshot critique - activity gauge: invisible track with min-width accent tick, so low-score rows stay quiet instead of reading as broken meters - hub header: identity avatar (hue-matched to the list) beside a larger 650-weight title - inspected rows in Everything/group tables drop the amber outline for the app-wide 2px accent bar + selected surface Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../components/explore/EverythingTable.svelte | 6 ++++-- .../lib/components/explore/GroupTable.svelte | 4 ++-- .../relationships/RelationshipHeader.svelte | 18 +++++++++++++++--- .../relationships/RelationshipList.svelte | 11 +++++++---- 4 files changed, 28 insertions(+), 11 deletions(-) diff --git a/web/src/lib/components/explore/EverythingTable.svelte b/web/src/lib/components/explore/EverythingTable.svelte index 5eeb3aa8f..bb019766d 100644 --- a/web/src/lib/components/explore/EverythingTable.svelte +++ b/web/src/lib/components/explore/EverythingTable.svelte @@ -734,9 +734,11 @@ text-align: right; } + /* The row open in the reading pane shares the app-wide selection + * language: 2px accent inset bar over the selected surface. */ .data-row--inspected { - outline: 1px solid var(--artifact-ink); - outline-offset: -1px; + background: var(--selected-bg); + box-shadow: inset 2px 0 0 var(--accent-blue); } .cell--title strong { diff --git a/web/src/lib/components/explore/GroupTable.svelte b/web/src/lib/components/explore/GroupTable.svelte index a78fd1486..86472e116 100644 --- a/web/src/lib/components/explore/GroupTable.svelte +++ b/web/src/lib/components/explore/GroupTable.svelte @@ -470,8 +470,8 @@ } .group-row--inspected { - outline: 1px solid var(--artifact-ink); - outline-offset: -1px; + background: var(--selected-bg); + box-shadow: inset 2px 0 0 var(--accent-blue); } .group-state { diff --git a/web/src/lib/components/relationships/RelationshipHeader.svelte b/web/src/lib/components/relationships/RelationshipHeader.svelte index 5f5d37fbb..34485a370 100644 --- a/web/src/lib/components/relationships/RelationshipHeader.svelte +++ b/web/src/lib/components/relationships/RelationshipHeader.svelte @@ -4,6 +4,7 @@ import type { APIClient } from '../../api/client'; import type { DomainSummary, PersonSummary } from '../../explore/models'; import type { LinkOutcome } from '../../relationships/controller.svelte'; + import IdentityAvatar from '../common/IdentityAvatar.svelte'; import LinkIdentityDialog from './LinkIdentityDialog.svelte'; const STALE_CACHE_MESSAGE = @@ -185,6 +186,12 @@ </p> {:else} <div class="title-row"> + <IdentityAvatar + label={displayLabel(detail)} + seed={isPersonDetail(detail) ? `cluster:${detail.id}` : `domain:${detail.domain}`} + shape={isPersonDetail(detail) ? 'person' : 'domain'} + size={36} + /> <h2>{displayLabel(detail)}</h2> <div class="actions"> <Button @@ -332,14 +339,19 @@ .title-row { display: flex; align-items: center; - justify-content: space-between; - gap: var(--space-3); + gap: var(--space-4); + } + + .title-row h2 { + flex: 1; } h2 { overflow: hidden; margin: 0; - font-size: var(--font-size-lg); + font-size: var(--font-size-xl); + font-weight: 650; + line-height: 1.2; text-overflow: ellipsis; white-space: nowrap; } diff --git a/web/src/lib/components/relationships/RelationshipList.svelte b/web/src/lib/components/relationships/RelationshipList.svelte index 8267a7c7d..a5ab3a001 100644 --- a/web/src/lib/components/relationships/RelationshipList.svelte +++ b/web/src/lib/components/relationships/RelationshipList.svelte @@ -377,18 +377,21 @@ font-size: var(--font-size-2xs); } + /* Fixed-width gauge on the row's right edge. The track itself stays + * invisible — only the accent fill shows, so low-score rows read as + * quiet instead of as a broken progress bar. */ .activity-track { display: block; - min-width: 0; + width: 56px; height: 3px; - flex: 1; + flex: none; align-self: center; - border-radius: 2px; - background: var(--bg-inset); + margin-left: auto; } .activity-bar { display: block; + min-width: 3px; height: 100%; border-radius: 2px; background: color-mix(in srgb, var(--accent-blue) 45%, transparent); From 3a48ce8e1b02744a95d1ed134f983823e2e85b48 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 18:53:28 -0500 Subject: [PATCH 029/102] test(web): regenerate theme-keyboard snapshots for design pass darwin locally; linux via the pinned CI Playwright container. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- .../analytical-dark-comfortable-darwin.png | Bin 59818 -> 60173 bytes .../analytical-dark-comfortable-linux.png | Bin 56373 -> 56135 bytes .../analytical-dark-compact-darwin.png | Bin 59613 -> 59966 bytes .../analytical-dark-compact-linux.png | Bin 56145 -> 55939 bytes .../analytical-light-comfortable-darwin.png | Bin 58684 -> 59037 bytes .../analytical-light-comfortable-linux.png | Bin 56167 -> 55952 bytes .../analytical-light-compact-darwin.png | Bin 58519 -> 58874 bytes .../analytical-light-compact-linux.png | Bin 55969 -> 55742 bytes 8 files changed, 0 insertions(+), 0 deletions(-) diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-darwin.png index 767808cf164c9449bb8c0fc160b0e9a5e2fad7a5..f876e0e0c941c8d929a6842ca2b89d09b4e6ec0f 100644 GIT binary patch literal 60173 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 zઢ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 k*_L@qR8vmLZ9l!|`Ax=<gZ!D(~0;N6gV? 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 literal 59818 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-comfortable-linux.png index 6126614aab0a98a1527b86b797de9dc6b10d8316..04e5d73884dde6e427be0bd987b44d9530de56b0 100644 GIT binary patch literal 56135 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 literal 56373 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%0AYkY=22*?f7{bcbWM-;H?@%=vIxQN+F}Yq9b{ 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 GxbZ)I0O#ZY diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-darwin.png index ed6de4a374a96dee23d43e103fb9bcf696caa511..d9d9106a24e5691e634b88eab80d907dafcc7f88 100644 GIT binary patch literal 59966 zcmbrmWl$Vl*fy9zLIMQ01b3I<?(P=cT?c|Y1PJc#5Zv7vB)GeKaA$Cr?L2wE{q<ID z?QU19AVW9ZbLO1;Ty_(pASaFhhYk1U%^L(s2@$0?Z{7pnLQQ;x0=@_kNWFdY2KkMo zh~PKZ^y5`%S6m$&m_H$)Z+4;RlAyc>(DHx(Na{~uB0w+Wr*)4IELIS^#S)K;1ITQA zY-|j1++&TowuI9`16JhU@ycc58~oov39tV~=HH7NP|*6s{~bf9pWmRN{5=#Cf4@oo zcV^8ynBV_Sj3+CC6S;CxN>oyc<@u-~?CB{aB-AQF`1jfe$xlB*UZgB7EkAk&(28Ln zmcD`dlbrndzuWDbRl&1Nu2WcvbzGcXS0lgsdqqxG=bPkx29g_3%o-m05C8oVjWRye zXOqv@gWnaAfv1?1-(dYM;`7BMzs!EPlwdRG@2zsOy8d35+f%paqK*Fd$*z>uy-5FF zPn<)$p$7HmIFPGEW7kDI<i9ttzy2$y(DqE(wu+5eeX-OALp)9ScD;|{qX?W@rT4Q? z0Pf`;L-cs3ppNY7{5%B_QLn75sHn+mpzOoLgM22B+;C67eDX}G{Q7jE+{;VlK*81I zH4aNUmseov5ta)(I}u({Nx{KTz|F~W617H6a(Yk{KJ0<@Oi|2qf&6NXiB--8O%eku zXvuNDV%0CFx3@RnYn#brIDu9LE-mqRe08<WbX0Tm;o|Axy09VtO1VlQyDuX3TcyTy z&&=_3p}bvnwO<aMdR?Bxq#=8SUR;8@rlxACE(s|q3n_=Xsyf&i&kAR`XrPGoh%GrK zWfknAHqb+@Rxwp%7sq5WRAOcBdH<www>QA{r}ZB9=ey4F@!KHV6h4QBT8EwCBzicD zXlApDk=C60y1K(T+aD_0gg%`E0}=!T1RH9gg}P?T>q8Edk0K)R*H0^~zk|K8oQo%I zPX<w&9m)=MD#s7es(n<Dwc9<LGkHC9+uWVH10$eJRPnf-6l1~7ir>D?1RDtR@bbpt zX=rGCL6IKQZdOXwjKt+M8%=Sz8i?`#aI<A0A@N;C#<7f;I5eJ*i<X6joScoASm-V{ z5Q(7RYTZNRWTl>lheunXPPtId`{k+G1;Y2ap}v-tkxi0I&RkvnyF~C=q0|1v<j9J4 z`}TBURL%8uH;Br}@pPr$=J{dsdk88S4!aGL=f1v=kB?w>jmdE0eo%b9ljBkyNUcu2 zI%KOqgshpC7VHf1O?cd&6h3Ck&dwGhXLEUh!okt9Yw`>Ty*`{J;I;pGW-D3uw!goh zV4=fyeT}!c_5Fw6cTF>hE+pa+4YlLbU^Dhl;-cavOBf(S%NG3&Vh{-L+#*7=J`$g^ z)oXb!zE9q9TO4c|mRMdOm-4jn?7`C;^Oc81=2uy{V+xbEw>M<19fPT~<@!($?5xtR zc#&nMudiOKT0Vnh#cyn7207iu#>1mi2%MgnaCCHRae+jh;T${c!6Lw(Up)AX>S<|} zO2llk+v;MWZ&N_&OI|J4_x-vyTX_;X3qtMr-KLfm{ODn*Pa$47l`madT1xU}EOFHH zVw<W$uYDw)Rr;3<%*N@+f%5e=f`SQ^cHTgAcDLBGrH-nyG`th}C8WNOEDQ_w;CL!O zENLpyR?-$9AHO>YDT!A$Y9A|_Av7#Z-V0ZRL}>TN!}Fb;k+Hl^M^lcVh_+q`R2M%K z2($iZPLzeuvga!UL*?nJM^4Uqjmpt%sVdyT@pY*h%Em^s!(L1Ri}?a_RH1y<#pU(M zLVJ01$cAIsK34TZ6Ju_Ce1h3%rjePM-oWJf;&fqGhv>==r#h#h1O|w9GhAf!>FFuE z?UQ>3q1Fv<VBk8JpUE!9YL2Kh?x1`YSGC2K*t^Esy|L2uUmIh1%ad@?$9Ex-Po@RC zt|jH=bbNRdm6ZY-Foq>gl2iD2OO2rEf|Db5TPDIcHfyaW!*k`m8_^TdM{1QeOHD*{ zw$|&}x!|<rt^R{Gk0TeEEKY9QwbsuP5)uMhbT>m*YLy>5hKj#$kjtjw@i~4h-1YT` z$>Q{SxIR*|@bQ?pSuL3_%FoI1i#teTucVOu{Hna#;~f_nd3uIIQYe=aFV)dIBB`ck zyBU9wEB+}W>UFi(#MpYKRV8ix(+{ip;@6F5urfu|pj25I=!o6Y^YMmGw0xot=J&;9 zrMmsy4Gx*0#q~NS^sw=8W}8&Pkgu=crytDO3k#p+i@!`4p02d9i;vk#HtV)M5;o>$ zXQLn^H`(`2QAE8?tIQJlc>f#<3>4$Ia&>Lm`fX-q#_k?YnxT?!S+A5o?0B+*a)2ma z*oC)Lj{*;DNrkrhKS(d1JzRPhTt}MD5fSgR_<dB%)X$TXlX2!NL#o9$x<p|R@yiMe zqu?lo0#cX@w#=jKjf}Ps(%5owJG-CU9J2VlluA`IA~U$hGdZEVt>(&+5D^s>6~_{W zp|b4Hy8=ej*hslqagW}MhJ}ZFzB~nZ4o;Ct4<*v5fmP}FEyT`OnroANxttE!ELL16 z{s@YRi4hTHFE`q1wYekcwte65R8T-h3^S+0=Ct3wI$%Cs1<w~okxpwB%4HNQ7t-rh zx5`7-f^q2R=uX$#mW!3e&y`I_l6Aq(AkuG)-ck6xiuO0hi$8w+P%YOiSFg@7pCrCL zT`N^DCdM2%m^LGq&kRSAk(Fh)QMa`)!N9?B*&c`yi@*jgw}TF1^M$yOl?9l90(Cem zeScxh&NgF&&1&J-RaCUkY$gX<ZxK}}I$m_pZ}xh)Q#`(?vRO?nN-i2F;D;Rqt?dk} z?N1b6Cd%TBXY%@^h(+QbZEw#Msv(u#N-7r+L-pj58p)*-k|Xzp<Eafy!m|1o%4d>3 z9=M#Y*i~pZYd2r8+phhdjwO>eylisZXwA%AJFGuqNaqLJb;FdM=Ar4eISWfjoB=ia ztRf<<(NJ%HzL7mpty}{O6_v^UZsJPU<~<xn&(`d4QZM<d2D2%P`3xnO6jND20iVSX zx|SBVii!$9|9SxYZf<+n)9<S<%(swbkNrPy!<j*SGCki`!Omjs`8tn?Q3U+-N_oMR zm5|>rULLmxS`FH6z>s?uXK*{~@4mNiIgiQhPfj!ICV~Rrr)w|=gfA5bKybeD-9B7f zp;5?MtY`awezWx>FlByz9tjDF+wS<T-77?yQj*;wZ<LOfRzZ2C+6oD^ghC!SNo#VD zmK_Z(E`L~!&*Onqe$A7Miwj?mSG7{z_mGN>`g|fZCPqNF)_n(rg(aA64I(>u_IkVt zS$_3%a|lbK4IyNwrDZud6u{Gb8HjqXQfo@bgU4;t1$MEeh$IN2W9Hy6p9$We!aGBV z2(QZ68_0xCC?CdRP_59s?x{B7cCi_+s+WO7M@L_1Hu4}N+p*iC=;`YnOJSuUBI2WF zQW~IK=&e$_IhyY8naNWfU2-~{gNKJ-28*B1zsnbWen6-ae+L6IB=rdaVK|}k$Ew~P znUoaN&98fhy`{Yd+qE2%*SFAKo~?Rqp7+vdXvjRnn|rbOL#^J|Tqke8%IVf0%`7*X zjr&4BR_R4$aTJ?D_)1l2v6$c@L*K(=bQzr8GSo<m5LwAC3lu8!cCA(DX5n|pU&SgF zbkt9mCNcCyV$)N~rP}x<usmSkwLCv;KR1@T&#K%*{DzYlAaN8?G3*@n+h@RkCY%Fg z{5Znnrh`$|+kgM?g~sP;ijtxbTWfVg8yoVgGqiiSI>2Jc<oCSO?+gFpdhn~x&;Do* z;S&}uW%16!@?PIr1Ws(}{8By0<MtF!=6<oZkU|cZnTbh^Xw45r!K3@DQhQCvz_&7W zgZ`TnKVxX7-=$*-8SjVU6=-_1-&Lap0;Nc=+&zLpuN{}CN>5rD`Wok5)&)t_7iPrC zr49cNkQL|Zsw!L#^We17qgk4yb5tea4>yqBADQj#EiGIy(Rf0$s-h~_`;*;z9qzw$ z7r?gsgCASmi;kC;mr06(BO{A7%iZNH?sz@7DV>}mSWDGbn;mdIaC~FwMG_@F+w2Vk zyPOhkd^JusUKUGzE9h5HP++XCHNUikUC$yzN6uDY)8>8$S1co2{pr)ya-9v`%WMf* z@oNU3gQkuS0;}mKZjNnX_wB(rirz4+EEYG^)MwRl-4n=O7;+<kvSLNE3xjrk1aJQ7 z^q0@#6UNK=Rvc^CcsN(CX}~2Og{uR0repfr+1ZK1R_5aJO6_eEaIvE(kMQvmrAo;+ z9E;k<%uM<mt3lc?*b?K&?{80+gQ(aorqqucS{(M|tZ>o*w1m%ZhZ~UG6qB7D%@>V; z{5qjd^W@?+ND8jb4vbLs<oDrER{&-lHk)-kFuw(}tEh-YM)ZEYA&x5;O-(Z!Zg9HX zrI`Y89PD~sA7UKi&05W&uWL1Z-#{)tU^d$sO@s7ZADPKz2@Maoy1}1QH{9g(hCV(% zX7KwUmS1n`Mh7ow)CD>!4`%XmTizGB=|3B?cM^9g3r;O$jr4eVwUw8ZRe`o0zE8}~ zvNE%aKm9oQa2^Xzvx1>*<ZdM0Pz596DQpQA`TsKCdT=s-5#JbDbokoMfsn!Jg*y5{ zxo|WZ_uUL>6;-IMIP8tPpr)6gTeV^D>1vz9;Y`R_26*%1AIM(NALQ#@8(w#Phu)kt zPLGO^5Gq8xsnx=sKiRQ({6@}}ZsS?J@MlP06LlRGehCPc86NjO-kidI{A4{@qS7n% z%2`PUmL%Zx<X~ZWM?}^@lP<ZV=2$Zf&<rLhwmfkpf<FvuW7-*DsRucB%awNl9G`5| z8|6eb+$R2HONG8f*whtl*zJ_mOTZ=2E5lIOlo^)o8>@-uknneDR?q&K8^xiJPe;z9 z(bRK?j*myYt2G_(ZS;8BCpz3zDN%`|#q{FckS{Y)3jY4?mqE7Oxm`75_2=m8Afe=+ zb6LEPh`+oPXe|~?t{d>cicB7-JSGPWC!qCakFgau%EcOx__8zJ1ZC9528mcci}!72 zTMt@WYg5xZ&(oywtjRLB^UZ(<1kz`#k&%%}%vzUML77^wemTE7JcKMiAsK}mUF#f5 zLA1iN<?pV?3(dpv7R;aW&5Q+?=KKpYz>cz9;}${A#Gc%B#o11S%gd!8@Qq&`T(xzt zlwC6?w;E=iGz^(*XxEoo(=f=%Ty9~L1QwlcNp^9F@E0)w&ZQ+5mKgxEM~d=#cah>Q z_nuE428sHLMxZW3lG=i?*=(N9yFKcjH|hd743FmM4yPh@IGjTq{8(p8B1m6}==U&a z)QmP5bQ?S;qO)BwY83%GCFmaYX(Y4B;1`ihy6a$U{uyR?hts1RuGuz47~`^Jsfu(` z<?k(ExV+5_$n&G?RSWETsS0=NvDfW>r=sNE$f&BS+;3==m6i9)CUd!TqVgE3(^^kg zY>jYUnvLo#v~M*IV{p41L>AWW(RB$(#Yu6E)ulMN-{Sp1WhvCu^m-&T&Yt9<i~MnM zP`hjJ-BW(@|7Za}6U&!3EXK4k;|ipfQpKav)W5Yny@gP3e^XDVks;WcFHu&LNMm{b zI}ALV?z2Fqcts=h2^Sf2G1p!3UdYM1_VAF6jjf=7+}HPQu=U<(hW`L&A#u8Bi_=jc zwo)dK+r#xqSmEO>L_D!rCWQ%&JiH$$!OK=gMiHN2Pzgs<S$iFO+%K0KAzxP-%JW=Q z9}!Q!hNH?jboIb;#I4#lV_>8D{dQffA^ZH+w)11rJlvG-7Er419`3yF&IF@%g|Ckf zm<bqceVNd<JEN5~JM1oFO=1hcTm6wAV;i1I)b)<$D+Fx}*4x{P9+DXJ6kl{!!A=1F zKLIKsB7Sr_CoL`A^Yim~dJJhlU#pMH=AF;d;CF$}3U2S?WW41DBBUQza>YvdCY6!6 ze7N<c2uGwL6RGu<@avELeSJM;>J4R1!@F8+K1cEa<=D-^ZVqNfVZDJFS71WLuT?%B z2uE{>cq|eRbS3#rti3|{->;G<H`jYY&=l<fg7odeaz0Un1CL7zwV_nCT+lOIl+k(Q zP8y3L;tLIDFPrV8OgfY$?Df%n7L!RyP*8t%cTn|+l~gyKNL3cUmr!?4UqssXU3fS+ zv8m4}HFp&@s~#hnyk<RTF`wyGTh*t&SS{x4l%)(zNk*EKYc>eDhLc`SQ^*6K2*1fi zd_dS#bsATDR|^78Z^x|Fd_Ie24Nh8hNM5I?q=Jym_l4+Abvwvf8yY(3stCo#rebc6 zt$evor`>RNNX4orFf%WWeQs=TJgcm%EXJq%{xi;OFZ`Y&oxmJoaWh(flunB)O2V5F zwz28YZ|M;IVXpd<uo*qoYX~CFxCwvr>+yXMoO*3$#E-(`CFU2BLhG>G;$kBbr=aLT z;&3PVRiassjXL8x+zXGz1i;s)E4#%hvIVG&bhzBedW&JUZ?Rvc)tStFg%Zq9k2MA? zPZ!!%YN&@3<h?Ax&L;w0mop(~Cc{a>9z7F8{tdRvT&6Qc0G>G5Ka8hTM5U;g#-d+o za`u9p>To#zf|VR7%HTrL<0mLX|Hfi9Tj37@TMwd&WDh6O_DQo^&cqWiFUmdylIA1- zybhqw^&AhLNm>RVSt7j#kJaucYr459%^24m{<Z7l``WP#t~%Rg%?;i+C<`RcqMH>$ zy;9wQQ6dqwvN)3El#8=5`vXzmPSWOM=c1*P=v!7=QXDr!5bu_jmdwF!m%9k<B&b~% zYS^za^)d$^WV8K0AOgw^g68*$Kash5wYzkxm70@{@(gt=L@87fYVO~^OC-tR5qFUs z&Q|g}o(WiE<Vz)qxz%iOb8&?%rpBl9I$+CIe6$)?p?sa~&Xq2VTzHGaVjircE~|sE zGhd;7uvTBoQB#mePAvo|2A8{(zre)UHpstr2V#tdW)1?CKL4Js)azJNn<$5zu7k5^ zlE`4l<uXHe15a052&ESb^?X=aoj`PSJx{f!a#>$(Xf2P;2ZegHN>xwpFLwcEN5#c8 zG&m`_#6h(<408hs>;Ai4pHpl4qI@>G|MPt>n*Vi28q2A!ep1NH07UjWY2N5{arOF~ zCYQxSbaR}><fI8`gyW~HY&PoibrudXg4$yoo!5~0{*@72P>p&AK%}~s&`IeKQ_YWX z`Ax5ys&lJDC*6aE_J&j{wA%ro{1`>R_lxmcIxW7(^>Mq&bYCC>&epWzqOfF;Gl)K& zpMo+{Xijr_qr4Xe`)lKvZlioIL3_R#<gGkDKQ6yBDL!L(EcL7<I_-9o`Acgr{4Uov zgF=bS;xVWF{{G=;3M;6ybrJDB+)$UA+hC;s2@w;u<x)H>puZf<n*FXdi=v3c2}m53 z%jggt2owu+9limjNypQg=$q%2l=%HY-Lci{pw^O80t?7glomc-+}94Nlo(`;W$+q} zmh2>yerv3iSBCdUZ4p3P`tGbakL}N%Zg(w<3(ZcqrC*d=efrd$2N5zU6$+4Ai0suW zF+{sPKew~96P&7-i0OKmV%u$Ma($O`G%w8+u*s56qeh?Sh5Yqb`A4@;#y?P?!<4le zZSZO&6R0JjGKa>-3}ML^>LjQCL?hGoQ>&zlN8(!8+EV0o4y#p?%cM8CjN{|ZEHzkB zD3JRjbT1DxYu#9X7CYTpS*-OVb)%GoC1xha8Re6=*|>ULpKUW655=Gvw23V%iPe0> zL{%+9ba!nElW+6#R3JvoJtXh5hpb7L9B@4UL9t`PMtns09L)dj4Re8<=oaFTmuGu@ zrv97$+&qGxAem#5Ad5|N8ylO%;~C>=MZ#lkI?X!tAQQEr)mFP)`-CAtY#o?3L;`q$ zQRYgl|72=?sfq1K!n6=BvI8WCE{*+mf2~b4Y<EGpS`V@YX3rnT=uRt~8fKWCMK!~& zcI`!67|-Iz%DdIAsjrP2I}x}0*&mhl@c?GnVD$FLLb-WSz6NKx+DP1H-C2<N?5AWL z8K9D9j-~U_<x3;`d{HV8{(#iQh=4&$>N`2Y?ta`9(;Y+xb|c_+cKrQAFd?1nIk8$F zn6QRs3>s;i4%_ir_kQo;fy(r7e;?`)rfeZ~>kD?iw(J&*kWQfY-vR-;r~l*aj-IT= zW)g!gAeCt?*6bkr5JN(#<qoFut9Q1PGo?yNzErLBL^=fbm8zD`RT;zyMMi>_8pMg! zqJQ>h_1I<d$64FEUG6;Iof}cdJzRMyi_oLP%ZBe%O{miAkxNuEqUE{SGpf+XF>*T* zo0^)c4{+#&1~JfXymnlzKemziJjwXB)j`ELHuJiky@AeLx0Tt15a{a@rW}uDaKoX~ z2Ge2sa13Q|xf$+7g8XtoYjtmZ1}5f8)d(~e$wyOHo7`53I`Z1c4-O^3c^^FC;U%wE zSD%+bPrViIa`<Z!sh)W}uRdCxbv4(nwm3D`*N^V~`9WC<sHJzp;hHi_4P+9_dJ}){ zPFJ;<9hlD_%J{q<H&`?)Jn!?ikgz8hu8|_bqp#ZgI-drzg^Q?fk7u>k1qh}m6K*6s z@!K5sob6;-sI!|tl!pQc0TZ3*>(}p7KhNXH<%OM0QtJ@7v>J{x^>`^{aT(UzZd^n} z#Y##fpy=u%?_B{!VOcMlEt89r)8O~;u=FwB$=;DxojGG&|AvTv+rio@m`0UWJP@~e zF>ku3y3Hl4-TX;OQB~~uYz!osl$FO$tDveH5+YXsVYH`~#$@QR@jloLP%U4Ir%^iv z(1`k2p}aJ`1uOzWv(py&9AuTd5f2Zqw{fM-<HJdb-<4rpmnfW3M43i?j<>(q^8=Dd zgy=C-;9w5b*RO<sju)9X10>Afp-go`Dp^?(SQB0gJZqPCWuEEoKK7*dN8+QB%5DS= zsg?bVg?bOS+P1a1wH45HZq&Y-y4Q2wy+gxwbg*wom&-Ua%6ood%G2R3%Yvw>rs%!9 zeO@)8@U#E!7fBE)-D42aad*~TMSm22;JXj}9v7c2a2y;ii;8ULX@7k&yeqORDX}ye zR`tAmz_9<iGo&^%F;S+gGqoG0Qy|bOM6mYS6vHd~_6#fQ7ZJ`l-Ki>#;-~QaMpZ>{ zyOkW72f}w(_0MB83yq4ComiMY)r3m+lL2pUE~m}L9DUAn1bm@L@}Z!hAFmJ045=+d z5n%--XztOImK#mpp4D5;S37MQM-hI}dJ|~7(qv(-&*3rF7zPoQVETA<+r7qW*To@B zlFMo}pUbP;IxSgs#U-XuTO{GUm7|TB2=Lh6Nwi{|pY;Ps1M4Z0lALyCAEGKV0mthP z2~<8~W_7iqaf#YHqPtGLQyw>L7V~cn+RcK(fifv9iW1Oy?A4t{dJ0Jjm)~#Yoh=7M z;SUHc(rYcRso(t;4n!0r+0?Xvo0*+OuxOjgAI@ukCnVer5buaMw7K`>V3+Y%X#Jga zu>1Dnw3#~J&QKCYR)fW?k_3D5eceh9?P3H%^hy6ly?XT~Xt`dzYQ9J^PWS`j$58(n z!Rg)6+vBwWYN#5AyX7_){#ZpS(3%AL)#+-Z<~8=fXP>9Lx2#C#dp@V@9Td9B)YG|Y z42-HMq^o>R$`6d?Btb|-fDoNSwwb@&h^E(<GS%~5QY(btP>!_J1+roQvQh>+uLcKy zSDR<pEInvj0a%by-uIZrTGKzCAzWlk-T?EhYG^>UB}--AYh-R)n7KIED&#nbh>;x~ z>9H?=b^ejm86ws&5sl$kp|mr&MM03iabAKkj0TeU(x5#2Yqon}K)qEPSI~kU9zIW3 zx#iqKDy@#NO@L7`WI-U>u^E=?BRo7Flk2a@NSgpSjO9A3<tE8EW;4=TIW+VS&GidS zO|vv=`j2`Pva`gSqQ5ey4w1>YE=nn1o943!ExJ6yNR&3Z)>|58OH{;fl=>=7q||CO z>+>Y#Bqdwfdiu4LI{M$9J>RxdGp@Uois;}dVe>m-WYA=1e8gBiN236IEdudxYHDhA zq~v&LkL0E@!os;CgRvh`-irz_xeaZ@#uds5^N>jRp~>?g#9JM|$clzyp5HyF+-Pie zHQQ~q6ebx9G?3;Rb%>UDd0d-M6iE(F<$sUVYOt34uCGuN=k;`_SiV$WErGw%ku)Vv z(lVCS@#C0^>1Sf1Dn|q{j3B)xZMb}}vZADC_b|u#n7lm=Any4GSH`Q577q6gtM>JY z@q3=V*$7WQ`E5MCB@P#Non}QxPEAftEF~;0ta`C~ibOx9DybwX0NZZ=<U`U<+#A|x zxq5ou{XusMBcm+752Z3*RZ;4tigvA6IJzv6<329FvXbFK9(XHs`}<99TaDhHfRF0^ z63aXW_$s<QKID0V02YC_XROLE&Qy<ezj_p2$WLPf?H(WVSlw6yA}Xo3*;uOEUSwYV z+QbB9hku@IqslPmg>AFr0V;kHUEB)RURMCT-*)%{(j){hX8@s%-ElzSK4gorkwwG6 z!X#nH`mZ7CDB<(&<^<q!Ir-%dN%VeotRE37go6+mbX(ua;dtEO741L0hONxxJ(i@l zxQ}Ubibvv;z=wO~k6yY;b6Nz+d^v}!PN_BBO6InoG0$_?ZFS9@O8<r0>TtUTXt>1I zt}imN3!mVnkL1nu<xIy@v<h|G-iKit5jZ!WTyQHtQU!}WTtg@x(L6c`gGF^4CAa!G zVr-Y{B%|w2Bu{G_UB*ujXG@U2@N7@!2Ek*|OE~+*(pb*SMz45W?Z?p8iA6`#X*B?@ z?(k{VLv0nX^zAW5wb*S|0R75S=Ky2CN3{k=D<YK@B@z*bacX`XGK?i@j)j}|N3T#| z2Y>b#qf!5SsU88<H1Lf<d7Jk$dH$#tQ)v{z-e8=#u<-N#Mz>$xQAA}WlF%q7ofc6H z*!~)Ogqb)_jX~E?EtShlY}NgGk6xlWj`PrXz-n<}LI3U#gs~h54E)Ol09v;$fG%8N zW3pWh+r6Kc03)m#WR!&p`5FIqn2wDcX5%*f9-dm&zAtM5<$P{5jgfJ(osSPNWw@PB zwZYCA6{{^qM#4)9pUy(D7#6Dxwg3w#m_173aI*V@x;Y@R7i%{Um2qB1?}&L|)(kD& zfgO)@^DS9D>p|Sk<+`<e-OiTQNCN`Sr&9=*%;p0MBZE`DvBOoB+U?=uw7yd&Lv=?J zZ?$MKo1So#xx8WS9=Do)!kUg}>af{M4b$Z}V{^LGsxp=oLe@JlmMe2Hyn={~jN(dE zN;lLa5RjI?=MN<)?%N-k)|3iM*l%NTKh~Mga%I&y$8dRS^pKiktkyU^raB$bsFsi> znk{G+@b8ReEORigwb~g?_aOiVZb5-*$5%eNTe$#u3U0ool{7Z1wK|K-(j+nrO&Xd~ ziYNj)T@L<yXBz9}T2mSEkL->MAK#l|p~%W+a&*p*&o3{-+o5N|h5q>Aa5k~Q*5Lf% zR(cFi;+49T(sr@+;CQjN*<M3LjP#Nl9q^KHxecaLrYV9Dv4fA*I-y<<xbO+E(uOaz zeChy$V|OqPivrxX?n4ACPKTzfe18rIhgyK$Z#h-UU)4k|pAsnCNC=zasaa<k;7_5; z%E;KOiRPVWTs-CO?yjyE85yA0=1$c%CF1hf0J2i8>^c|xwAI$urqy6WBFTwK*#zph z@ihE-#Z8<`nCHK)ZRKHRo#51J?{T?vSOH%g1=%<a1ZlM<y~8JSA#ijN?!y2vE-h&e zG(~=4uU0>gy>Y%NDut}oKVO%klN`?U+ucTcDkNC4_8?(1y0@N@G}>raYIPU@sgL9X zud97QPtTPItN99@=DgA*!$)so{&QxD3%5V9%?{6USMY7_*SJi^5()|vfVebi>Yr%p z!_UtH)J@5aF1a8?0-<h!97vVt{gC_hA(2J+Mgk4lDr@fzENRW*Oa<MGVbK0%(zpaV z(&w*L--@RIzh3g?$kQ2vl>KFHwaVZb@a<xFP$;LPS-UsXl^eA0Q|k|pmzo^_#i0lq z5K0?l7VW2$$DHyyjAMQm$`fSqxP@;|4JaB-aG+ne9ek{3tj2XRY!#jJ3`?B`ToHxm zlj%x+xb;>yW|xL-sI+&I@U}uZ&9HEAJZ={vfd3M$ikcU6U0<q}&vJPVd%o$|Qz+=` zE5VX|7(U47`CU)OGvCXy0~#zQe~>_(Ba6cx;_|}b{p1>ih^O1`Nyf+PajM;pfWzgy z-=x3c$>a6<(&pM(6`fcAxIL(>Tp%O*snwwS)u+o~c@D(uagEDpcvovS{*$yHdeQ#Q zd0SSZ)wQ<}`UAq@Di~`_Wdp?k9+Nhi)he+IJTNpK-YRk=9e>;xsH{)6H$)~C-$MXb ztpmt8f(n%BQ*-4ytNB*9av9)IG7c6xjaKC1RbzHr!6o)e5!k`&w$1W1gP8#ohutyZ zaB7Jo`80ML0Y970C%}fM6Oi!dPw!K@_Z68T;&ML3Ma3p|e&he{z)n|Nc~DL}nnfzp zp+fr$g=t+92}Q`RIvrg({NA{<E|(8qThj6%TK~!M@%K~Quiv9NO0gKyJ7~)dBeBU+ zHXg3YD1rwD0NAw`mNEz!pYvrJrC$A!>d6C<q3oRxH(njKpN5m@dQBDHfKVeNlXnl4 zDSE~{Bz_>_@#REaHMjwuuEXBgL$PoVLwC3ViS?Y!6ifJGpP=nl;PocP2aaDG3rZaZ z-3C)ukQfD2ZtG!Euq6D{Nk3p|*KrjWl^iGO&YyuqDH>OLs9mRyJQ{7*%+1W!TV%DK z48~U*&E$h~d}LfZpWFt#<0&=ZeEb}jwW_U)?;MUK)q<k82ci;LtpPd#S#4a8_Jz({ z-&~H&y803IAstb(&O89b#>8ZKlt||fgCw{{Vl>BOlJ{EXHOTqU2~42^nY2h%B3#l! z`HYK`1ly>P#wX)`z{A@qj3U_a$%o4FJF|ei`1@U%MUQ3j)*7s83fc$E#~|WyXP-;7 zlXz_^=S5dJ4jO8CJf_s{UG2|_Dyq#(=M=-DZEhO+@IUn88i;o{qWQpB%!cl`<j-fy zWU)E*l`VpAB>Xl$Af1L0#G9XXcwY;s`0O0kbJ+b7ve$oo!R37Qq52|cY5qgFwrgdP zncv$r%I*y1)FyU`TQm1?raX)}@(C0@p3WJ-7GSg5a=Sl?79_>au2$`Zd}PvB)|`yi z`aF~{Y>ps;PN5Ym`Qe>K$G#H|n?Ze!sOxm#HAfk*`yKLLlK1n6yGI`_!{p<#W`{{i z@|0Pe`L_H6Sr_(3z!%!!q1Oc$s&~}R;b(Nb?w*!H?chDHc=GW{dSaQM)ADP%t4Rx{ z`lNDtT_nmLExQ~mRSFv6+BVuG&OD4|a#{ch1-sBZ_~h*mE%>~@4=FWUw~yVzh)IY| zo~ckl`4T2%c;dK<Pe>svh#4(&B|2EPE7e}oIcfo<to?wTGE+SNC-F*mz;D|SvNgxn zpj;pw`J|hfnKgwuLA5htLiY5DhS?v3*V5zN&pgy;E-ucTVsgUY9o8Cyyv)(+aoP>1 z(FEOqat(ZNT7Y0+vueKx_@0NLO|8FsbhQyhdTljDI5umIc5{uOZ>L&?u3&Wd(|45v zFaLLv;^va61O5GlhN6;TWg#Jh8{I)8d8-S$9o`uXdVFQf>8s^s<%k4aq`SGg65N_b z4-!||fdIAt6NE(2U_sJsufLHKFk7mktfnS@bGKcr4E2ogoo6VMHz+B|tmZB1962`Y zQ+`tNkT4n^TbHq|jIF6DJ(%qxXr$Jpn=SE1Pfa{mUqM0OwcV?#90mr4{NcVPD<f5+ zRnOgBpcW;vq}cH%8w*Q|;{nuCb1$9(e&Q44X&;EL9nH+n*v)m>IiGL#j*TVsMdG;# zxp&OXh>D8B9*_fU2q$*?0%yGP;6d02h*`<XgzL4tQoWMU0Muu&Yd~FZ?e)YiFT1$7 zqJVsN-&?!}O<o6vmfiE6(!1mrAi5EI^KE8oDsi<?_GdSg9DyO$<~M!jugvVXH&Ra~ z*H>gNHOM}OQoHlcmhXkaL&;;~O$qb2%%mP|Yli8_%tZv2UJDySvjPwnh_awj8H(5` z+^PQ4bSn!AB3q0!BX0mnK}AIw5JH&x2>9$-4ynxrqo28k3G{~JE2`2yf5t9o^RdV| z&tDapUiuLx3y=$j2=K!CN4Ia^sFhwd2nTlzEDsH}EQuxT^$l`Pf1<G2tl3`;Y<R6R z|1PFA<M!|vuhcWSeRyG`XlcowvSJP(&}ep#2pcZf;dVB+G#_Z(*+8av7A@_GLx4w0 z%8FYrLo{JaO-&6c2>=+nloTu@<16H)JU2J(_%QCa+b&ui5h)7U8)H{WLzI(i)Tt{A z@dr_wZ=|h6laTi#R$66S8}Fv>Vi&K|A!*|EHkI;Pt9Pyatdubgy>9DFqpf9!MwGj{ zwYALD<023#%+VpxFd<U;*lfgHg2r#cTJuI-A^i&ehcdh6_5d4b`OiMT#b#c&c;rVo zR>%ElxePAYDQ7;<9>7ch2t;Bc9<vS*Bppg!ayg!<%HF0g-b8`b_+{Y#fy}a#G$-5_ zb0Z)O^YP*C)BZX;uX_+#jrjpnk{0{z@e<iY#}Sa*bR_fHGZS?8l^sqjUoYHn))h%n zNT61kWFc|>pcK!K@rHy(tJU@V5{sTkG7HRtMXUbHeur99Gt~t!JnVNYvDbTn(CQR) ztOS{K;?%^9>&w;Rb36nrEb`?~;l-f{fBXTIKBa11@*OiBur7JL9%S-zAo+q<YLCiq zzwh?^OT13eGXsIwcsM84`n-<AcviBWe(^$glfS80lo%e>+>jTh^8+_Xeg{0(`@gR} zqXVb=d|3+?4Gi=9n>h7`gVxXKkP#Asg60>*ekT74{a?_Q-~a8`$@QRWf_6Tiw($Q> zOe_%)@xl4&GdVdqq$DL3`+>+RII3SQ3hQ59mp|n16`^*7e{VA>Z-8a~U&xjC|1qZf z^$q#|#9@8ke*Jqf3W^DA324~`_3v$g<Hbp8Zmt39-;X9c<XBl~W&Vrt0zuySb8P+p zKlcH0!2jKYQu_Z(!uWrE^#A=={^=<LmCer1zW?{M4~@p<Z<@@j4FOfRQ!h6#A{Ni! zA^aeTLC5>%IHjVZLf~JD`4w43&<+ugM?KGcGH(YK7S`F>StxKggIm5=3WM?cMGq_k z1H-2UbuECUVL=DLbK`NklOAXd#nVTKv$C@G3q>|++x$<U`l?dF_zf*36>N$m5DnvY zISuy>K05;fxP?1(XcRK3%r0Yqd-dfR^}msRHbTdK<KuESV8BnHw>TX<AhE$<cR4o- zi0p9xp6zN}GU<)$=pk#RZkyCM*Nv|CfKa!vumFTK#l^*Qd*;S&_G5BCH@QH(ljt*? zoSd*|ll5{$U;)ZhRdoia%rY5lJ3#*Ib2fmM(a2?f|9*N8ztm_2xVJ|Tf<F+5m4QJ> z*Wqkw7;kz)d_3R^6&6&So}83dRAka=v7P4@0+ug8aI=L2hfHCNP8XVUU8^@gkM{Sw zEY%hFM-e2+_^dr?)>|3;5(<D56cpU#0fWJy^#-qj38Lg`An%QZm4wghE+Q&wzyb~J ze}B1`M601BFF!s$j>ODqxlIX3!0hbvKr-)t-f_Hz@rrEK%IYf0CN?%Uo8{FB%(s)J zy4eKxhpUj1l0zVkXE|3EK!^r-faXb*8iubg&;Uzu4W;~s>#(v=4FJ^60f55jwR-~b z!+I+Uxhf!%C)U?Td4G71VXLUDj5dt2lQYpXBf`cE2<voe4e5b_FvJE94z>BxNtm?i zZ*~$@6E|6UZvj?{*X?$(oxuf4<uIZY4|To2*zsct<#WHH?6UpYIW#l`=<9&zThmb8 zr)9rWgZ$q-53Y&L>6QA7{RKWnz2)6+z#8OqJo|NQ+Y^jJsoiE-e`mQx9?|PtGFt*a zrDa?W0F1#{GEB_aU*|yTP&SPfC~4J%2-=O11$usyo-BX}ueX4f9}sX<Y4wOO=ZclJ zaLw0Rn>ng%PL|re9@m=Oggsk))?YgPVI0(~sQ!-@kj3jE$A<T_J-88o(Ky)H)Dpsr zs^!ahS5C+)#M0q15WtJ*jg?BE-mu%B$W~QV{VbsAdbtZEDQ!edaY*3*XL0<5!^kij z)o3!l$Hu@&Ac$Nkkp=U(T@Ga~EiB#QaEJ`ZOy$GZxfYsyp#zlzuIFm26EiI>Zt*H{ zK$rxdy2@-{A2u`rpXWzdSSrOF(m+O`;Y2#dv$IZrRI*UDA_Yc3Ka;Y*opXPA0wQhK z#`4h6&@oC4wYB{mAM>OWr~|qHMnI!h0XJncvNN2}|NHsFE!yhk{or(Aa5UrD!*yu@ zJeJ|_Y6T)9li5<WZkY-|Uj?Qa;KBxvpzJ3ysL6Rc{rT++2vdE(Q)<noT^s5C)j}D1 zm(12t&AOMf7d)8w_~^qhX|E>@=7OI=0fU)`IQv>!%Wx@@z$X|?-QC?~B5&K=6bzk2 zuT!Si!3Q<a3V1Ls-;qg{Gr-OUzeh&CMc;V7+@0q><((;|Oqm$V17S>(SL&{@xpFNM z%yNxdav^12US4znx9Qx)04pa9x<3*R4O(ATHX>dO5dmKKiUtvoc_oi&Bed%xA|eP! z^}{9^R@%LA7WFmet^Pf8M^f5RlATdtTACRFleO`oQ6iOAyJT&mUu>1vWnZB@+2q_$ z$!Ua$Pbw<2yR_<lh0KMraB!bSOf%cevR?Yg$=lo80UyEhVt?Zo5sCfwNO5-d!S1f5 zfq~n`O3GG0BA|d=;e1`_o5O_u*pWo1K}ab9jDY_KBuRHPS^*f0w_jnF>TS)8K>ogW zcXua1xFUsK`f^A}R-`tVPFPrYcIK5oj%W&h_ySGj4Y0-2X`<6BiO^~{TPB>5R8N7w zOTri$`Eb|^dA3?}(7(Kb@Fy7={a8fAg*YA_-^DCq{#!yX-Z2(0f&>5>I9+LHbr^$^ z1<_R-FU6nBiH0^nCQN3k1ZyTW8;_7bf37m`Oc#m-i0sv8KI0$l?VaUm^P?jp4!boA zP2RGPkvhn4k}B0<Cm{=p-7$J~jpKBba)Dx2!J*Jk&k^AfrK-h-=H|h%X5(35*+vQq z(LfAFmrk|Sb&8#f6GP6~53ndRGcy-!%@|Zl;*+C9e?I|pS0uN%P+sb0wsd|oq5uHe zY!17$pROS5dQ@@GpPBwE?uszMNz%CF@FBuDTHt};U;`2{8Y(IhB+Wc?#dr@lcWPKE zP0b~~wKYSRB_zl?u~RtdS#Hp6r5>1=QVtk%1Z*~1BCLKfQE|v}qdYF3-Ql-K=aZgC zoz7j^v>x9^+qFbW4XEn#WJV*PP?eXKf`NSw5GDv=wRxJO)lFYto<DM5=bc?W1zgr7 zJv}}3_yWscyVY@UbyY{92-uT2xus77XLH5JQ(3Kyyq1@ifR2v8NBlb>T@#ZXpgwp% z-O16yA|h6(bubba-~IgAbl22$XZ!51y1HU2m+>t-)*&L*>5ScUMnO<;YA3PR>+vdu z&8y|ub}XH<CP`JoM?m7I;|)MrdN<xefA|?BO^<WBSi1*gR{-%Pmfy!4n5V$-4RMK5 zSe-1@e~jHJ=ulz<0`-{Z`B3jKfM$ns(J)Lup$tAR_vIT~_Tq+yhMM|%SOj=9AA*1V zNc-!AFB1_p7xK4G*G~^Bz#Lo`(DMvkfg~|x!DG>3fBw9EbYx|0jEa-?q*D(>(uBH% z5Rcbu-KM!fcz7TXNKg<x^=%@&i1Oya!NJBx3?MvwT5oev!vyEF8u$mMvDpqK&?MbS z3%MQu*-PjTi0DBbK0=#O0<KRR-5)$W2FKq6<h8@&R`n$xC<YCdi&CxhSafiA6z%9Z zNq19vj5-8a$gj^9LN$6H%TX^JnC!aV<^2Kzo)KSMZ>|nn?=MlR;VlMU{&iZJcspRx ziXw;ky1^%TU4PHwb+D|-jlk!g?3t0#(P@)ij-=D7*XnTIIZ|V2YvbW>_2CEgwJt0q zTPTymz}yk6bQHRD_+DZH=)@W`>#O(3nQ@_NRkU(0`Dm_Dj*7}RK%FDqmBJ7j?3vLX zkcp3v4@IZLM#Uv27%DjJe0~(`i`CQ8ZLm&pmEIFUep6ahM9IUxTp*hUbe9?EkEYH7 zwX0tMDAkNe0PeWe4NHWR<M{XJ&(O&2L|MVE90_(n1`h!tyv?f>bt>9S7A%AH<~1Jh z)48_CD~pRm4C>&}slQ27OH()@A|e7Ad(^x|eE?;NECTB6rnGPnA}>HF{s*6>h_z@# zryBsyG^JRFfx!kIxAVxrzFPF!jC<cmN_BPhfF>~Wb;7;3wzm4CaK`tO{=v=Kdoc6w z=f_($MMbxZtHDtm_Sc!!!n(RTsU#|$I*CbOO{US)1F?{?KOE`*%Ffg#1o3~+YVg!P z<P=Kwe^!Lp|Bg8S-{WFI6$KV8EilhLZg_+&@GSpf)f^uk5s{{jto5|{N<Oa#LgZ&f zO_;^d85~QMrpN~0=CdStqzh*+&xehW_b7t8etwiG$<+4E@pNj68XAM6qpV*E37MFb z{sZg}^A?tt*w|mRG&Oof`d6HriIAU(g1vFVx$=$DmG6RnzIVk<(-Bu>tRjZi7wKS} zoCLDM|Eg{cK1CrhbqO_nqf(eJFqoU0o9~GP2_Gz^sHp+0LrvADU2(GFnZO12n%>Cn zqPNbauJA5E2in@oDoUmE->}z|QOM&Lmy~E|Ef=r|d8U&-$w~yv-&w(4QIJWb%9A2c z4%X6YYu#b(P^+sbAOlyYQ)G1mQ~6&DAnge7ZYuq|_r?k0yjbb^RKtl_S?a&3e2Wo4 zeL+1xKd-i{R*XSO_8}u9^L@d!79FBK9BjWr(!NcjeecZ>@cwsSAGz<_xBoF2MNG^; z5HmyT)TvU#tJ31^;(`MDCl%z<Qkqg4GExySQ6`vzj-;-obl`Jvou~P`!wzjKkj+iK zbVg+t6_vpH6n40bg@}Z{e)-o;DAlVsbIBR=--}<+-*5xTp;~7=W#!AeCRYLi$d))# zp`*^v@Q@HmW8-h?>P5fI18_6i-J2fo?_s~}M|qmVRE=i<4TT%sk1kMyR2n5Cmu6Aj z-6Y%a5#iz0BtYK^6*W`W+}z7r8+V|Xn5ya=FgyQIw3nay5c&2je38Hh?49pl$92TW zytwxGdAP(yRaT2nkvHUrgUQqFcW4>_KXB)A5A0&i?W#WxcBupm8V#UPFuqUx?5ovj zm!c7cTq>7Q*S3Y}?&7-u%e5TY7r%g?;n(7PEE$BMrQ6z~xE1SFb{Q=QKRZN#l-gt! zab5>z`BHM)UNi#$pK&-%J$I;8B4lNx;%TSrM5WPpM~{9m{+-A}n&{3XJA7>(025d6 zCKS%Icu7c5vEQvxP#d3+&WK4H{G>AN7m%)CAZPN<3)&w@u4a2v>J=3h>eXu^U|?Vn zL3_D>KfVheqUR3e<6^p;I*hyvi~NW(<=b&&<kMIvL>_(mwyM5f7O)_8M^o%hj}L&R z0is2x-I+nMuswFmsg)U)lx66m;$k+d$-}vbBcNR{lgHpw?C%q9M@&j)DurJP2Q@pr z8)Wc%<?0O*chdR|vNEGT`H9r%rE$l0$1?)F{)`-;ZHYzw2j-Q|_9t`IDs_imE_Q~~ zj?JNA5Q}uc4NpHl32lbNk;7S-n(H?i_1`=pBKi>nZ7+`ZCD&UNa$2p{vRU5#Q`eCi z&Npr?H~S=rSPY6K?xS2nTwht3G@DJ)N;+#|O=#xlttL1YV{q6kRZE?pzZIBTSm+Ek zmz?|9CYl-;EVi0xA;`)S<&u(;uJC_d>?ts>O+i|weNiu16@s|XSIEo3zt%ZIpmf@E zE@y|ay1Kh}-~m_zv?%GcXa}H)BT--zEc@2fTs%LzRP&ux06O#bz&Tx<YP(QAHtUhO zrG@wXlf`N(1|wtj0y6k^A7p8Y4>TujEhPZ$V_&|cg@0z<J&2<S2t()f{N6EKjsse9 zrrcLo7ZtsK1~;a$S42fbbmrMF?-gs5(rf0ewRzQgpHSa_q137n3J6Vrf`*>_fsTS= zxBaJGJPJQ&rLV8T348-IkG*|)(PFo~dzgg|z@X*?Mi{4dd+Vgl?m6|x8y`CuV2@HN zwod@S#&V+#F1Ndc_XEJ`R^6{gO$<=*@trRgCV>!gUfsTNR`B9NqX$JQlG1&ThYKW; zR)ve0_~~j8&BbON@Ti{rJzRiPU|8d~a@}0lkf}>kdHDwnQoRPHorUCNTuukMG^Wm} zt`e1HX8?_Lb_!~-mAgD&kR6~_sDO2gnM|r9O~+FB&E~@0uW|Z%`t)zk1OeTIR$)R? zL&NnJGt6Db)6=tGPDkaN%DIV?({<I8i}iB2Qz)+WZKjv3jBIXhZuNJY^_G+=REfGi zpevz2Jge2R7uAJbt#zr^g}<zz4eUa?`ZaHT7|1b?3=Lt!TduEV=<)ev@>$`p&E{*3 z1JvbsrJ-4F^<jqw$dRkuA8xeUj1GyU70J{evQGN>%l59X+l~(HsQ($1!!?>KQdw@c zX9bWIu$_>lN@TX)w>U!re5t0*GPvwMjdCTFs`g_`?hJ2g6_u1Y9n1+I8^7Fb_^*N+ z{*u0URziKx^NXE1I<1~Upj8jWTdDqVw$_9>i_63kyMKOqo<_H|3FwK0ve~#LYztvI z#!E^Aa)q?IImXkb<qFI`S7*M_d<tYVia9fVPO;RymVu8q$LLeOv9ZKxL4d*eWuozs z;8k&_sOm?NLI=;zveo|QjbfeR0&2_8BV-cZRa`gcgYWh5GlL`GfXxD|o@-Zv9jgUy zYj4;YO&BHOBZw$Z51k=M;nfS+Kl{TBkE4-fzJ2~=nB3t}`{lOVzm)(l<NA751w(~( zxXTHCo}jpdKICxAFc)%~ady5Y2<lE3#SX9gUPkl;dyx5dEQPtS)QZWY3|wk8hRYe3 z=;7u@9C|cutKS9_0aUM#aGzYSl`by38k}z;Qdz)GN7zuc){C_+`!fguvZ)OU%F4@C z&ex+jYNg*wBNY@xHNF`52jtJ*l&59#`)D?LcXx?~;c?datOG80L$Z+ax$*@U$MI_I z<;)<O+a;%dP)ycaCj?pMOY6f!Y@E4rG0LeH9dT%3Xat1O)3%3)`-dFl7$t}|^g9cz zu)v7awYDQa0dxcT4qtyo%CR>>J)ksp-Db!39ETlZ(Xe6p4spoKL)|a!|2r4(0F%a6 z=Jm91aH$F!8Ht7bDKDnF@A>tM&~p<IYq^{c3M#T=hA5U67IMx?gT%Z0MiuT3x*t)= zq{HHbt{xAjgN!@;wue(w?jNKK52_}r8t}L}JYPIFo)&?mF@Q&<onZl+6k#+vI9jb- z9TOEd519Am<^GIEtk%chK1U#`S8z`t&bNR*pj#g8?=mQyO$A&&-kt){Od6erbixDt z6?#4*gU%HV^Gx%Vyk<+dTU42rzV5GK*`4{r66Ag^i;H{SH&=EwIDuClDUS~izmUt# zuk#}ufR43ZlFf1D{wP2gCo1E0)E^((2?ILrO6MhBu)5E^tY%6acBbCbve1602HV%5 z^#}euyPhk@34VeH_#F#gS?AVN13qothg*o1x#hkDmL|2HYBU?ZL3X)jyW?#nNHi2< zJdPskFB;Vd1e$s`y2X1s08t_gi(w>#Tio+tFs>h@*(kTu_PJIe+plQXKt|A=Bo=UL z+dc0$JN+L4swF-MoCLI<h5M4rVw12d6v(6iZR?u{Pwy{*)lL3{AEuq?+G@9j9jm)H zp1D@z)&|6Oq^zw^Oh<I9JZA$BnD_Q#BmF~<>b_i(%Vz{^A6B6lY1P>Tsl~7WO#<(9 zM>8na0Z;3Es*8+9I+2<L$EPdr;4^4uL9)f&{r(cmdQC}5SgXa5P6;uXkX$~EZcJ?c zv`sOX2lOh~jq1dv3Ml6^hJn40-bqwi29r~>hH&!(1LnKO0m<XlTZQJ9mf6NVMdu{_ zGCeMx>B~zyx;35=4Uu_5!PcAwKkh|Nz743Sw)6#Z;$>!N*h7A!-|i(5cSDtj%`w+g z`6c1wr`%sWmH5qiB$;y^Ot*D6I)4(*-b=%|y_K(_)h<-Jb)qi8#!_pDiii~Y9OZhq zSlrtT_pNupj&Tx8txot#tL&U3h?-YVZxN6kw9mI%b@kMgRO#2O0VUyh_L|o)_jN@6 zW4;H>=M-Tgq7*(Pps9(f-i=oGYo$h*Dh7>;bA>i53ybklK}7JnR3g1UI`HQMlD<MY z=e1np7`!paBa8@W^=hS2D>en0?CkCaqP`>IThskasg$<>2nvujyUwbpsK(5S%sUi% zc3ZwUd_-zaZY^~!3aM^lr)KACro2DV1euH;x<K<WGNib)^aKzth=^?VZboza<V2#i zTScgB8OG82BmdAH&6i`dnnDSg*ui-?gTz-N(?NVeP@-WNK<R8(ugJ-XrAc~kx_muM z@svQRQ4$?Doe#ekOaBhM;uf8*4YqXC*2Xo>wO+h9v^*^K+^oRUikZB=xxr^Qum}E# zf~|H(C|RI2vu;<l?cUtzO|t{VGn^|c7nkXBqU@%~SxE4xkg$*%ogQ%_sZQ5G(1#B& zVks$L?|WMQ?tqL?!b>+ug|Q^Dt#-K>T{Z5a;z+o$4mzD~6=D1F-)3CWZAHK2GuK%9 zH%x>77in)9RQ2M<52}DD2uMnbbc0Arh;)NUBOTHp-JmGlNT?v)-AD^cgQRpwcX#iD z_uk)sXJ72>%<}5aTsfR`zV$qxEU=-h?u=padUN%#waEuo1`w1DmB`klrQVH1V|l$* zK;oSy-{|9tMu-ww>Q8CCzH)-Au4!)mN-#2HSnSlP<7Y8lH$vzbnAkB}&yO!XHn+%3 z`U4&?sN9N+A4%04k3mDYjrK87&R!{M#OP6<kn}}oisj9)wz~qJ=l8v?r+gzSKxkK{ z8yX&EhaYc^RAH+lBOnOxPyrTBqBW^BRioQ@3!rkPcGtu8fJ|N|3-FAt50$&U_eFyw z3CETGg+7Cp&B;>Z^J5$EGP!O4c^4GaxjjRH$D&<k+uXtLc7)QfG+Dz1f9t((@bUf* z!ppCqa6w0l&{W}ZUd}q&nl8{TPBU6N6!r~!_$-aIJ}MxaZnZdzveIG_oudG0?=?JQ zb8QAxFgB-Qyk3|bugsJuG4Cs}96$W3kp7@mz*$%RZK7A~W?lT(+yq{?)ut-8HnpyV zr?}C@DWXAZ%Mvj%@Fa>r6MAu<0C%)hRlu-0l$htydBrJOTvYBKM)aTuYoIqN^k^+e z?~H12-_i65{8j&KN})f|m!l`<;``;Kr)NE|^D9HCM}bB`MIACSGMLo0UPpK*j!H$* z(a{QZA{E5t3&=;C_M1bBZEuuME!<pPcl^fPrZuBQ5QjvGqAWPDO?43q9MfMdGQL3b zImzAs&Rwi9Gin=aS@*(rYkaxw1%nq?B8C&@sTq+}^%`DQUr%u;<<q6DQ}&TH%XBMq zrg*d2Y`xIh2)&!L#w(vkV@#fjhGz_DIo?A(JkgEMeH%X0&woSgld;+Bz&o4@Pg7XZ znj<|>XR(W4*^-H8zGl*JS{pTQ8c{HrY{X+R@54+}P)`=}`BH8XGC?h*BH4G8aJP*p z2*LIA^x@{_^wRuQv7guX${JQ=WaQjzO6@xe3K9OENT!v}!QuSIau%u1Jf^GnM!6+) z06zfpgS7i9jpdM#VvHKOv@3l4q<7+n1?!`f<KhphLdN-g?meD0B<8+<5m;)ow+PNe z#Y>TOtrC_{ByXgLcz7y#B6PJ65H=rLArYfeU7&bV-njG{=xn@jBjU8;vFX_V{`Reh zn^T>~UbRF-N;~sR8fDPf4_V@tC*rqX)s9d+{;tC)^4OFLjW>c~b0WR9D8is99;PL` zh23UDnosM5;&x(?7}6IQP<sLUvLc~u_$A_WCDOuDZeIMc_AMCIYTd?>#KqXFs;a83 zrplPPX=zvZl8WymGnT1PSI>F%2h8CwEv(StRo7JA6`&inwwakwcRS<zbkpLtd?Mde zo!xk4`QOLfTOS|MOgR$PKN}l9H;yx>))p2>@bC(9bI<kz<|~9N935F%S@~TZ9_Zw2 z<t_$BGr&bK4O~Q9TNdCKDcgX*JD+dQyblWt+jdJyO{EDX(XKL+oqzXYSSZB2&wG@$ zkJW!XqAyz^x1bC)xuX6ZWd!!ahn^cbl*n&y9Pg&1k_yzio$f2F^hZAQs&TXclv{tH z<9B|ZB|AI&&j4{Tu}7~SNnmBl-sex?_N08SF~0xiN*<}JJ$tzoV{2<7M$?STY~<}X z=Vp7(77LTBBk=?UgH~h^fo+aO2W7FRG*QR6f!M_`^yc7G64VEns4%`bT#OkBzO=h^ z%y(!M3T?|<AECbcER1+8OiiOrP2==+y2dZsJ`1V0XPp?#Zc|eu1<A%=8UhU!1(kvY zMn5&yC$c-pY;F>5UCKOjJF;wC{j3&<Aeaz&M_oYt;g0E{JFa2s>gVx51fCW==ipfF zFJE@xbg#GH5gOj)*WvSm#1v|VpK@vynm%Alq9mE_7^@C9nEBWmrhhy-K;xfU#P;ai z`Rp(xIeDhTC}=_4YAR=PO|=JOeVpXsLwNE$snkb7Mdi5g9H3yY2K_+Vf}j8Wt#;pH zuhSD}KVKic0b*XWZ|5dRBwxcdas3?9tBlf&F)2{`)6CVf=8@xrgF_GyE{v>qc0Lln z!0Y<Du(0s?l<(Nt#jHc?W+Iz0Gbt&Hj!t6KGuCXsqb*zp6|9@vsmMp$gTupIE2?h_ zUTR&gd>yacHa0c#d0io=RUJmkMxIWU7e6m|TWyefddrMjdpho=FX`tIf&uCj$r8s6 z*8@WK>l-tE7fs|kAqHAVqbjp<#y!qt@LZ;`YtS1W=|2D9rF%7X%Lk?0;&+AYIJ&tC zlRVE8jpIB^3)Mn3-`ntXA$q{1!I<8DdUkSowH9~2?@<wXfv2VeuD|cEUz8{jFw$}j zv#6%i>0e_MD9l>b?7H8dpI~t~NdCSUwxY!)B%Gb0UI}yD7(4L8pb}_YK2d&25)u}M z&Bl!Kp>62-^dE3n91r{+h~qF;_-)3)q8&aFce@cXLmoYmv7W9A2n&k+_5Eu6Xl^!* zE^Dii-WEXu_nEsh#?yzCL_dWjk6va&l84fwpt`yw=Ry&V)xj?63kJNcP6`QYZz^=n z>Ngd3mD;mLuFREIcc$w*D^G$$f}+!3rlh7|$-Yn)<+waM3JD0oeWFs-)7o<sIud1( zV{B%mzu@1%5+;x2)-XKC)tc)=6w0iYUwM9fgdqz9lU<vhL8p+Fe2>=jAk7N<L6^lg z{_39kv<%kfZI`;BfIF<^bo7RVE1qaY4Gq&ko%!A)RZe}q0nU=AxXdqe<tBe=xtW=o ze~vSj=kXMAHe3MP++fc8d7?<*r<H*xZ&^H>T`_<tBudIY+rbp0I0XahJCUKt9VYgI z6?l!3B@G=HiwA3gzkXfIdt_zBgNao$wSSa#u~Gl7mUS!J9L%4--?*KtS_rtodl8+~ z7TMn%?BQ<l&&(6vB{#xk{CFXyS^80#)pRkN(6i~ScbY*emaMnxtrPf8wCkLi<DPJy zCVfvDvK*hwovCXp4^(~hfOg(r;KarGY?CR}s(-Y%sUM%uA}u4vkv{R$U<i?_-J%fr zsL=Ym_;uBAQqqBV9s{`@EGn%A_mk}%4<R9UDQRwQ?mJtu`))#8)Ajjk+4Q#@%D4v$ z6tARFRy^=I^<_%&^1a`C11rlQ5$byPI8+%HjluYsbwa;!=E`kB=ixIaYefPk^*eN^ zc(y{exN`<G6owv<xAo>llC!%^HQr!)=I8H8H(=WXzFF3&gGHO~fhyXN#rPq~hs3D$ zN|T}5g(R}b+d+Vs{F%pL(W0>AwP${G!Ji*vITjEb%VjzAvX<T#5bH}lGBPr`P&t@@ z2sq3PHCn`Ng>-b_z9AL9FS#$oS7ATeZ_pa{N<*Wixpk)4x}hGFr(%-^D=*tUUJ40Q zwdVk;)oGjGz$m}zZqOo6r&^y97#Qf|L$zA=@Zlz@fcx_hRH{rlVo}Gz{wAqs{4qE! zg98H%9&0U)9yJjeaz-*cDj9%q&Bxu=Zg2&iopmCwe8u`xJZAOLEJ-%9$t1ZTSluhZ zbsBt;@LQz2H(P>LTAE>SU_w(MbAL*eCD8ykY1iWQYi$7^H>pfgL;J)8ZFjrzM?G9z zw<dJzxEtu{2U*<ecj<G?R76+#1xmX%Mv)AC3$0h3bTxNImfH|bQAI86KP9exG&YW* z<dyvqe^QQCIxWE6ytctqX}r|KXtKXKWJSg%d35jbgiY1S%w$~u0(}&tA}1L$x@nis zp7BD5(P+7NVaVFB`{{AwR9~B2U?rj0cSeb`FM-)ra1^Ah@&^kjnu?Z}tuGLn<9QWJ z9Va2`h@9b*#rW^bx|(9v00qSUU_Xpac^)~T$iyc>{&y;iOXK(~@iJUi=WTXuX8~N{ zvhJ&P7d2k5j%AJ)CqCoePVRQ#oyRElnq`{hj}ZYG{v5T$X4TQ^LJ$N*waL48=cIce z-%EHbY3p<oF&!gqWfYv^buN3W&81gY?08shyp`61hc#*ek;gw_ulN97F3<Q`n#(03 zNyWnBdJ`?|v??9G*$v4Cc70~b|8xP~=QY~_VWd;mzcctR4Trx`*L(oAlp<uh0W|#U z%g)sq%I0WYY2xRnw;Wca_NPirg(DZvH^S{#W={YHPKvl!`tA?k_vym$RBXpH^D9|v z>Y5g+M9<cA!BlFD%}H60G7)Lv4}YpGs!&YOM$7c_?S8LPh)EV)ioVZs#<lM+f3wzt z*0nUU^N}?dl2n#gR?^vbVEYCmZm-nXE5TaJae2wZR}<fC@|_ajnMB{GBsp$?Bj=+^ zLh?0*ya^tai}Ol|vM_B=v1pcMy-QAZPUeO2{DLUbG3^yB-FfPzp(MQYjtY2oxY;8R zxCa6Y2#%Wj6W$;lTTtV)RckBylFkG623XC3{vg32=6l_R4`S0xgj3Z#wVR*y3_ZSZ zSZCyB@|yJO&TvgMcx7(a&EC1wj9v$G7LV5^yU@?_+*qfTAyiyx(}lw~tHX}Ti(ryL z?bkovlPlkv2P(Je$2c14Q^(QSlalJuMg%u_V)=!HD;?JIH6-xcNl9=W_tJztOz!to zI7MYld3)okVA7TBo9L>^kJI1NEi(PsXD54^{?Xvo@@?p$qY3X1sIKq~5j?ZDB6YP9 z<t?U6v>SHEt<g=A8Fyjk71z|Fsbb3w4N&%BW>Egkv8TMbc(O<>rt-VDn5mDwS9sHR zX8INBV~yYSyjB~;o_baDi-u28Rhas9l}Zv53Isx<QK+yL?F2E4I4E?_vMYhzDAG94 zfIL8%Q!G63iuF;<IVzNXMAKm%5yq0tHZwC*)8qh#XpBfr{eqPEvQEL0_*^BY0!^3I zv>hIvYLi&0!LSFW{o6%9eZFXDL||ghKHg2FXbmP6c#T*uEadJkBeSbD4ic)lS^Z{) z5WS%Z9}`sPvqgt~R#qa(hOg%|mwA9Y-HWNM2n`+adKJ}=rJ|@9-Q92xkE3Efs=l1> z<UlP|WrJK)n(|aV$*@&s4pGtX@k_oxvkX~TSsl#FvZhSBxR206Qx7pRgA2nd0<yAn z8dA0Tt=~D25MeWz%SLtATHMH851STFg!}1yx#hI!fG<aTJq}3-p{aL5Lh|<Bn?o)x zE@2+74Qs(0cqAA3xkbsz3WkOs@)ke#A7{0tYnOwxB}(PF=l!}Q?I?b6^u#SLpUA%7 z1H#H|A>|Lc!XDLS9s3!ipvSP2Qs>9fiKhcKYy`QO!s5ZALzc(ko&nnZ{X?M=!pIi5 z*=6psRT#?h6}6pa@-s`KzQ<yI$SYqo7_N>sCHz(OQ=2&KF9)pI>G>pNg~inukUuNc zD!h29pnNrnj`SqX)%XDp8O4+DbS@{eC(>1$jk`tC&Yl-v)};fQ5I#_4i#K?e&E;<I z@vnaTn&)mv`_Y2&i)$^D*RtS;_Y`+Z8il?X&&8(dkogooqNp+*@)DOYy-O3~iDaXY z8FKF0^_gur7pDS|tex<xyAQEST6P=PaGE^2eESEQ47CbY&gIbWbIAtL6bGg_m(d%k zI@kG|w~Vh613i7Nd_&(&TVuzy?08|*i(0i`;O97tFSARXol}kzDQR$F_m9{5biyVY zS(hv$GR7<r8l-q0Jt82g*n{IG*{R&?on#iC^+I_3xdg(F(|fjL0fHOGa$rC4IQ~<x zoqK-nzSI?es@RJDp<{2UtE)q=YepE^izZF=PQhPhiZ=3$jmqunOPBl@;+3&$!D|f) z-$QxnEAb9W{2MfQOjPIeA}>s<`pWo-#oax;jvV*A%9Gg3#51`5`TfUjGC2$d<|bl+ zAx3Gj_^HzkK}{M7Fi*~VRrtZF<4=n!6n*SRqC-JJ0SnLUtk)ZgTp1+0AeWYSR1q&W zAEUPy7w!N{@;A8tBePJCjwlXD^a>PE%1+56d_bC;Mj7x98w=;~tc*Zdaz%dfTkIvm ztBB_fQhJ@62f49$Dk}ew(6kJtWhG+G-07;oIehr`0rCK-(m!nV&Fk2pMqVM-2fx1} zrozQtP-1?Bfw4Qp`J$`v+>rkGnl@&<ae01Xa7cW;^0=HHNBm{jqYk)n=?HNNcBp=n z+AAw4D63osZPm0dbFtaEIX<&Ksb~^tg@-Q4Bo2AMr$A0=ZcYxYm|)#XzoanuOORjP zhVhQV0Gok<LFw(*L_HO7&cI+3%Gc&)8&IAL0}_ZXpHGarZ`W`$1`EP5bqx$+X>^`p zV=FS{%bX6#|9uq{V2U=Emd;jxGYt2TBBcnRu)gWLO~FKBevaUg46uI0na3OZij))` zGX7itQtv4;%Z2ARt>8ewPfFso-uLg%|2?Gk|FeYDJ15DcqQcQ-_bEP6oQbLFzZy6Z z0Sa>RiRqrL?lo0NBD{|ybuFEE5nr-EPtU+`{Z-sX6cH2qSHLJDAgri3(AzfB-$75! zvslrICTN30aD(DBBp}rDbierTcMch~Zt?MfKr(-uc&T<TugLtZrR4^k{{KlsBZNT8 zBcZI^JvOESG&$0DcvIi1JTU+HbpAL$|MKMTlOCbe+>I$NmehUz_BnwNMC~OCd-9%5 zh>5i{=}<lTeg#Z(p-$aB42%c={N_vCzmzyVt$))kT|o1u4(JUYo#t!QW%$*M``8&H zAY7G*AqxvrfI7YFs(H_t0O7B;Lt*v)Yco@Zb!6XT-tS(d^PZR&&%SwW6$W|U+zWo{ z<Yd>m^y2wn0Rnl)^S~<-T`6nF!>Wig+{@Rn$``7nF8)A1nnwkVDF}1{h*kPMh}c1> zZ=g?}N^X#1fQ2=CeVLM{*vAN76TBh$^WMr7OiVshg}Aij<S=er0fkwh^9=QKLPW&Q z^ry3EzY}@;gC{9EV6e~gPx^BMQS_D(n#p=^-%xW71}dsF=j~tfexdiaok+f3fAl9+ z!FS$5Y9B7?gQM+6EiZv-fB!pVdwu==Pgq$?OM47nzdrnP$^Y(2{Fmx4l&5I39)l%# z?2fwWG<WW<u9`vEWPo<}IlSQa)ax%K;#T?@$M>uz-`mJKHLmTG-5diddaMRkRyqaR zRreo0)XsCh3$K;o`&!L8>$&<%XXj78zOPf78fJSVZaUjvf7X{t`)F^O^7H2O4Pcv~ zIH%g2bdZ#gC@L$%qBY3Q$u3^9i}6uTTyENG*!<rwoqDVg-56i`b+I$&Hd6>DZDcsw zw=gfBdd($MAZ#yRUr76L{YQkl6ch$t0y;B=vukSx78d09`x5uK9`Zc75hi#2ZyM-4 z*uDef31A_p+oqO_u^AZ|Im*j;?AI+!jhGDK8+(Iv`RfVhh4=JR`*T6aXI=YUQ&<m% zmD7+ngIG8r;xyOCYdUI^t5FIS<YFH)dpfTUHUu6g3SUY^$9KjsgawPoRcuJdF#53O z4rg)b%__~vzfe_mqF+x-P9}TC!TxxCtUPEtHVed&ub(mbsK5JM^)v;?+??p$rLAH{ z&(E0@iN2JQ$kbMO`NXH<67qB;K4M`c0&o6=gDBO(!9I@Nd@9REk<2nfE^cOBQ&fWM z_3P2y`EDA4?M}dnq@!q~vx=URwOWjppb{{MhsGnJ;9ScomSabjehrkVO9RD5_7!Mu znXjzKUR2W25x9_zJTfwd;w5qW0{x9ft;^YB6QnY1Lzba{*X3ZX>5uL0?f5E2!?`G? zSM@TZB|^S&Y;KOJEg=xafSC)kVZGPYP0k8V3bEjkZ#Eyd#Xo=g1d}Ykf*!=k6;I4d z``&xFzuh-S6P9>h^cY*CUO(h(T^%>^L%a&-9d9s4qco6JdOSp=(bi5FX+b=VqhpZ# zvfUDvIFaG7hd(Mi81=zEx?iD<Oy?5tMDz^}y>G@xzd)QLGzN||9!kpjj%aCb??1$; z+1XERM;`5%&X9|uJjK(leN$lZ{8(A6MXdW(IfNw(aM8fx-3V&*P*Q%zGz13oweu5H zh`}RQZ~@wTV`C4T71thslthTS_A)0TQdTa9DIE#v_T3e_@6*<^-xS{ahW5s+HOH_S z|AI6;d3;`}W9%oQx7GGf=2{I}L;bz{Wq`tR+~TKA#3x{P#$Wv^B_$=Xo7CYG3BI*0 z0q6an*>*&nmTVjx!jenOlcB`C9F{E>IOKQVGfr2KxIGT8yhp<M@)*?7c>bQC6I4ig z-Uufk$OZWKDCI8B&%=pWw5q#4(#Vrec)h*JCyR6Ox)xGQ;=X+8i2Y;QDo5g@LlI&u zm%w*<KB~xMxdB*zZdMvcSyJBfSK`L&-|k9w+7N#a4Qm*+!~63sf=yiR-f-cxMEFCQ z#}@kJqf)AQ8a4K#^kh|Y0U0qvDO8<diF`o<!S4~A`cuPAE?SrtM@t9@36dweCfpC> zPjZLyup@~ha+C`L!k!PEeor>wF#p|h$1g15rvbnx6;{(!w6`LQ{XJQeQgay$A!+P& zhT=XdUKL~C^YAjrJ3#=W<h2-Ud1<oV!Jz-RuWcj!3A!2Y1u{C5a`n1@-`wLzF^__H z?T?o;x?ioVtf59))zWu=sU+sNngvSmXRy`pRSppJh2Atb9|y#oig$s>fe1X(a<Z}> z-;?zCT@P?k_ZIDzC!PMl2KK7blqIcH2IoeudDfbTUnZfMSx!!l2G5l>{Ws^C^WnmW z_#>rmOCK}kJ5;lJGc3;vA3i)g++by66XwTdSy^4p$<AKr(fQMKV#kv3jD*WGrM~{M zV?jER-$SELOjf+Lsi~*)qqyz(?;0m6$uKb0@2^gdUqkIHKLZTF)KKenm4J^$L_m<p z>x9=H4$C$gA){o&w{PDDvlI{zz(Fxm<HT|sBqCTeREqf;95OctT(<vsM$<<t=o=W= zZHy_)EbYvRZ;VyQdK`xHqM)Ea)^tc{K+BB;>aUM*m^2U-f3UL>jkqslzNmM9+C`(? z<foy2wHcOIW(s<eWd(4OwMt=1KYp8O&>C#om*TT6jfI63!=lBfe;f`MB-vdZ&9S?7 zr+H8BaNhsi^{PnXT&y`LdZg~?Mhfar{Co}R7$U+Kk0BZnwfXs-KIs&?2Q1pQJ@xOM zI}<$o7?koTd#!)$FhEe+Nu)daiC5{8B|W@z2shELmZ`#`x*FhcI`&s+4vPKexPcN% z1ithzLbj>7Tx8C){{DF;v6dM~R$ld<zhODIITN$KlO{d9dCXknw83q!>jfz$Any3Q zOiOw8t<KF+R73>i>&blFw0De=cDQ4f{n<^ndwYAs)1~ozeS8i{ALBoemwfjh7C^*C z9ona<)v!KVuK41ElyZ)z^VP5M&Qi^jKmlTcy{GQ;pQ=z}WD+?@1-Yfn2J_xy6WO+L zL)>7TBZ)D{Vn9!JNK(@lO5|~h(e#R1_AA|?`{rnkPP5dnPdL;mxeO;m+*tbf-N-R2 zLvz$JU-_;3;1vGoSkm;9bpe75e?vB5IJFyVH$<=)xOX8?dv|B(eSGaqv3N6o(Blka zl~?!iRDYIaDL{|*D?@CfE4tZ^8&jxMk*os~nDAVuU31=F){~L3Se^Uh9;j#sdKhM% z+U?Wd(n<(sLj{VZDw6{=VW}|#>ACaQBJ|(4e277x<G$D>NoCr=2joW;BW0aah)nR_ zLTBQ7)Yep$!^P^t$jCPezoSGx8yQ9js5~3zDy*>3+}hlv{(G%ZsEn3wg&)(revRdF zy1%m|pQzw7S?rO!7Ta^MD*5CoSdfLfcgKErQd3hyr10#gAAV@s-*h+ExC$c(n~oua z0J|m3YA|WD)VQI4@@TAl?dHu(Fct_ck7PP+0gA}aPj&Ng#rw|KLT+AOzYibe64>uj z!Fd`G6jb;kdA7`S!1ZWVxz^bR1LLUrfYPA^yf?VC;iT5tI+X=}etzfsaqOn<%e`cV zRywuLtTwYEQIFmRhlYwZxd0NvVK#K%^JV{C@APz|*k4+4oTgt`bfzG^s57j=3`{9Y zJ+VxRzq-3EI~Ti~ww^j&@?{tp7}Qx3^_yTpf*jx_1_US#t%FLos1r#yuHL=(#beMo zAI!#vh*M^@UYp~#J_%I$&(E+!B#DL!(e9$J3|5%~p`tOS;cs_8lH{rV{d|OSvtc^L zig*h%=gu^`f!R6i`{8ivl2tO+w8VLif^eyb29H5S(;aL*ucX%?WKXF2Gvwcq_pln! z=x%hPbVf04EiF~L4gW$wK6G(&i-t5nAIPd78671f=iuZ-N54y_m|;<0d-BG;!G1@- zq!oK7rt|L3T|j?K29wag^kb3u7?L_2cA0h|pmWfM1qMp7`^{pmOrAN75bo^5a}nbI zEQiv_G_;#<9D~@6ratk8ry^vPTTg!T9A8k$eko9|@v|}cZR>C$1X1z1&g*VeA1HXQ zvpyLC5t&Y{F1okOx=7Gs&#PM90^!SB=^YZ({>=xoqXTr<tv)`!>`}h4V`DsogkQ)S z8_604)6?g4XFy3VDkieOIsjP!ydPV_8tIp(QVY+;DW)9T61_Ip34s16)J(7b>S4u5 z%(w>{x~@;0lET7uk#<HFoZ=WHAXXyeHFLJG2#$%Vh)CW!uZsLon&C_Rj0-Lvl|Btt zmnOBYit>=J)z#J4t4PCqZ{vm2*0*?GH<ai^{bngj%AcxP^Lgp1270F11<CK|D`R3} z6eIM(@JuR@c7V+2;o;HX=^!uRe;saD6PK%okBzOBlw@!i|I|wJDqospfE*j?J3zi7 zG4kiA_wF6f96MG~1&nf-bNwllQkJAfj()n6E>9}pkC3aH|9vitbuRoOj?(}y$EvAE zI51mZbV<F`nC53I20r&62=8d?4P@y~Y=FS7Gl-?rufWbu1Itg!n8%;*H?6G`c^u3V zpW0=p_329#r&tl=;HafbMbA=6#q&C8d!Ej<%0liCc%f9@-9oaRZ+izukA$VJ&vvb$ z8?42}lQl<wuLYDy&HW8z4ANTngVl2%)O6`90oBFjd4H6Yfb90X(bDkJ$dIefBH2po z=}Tw_x#Q}zF%}?j?~f?}>f4dq(!Fnr?1t+pTICj4Ui5Yjpvn>%bw)p+RNY&2w2d@8 z2*RWDEqgq))rfEg)&pLTFO=(rFVl059TD3hjG2U2tRJ}^N(xHaXZf@2D3<dTQsFyC zpVBT-GJWSc{vP=VX{CHwt;s~k`53fy2_VeQkWUPr+xj4>Xy^k@r#t%1bMLEMICn}u zLi#wEy`oukFp+lv6oz3onn~y85AG5gH$L?SM-IY<F^A=ETi+v~8|1Sw+XK%4XOSj% zu;{f74q9A#H^Z#5K9QcAWMZ<`xVcXHCoF@DQqIfSn%;fqK~LgP_A7zHGO`;sj3{6M zxgVaY_wD&RA^gVH*3QmOyYo+&mDZCu(+&%}qQt!rRb^~q$S6<_GEv-*hrhsf{}T!h zwpTt8@FQG+jBVM(+_>{H@w1>eA*pJeYd2JrB-7<Q7i=fVFY*W9kB{0ZsHW6-C5?}N zZNz!j*W)uquYmlA7CcJJzaK~&Y<$5rk-_;p(6=o;4p(<SooCIt32$?<$jU*}f^qdO z^3g_lNy$S_{r#)8k$z@_)zyGfsetMv>JsM}VhoJR+h}oe@qEG(#-Ep|)j)Ik)MeLs ztkucsBE`yhI<NhT<%h6ng+-O^?wn?WBd!<dU{`vRfz(vVAKRR$l0B9jE&05*x@tXM zjpmglR`YFQbtHSPk3t+f?Xi%F^Cxa<vv8BT`+lH|2@e~qak@$peE+4=WXASP&RtWp z^tCLe-sX6vs^@8DPkc)aY>t6lMfe(^6FYAopaD%8!7~fBuuXn?<<qKyDoGkziKb)g z$~%D}p%D$fDzBum@V`|{Y3T6P@c||tny+}S-Efa!7jjE2Co6DGjrm0PubT=6rF^Eh z7rV_3hhaT&m2oXkE-5j5u)7<R2DjZ8mzt0*{zKo0OJgisSXo)t-8ygIRHlrQB{S#n z%2Hce5j6}{v7gZC_wPzcJ)7pO9IzI;7u;2wM9JrMs3hS9l9!(e%+mq<<m9t^OY%Q_ z??sVrOne^5*MN?U3jWz2cE<7sNt!1pux5_@`N*H}r&ucv60wFt2)VLfRbI9!%=w}} ze01&$>0cIVmFRn;`EFs*i$Th7i(gFD5gmm)fTs>hBtFNL!~Ea(BF;Cp#uW(~WNSiA zl!PLO_Hz9cXUO%*zMTKON!;7(8L`Y%vgVVLicM2(T{Dq4oP>$@;7kAJWYt%GaXFmR z6Dh~@@87@o*xt3n9EwP6NK;rp{msCVAZ=yr<8eOzGd0C$UhZRG`@ygjI|-?E;ZwHE zWK)FBD!IB-NU?00VuBW;zMhX5DBAVTR<Er*&qwrd7P~&9Zu9}-g7I(yo-T5k%#*;N z*1PQS<S57BMWWI-6GsPEW@biDgpH9mzI`q!E2}(&O>}UGaCR8~%tgO{kU*@3`sSsl zP<1;P)62FG_5_`Wk`;~9Yzn5oQCAJDEEk*X>Xg|d^}h!kJ)mW}?&!O(?6|kLx3#3* z1HA@&C)za2FcRLES=TPCh>=^-O-I(#RJ_!;eqCOi8GHb<Fx>Pm&QI*rY8nLvIdW8| z!?K_*$Jlu3&!790Ax74XZD4`5Tk0ki(7VN%3Qa#aa`EiNB0E?fr~98-4KhYnxHclc zCOxWhupC_<EhQktS1DAuX#aTSgap1PX0x()H{TuS(S(sg#QR}fU*U3YCe6!wXL~z4 za5Xv-UKxE#Z~xdLAIF1vS9|Am!>lUE7`h?6<=(w}-QC?VbcOT*-9lES=JnI0)^)Ch zV`qa&qasJEa%w(1@-PjAqV3dfP-0Sxq<l_+jN$C!Ha<EAgVCL|M<)4gpO#hGjSugg za3hb4)w`q1#+B1*r_BGFJhLHwp4_U6v5py|axK34ag*@fM<1UB@M-M4ENT6yUb;le zZQnJ~b>5LKJtCVzPDPe@t{`zt0C9oD{GP5&KP3xw>YuxjO8xxRe}@}2HCk@7HQp1A zup%$ta;Q*?!=c~#d}F$%CVT*6)Ag-l-<;o380fN4T^H(HE^;7T4m9f$#s5YrWy_;= z%eOgDoO?2u76k6Sar$B4tajYIw=^-SS)AArcD~$)P=+-HOTbvPbhT@>{?c|Dd1^{o zS-E1>mRA4DBO9$cl?_mXfSmn-v7!OC!F83^JiEo9{b8-BKjA4lKD(isS*3nV80xQt zXW9ArY?g}?Fl3jn(VjdV*~Mk({>1OKI|z=1s-A>rR1#)mPK=ECpl(m3FqS)6(X4ZC z?`U}qB+M*RiTADdz{#_juC5nj*E>uTJ+o!ZV0(R@25yT?^L+-xoO^FHW(%Mn0n+#~ zW#bOBs;;f$x;I<FPt41!DmZCBKGY4251g#!b~I0z7*z9{w`(e`<rBE+&ujVJ4#CE3 z3=YLCiWJ9#<Gwji+2PSE(XQNc+8DCjN~nY6)*sTa@K-s??jnd*ZcSusl&^*maW!up z|7rB9ey{(Fb*#3^Zm}z2;eh|bdF$6COx^Y?{RF@3!BCANKrwpPyP@F<dJ>A0!H^CY zha6*+uh-5TFsM(Aq^@pW?AXI7=k5Jyd84;pSkq$NTC73HcDw~LZej}622#bX@4+BV z`jnff_-AA@>gfR~ySlBq_4((4%==iwF;h4+k7?ih`A+7|E)RS1D3nJ9VK=^tVbU7R z+`56t=UvwD7S6nPmk)P|ZtLfpD$;Z(ls<RoF>M;b%^h}FW3CcLKz~sB+VgB-3b<H` zB^Q;`AoH#1I;Qz$`^$56L0gN_#C_)y(}B~^Y)dk6tf&--NJ#l=^&%3sIHz0ZX`GdJ z?pRMd(&^MUoyw+Dc@)VLV;3HLb6t1|I75s4r~X<~;Tf(+&%sD6JV*~ZyXi6)>77G^ zyG@l(Kce~nO5~kPQ69%i?{IeYjAppx{58r(W4W<TEeBE5XQBe0`!_b~C7r}hOlS9p zs$PXL#Im6DmzP@ZwSe-Q!)lBe(A=xarOx($^KfGFV>4;UjZr22FslNj1(rt#T4ld* zw4T^bn6#6uM_mdC2}unIc8Y#{&9s9jNcaQPrsQxdjfhVbN1tUmuz67$d6AaVHazTf z((hwJpsJ#Rh89g30??HE<pcp5n!~6yU2+AR&4dKIwx7>s=2>)&-}pWq_JVY;A<LEh zdX}EASW7aQpg=9}@Rs5e){ExcF|3vT{NZ04udr|{fY#esHS$g|H8E+kVtcP7Q(-Y0 z-X&>t4#*(1OI%zT3wfnMMzmxDYKbt??gQt8)880ghIiD5lKvzmcBHPZuFeKgN(sE~ zd=23UAMJT2-3}$on$v8&{igtj#6+V$!((G(vlv2=yvh(wvDUNDKV8`KON{q5l<pns zySsf9%fimeda!rqSj>9s53EeT;<(PBrosQ<<J4D)4`y`@iHYYvK2eQ%c^l^!mtFzi zQqW2KeeBB|&d>W@cU)k7@jyVZ;0rjUK1uDB7o>=C;k4^148Tw~*^uhQ`?TJ-;-u@z z&cV_2gW@IOO-UtSgr=VonVOqt+K>GF8Lb?Z>QzR``emIwlwA&4+=6(oN)ce}coS8U z|FD1yq8Cpm%Jf2#?tG?2h<DsQkVCWp!tjx{(NP>Z1qEr^hW0Z$(NEcT&}0{}I8GA! zQ9eI<GLqdjZmpeqCN3gLMIROB_`*Eau*zUA@(?|IY3^bz=*ttfyrV%?5Aybh^4{+w zPu?Bt1c3sRG$gIB@R6l4?j>TEJU#rU)7e{{7^&1`!&y`RJTGnx$xMox)Aw+Hlh8iB z{T&l?MngjbQ$X?N#T<C`C_GnQ>UDg8`=FcPSYz}h^Zx_;rd9)Y5jFMeCY|QN!7hQX zEVa}rgBKzH!|wa6rKbO`bQOm#9~uHZHu<)u8gTuWdrpWjNC@8k2W;2hEP_5iDzape z_9sm`P^MB+Tx_%1PuiRwj6;N-nv+5OBIbiK>Djc~;Dy8gdGRgjqv@NTZ>|zzDjY?Z za$;hJ0A?4;_(1SuT^S^DTEcUEKhAC$eQV2-AhFbXS3+rN`86x+XC>n)1Nwh|ogAsd zzvekudwZIiI*Q*YU>jf$n)r0lkn>ISOmAJ(aBW&y-MY97D#|gQvAaS-&dzEUU0YKQ zF_(C47v!g+!4DqBbk%)*a`it>AniH9KAQy46WcW5<cNsJup=PdzJ14wsY{^V$6ZoL zh)?K|1Db;!8~>rtFP`3!cM~is{G4wM#s4H<9|nIMpxH?NFm3{$<al@p4^!+*4Y0$I zPBq9Y`QN9dfAS=-VM_9vSrA#Fm@K?W?c;W_YE+<!CmIy}{GsykxNZXL62Q1XM*W+T z@g%(I2^?8Wux&)hOs6*huny3}q8;svd@)%dFJ=m{?*9p!U|A%c7W#)+lEQ83y3^c@ zj)MAs<5Ht8@AHH98RM@Q9EP)o&bE=CJ=3Z{nmN4lyjV^5Nl*Wma!Y#zW9J=CBqRm3 z8|No`ts|e*TAM`w8c(m8j76n16D7l+u|0{1ND6&JLzp-}C%b=lbd2@)^MKBZ6AB19 zRsMM=<fjDiw!uJ&g2#ySfXHp>kV()cN=Bv|$p=o7&W|jK0Rh>tfB(nzv5}KEU7Wi^ zR^!6yNovxIK#c!f#!tvD(f^mo^Z(CeoP`pYk)55Mo}UWELMbWN_t)#6i(mj#p=)is zs5dK1LNFPJ_{JI$K0V#tJ}}bG=z)&!zu2Fi7cXDFR8aUmI5gDTdj}mI3r9RcE9v0= z1vM>T5-lw)Pfn?>i7WCHowskEaB@;Vf%-+2A+E>wam4j*7Wrnx1V-8#Dt-9xQ;(;R zlSd)qa-hiqcRYlo@#+<gM&Cv0KWRWu1hn}1wMevpI966}o;F(1fmSrYfe8rl2nn$w z$4qQkt9f{CqM&f%R=5rh4w5(h_~%HaFd|?UR=HGs_~0zybv5#7`S{rRUB8&Ry1KIL z<j)Zi9r<a9=-hczlG@zdlC!dmf|63i+)GbSU%UB_F}x%S?)u_lqHuCxlHj&4A7$#= zh)UyqbyKp(!84FbCF@f9gK=-i>EA=->64J~%=Yc2XZ@{<+X6bIB2{XigiZ}G9R7Y4 zNNsUFWKNs4VApLT#l^qBeYg=<X{~+tbAJzWy3D16{ee9gG7(QLAy(qHuP+%{qY#h5 zKSitiHM@%rk3mQ6c8GY3b0$p4LcPS0fQrhGtB_5r_ZU^0j?Ie%u-o9kK&<6TJytW? zO_iQ(-G4%#Ql_J!QV^E?iyg4SXUM?CFBqfT=I*ISXS7&|kV8)otpTQ_I-<(Id)w6R zM;TH*g+z?X9ty)pkbCUyLmVCD$&dB(o&k!t5EPK+&~FM&D;cid_~y~djD&)Rb;%A{ z1Aw~1iF0QqRjPdTzj&yN^7~3@IK!jk<8T(;A-H1~DaT*j-Bq&C9w&dSoXwc7l(w@L zmHR?ck?k{1N=1cAl`ZZEoYzy&oD!6*8q%Hv?1Tn__^~pV5JHYuWfscSGU~;(x{X3| za&khqJB;*z;}PU2Ph3OQ{3d}Z-fF$~xra<=YI@|}WgalNm^{C<iqg!Fw5OlFR7a90 z;xLUlt$|2x)TcZUj>S33X~}!X%ipEK0?N`VD~hGhUiClw0jv`=|IuofMEyNAuVM%} z%t-`X99MRK*BlX#6q&|_hK6qYWtp3lZS|@jTl#yi{<fy1B_zBfm+(x$bscd=H9I?- z1DHX$6|6|B3{@Nuai}OLNIEV;*`iLKvn1J9vYl6TVoZ5Hsw_GX3En(B+zM9CJ&JSw zygUnNVf;Cx2*&uQ?h?nmMo^iGeu9car*hu+{>+qEfQQGmX(R#^_4v%o%ZA2@0BTw` z>C6_!>0O^<FTXwWK%S^}uxlx6AGI^ose5hUC-+sgP!r6HkdOp{DD+z+hl&>tY%=2& z8#`*dT(&~~ge}3up!zA)uEz(-_qI6Mgs4AIV5Km>&t%6g4TG6=vCDMlxR2tbk6tCm z!pbW2CMZW?%)2Y(4cSknc1D&YU*XEqA)RJWrSu7jV@E|tfAaXTjD!TY`|{iMjnmWH z(eVuCrlxxH5JNa!XGi3Ng!+{H6^KI!@$g(%W}AUQK9TQApb&??q==^guvU9r71YEL zq1es0DW@1p;<Pk3^Lt%Fzh^9<!MLSsKx6Sa?!P%f!R6p6m;1`c9A|8J0FA=T+T5hr z*dZb|Q|{<s?2zCvsMAG(LJrI)A@}O;LQBHNRGka<!^<n5g8Y0asc49=vTbf@dFFYn zJ3XbfJ175zXAca2QvC^cA;w_9xLc>f*;7K|MVR&lB;femIq7fS15M_SPtGfyOJT4p znwhP}GAG!z={#0t4hj$d1Dz&-%l^(%Z>^HzaBwIG=Czw>*v0u<=QDC(yWyNOTQdPq z!o#O!0Nd7lN>3YzEv~4r94;;eqPwDspVz6@<BZVfTF3h$DLf}9a4nVvuyL`q(DQp> zHmxun?(1upl$1RA-MRFN)AIYPc$5dGV|mdTF^jOQ{#8btJ|J6nC(mDZ@jM{raF=iw z@>cUaUT%koFK%4sV4m${VPsBgO%@3+Iman)QHar?6>+uvamwM%&QWc$vkP0hG%AMG zM3LOftilG9b`%^j5s^HNs`I9kQi1r7#d9=sHZAKeycL$<vXlg463SDlf*E&+CxHnX z(X=GTAr3tYH`NpJ!m4aXv6S@9e`miceyKcYyMs4qohd`iARwy*GBMF$`;FCfwoEyc zhQ=z}-@pP77Ebr}6>mcUuNU_-+X_hT|IBYd?<*b4q?-M50T@uZ&(F+HBAY!upM9-4 zykEmpGf^Ar6T=oO8m8^h{CRk2s3iqF^Se7c>UFlJ65Cf>PeGwDS**y#@4+u1&;T;b zYfd^41RTENtd94;a(lA(aRUb2$aArKVyEdIpP#Swxx`4zYLf9)L2X71-Y#TBP%n;l z*)6rd*Tw3CP|fVZu&~(J*mE3ciw%>76cjg5tXo=G2zZ=l6(nl|>-_!u4U2aW`IM_t z*Ro?EZa7Clp;hlLYM}w@?aI<pmRA+$#i`==JM?+(B!iX2NKbEJUTNgV4+ORswITr@ zj*o|}wV`Y(+n{=NHPS~XgK_a>O%=l;@RQI1AqOTd1-UToheF7caqGIB5uSbPPjJm` z{Nx58B`Oo@qA$JLD|=I&yhIuxyUBHTUZK6;C`T!m><W)jg~ak}O{JE*M{P0s;l>o^ zef+m?pT0U=uW_>U+F3XKT76K=Al0f=T2jI9aIOY(V)<&h_pMtnJEubGLY=Fu#R7-f zZ-~HR;@nwY&a%a&@9+}I<=mK^HGrVJ<DDO8QoocHBG>mt)gtOn52gLX#;aX~MVv*3 zAJ!=;1J`V#1mO}jPF7#}JO{^GGE7VH0$$@IheB;1V3M0ZcPQYl0C!HrZUOG)tStIO z9-FSFeyQs*D#fc0(pgUTiHfX#EY@Ej-@c7hck%Fc5~m47&z(8ub6ThkgLYShgH~ko z*JO8{(OwI0g|+2aUR7jSKcCZ-5TAY*1R9W^!~kv2_m#ug(?UijZm1h`5{-a|c&7zp z$#8MM-I<un6;Wt3=eIfNS#@V>M^Tr$8*e0}#EfT4J3+owOBhK&+Y4!Fq60^wSL5`` z<`&b{He>U5U%y@?B)mF_g7%)L^F0Dihrt}VF{%YGCx1Pw+|J$BhL|$<HRvKLGaKh* zvUWM#FHTO5aNe*QEy0CsXEF)vwqO)aau>#)cI*~r?Z?AoE$BCqZaI&3QS9^Eo&KD^ zo0>{aQDeVBC(y0!aXQ`LWZZwuIyI#|>9&uHgA$-=Xx{uQpH8qIM{hm|F7gJ?kSIO$ zmFoI8mlMz=1&^4W<rxG)SIcZ}TgcN+mwxcmz2sg6gF8yU^KgOo(;_cYm2_t}CWzmY zPZX%C_7t?mpKEKWEGcWs5&wP;Z3u2oTlybMVVx=}6PW?+d-&I9Zj1Y12QRt@QWyWv zVW9yoJLjL>X}qC%_EE9>#hCIurk0i#7%PpAhN|sPurjWTsx71m_7ECOqihIt4NW|1 z>xy#!$gB7toGu>Z5)BvVkF;l-Bn!y}JSlkFLX)S9tytO`MzvwAM&&)f`(D4J|Gf}6 z`;NAz^#7a;v~pQ%b3xRnyN}czoru2Xf~?F!QWBE)j&$s|^vu86o?s1yoc|Q|7KXdF zdV}7_VHTbB$vPl~1w3~1abZv=#AF%M4UA>XPtW&p2&f=H_Jh%OMm)^}qc<`({QlP3 z8il}*=D_Ih1_fL1tOUiyEwV>)?iU#N79WKY@+wG5{9R3!r=QrIe*%3!r^O(?IzbxO z1EzQ<VBU7pLx_eDkIw*;D6$-PY-_4>J9C_#npEiPgIExb$L!B(z17Ges>Aq~rrlE4 z&rdXR#89)Mtt42-SLb^#!hTIOAFC>cx6(S@qkwq#L9NqL1-QI@S>I+_PuDim04BH6 z=epl}b0<at9NYVcyRK9rOn{h;WlM9N1Vl0DVBVyuf8eXY!2F`9*Y9ZSgHC_4FpbhA z+^Bk=zsPkY>5>#`!dPwb-`Tb?Vo(wZ;c*Kkj^iwdsQZv5uda?nH{BZcFi)%WmO)1Y z>*L2S?jeVNS)D2k%OfskZ{m|sGAaBI3-}*DLqSSSt0W=uSYOp$B;_O`lU^Ch5Kn~8 zCaNL?n>Fwf={<U=$k%0feEcvTM$v0!K*Boy;o(lb>q$w({-WOQ06QmZG#U{B(VPOY zj!Ft;op3q0s2{DUJ@i<r2nv7VPC?n#JafZK;Ac-uzo}HcZ4sr<LrM>vf#%O5zK-w2 zLUHJhH5^t0<eLv3kRIjVz4p6+>X~50+BzWYD3*B{-14;^7x@%|_A+JMxPq?OmZPsg zT>(KZE1Fe=k(V%MF{<b6gCxbUQ2*P@g$^VX9{qxFuLN!@C8>c2l$P|791n<bf>kM+ zeiU0GA|vZ1%*{1yq<~+xy{oij-f^nrZeac+{Lp}q=wB6SB(#E?a{q|zFkmz<DlZi` zp%=?EAF{IOD=BBZ5gKr!f*cwaq^cY-*q+(*MX~5Pv`lM-+X;73)CacFFmry+b@cGS zjCa-l141pZKim;OAbs$Flm*@3hCIB7Z9!1rKpM*`c+#ZX#5$~U@*v*Vo(I3NN9W~P zo#z1x*88SHtSvu2n^`46jo*dZsTme@>9ecR()nX$M?1T_2Rkua1;J6{@t+4eVn8mS zpU!iz+VhD={Zi}hbZKlrOim6v%j(h^g~a>$9uQQbEX-|K8W_MD)4*7+lKR?Ggc;<8 zS8=fKkq0^MG`XB`=}SLf`yN6xj497)IYfRSZ)0cIoCqrztP$PaU3*JCVc&_eci$r) zc7{ajnw>J;7DGjS2wl#Em=DR^aM=Q`?ol4C9g1%oSz5m5L}Hy%c_~F)s+&+=Uf!(7 z+D(LyW5Q%;MsUF<0SyMvMd;B}O_9-V`%7WQ>Z3j<dMQ=+1Jjb+#GYOgQbchXp4&EV zr=EeRl&vR5>sde|Bm0v5`XCx_OGz^-<TQo^U+$TUnH&qYpK|1e`-^oV3QZ_wTtP+3 z&z3ftX`8A5M{WzW2n9?4TL=33paC0qLz``>YilGs==>gq!TD;lU6k{;8c?^Y%43X2 zUduaPysWXR6O!{8cZv$`xp%#;_X&szE85MZq@xjW;iknVtS+xQOP89Z9B%NEFN#>T z^g@a~A=&uY2)v9JXbk!kuH>;?U0o`+M(*NDgDlD~G`@|wTZa51D6-sK<tw+%M1Ma$ zIp4V-8#aY3-xuSL6S^y)dT5r8>W^yr4g!>LG%;=C?@2R|mV<%6zoGIHzTbYX$0p<T zih%eMa#<#rcteKq^Sa{}C{ZfV?EgI=x(d=c2?26ild&|xz1JaMxGax%oA<=}sSbrD zWK&bL^Tl9{Eo_f{K>H%gHGfQ^mI<f@WgDOtPE<pJW!Km3D8h5KOJndi1NkN<U$l!C zz1cZ`3duF#uop}QqYV}b$pXY4thgE;VDjP<5_Z^Tl3(5MG7JDUO)S6op>GJ`aK5U} z@k6^5%an63mU$T!F!^m4K&G5KiC}*l*4pA~pe&NQ9%9{Z^Fc7a`2<dUY07oBcP!Ai zveJf<_(diSeY>V%ak4QCTXi+198ILSEa1i;O@8Rjc>h@Y*}cP~Bd*7fpVx*|JOquR zyS=7GUU-#}{S9I);##{imC=C%bCr~*$<mbxFz2{jZEmzv4toa`Xw`Wa5ftll?pZiX z8ybpSy%UB46g*sl=&ZRKTC$ldb0kZHWIeJ2BF+SMhudEq(Mcv41Qx+}9f-^FF4lIb z-EF1mBrB^;DI?5fq~76U_UBPBY=b}Y;&6kgj1$sct~!@`z}>n!Q7-=G%{G~-j>!qZ zGxz1|;)%ZJ942jVpzi~*=NJcP_VGxuqAISb=O+T)zhjZin-{$WU&q}JR_)H`M=y}W z=o#o(bV?)+b6_7R#)dNa&z>v&K5*+xF@$}?s3WGi4O+UwLpVLVBt9^Uhb1QPJ6-0# z;^O0{QlGY&6I&=LY8qE9RNq>gn`3y;mX4?{GAIFk8zJ|)p_?R}`YRaKE+4;HEStYn zmVUtUd=P51B@I>P()if{yih6B417djnqX&q=@piXXu1ef)!d!9Opt|HF4kI%+Jb9N z@R;gOJDd>21UpO!F7w-DphOSZQePhA_dLGAVYZ{)>dju`E6(LKT<b#ox?79eWlSdR zn`VQ1#Fl(Wut}^H|8`D#bhPtU@s_phNQDc9kniXop2PCQ!N!`Zw@`BNA84LXh=_o6 zUqRZSw*x<Z%r$y>ks=4|_x8TXOA5sOidjBY$8e$zfTZ}E<U)t?fGxpB&=L#GH|9e* zApwZD5kYFW*&R0w(5Z^bkaCWSF#Yk#p7HKYGav<0{V>woYv~~?1Clwbt34a}_}q_g z<R*bALxO^$ACs#{wNOWoj_jd|>eba)74_F8u*t@N=1B6V$cp*Qnbe71;$d)_p}rY2 ztx#w@-etSP4LQ_xRaok6Yy`g1@DjGqHXIH04)zPtb4EYg56~rQ8hYyxzn%+W1-q4R z&t~=^I|}l<4B03os@HQNLGMpnWdA(_M9NP;uEwAFrEz)dUGshZm$nXTTzqbeh$*2W z3lgzpi#rE<XuvPvCU84gfl(TMviLiu@g-}uUg+x127tZJ$LAt*mrh~}i3Avzb&u$9 z<pTB6sdnrDJL#;54ju!2wQy?ZpPBrwM;^{wQ=m5Kh|2B#CRu8e>IZFx4yzNEJ)0z# zo}QND4qXXsna;xtBG^hQ3ZbEaSpC%L=~2~D^>Xwt!&u77%bTvISGfA5$7W31vO0Aa z>fMg8E4jh6CEkHkHT~hkv7z{LUllD`Ci2;<(#*U(Z%wDM)Z6SRx;3X;TUvBv9kPcd zdJ!=doKL(0Jjqdg`V0E50<>7!B)uf1Md>$^)un8gex1p=I%TE>%gQvke!CkOo;-Xb zlTu;g6V{2xvnS+2#{?s%ih;l}X!9sYt`5la&=K<*`e6{95Xd<qiZxsV)K|U`o*Kod zeh3YO0V%xm^;6VU9T>_3BO}~9C|x28lBqf~XXRf45NWa-1Mf4(Fsd(3)(lJPL>7ip z=X`nQasJJwG=^!sOc>v8`Fs7RPipR%)+~jM!s6uF@5i<>(9Az3{^{9w_5deaK%~*N zs^9HiaN+yPTDLQeV}k6~2Xvwh{jr|prx!?2zm#pfUTh?CUipCa)~~Fr#N&h4l&g_Z zR8S;=(!a(vp!R8fOQKcsu2+Ce#Ld4c?x*8`wXw)2xcl~>ZC(ocGhx~`c<eEJbzb0o zoS}W(>FG#!@oTvK22>LjBnmV)UvbRnWcU!5q({q2d8}NO|9yaA_D!R&$e!QzDm6sV zLK16e5-5(Ehh_%Qzl(^ZSx<2@v=s#jvz=0_U4LWO-`Ss}M}W$|^NdT5z{JZ4zr?@s z*BF9%5m;1Igh<v1@uW%9x@Kk%NlC+e=+XoLeh&=_QhfQcL&@peKk@TYs$g=3i;ril z`rW(BnVA_$Yj<fn$P^st?}rKr6=g-(*#AXfWh-zTu^A~029~pE3#_k!F!ByF`-6RZ z2YYY~Dl5Chrk4c*yBQpO4-FL!?XIK|yE3%bIvl_-DTs+v;o2l5TxM_o@4Hk`d4Hez zfHu_g^OqbReM7*b9ns#{+baoa#{0&^{131$V=srr7t;bT<COe-3R(#zr6^zD4Bu+s zM;GnZf3H8#X4BBo(E%Q4YG&Hp+UkSIu)EodQgd5POiNf;{FSP*EY!G*{*Nbs>egab zR=t_zJ_VG{i$ENgfQnBC-JjW1r)tpwh5&BpfABjPQjYfa-1bhdJ6{k@h17t<FnZg( zTxn4isHc})P>?vMKZnB|`_B=H5`9#yq@Y}0QZ_U=RPC|moAeD|En@gaES>7qz>hgV z7sv#4_oe^&#|<}TDE0q<#q`7R2X}PzO{;=Hl*hP^4@_RYI%_ME=pq*ev)=X3sq7sl zU@Cw<fUJ$bOi4WWv{Wm5{(YAb<d1GUUd(<OZWR1$OKNG!fRFdz|MdRZA`<uI(_Mhv zYo42%o1?Cu|E)R@5kW~yqrA(BE28)J83%tTr7QUVR1y5Y5+7KDgGxY6N4>18EDp)H zTzO*T(-712ziigefiW?!MMS{;9{<GBH$Ok0+kN%tj#iB$HLnH~ABhIyRBhNwAM>*Q z)4CwCZ=Ex*Y1CGP*+Sit9X14|$3#SN07E%hOgkPGXu;hTP7JI|(XRi_?^1|6P$|-F zr0_$B7-DjufkthlqG(<AmuDcmOZRv_zyJ>v{7m`VIC^iUFA?k=93X^P70OscA88bs z?EGKsy;W3P-P$flh@&BdU;#pKCpf_&xVuAech?HRgA*ipaCZyt?yg1Q?p8QGzyIvr zJ^JE|KL0p9`ldHG5sFG>t-0oW-{*Y<xXS^)Mc7JMQf)X9dw1uoVe6kZJrG1@Y^=-E z;{9+{?wC*kmI-JK0o~ma=pa6%e7HD22g{t#)0N$A^=o6fVth#~4KT8m2i|{x&kJ;? zEM$d{m&~pI<Muu>p`wPx6>E+p^0bIW>5J=5O;5|Kssf?@V^iWxi8_!v+o7Nk@U8+r zU6Z?G4@tlVEE{~~r&FN;z_x(0FfYF_{dhfiO(+nBiJ7&svi1mCxd3z(c&ujBxVoAH zfE?J?Sf`jP1>Bbz)GJ~Fa*Qu_S>tGRW*bZ*wzszhpCiBCiTKwY9cy6#Il>(xp?PY3 zobsHAs@x*N!W7iCl_Oz``@69;;G8=S(u>J@zyR*OVwX3r9)bS;OU`z$(IZ@KkLVSe zR|~;01{kF7@XKN_8CQ(@)C&i1aj^@{)xJf0Rd)nZFthkT%*KCxJvO5Q3dODa!EWr= z4G_Qu+9yE#h>r!%<D`Ig@5CzpEjcpsqsKc*!%3wU9_h>Ou@k$K3ihA+87*D2pUXe5 zwl4eJGiW!)P}rm<RBpwT$_a=;{=Ft2jDW=*AgU&_=-n-_+e8zrY=mslY1URpEj`B@ zJ1SctaZq0uta-m;F`!Z`A0&#B`Smgm^oIIfAyhOpGkNlgS7dXQTR5N12d^DWM8a?g z-@e_CZ22Df;EGB6U(Z|yMA7hT=MuC5P^)n<F@N=&o^1^!e6f1*@MhyvipRpnbQd!A zv%ap@!g{A8<raYsKu>qFnCUg~Mc;?PYIPrnG;4vy(LfG~B)tD^%WM0z=wldndG3~~ zGV4^AWPDFomw%w>2Tn&ZF^B|t&gPF#fJ?vE+6?{IV|(upki4RzVm`=CP>@oymaJ^l z!Q=OTV*v~R(b&0q6?=h-@&oayKkQFQN%#mVI=ZA$GSHXU%$JGCJv08-tV}E@EbRLD zL3{kvFjH3`ar1s?8#^Q0FSOm_UoZH7F$1LF$LjX>?!R(3z9vKiJ7b`C`_~>#si^<I z7mo7pyZ=A=z)Jq-xj8xJsdRz0@g`DIU_>VYRJFdozJfn|ogAG|QBZgmp^P#m$?u=| zc(1Hr;7m_WvV3NBaB=8j?EcKc(#{VQ<Y}d){5u_Bw%I;D-_yfIOUuIek)Jk(&NC{a zR@Kq5YCpU2`U9{J_VB)^#%}|-V|~55n3&l2meRk};JAGkak?k7NN6bA6-GREH<_p0 zGjO&C_+jhFKw!Cl;82*K-voi9ol)_%ak~*@WMuF*LgFJMBhMXxHE4y=2)(sW1p^i~ z_8zc=!Y7J}jRl%7Q&V$LrH}(NY1+;>DoUE*4p4MAH}fJIxr0LWvhK{!KfwFuikF;R z<?2y>yYOnVv-7LCc%N#$TW`$*SoMEezkmJ3#Ka~qukwDzLjwP4jnvD)J3T!;U?vh2 z5g`<u9u*ZHUUKKy?Cu)G(D#l?V{vfsZgEbrA{r#H=jGi!{tlIXcN>3qau!I^t0Q5w zMJA!jaS7z|eW|NTCHdJ#`ue+DTll!doD4X)xE6ccLAkEPbx?{qhp!TWhlV3ueTP(t zfCeC@u1+R8oP0{QqNfMs6{mJ_>%B!KJBKUe_!RN}My{@|-&@im$@c1TF%U>9b|-9> z6O{)*ycrBKD#u)D8G=2ChT%u8T&QYa!US}5q0HZU%a)gy=a_Rk!M(zwnr;*>1O;*? zFGV=!+qa?2$<fjM!1Dp^5Ef$hOd-#j=jI#i6$%O%O$LYmRBv#v3k+<qTnSkN@G`E2 zoRCnZMTE}uG#lV!&VQ-5v%_)AR*T(%x_tjwT(rHroBpFh|9TYw=EK_Vlr%I$Nz95M zhV~MS^a=54UJG06v^cNasb41u7%CLI4;hG}_hExPj>~;011$?TJ@5OeKfrK=&)t@2 z0~L>zPB;CZh=YQkc=|JdvInj6Lf+7>zieH*tE)T?T5JC<AT(2|eX)5#b0SIHtExH! z+7!@XtOIT$qfGfKnP!zaCKj%Lv5c(lQD->`fS2EAX&PHvYAMBJ>1-Ts*4W+S`7p6> z^(*g9S}bpE<z{CGS2r52cmlsDNg901v_e&SA)43SX0Uu3okVtz`eUz#3pjjYW+oyi zsI`V#WkQ(Xi!;bPN@X=Dh-~j3t#T^_@T=c5Fp<qmA8yivn(vb<7fw@x0Y!Rxc1a2O zp#W%8rQT_)mdZ0zQi?TaDJNb2e)-v61QR`{&UHm=?&J(8HuydWu;m-N==f0Hul%+# zwx+L<|0I>dYxGHj4kXjRdxyhfGR^W-$T!NM+rUDr!QDTy{qWa5=cjBTmx`?vL-;$N zpl#i(%)*Ik4_Oq@M~RB|{E0hVy{-hr-QFH`kjN<P`w%UB)`iEFh-P>Eb>c4Lxq~0A z`pi%ef?8HlRwx3w$K?k}>>;=`eY}OK>1?BNQd5I7sh1g;FSr6&5=AhkRF}tEtARRF zFg?}h14hAvsYoJl@*2``Vzbd;XeOoiOfY9P2Mm8Y&5F^B0h6kU2wc88gIs!j*Y5$r zn(QS6oStvm%wZ3{Kw9Sx50{ik`|ETja(z?JElEQ|YM|TVNvs=`Cn7HH7MB%Fiq=+; zSb&FATx><7V$bWcIaybkdr}3a=|h?|&5$D*Ev@9p0ssjhk0&Z#;8k~OC=E<ofg#v; zgx4KlpkAt+n>tXur(bh?c#w3ZfOt<vty&zO*%|GB#;665Ll%>d2r@>VbIzK7zNG{A zC^dEU%yiQ1C`>C9z!d^G1()01HW;QJI|3yl$hT0hcYOf9C?~2ddTT(@i+LcxM4n>| zKU|7pz}Rw|4f-r00h_med%f~1IO}}u>rVg3jA-*1=Cx+>+=BCV=w<(6-PxZgdG401 zs@tF=umeY(kEZOqZJEH|eadLT4liHb@dn&j7cFZ-o@l99ET7MWd($>$))x{elQyF` z5vK$_-Yq4C85uv!Bf^TIa}yLGfsWqL8W$U()_g%m7xrzVf-EnIoU0uGa6nCbvER2A zlk>e&V`7ue%4#16A(zMr)o?yGBm3Ymqvn9?Cs-K!?Ii$z#()1l0Pr&QIm>~ApK!<5 zjNt9v9Z8+AF!2pv^QjViO6ef&I#2OwY?+vmbbd=qsSK59pj*JEU1u@683cokrDiW= zO=P44`-QcW`D&U~4t5(YFpzHTpWh7Zqu<X4baNm*^$!eOGXq>k40~9EgQn-jWZ&A= zudvX+Vxp?i;;ul*cJJZxynm6`%QHgSX#c7Y@B#ihUr6i%#@)e<PIE@3kB{DA<8`C} zD72)hNhs!VED!b>_wDxru68TPko~Iq5(W_QR|-`b{LW)cx3Gek{Cv`*{vJHPdYSyi z4dp2S9BH+Rp3T#IZd6Fj!XkZQYu+GRu@&?+(Av$6^XLtRk$O~A)a9jHWP^f=%1{DL z{JsX?*wKs=2HHE?ZP&G%DH-j0*;GdIv2*66P~`6z_p9Y%C#%3@LCo0?&<dVEe_4Gr zd);1FNhSR(fyW6I)#n|c3r%%SI!^w64MLq<-4#oMfp<Hy9YUYpBs~y)8?4ilgX35s zPEJ5y1%(vp%X<+fqJ{f`t$fx;9NYv@s($15jEU!x)fp*G4MrNfi+V$Jq?N5foZAkl z6#6f@?6<$y)T}pETn9(16ae_hk0^oFCH<o`!Tx-+^38iIZV?f(mQvti9E9A~lLT@t zp4V8-X%#xB0IRY%Dpf)Vuv(Uul*|GZvZG^6eu6qZvShgzg?cdC3%dafd=<z~p@%z; z#B8`zaIiDjn}j|}Vb$^^GZ^~wF6jui3I3>pq^L#T{MSx2maciV^gXuw3=Km*?qcJN zBFh8_-#~%}76HM*%3*ka%l;IsbWcM=lccl3ZOy#+i<2_hJqs>^B#;kx%uZGT{L00q zfwcds@55{ZutF8A-kp@ZRK~=_1Y<m~%$_M!T|MhFngYEgPzriKE)kskbc^zoWwUi~ zq0?z3Rq=C`CE#jUT+v%Ob%6^!4D~E5bZr<bDVZld03n9LE@BwZ@$}GMFw)RqWz*yF zKgk7v87CANL9dhX4)L3zdkT$eb>#>hxgQ3nqzV6c`wxlOW}oIO9gu}2V6P5|u{0NB zWiek|Y#%?p*o%*5Ak)Fni@ybr;*WFpwejo$BJMKHeZ;Uq#s>aJVSDxUdi(27_E&)6 z)*%{3qnrK~@mo2Wf^I$8_nJ7Bb@+k6w7){6N@<}=X`Rv97r(aEVi&n4naCv9jIfon z+=w46QDoMG+5IuV1(w@kcOBh?$wud;4Fdyl3fa6{FjGKFnoWk4tfFM0V3$OB`R6<= zR<k9A#%ZdH|Ee%DgleVmM!0|x^m}tIbfU#Vwfvt2iPZX^Cmq&x@9*nNqS7&TS7iJ{ zc->ApSXjRHE;7+0c!Ne!6}|T4SB}%cl0)~Tc;8Yl5>*sk(lq>#i&364+8fN8V+)nK zUZE3~p%<)n^>k&rd2hkloZQr|R$BvIY&tqRz_dcU!f=Q8{zWRcH?8i&U0x8#_PDnC zWbfn0t>o1i+}YjPc;jtk0|dcURb)etI=Va?T={r7cpvM%FPQ9RM!ThxKqv<>)9Z*= z;D~?qI&P6?`@X^Dl_SVYZL}GoC&dJ^^_`<53g<9jtuGCR3I!^qCnp_&KqK{S>CMsr z@Un;jnLsFnY!%k`A~^ijD0P1UU*I1a9S+WDiPzRMVq#->t@kq6m_p8o-sAOiy*Ba& z%znTenyWGLEe~FoS&nY}_3@AN%LN1#7dhL1j6R~u`@)WRwlG#VdlLr6`{&!z5tvkc zo;-{PpQECpa(SQ20pqC+i<Mxu0Fdgazh*X*KCuMly0}11WV1Lq?W(LAG#u7?UqKH? zB3?*uu5Y-$z7F>PA5T_q__d(mkJ$oX0fuj66syimK2r$$`!!f$hF-V5jqU%ur;(>_ zz{BTlo#%<?4$}td(|CqIB{YxYHq%Z~ISR^(_*~{3<4Os9H&{$e2m!~(LH8c;D(i)2 zfsXFg9cnWiFb2N`<5(;@9l_=@gWM7v=2jS_r-ttCbC%<$M_>Navg0lvFllLWeL$hv zz8dTJ$4q0afdL8D<mK=UMpze2%KV&%$II@MTJS$k$jF7trDSM}O&%Vw!<34sGtPIN zBpK6RF*!Yt`N2uwi7tHV)^aX1HrwKSQE|v~>%6~+`cG_HJcBxEIOOu23dowP_a?Ek zP-#F6NUW9R>i59FTfl3TjHP#2oyCwcx#-XK4Ggy&D+v48B&9!8uu%~){EMCF;aT5> zrT6MVGbQsvi;2sgGeH0v5uMo4fv13b?uXfWBZI(j?NMi6Jr|eSUlif}OZtGU*7_%X zmQl|8FqHI()6IG?`H%+aE-|qjuaVoqxgUjr0W|c7<He%DZd`8rU<1HkM8g2fA{>4b ztf1B+UL&MkZ>!U|s~}c!hV&Iz(0MR0s^bOm$anXt*a2)`L|@xx%JuEpX2lt|%G})M zTT&iTQjhKT3LM~ck<Z{jK3H2F`9ywyKL#t>)32b3r|af7-G<pU#YP9`*Zp~=U4JoO z=M}=Q9smG4e*RYUquRU5FOIwVW@dCtyUbL8RgYy9i(nQ`FYjh4Rkbhr(P)yD<^1A& z;=$J#)LrJgc;XP0xQ8!*Q3d9Hr!Cm`O^Ym~q%3szq!^iaI5_TZZb)X|3=KbE;1WOH zis$pcC^AZNHl+;PK=lanwucjc$|%aWx|K;{quiuOWMX+L_4pQcF5U%T7Z9nzkge+b zc-xTAM;j6T^EB)xxbT_``gKn6JS|e8P;|a_K`fX>xFxbmo*e5Y-~||N7@PyIvwSF6 zuG{RN5po8NCIYGv$M_CbuN|;kTn7KaSu;YjX#d6iHG1;@(8vB8#FdY0{hv7}nK^YK z{u>MU2d>DV1sO)kpxXJrwccSBipPIl3<dtxe~V!N;E0|w=+o0>fB_k(sr~*J#v0Ri z{Qq5==tHRalm!T~Mf$oU1VWd9MA-G6w<d_L5)H+nziX5jj=kKUGtko`&O#S@+7U$} z3g%wK^4B2lD-abQX#Hc}4-_aD2B6@6lA8|D9vNQJQNOO;#R1KzbRyft=;S+Zg^q4% zd-bfN>ug)#vl4<OIq(}ea8m(*EYcVdZVTiqb0UL*o$6waG#3!jufYGHjs6iJS%GJH z31@j312p@yR<?vPFk<pl1Sj%JJmos_TY2}Zo~O&8_uLr%_429H-W15(z@~goA{sH* z@Cbq=w+@>GzwAxcN%|wjdUGV+g<36?H(0<45#<Zc&(A>&@AY!qJxC&3J^>yZfN$F_ zJ71=E|EJBjb7iT?!)b4tF3jcwpZ6j#|Lk1RV6_1J!Fqi?Vz2dz-YGCU1_HD`Yyb+X zl<Y6&$=A=+M{CpOQGMZ3HDCN&e#{Ig?|;qCy6c^GU!B7{@}0Z-r;=)|(|Aml4x4UY zqCV_QM`DnOh74tXsjmn5n#`Zs#0MIH=2&iQ4)<wL{X&hEDpv}7?k1gPjWtx{YHX1I z7L8z`+J+$u7jYBB_#r{?RGz4O9Y3|-=#NNa%vYLY0$kJfqkPpCfuu*mW^aLAn$;O& z<HH}qfbr0mLQx9pDIhi)NMg{P&-J|suTFDJ{MsS~Pzm6jbtjq4Nv<jr5}s1O$^h~C z!Iun52lDQC2Kkv$TfivT+S-$ZP$?5vGJR&%u68fpH*<7!ysatrPn#@IP6BKwdXI)@ zCLe({WKRUq>lp6r*4FPQ20cGzL8`j{pdb|~sUw(n-n<Av#b+(hZWsm#A($<HdXxPP z<fDSHItHCa4!2V!-xn8PB+hQveifA|@;LdmAB5A<#CJt^N&2NJ<;qx*WZDEr*{yYh zivkQ*C42b+Fbam4U?PD1@a#h#WM$%Py-&MFN}XM`_E)aV>+|@%slo`K^p)O~4&b;Q zl(RLgT@OfM8!Nsq&C&$>6tY2R>*4M$^<60NVuCI2+ipmJoSKDcx)aJf&WeP#<JO0J zzm~}CSk`|oHDK$lWpNuWBdpXG>r~qxBu)u#e&hps5gWBy@Q5(uSVJqWWvwIus}cy8 zj>4@L{G=|Q?;L?%d@R$2Z|D1bJu3~Y*)3(?G<}7bV^^g_V~DTy5dfuB($WCyP!EX% zMNvkv?cHqDn7lltV~JL^Ow)_Dwziv_n_6ofK2UTYmku;ODXWMk<$`8vs!nb(z9Ax_ zxNBT$sz;7tv$H!{T6!$a2YlmVorZ^-YKy(;5@7TLpWI=_9!&B8WAGAay>g+BqO>#} zC8O=>x;U6|DCQ2y$t#!l8l~rFUjX(q5LO3C9|2xt=NMnS|3>3Aa*1}s?!FeT2?k&S z#?!0QJ_0Yd0L2{HktiNffXP01-2x-gXwz<B>35eEN2{Th@wKmFqn{XoUZY42)_ngD zDEb2td;&NKA_ZZW%BAWgY~<5}i;cQzOTAQ1dt-{gOO1uaaqP!6KzG}+x@D7<_~#if zPu2p1*8nivwW1ANl|x^l4}j(<E$t{#xo}qEwAR7izTKeKng<c_jvd@d&p9Sl7Z<RF zk%*!-d7e}#TZnVbgS1$rcR($paRJPmi8Y!Xw!$kb9lptpX84DI(I$=cQ#Q6~*;H<N zt-nDWbI*~nC-b%O12d~Frs;Ld1tXIglTD=3jP>>L-n|Qr{wK+EAvD+Zwao`+V@u!? z$mP603fvC%W^4>cQmxK5=~PtWW|4tUz+{~R_DL^!fqZ5+Fd{)gMSa7s^Viibh{Y@b zIy7-cGfoShFyLmxFU4y%{!EIGn%ey+m5146=q=15m2Tl+fu6(6>;9%%{8tXh3kFY^ zlaqtR_}@OdcunBHJXf*tc|Lira&NMD0@8{=>j&hVYMPq=KraF8_Un|_+3}z`276di zLW2Gg$UWA9AM!A!U48kz1@4sIv;6@uBmuFUZ+g-`d%6G0k!H+)`Vw6#=?f~^)j2~+ ziF6W67YL2r1Ie-gQz)D%U8pews*~Wjed#1N;5dTP^9CI~0_>rg{`x@JjZ+6AZ9#bW zSCV*sNG0HvuiA#W;{*Uw9w83S_QnpY`JkGjVkJfgDZVq19{<XbA{cG4nWCPGCiHgM zpZB$Ufrb}qWeE(I`!a+;;ET2E$*TW_#muW{9NLo;$k=MOsjJ|Ax!zfvbqZJULYW<K z(a)7ljZD!k!!?-DZnF2kEBcf$S17BSyk916(60&<RkCV8e#IbPVW?!d4w5y|2x63; z{Yt&+r243!(QJQ_tj^r!+r3|RCip|BtfPMfWteNW^fQ_W|6vt>+mAdK{p;lqq*ja) z+~|GuQ&|-KZpiQR^C-~?xL-L`yB`FADO9m~6O!9Nz0)2^t?&DT9mU+Buve>r<RF8r z#l>ai$2Db}x~7m2kLa-0ERkpgUc&i@u%~D$Pr4WDJdT>1JUaam03WhIuKewi|0z)1 zUftfxCVT-ZmwgUI;G*!H`MVvP<^1mE4VmmxBS;3u;k(ovFaX{u^VJrxd?7B+XG_gS z3*jkTF5d`ur;BF_MO{d%bEMPs3@ic@fW<>!=#C=tdukTvML%aNjg=<6#6*zMNx-U) z(<~!FBS^@`_JKkajp(PIl*!^%@cr$j2r7k{w2-$7usNn1Mrp>|ECYvSEVZh7z2Vp@ z*?{}SKH=LBv<tho9~P?g?g4WNK=KsQ3DI1&SxGBkfkp~$Pd9N*H~jSsK*iN6kTSDN z#7UP<a(3FC5=wcN^aU;Hw}T2FWf&Nm_C!Q!*|NB_)rz<oT7;p5H8yg8w)|%5_vUqy zsob~GDVq47@mFonu{q1OG}UxCTxvv$=PTNGSuLP;bxy~vOXEfZtk&aTm+WYH@0=|t zEnMf+^+OqH?nA@vW^c;O$?NvC*mUhb#e*o*7sV|_9&~fCdESeHFY{TaamUAC5HEKJ zETEQmeh5q2j`l=7ha7xyoNaVkSh{~1U#u3~5&7}J)XzSqNT-tUwPDO)O3NRsg|uz= zm{>D2GnGOep*sgh$96<Mhg~H=OP6aR{9@@B*uYh=HfsV<s}POe=&q2-$*C8pamgP6 zd(RHB6?X%q;rf3dC<O9I$CC^|FjXm5AuyqGQVs@o9vPc<EA4K{Ty~#g9v63*O?G#7 z*Fr<^oWZUNCc27=Lag}+j|e>4P*5@4-bxCt9tXWrVjw*$&Gi2gSXz2iku7O^cr@2A zQW($B6G7m+!h26jXLA1%r4v5i<ggVdo%9ARjDSzTGrqh$sx|`NzcFANcQ9KQ!Vdu! zcT33QjF#P@xZu><T_}ZBWDkt4V_;&gZLjB%`hVo4<zDRR=@}h;gKr!BmJBegtqe^m zbtc^}c9psHvHyA>e4#d6H7ryrxdVozN|QGiM}apMHI|z+aSWox=jRE`1qIY=-QiRU zS-t+q@p746y{>CuAj~KbTAEE(hj!iSb3Yz*6CBv1>baQA2?EqYuhq-GHJ6ltyfKsG z^jDP_=*Z(d153Q@=H1|A8U&P*XtI#7Flp<hMqGUSiE>3zyAPC+d@V^y_;~L2n}eKY zr=%xg9IX1w16vHURHs}V-~K8Xnd}GuE(b(<lFb(9gZkWDla2sXBc3K<;Je7m&c4uO zGP2-u)E`4N0mNbg^W}4$>zW#xvgoX>k1k%8lYhrTuo=pq%~xB|T<Ue_;!8R#gV5X} zx%4|w?`T^$d?S~K39o<&IHUSAF?1r{!C0D^%Y%h6!vHN@ozv6PX4jJpaIt|8(PnSd zQ;I$Ji@m*_9cZH+w4>ubJOp`OtL(GEGruPoQ5e9$rH|0tWGFdYw37nZ-e6Oh+Ss&s z?nJxTz^04Tkv+63dLYS;WynA)V_Z_D_lgRQZYQ<_oE&USjGgCp^935!iA9<fc`h*T zp#05|q!6zsxhOF~6trAiTvBg_Wa%^{5X%f@JwJW@4!9)%>B7n~p-fgkT(lEsVFqHt zRb?FDDQ4?vKVNAwQ&XH&TvYTD9XD~Go0MiJ*dCfV_5OFchLiinp|6<z*`{>1M7-H_ zQ5S$b;uv&)z5{L$ilh@q6WCiDRtq)zu$EddwtrRsY)9rN)klG_5$Veh_p4nFpT8#a zSagGdtzl)k3EVnaE$WF#-5&M~xaasd+iPn;c@dcSbh_wcvE;|BuOw^TsC$MsohB=c z3N^`DpV1;-jmc*e=hG1qYSvnX_?->nPOw|eE-U@DO{myVQYOpwIB5c5tkLR9in2z+ zy$xP_@7dT+#!u#*cJ-g@v65*{M5Zav4Gty}&mjUYiYntNBxS5V3XtOaO|n}B`z_v& z;^C<a$u#f#a+M?&D{~W*7Q6fUVokP%*;#!HARFMD=9=b>LuW#&FpHyC%n`3NuGQ9J z-1_}7!^>w1{RR~c?fNCkgL7^K8bQNkkviAG(f#>ON8=o`PScI-PY|B)Ra5lk(;N2} zrVlEv=g0&!T!wl^!cI@LendFkKxE+L8m#@~m|tE~o6>3OgJ?yqaJVqy6;rxd;) zjvrWGosgw!dQRtdVi!G3!IAR^_pK-m2!M1tnlF@X5eZAA)%m;J5eN^7r)LN(TCOfM zt<<Wve{S!E{GQHwp@smF_``<X9R?+8Rkf4*`92vM<oXow5%suStV`Fm+?E=jbFj$b zFzgV7u??uuC8jr9e=`Lt7g4}yZ;5ym=2oA!nLP%EDeN{&t{}Vu$VMDCZk&DEZk#Nl zYJ@|mhC8vSmA-Z~s)ZwPYS!%2#L;;Hz~$j*Jf)AwHgIwn82Jt2Ul*JS&Mz(`;^J6} zQu#b`zV<21q5!W7{}jizMb134xj9K0W7D-X7Q4nP`s8vJ7S_AF`vC7!SmyP6W@lh% zhmw?#w8Bjtx2C=NngNjbfmaU3HUqos@v_(L<wr-$=_0BJIqaR;^fjOjDb;RJX>dN+ zohl@G?(^#MG<%?;mc@K>77D#ztg8fMSz}}4r!P=|vlZEDdGq}P%%xm$YgjgoTUeyG ztu3A3=Pp|^zFPzs>5V}fF1cNgzb(#|(<QPKv3q+0i=sCU{Vf=8=txLT4x#I~cbosl z0ve`^7rx=^RGS;|^MBu0?wplQWD$}?r49nr8(@+ov@@O!3Umt3J$Ws{h$i=oPHr1$ z1y-=GQbn#*f?o^+Qy$J1@bm?lTOeI;q})LCY-5Pzu@3xCXtieZjrLRq;^{<L9<Ig= zNJvNqQS7?_Ib2>rWxm|NqvHkYx`#VH4&FEKMxG}3%kAK60bb52e>$n(j4Pw*9l$6g zk>|f$SvPJORf`p{ekC%wQ3ntJed0#A5Agq3+XL>Bn77d0D3UTpP;xc6of$V0^c(|v zDQK-a5;W9>aedTthqbw!cTYj%0Q@`FmSLHyM|iB}E3fvq)vgY$u<h2f0(FyL9S5Bu z%diKCL^~&6>`g=K4t_A-21sjyHaX~n#`xMJKn4x;Gs-oI@oAGVEnAH@H+$Va6$TGd z&u87;->sWe8{F2oekJvNkqJChgXng?Q$C*%SQv^n8i*?GwymJxjS3kk{)t5fugv=d zU1mt;iy)C2Pw-jo3W3!-AxB{aV4VLv_)0b8+h`~olf-V-id$^3HLQmBN+7DVM5Q>f z1%hBqXybN!<J9#t+O)+mp9%OBwYc2}e4M-eM<75LD2mchS68A*A)X5e>qvbmnJ3cn z59=oe*PH@!fjNPea}Iw#I2f552fV?vcsWLEU;MjJk%E*0UTHG?fyYzqAxzA`)$@X1 z<+1z1!nKo8!mt$hWr!0wjid^PWUS*T{1qF^8X(Tb6Z@n#V=B>_{?K`{1_Zb4CEBvt zImAg*<JtW|Ha&3x7gr|}qD7g>oDRYMc}{;6KAU&GDpD^xF&@(V6Kr&p3cV8IOeEK= zwl*-a*;-o*y9oa!ma!4gja_vEoKcjtPS3Du1?RQCUHCGE4dG9t;_~VPH=EhiTI)H= zEZ;=o1J&e4kB2^rtRE@J8pse05f`5&r;9pOx$T|c1HgYkt82t-y4TM}RLKvGC{Xyp zB2B+XzrDKp_AQ?K-t-W_cUHF<#L$a+;j1x~wr$)jopCg}I<l$^s`XH}3tj(AhcumF zo6pteQ$Ow2C>EO>dSo?h_;2q)j|z?&+ZWM9bS$J_KLka{z6m60C&PZyFX9dqRvgDu z9UV&#kDNNUi_t6IPha#)M0QDhu>qpkxZ(7pT`U?~jRGAZ`@q2|i<tqD-d|h{ga%oC z>|=-8TuHU2!5_IvNnJbYoo1EfI<BrBe6OelqXO`DR|zDHjs!V;cy#}?e3-x8o3e=H z@u(pWORBP-cyY3HiZvkrsMg}mgSbo{hEMS-TB!f#0>AS_en#Lt<O#bA`?J<BU%vFB zP^8di=n9OF95Cy%t;friwoe2otiD>kzl>IHRI}s$)JN;Bbwuh$==UR*S~=!Gr5{!+ zwC|!Xp&m+T1FItu<2nv$io!q<GhUE>`+_9&9q9q@BWD&7r@iCN`A#gg%6XhlV57mz z@c4MhP2``2I*UM#@L3a0<DFG9OmZ214ID^ns!mY%*pH7{`-2IUKS*KpeHiNM4_kSF z-{K=W_wtECvuRAHVb>aG2NW`us@-sGVr;yWtqFfiCQ)Sx#gmO{7e<`kxTNFZf%?K4 zCUZ89_s9==@ZY}uQOT{ZQQl*gvBrnEoMrQ_$Ma!7|LPn}8-qn~xE);+n2Zc-3*u6y zBUd}tW=f-ZBsE33vN6-PN86r?No%TWsuqAWzlPd;Ery>Cqk%_54HXDf*=~{X<H7cG z(Oq252d|Nk-Z8KZr;_B>*5+~`p7!bg3CY$57{Xh8iJu$t^04==a?6nU8ig;Sxt}7= zgD4s%--wO@H_zY>Uf#aNtL5=)WV&qCKkGyBHb+Z5rwmpLjj#QEU!dbM+KvL<jj>Ex z+&)E4^Cv^w(5`Nk{OGex(TK4M$nmI;k~v=Au(`r)9Pn<zxJQb1m$~EJxg!Y+MT&la z;}_*9Kw1k<PA=aEf*jY-p1W(ZKu6HS{4!lgCkcOzJ)B0Ro#T17S)9h>MoT&WQuZ-i z8A-IOo1GyZvNg5Uo)$Ouq%1s6LzxEO<?)TOQMRDUrYxUFF7HFe^D_f|{dGxwgBG6q zCFYZb??8-yhD|Rr^#)6zk&I2{WapMPPy6;#?RRg=?#@p7!&rtg&DU3zCPS`Q%L0#2 zo`zsCM0M||EzAZHvy9jI`1n-V&oD$M4$XefToNxl>XX1rXz=)(i3qK6lDrmV@f+Y1 z&VB|750~+Z48>=B^Mr9V1LQgfm1?Y4w8C#HUBuHJD=g;}s#-!nu)0s}PFYNs2s`zI zx_@INm8XYItH{;vS?d!YAF<>dUIMJx$&xqM$MVsH39KHMQ9`g1jfl{nQfBKW<6knm z;Aj|PXRA^U%gX{MR0S==!?BUpc<j!eCvUsfA#GyQPi~>N@aku6#JbIZkAZ`O<G8!H zQ!`-onMGPm%#JxuOAdZ+7WGNkd?pImZS8M=mZa$$a<YLn^O!EzF8SI$9_#=%hV>v$ zVsgFXp<kW{01;EN3C=pXnlqO20r6!TQCK+SeVyGJ`RO3e!|nzZA4nFX+&7vkoQjNn z_TssShzKr@XCXrd&!^g^CRc&#k9BSgdyvN;OI|libq=>}_qV+PLn%BC12qKBHO`bE zMss#luLGaO^4*)|qk{|IJd)T~6mx5jwJxt8-QrydDpFFKT+PU2bU2W&ykrqc&eTh^ z>OfB|G5k(OCbK70atBk9@8tUWMnCalm%XgAQnT68Oah1)7B)v;kfN<A1$BQ^#+s@c z6q2CmS1Ddtr8PD+RjM$|2Tpn4d_vrW1>B)UVPPOyp%kz}Bj=!h^?V}>Vur`^uV_)% z-&~XQMLbbdRJ;zS6Hoc#RG}S+1`?P7WBk_zA|`R+XIFlR5;Go)<zk-Q8i)Y{^xQhD zKp!9Cb`S2Knb}P*K3Q8{?9IU8XFW(iLnA*VE0&sN4FetxYS`{9ZI^*P@5?{{%Yl~= zbpVrI0jUQ2DLnPCPk<h|x95BL(N3+AAN{4Lqy>!+<xo{NfyUY@Y3{yS-&*aE)Lsi8 zJV;upF<4<fU78OV1xVvhXYlMDEX)9><La6k;J26gha+5NGy6<~+x5}Ov|~J%sF82| zRN-oZ>Bb~-UWs-c^F#B>T?eZPk(TF;ooC3;A3xUnNcx@t?*lV%+|BZ?>NAgcg+8Kj zP|VFOgg`j5UyrBlg7?Pdv3GTJoTS_dRu__v9zQufZ|4AW2g%z`kIUT<Oa}lRTUs3u z^4&dJ5k@wlzkW@?Vftzq^*J)ue1(&ThBpg3u31$q8sQfd)Lk4R>E(*b=2yALR$R_A zHlxQo6BMR^jG`un!J%66<Lvz|4-mOIySQL(>l&JvER@M-4kfWW9W8-U)G99oG;4Iz zO`g{k>FGXE=_}bG@d|0LKCvq)_jt81xGUt!NZjn8Zo^vSe~FxJjtJ>klvP#o?1qbN z(yIOCe=sQl+s|7J4EeueE5L!cRHNE_y*CO_jbfV$KohMZ4ZyWX#dK?Ebc+pajyd`^ z>T-X|VS6+kph-Imxe>+1VUaHuYL8mq_Qo^7eo;hwd;P|oYzOGxRK8?xYLok-eO<6H zDc=K7wHz(hWtJ4UKR$SIV>R%Tn6ol7YgV~}Q?%jjeZ?Mda5ElGu5--KT7rm|sB!}L zdqI#@a2g{HW<7O|*Q-+&(WF@B>tbumLgkhqaB#N7cbyPb-$L+WcWS=sVMp^`(byo3 zYvhlB(CLOlt9-q6(~TN5BI{%;1wfw6GX=`G(_u;ZvQJK|0LZCuQymc#{%CqxQCa)z zx#SO)Wt7*i4<~F8L?~685fgj6EaZgo#_xxFdR~gRPePox11XyKW=etUFxFE*1;Un$ z9!6;dQKG=Gmt$gXc9veFq6nZ`X32q|3f$bBv;DIG0FugF*_SAIp+@$5o6_QXYvdeH zWzs%dfCc<q0DxhI8pSVEUappq4XA|T^L?I+5OWW|jCJ<2b1q3%Fjh#WVPRYo8c@v( z^0VEA--vX~0W1vMAm3h2jgp4AkM}Eu*#oNjK$%q;y@zhF)+JW$FjJy7V5)#2D(Qjx zb#d0VyT$qb=W!t`s+*~9tB)h_+gAfJqsvq_ubk%8=7M?+ODW6mX++eLfD6C++w}S9 z)YLD&2g%Kz4Yv0T&M%AbSR9t`KGh79`cJW#EZv<yU|HOR#89gD>&z!)n|>DOL#uLl zJ^mh-MZ{zv4mwDS&qjTH0<!#)*>!nAT!B&!Ra0j8Is#2o##cIO>bFSmqt7j9l*CRu z!k+j%{18mQHI10J*Sm^4lQupQLcqE0vGIv*dvfd{`{)*E-RumX7?bPJwRN5RK~gKj zC&i17$sld9yh#4=V*fIwp(R!_K9TnGCx@-e127em)~qR3Z_tVR6G<e!4;P~vo0$Pa zvDAH8drtk@!&+-7&;@AM=?gD4x&aQ4%kk(FC93xf+H4k=i>+yMJfnBP6PiFhV5_oF z=g@E`Igum9G&-YOZhn!~r`Xik^iPICN50-8Fqa^iZ)CTcn{PJg0XcE5?hh0fexM47 zAadHD#MML#Cs?Sp#vpZROW8SC3p<-m27~wxk{odxY|@C=E0Cx553@^=Dg?ZqY9Nt< zo2Ti8_hVOhwM7dw#;7BJNNW|CBx6jbfgF%X1@hg4g<7#keRf=CcgL=uSa0@$4>Q6B zI6!}$x0PsOGk@gs2KaM(d%H%hwQ{zE(cpNV{9_CJ6lfKIbRmsIG`!rPJE$%`tEU#k zDuA?f>^9&;%42C+WK*{|Ur8a&Hw~l-;^mk_0K9J*5f>d4Pg?>M$pgH1I2@*<5Npyi zzRSa>5<fpZd20L1LFKFL8)1|`8Q(FwZ1um8{6>@ioUHy1bCWhmLhkdYO+^fh$Zx}( z-w>W-VV!V4*Q)6B-!OM^w1G0x?jeR;ctzCO02XI{37R5ASymS2eNUrOWGgg+FX$u0 zY13Hl5`9KCc<l0ce~H}j^;)CkS99uQ`I?>{?Ouut%x!&z&u|A*hT|lV=m9gp&Exzp zWmP)EK;6OOYl_sXwFtf%s|B{ngVioc1$YA?eC{tJ;aicK5zbuAc8!vep{5QQdnhUo z5fOLR-fN0*eE6J$L3dVaB8Q&f6_@4g#B<$P_&I+4w;GsU;GV}NHHRh#%g-;5fxP*Q z@7=ec@b#3(ewvb3L!HMv4%YB9a_JlQ^Sr1!SuajIeKGwN$IrH-_X&{2!bi2T4Vt@U z596!jNkoU-cN)Jnt((nms`6KU9p-4>&}vjMqF`fJqKNAe#bE2Y@9e8rrnworwlvp* z+4o72^;YBEc!*8Qy~@^b&!W>vt1Yki{j%VZPE-3!{n-yLP%oQT#^{T>S8}#~*$7qt z#sb<#wo6+cs8P~V-JEL~hqaXQOX_ZZ?y?r3|2-?Q-b&_L31J`v5T&@rhzX3TY&C>| zJKp9dXr%+-`&`>Tf$bUXJ?B9f9fJOl^D&U+aDZ21e^Afq0v+hr$bT<}6F=N6Ei{}X zE-^XYOEbCR_4LSWtiotk!NX<ycra*1kI(ZC76I)3Jw|=e1G^Qr&BceV_qWIN7!D?m zrW=VbueZ`rp_u8?Z`q{9+Vi=ZHewqdZQcbeu`Ok}Vd&bB3#Pm5Pfm=~pKN-+B=p?A zJ!<B$vA#@w{~(ik2ffDDwqNq+c~7TR#XWLCZqs-vQFn?ZC<*_&7P7C{usffcn-T@3 zuU{?LtJk%acV#qR53<s*!d^0ZgnYQ$9bg#J9y{PunWkB5c|2Z7i>}Dh@^!)^&tCES zB_mxk(!8;3u*D)hovcx9!SI`p&&eO5Pz-PswuX}8kH*h4{8pyla5NCkWNX!(s;dtu zxSY}Fsesf8CT8IMP{AbFqPlHGNBuXTN)E(n;6fVr#KmaS>2#DnUk&7`mU<7wX?pWM zgv>d3HzYGy-WWt2*su6wKir*-<gX-RAsysq)6~ZqcA&Bk_V%is_h{GVVFxJCX(w_X z!H)*FuvT?B?6M!*t)kCZ=glT^=<X*}|7Lj?xlR`)2scUM+zx5}E)z)?*WQ+`obJ2U zj_l%WA#-g7d9?>K?CaN0(Kr|{EOF-0ddzk;r|a4~fmyq3(q&+2YgTvMh0r~#pHD>@ z_J$|P&STx-ChvW_p6l=&&4c#h=*hdwz;mJv|J28hGLa=0sn=-?oVv@j>&!GLjO3go zetkc)u9p|8+|Ixmuj?_SoX_Q&%^;QM!^m^Q^}YqA>-vurl*Ig!s8pmxq1xPaMsS3C zhcB}OIi6NWt4xw!H(xMqK8?>_=$5WXV~M_IUthH(+5BYdpVg-DCy&h3Y~2H%!2D=C zk77{6Y%<uZDxALl97blDm{@JTp_%eS5L1{Wu8~@~K&{r=H#BtT+PM-yuy=_L4t`lN zllhtyFzA#1#9{4l$hT1*aWl$>mZw@wG~AWPTN_-r*orr@a6!J`s+78ahbWTYAq|(2 zS}|}zaEa%eVjb2dw9t8v`S;k0g&Z5NRwaMdMXs(7GN!8EVZX=X_yuEN(@FOfujm|) z$r^QeZJ}g(BmJOu8tL7U?6PR5dKmMD8;RaZKHL*i7}gTanU2f4^{7jR`q_B&DSDvs zqSK6yc5-~6^AZv>fBM$r4<Ban+iQj7*#w2jmoT&3wDK(-2>R39>v0=-fA#20LnF%J zmqvN@bI+SY@Y`pR6m`?PC}mV==XzX-2dGB9X{u()EPU=R-$Uy2p2NIA|Is#wR9stT z_H#lc*1L~_*}*K`#e(L-Zk{RCTXII%>e=rK^7GX|yYCr^!)5c96Xr=TF7_Z-D*)v+ z%I?&#nYme$<&{~EXrEQxo3?6xxG5Y*k~9(p#=V8gg@h_}>Ri{MFQ(s%d{?*dlP5?k zig~`qiyvS1Q@rIJb*NVu!L9tMYE=!S6};+GRs9hnDgpw6g&zV60s{ZLXWzgdGCm?a z1^d?XCpZWQ-zi_Ug1`DIfbaqV;Uf|vBlv^-o9|B$5P}4|vd+0(tQL-Iy?veDg1@IU zkYGj_NsfvFzvoX_e`^YU{a<{qAOHP-{y#tR-@Re~xnlphV*jlt{67oke{I3=BO>sl z{CBp_|6JJrwF~=y9X9{DV*j~f|Nq##(kBs*e>_JnP%2LRr;efmxeoI`u92e^6wIHv zc87)n_6-I2hMdIiRB77hv-qr-7!8C6`&De<8JzrIKVi0*)u)O{L7JTf-U=Nit5f0H z$F^XZ1ismtHf_Ezjf_qEN{^X}468u3G%6-wL;Q3Nl4Ch*m`N_prqRr;o><2s244Kl zNU2!E3L3CiZ92x}uoDf3c{G3)YXjJ_NeI=w*kh5B=s1ljDhgv;GCZTxEK-C(K|}k` zhbv=xKd2iCJ{$vu)YZ!EQGe`+Op6gcC@Wth$OT*zd;_of->wXmq$Eb-=xXLYL+Ak2 zS2sDqWGL}^Djt(U=Blw%lG2>G5Z3>`L8<7(NteVzTMl>brG0FaxjI(AjB;r<#ZS-a z`f5rUp8;d#+@cJFO4?E;1Ub3<=gXrtYBo(G49$$jmqqGb2`$!8_l2V}#c0|zj+GEt z{3w&h>yd~Ao(45ZN=M>C-Pb?0si*hrOY^Jg^Jw><*7eI&7a<vC70ird3iDb(HQrUW z8kH3&j?1W#{2L2UftITqtKuS1D&=b<61Y$E-5o>qb6_8<1bOd{n}%vlqfB1YRm7Ju z761BytbQ*cx35rcJmV!@>?B~MN7SqATwtV3Nut~omr8l6zBirm`#HU@a!SQ+_z+V= zKLUTc1RcUmp=!(34m;Uk8r;0<Ep7U;ph`<L7PuH~PM{41jDkY#T$u{I;uEirs`66` zg(}6HL+mtwJ9!u*oy2Yf^N#SW^iR(+sQ(au3oEX*p1SD#WdS<ccf`X5I+C*Qu8W*3 z<du9}pv7LZ31>1?7U18PkexJgh<);1usA&F8>@B2*zYYJ31*?8#Og2m4Uo2-JvK`m z9kqOgY|<^n8hHbqs~M_XG9w)TqXg)0Jv4Z=Ld}e=C+ZJ4w^GyY$Q)-)U}a&}__lCN z-#|3?<tRWbAT=7_)bwjID>SUmQ^@Y;fiLm7_Le==LO<J|pS>iS|IFId!F%g(n`E<j zpi#@YyFCmht}^!RR!2#<e$Y&b*aC5%dqb=I-6^W`=YAartCFQ+5?M}3H4D}=Uxv=Z zQ^E(~lhzGKb%*tP<iMhMMKs*|vMUGQCi>>ciG7j7wfESNY^oIYErRDUwMYay(okkL zAss!HS5?qt^SE%9n%9tOtDqnuA=f_DvV!nY!)TR$bfIdV9>p0eS*Ll?rb`nnE%wXi zjSloBBG)6Rcmno|o6V8Oi{1`&>3gnHk6IRHo3V_p;R9#shqp3(Yx^!RuPvfm7mgD@ zdQ?0Xj?1eO@q{X;!h@QH606T9&Fd9~3K&&}U!1<!-=nf|Y^-AnR{w)fQKWfFEZHr4 z>UeEr`=T#0Fgu95F8=f6zo+H6Et)VM>=i@Wr?8MzE|mc+($|n$n0H{-*LIZ>ptVTc zX~6EKIB<a#j@>{cCHh8lQ}1?iA9cKjoL20T*Z_74-3-_(l~7UGW?3{CY5FylE#T_} zWFVK*nl_|`@@C2&9B+EUZfW6P@4^2IT*zl+T0{6~2>L3CE<W_-ahf(fdhOVIr7Z&K zBY;xL6Y6-+Xw=UNkXlQV!eR{qd$rtMR&zh&xwDb5rQ&-+R`W?ucnW)(=W6-s22HY? zGdzra&amyrH98cc+lvLjr|T|kXW5D#o$vy+^3`RE2d`eez@z$w!+|(Dg4515gC6h5 zn8}Kwjh2TSi{{{tG+bu4)7m7f8ez&-s0;M^RFa{}rbTqiARIM#J3)SgU~DH6_@e}> z*ib4LC$fTl#ck{w3c0z?okV`Tg%KsOLI1jx;xD<bRkIx72NZ;Vsh5l`PzHP5U9-m_ zEeIzIV~3|P8J*@NS;w6WFx=l<>M%mQ?_caCPZfJW>o2^2pWa*D-;(y7GLAqXcXpXM z(k=JS;{>%Ak`mOR&f&M`zmC7{jwpI)jq+GrpDtphap<+P6>HFG*E`OZrK2xFbfYKo z?Htm)_lhd5e{<+xl?lM1zt&M!FZTHU$Y+XhVx~1iMqBrTt1aLS7vYS6Wa{q%<@`9Q zd85x_Ue%TW9Y$T?S&QHCvZ=Eh%Mcc}n)XzVimj%tMQpjTUv2d`+_vk(3Y&)gnUVH7 z`r8Dng-J=MEWz0B7Jm36cWfd@*fdlh<)(SRWrLq4C+5p%y5p<@_+hgue`1(>O_%sE z!1_;NN!{Ub_{&GHBQKf$;C{Uxj)%X)nl{za&G%WO1!$R_@mkKp;6wwb4ibL7%Ta3j z2&ELvZOeH}cQz9obLog4u-P+TIE=2i0Ilh1x_gr4<axbCiRpM2$U@epQ7$-#*24UD z&ByfjY)JvD7Vt{UOIt2a^GqsUGDmP;`mtHn>Nc8=h2k<VdBBTnmppltSAdSxd#ji| zke8L?8q*nOPzqE{A6%E)((!sC9%=6f^&qn)P74b*sJD=}HK&yZA_1r=yY92zw<+Nq z^$r}wo|iim=sb*!F7CGdW+qm18Acpz0L<fY8D3n>j-A%fvYYFILmzFohdI`lJC;1p z!tBz#YTD!17YrP70}c4L9A_*KlQ~@$m*DZb-me`tq5OKOw#$!9UhV+&%#!gXE9*)1 z00Tsqv3CC*6R!HRa&Z0_s7#+?bfhBTH;t~*!-gHya!J|=Z#&Qx59=&i6HL<ZRSf&0 zyU<bU*^7+TmX`=Fc~-I%+hd;8Uf!SdJd%=CJI;-JQtTpOQ7?|er@>~A93fQJQoeN| zj`32`9c+231%Bv!9H#BqX@Q;|&b4P2QzbkuhsoYv)`>h8#`5?j9nH5#WgWxpmyO0C z%;_~k8B$Cz#<Db<_2u=IY>4~iaKj-g@yepZu+-0Vg$;?lqK3nR`&({i&+R9V99M0( zFPEkt<5FDa=(w>ol+wK*8%8`j-Y5Q$6lb6!U>3A6t{kdGk+qu1IRMLQvGIEIz}X6- zSZeWj-z(JfVTSeDW>!{#YN7MNMU%qg{K3satG4Nq5q;m?mV8Qc!nyQJ2}J-E@euFj zk?7`d5_b2!V8Ls}lKRaXK!)AhCgGAv`ciFjl;og`!_;xo<1E8Bu28O@y;h%kHyKG$ z(tNra87CBk<okh`YH08`xQAPTlU{@6+8vg;sm`16x-x(7qLBqzI7x4RF7CT^dtLTj z5O-KkvM3XiF+1;z=BBw_whEx9+NspeIc!QLo;z7z<CoOx#2bX;v6wDLi4vyVbbq^r z!mo}Z9#Nk%JbIt6gr}g%hJ`FSI>3(E2i=aKF3w->dQt6fzPevD8<`nz@<s4+v+y*e zxd`kct+1eRuaA!AH2%4rOE+p1uaus!{O6G^Xw{QNue;nG5K`X%J3-%M{g<4EhGw6c zOtPhIG+ov|MQ^(-Y6iXw^^V}_z_C&Lwws>_xb&WiBi1Hqgm1-iNh%mhi$6>k+h5N6 zuU{`e^%bmo#noMuz>IuI;jiM9O&u#VGqT<XBXH`}BYvXed~LH9s3+^Xlf5Nmk7i-l zonVqp9w(u!TByR~U}l_`mcY!UVYRRs+(X#B`;#yXJhGmZLyDsRY$=WKk|z+D2Tblj z)27nO^c-IQPyn{Ai&|866pO@y<2G6D`MsM$m&H104rhL*VS513C&|pllSAN8`4p~F z+XvgmKq;(onY{7~ykb5uF#+pAr&{CUSgEHY!gGJ@Wru`+`bA`$eeTQj(~k6aUsS@6 zZb}!0V^=k#05{?oGvfX%jaX8(Zl@bxg#*&Y!v@b6LOW2ZG#(mYXgWSSNlWqU#}&V1 zeGZxB&wmQJyOJ%fzl+Yws)2d;h|_#wF`q8LLVCZnk=kHwGiJY+6{#qvV7rwM1Ij^k z6qMT$Jpy!eppG2y@bk^sd)=b!u`P(ifDUzj<k%ivmwm<KTSu`wN`{@Ye*tewVqIWK z&K}uYvYL=i%5Bz?+ir{2@x4K<7`R(K)0X8X>)S~{)wW5`)EiFb^f;>2mB*)dY~F|J zwLGbX$9g~cHl=>{goA~@qqi^Ok*Wfl4`suDb9l!jIrg0_@V=PL=kq$b{yVC1Z#+Kp ztL3)Fvjz#Ac-I3AJdb;&XP_|XLc;2wDb#)Z71488wkES85KBD=Jzl7`!q@QgIBWv7 zwPrxvNkCW<DMYZhJ|8j=XS5;XxxNy(Ih?}fxczHI8oAl<Z1fZe8-T8ED+MB-7Z-p! zGvGyC^^BRxyKhLwA4%x1-m>++D3M=zKG=N?&gi^DDrC{UK84bU6J898;IpDWqLOIn zd7*Q!m*KHmSkD;WPI8wv-6(8h{ARjtctaW;U4MEiSpwD)n5`T%!FkcS&rJipE{D8! z9|YijAK`<?xg7{%4bF4-7(%I@t5En7XTyTqLbWyKxXrGLEWYJ&?aM<VoXrnB?(2M) zN0E34FxNF{YSohQrrYO9mgBURy^N3>eX0BFT$P2IL)T7u25z_|j)!=q`kAtDUhC-C zn-^joS5m#4N6lv{<~2nd>f+-57{&A{5uVP2c>#NDjmPaFMBe8oi@AUvaXl7D<jHI{ zF3WdcdlEoXTZyGSwIT)<T<Y28(4Jez1+kv>U%fTPlj(<ATCK9<#xoDUrSFR=1xf{9 zTzCAp1m)1t1JK+ubedk=9zo>t4j4P**Y9}EaaNO9b_&T7B&T8D?&&nEt~~GlGQPuA z;bb+R0ezcZh-FPzoQKmad!u;>ohCV%(|&nRVEJ~AbW%X`_vwXdafcMQ{!;K6dV6Qx zlT@r~VRrp+7=w0V!mZLQJpnqO`^;qn%mYra(%N@h0QM9#MUilvR2K&7uIgU;*}MHG zeL&=u#W$<?bKbx=W&S{;TBG_YI&YQ8kVcq2qGlWFJ8pL4Nx->{jyA6`s|39w!#(#u zjiy0kZnE=cJ?acT%W+9onW9`<h5v*r2SwmP#oVT5<5hd5o=4NwU1j_^2SfEBW9SFA z&fB9F%r8{Yd<JeRhrwT*ena@&>}M0bZlE=i4cbC4Qu!d>3Y~4yOEe5kk=mQssfA!r zpHlCn3pv=>naCk<JWdDvX0t}UH9>Z*E4gj^yVv)(OCSJq{~3askxCEQXnunlmQi7G zO~TFDBnxx0=jutr{y2T8i`U)Btjm%&je69E`kdF+z?qadE1A#>G@<4eS#_N{{f$~3 z$id<n15z7PXV$*GO3}L;=+=hT+!ZKm29ww+sPch`P{-vU*8l$~>`ecXO4~T@OpTU` zR->aeshO3Tp_QfO5>Gnjm?^pM5aqs$2`VC!MKz`^YFbJ;R%l_k5u%Z}Rc^VY87`pV zz9A%=D9Cf>4|qP$+w<bQIOlWU_xJw&e%Ezfg!7|&Cw{>*NEfKfydt-!>q%?H$Bm4# znKLjm`b*uMul)mEUF)%|eQzRH_-r6sleB3@zQnLUqWk%&1*UWI;8Z(yy)ECw*-id( z{9w|;0224A;n9wOO2nVwlM5cc!{bRjxzpZMA>b63q*+=BXqlnlZjzrNrb^A|IXEyz zw7J1^eX=UI9FvJb{2lKKz~GR1ASnJ;pMuV1!f~QGZR%MkI}2NN5SJCl)GvhnXqHQU zqx!4)I0D1c3qv2RHoHs_DjuqgW6*2N=QBPu;X+gB-GbK>YCWH2;A{Aw^G%y}1-ma) zSW`_rG4>w`4)NdRn&v%0s~i=BizR2d@wl;LrvhJlCFfGgiTXhqL)5`?s3L0erUD|` zPz?hI`<R)~BEC%SKzewRX4A>m)Vs5BMdro@vT!dnD{_u6=`yWyMOrQaJkv>j@I0Nr zX%mYcevKlJ#xtY=;|nw~J(9^rY2H9?EMuC7d|?F1YvPj;Q#wF9?|-6eF|V+1fuPn4 z`NyxG=h7yAGJYnk(uPG9husHhZ!W!uJG^dfqD}NNkT1V0A94HSW^^q8Ki<iNW6#KA zIP+>#1PphG(|kjN9bO`DoqE}9^6K(Pfr0_!ybn%rat&75=@Gp;moDI1N!`AdZkc)O z2|YV`=Y^P!R@?)Y6lcxMA}mBJx%%Zp=|*BrdaBRbR`nbF(!2?m2_>cNF3x|P^BSDa zt1-CM`#PaRi^{QYZ_n6}8xg3xlrBH=#~U>>Vdyrj1fr$CjBAD<$JT$LvZ9Lr{uKhH z5RQeNMo!<Uxe9r=mfOwrFQ2wj5Ke?5>A_>hXRPuyu(H82C`e%+h@y!xj7NfxqsJoN zhjdQ*!ZJ1=6~bTn(|t%eR8migB3cwR<PkgH*M48qb>LVAMiPup7tIuc_Pn8|OD}x* z!ZXJCQo_iea;#Lz0zEWKIu?(kpRAFuU=EDH(^yjgt*NT1x2XZ6MpIFrJ9tecL0n0+ zjRLTKE5uxM;G$VOP<>nFPWdE+m(86WBX%d2?re~2*nA`V@)`-lb%}#H5Pci*r)Nw& zDq$tYv)q-7Wc@%a#WW^YAdJpiMdf4NCxLrKYd(h85G|Z{N611DJJ~7?V#xP@j=f5r z<H{XCF{5u%-HWN0y)Q~P$NK+?f0sz%FQ@7RvC4WhUXLs?&83<I0&;PI2CAn3*#Kwp zCC+&cfMQZZ|I}1T-brsgOYAl`PD^AsL3?xrQMhdgM)x6~lYriHzcCQJfYpt~!VBJW zBo89;RWpXShrb?Ob@P0BsUZD?_yxEw=8NHUmYb7hkU3_bb#@(of6ng`=i#j!?}M8I zEp9fvU6!8hZ5r0^2MuqdZBGl$_pWZR>jqOcxK_pSb*Z{Jvb7hQ0yjvR3lsRG%gJr0 zp14>rqw6!q%f+k;hg?DE6ueA<n=m6&x21{SnFw*9da}Yu({6ZyZ{%4R^U{4@zj#;e zOeS97@UZnpzls+D^tKi?f$i&Wut2Y{gcQ7&!E+uT0Tt1SzU(xSnm&4=TP&AMa0`-@ z)#Ofvu#Du|EG`={k>|GV+r+RcK_Z#`MBPu(dHXdAgrnR#Om5OlQUh|vB11|{%sX@y zXwxYcKESKZng_gp)eq7+|2V#Wg+vaFEk@h?tVE2{zNZ9P<vReIw&nVWj>8Mtk9Gra zRGn+lDU9e*EAD^^k67*tPaS!2-)@Aah;2`nhI{OHP=;juAo>a$MZNt86~iwyiH9<5 z#V>$cL-N4Os@5qeM4ED25517=so<xy>P+cO2g!VXF2(EYcUm!rU^jZK!b?PBRaRhV z*TJUyUx+soNtMO~+bh$L`_tiX6A+3FZ0`zc<#@Zk&TJ~b<(vW@<`IYI{r;SNhvPtp z_ZxM(oq#}W(n6xr=)q_R|9hLg!G|8Ju!+m4ek{5KP#$h*oV_+u`{rw7!iA5lGfDcm z<u9I4J9)mRn_6i!AQ*0mlPg*)d@8Jw*Ti>N6;%w`$GNGt8|BY{p+tF5y;HMzfMaHQ zQPP7t?6i%iec5Sdox86StWRMMuhw>_36~)IM5~&R6iD`Pxw*4$&GsoU0t@ug#!S{Y z*)LIE^%PYd`OO@*t7w6Z%!rKuKyQt}BjS!{u%tS$v<)&XUBabxcG0PDpnxsjz&6CM zw;GB^Kq1<<{_=p%ut5cL*&o4*DL%%1!?}p0;T*$jlx!a{QKy6I8*x!xP6pT%j-yTq zuEjC6q7r}--&gMvb_6p8WtX@vcjvk5eYi4Pafbs}!ip9zKk;Og5CkMgBPUxulGWZ^ z1o0Ub<>j&esRm^7wn5qScvj>GKElqd;b?YVIj>C>(@OBCw9a-&u-y+@D&Lib55o*+ zG6X$Lo8|hX70<x<!9|{)7z7nHN<U32mSEP=Um658fKt@5J($7^wM*e&#qs(%w_uI* zYiE!2xTvnw{2o7WRP^;h%Y9M(fsJcA`Vfosh(yc&uF=G~;6u>OI6S4N<MGbiaaU}d zDV)J7u4*dMLa~S@W58ZLJ~d$4mUXl5VGB;S*PkJS=3KtrRv|*gBB5WQ8X2+{6=m$E zG~BoZ8L+rxEp32Vl97w8Xt38T5R$wq2_%^zGz_=I5iwzQizUI<dWk|A0TZW9<%IPW zNqM&oY-j6%jGWY3J=a*N`N*Zp7Hd%6Kte>j5iA{XG2-srQ(fgZma*fuCv#OBT_beW z)o&!z-$vuc&xC7HZ)&TmEmZgY)K;SJ@mcCSe6-T9#-jo099A=S=b$NS-2;%kAx9JN zZB343jIO&s!Q#yx<rnKhgZ{>`1@8*)ZrXNLH-8gHa&pAWxOb6)?A^N&Cymfo=C>}O zzC{A(F#K(V`*;@UdbJN3sP>$L|EX*K6aXv6k_9jvqcMTgYEN@16<q=}6+Ws#;Y#_J z3m82+yZk?62DH4vK>MV~uMzV2Ls+@7xphxqb*8tTvb}lZa@W6l<1!?53DUXyaIZAE zQiioAdJLsz`JS?%beLDgRkz%7La(d>s~b2Kz|$pkUcKGWk9@6RzXkHGUmfv#@%Qq} zOcwV9-F@yBjktT%zxK4`Vs6G3a?JIT6y$t=`7QO-)xRgTPUIh>Lyz<Y&e^OC6vpD` zxV!QpIRuBx?knsjPgOfVk1Ri+55<?cUiiGpd|#E0Qg!VSuPT_431}u4*<&>UbT8PQ zTKpG~7;9Y%T%m0hkOFD2F+Eg3{l2%9!t~W2!w1UGO{7!6VZD#tW#>bp3?Ubv>>VdQ zHup#5c6L3~3f!L6*y^EeTvX;SVG;4Q)JXbO-DH#c?W*j<9}C0C6v=f+fnpw~Z;|%3 zW5-_t@0f@}oq$Ng6fJc{=@Y+{LCT%94q9fUWpoRvgLWnhXPD+bv`f|C6VZ1uQ*1wD z>c=3#0Ku4xVuRH~v{|?lNm@ad0i1d^33jF217JQDaC*+&-usQ@y<`Q$%*Y9*98aI9 z_#lvRR`qX=PY(}hC4(v*-HD*U0~0ux_rK1XCd}_UEv~Pmu}7^Y9-VO120r3M;9Nze zFmywJc7(lF?K`t|wdrc9ii^jxx0z6DI<8Ntdi~MpwuD>(dlc{{+^b+UjALh_zkRM^ z5Kbqy&K-o=y8|wTPu(!_N>0_r9{;7xu8y-0Dzd%0NtaNhG_alfH02xFF;O+hvcfu4 zTdz1Y6>teMfvgaaGWyRG>MTwL2pTE*Z23QVqyKFI%QTgg6pF(GW-h>cl>f!5ElNga W^bcf>#CyOKC1*#sKPv4nXZ{zD4|7cb literal 59613 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�+Ip>ecY^cU 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 AzW@LL diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-dark-compact-linux.png index 3ca230f729111f496ad66a981e5674cd4c948a08..185b2bbb35faf1274625527105a90137dfc3cb07 100644 GIT binary patch delta 51655 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 delta 51814 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-darwin.png index ccedc5c20ff7a59775b7d100413055f8ec7739b8..2511e0b7b343dd5331131232f0e5883ad1f25e5d 100644 GIT binary patch literal 59037 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 literal 58684 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­T>!SG`xBepTgi5PEEMX@J5L5cS3QMR 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-comfortable-linux.png index 14f4b8c72986e13ebd6e9f1287d332ef96e79c83..7082bffc9616f7a8078d99d6c014c3b2ea1f2b97 100644 GIT binary patch literal 55952 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�BY`e<lJL#(p!3!umtwef*|2D;$C?7Dtve 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 U&TOydPXGV_07*qoM6N<$f*Z)<NB{r; literal 56167 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 FV1m>{x$Xb} diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-darwin.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-darwin.png index 6d25bf645ff309a21fa93d9e695733e7d0bf18d4..6b58250e64450806c7b0ae8671bc61a821c7a8c4 100644 GIT binary patch literal 58874 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%0Amv*^1|uPA|@{BMj{a*Ey$ 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 AB>(^b literal 58519 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 diff --git a/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-linux.png b/web/tests/theme-keyboard.spec.ts-snapshots/analytical-light-compact-linux.png index 10637b0af8dd3f536078bee0d84e54b287d928bf..4608d8077ac123857c8cae2387647a9237626bf4 100644 GIT binary patch literal 55742 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 literal 55969 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%0DG1 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 From b32120b215bb79443084d789529bc4f490e13f13 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 19:44:21 -0500 Subject: [PATCH 030/102] perf: late-materialize explore and files listing pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The base explore and file listings (limit 50, no filters) took 2.9-4.2s and 8-9s on a 2.5M-message archive because every participant list was assembled for the whole archive before the LIMIT: logical_entries references the classified CTE twice, so DuckDB materializes it with the participant_ids/labels columns, forcing analytical_entries' per-message list aggregation over all 8.4M recipient links; the file population additionally computed concatenated participant lists for all 208k attachment rows; and COUNT(*) OVER () materialized every pre-LIMIT row through the window operator. Two-phase listing queries fix this: phase one orders and limits the filtered population without any participant list column (the view's list assembly is pruned), and phase two rebuilds the lists for the page rows only from message_recipients, messages.sender_id, conversation_participants, and participants — the same union the view's lists reduce to, via shared SQL fragments so the expressions cannot drift. Chat conversation entries re-apply the filter conditions in a narrow membership rescan so group lists still reflect only filtered messages. The total count runs as its own slim aggregate. Requests with participant/domain context filters keep the legacy single-pass query: their predicates already assemble lists archive-wide and the fast path would pay that twice. Also cast the explore occurred_at bounds and the deletion-staging sent_at bounds to TIMESTAMP so the TIMESTAMPTZ parameter binding cannot trigger a per-row ICU timezone conversion (same hazard fixed for relationship decay earlier). Measured on the real archive (engine level, read-only): Explore 4.2s -> 0.30-0.37s; SearchFiles 6.7s -> 0.17-0.18s; Explore with 10k search candidates 0.33-0.43s -> 0.23-0.28s. Fast and legacy shapes verified byte-identical on the real archive (top 200 rows) and pinned by response-equality tests across 25 request shapes plus a page-tiling test for offset cursors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- internal/query/duckdb.go | 9 +- internal/query/explore.go | 193 +++++++++++++-- internal/query/explore_fastpath_test.go | 312 ++++++++++++++++++++++++ internal/query/files.go | 154 ++++++++++-- internal/query/views.go | 16 +- 5 files changed, 642 insertions(+), 42 deletions(-) create mode 100644 internal/query/explore_fastpath_test.go diff --git a/internal/query/duckdb.go b/internal/query/duckdb.go index 8e0936fb1..e29604972 100644 --- a/internal/query/duckdb.go +++ b/internal/query/duckdb.go @@ -94,6 +94,11 @@ type DuckDBEngine struct { // executions (not memo hits). Test hook only: memo tests assert cache // hits and misses through it. relationshipsQueryRuns atomic.Uint64 + + // exploreFastPathDisabled forces Explore and SearchFiles onto the + // single-pass legacy listing queries. Test hook only: the fast-path + // equivalence tests compare both shapes on the same engine. + exploreFastPathDisabled bool } // DuckDBOptions configures optional DuckDB engine behavior. @@ -2239,11 +2244,11 @@ func (e *DuckDBEngine) GetGmailIDsByFilter(ctx context.Context, filter MessageFi } if filter.After != nil { - conditions = append(conditions, "msg.sent_at >= ?") + conditions = append(conditions, "msg.sent_at >= CAST(? AS TIMESTAMP)") args = append(args, queryTimeUTC(*filter.After)) } if filter.Before != nil { - conditions = append(conditions, "msg.sent_at < ?") + conditions = append(conditions, "msg.sent_at < CAST(? AS TIMESTAMP)") args = append(args, queryTimeUTC(*filter.Before)) } diff --git a/internal/query/explore.go b/internal/query/explore.go index c6463fe13..2c79b1b35 100644 --- a/internal/query/explore.go +++ b/internal/query/explore.go @@ -53,15 +53,24 @@ func (e *DuckDBEngine) Explore(ctx context.Context, request ExploreRequest) (*Ex if err != nil { return nil, fmt.Errorf("read committed cache state: %w", err) } - conditions, args := buildExploreConditions(request) + conditions, conditionArgs := buildExploreConditions(request) candidateRankExpression, candidateRankArgs := buildExploreCandidateRank(request.Search) - queryText := buildExploreSQL(conditions, candidateRankExpression, e.parquetPath(datasetParticipantClusters), e.parquetPath(datasetOwnerParticipants)) - args = append(args, candidateRankArgs...) limit := request.Page.Limit if limit == 0 { limit = defaultExploreLimit } - args = append(args, limit, request.Page.Offset) + countArgs := append(append([]any{}, conditionArgs...), candidateRankArgs...) + args := append(append([]any{}, countArgs...), limit, request.Page.Offset) + var queryText string + if !e.exploreFastPathDisabled && !exploreConditionsTouchParticipantLists(request) { + queryText = buildExploreFastListingSQL(conditions, candidateRankExpression, + e.parquetPath(datasetParticipantClusters), e.parquetPath(datasetOwnerParticipants)) + args = append(args, conditionArgs...) // membership rescan + args = append(args, conditionArgs...) // total-count scan + } else { + queryText = buildExploreSQL(conditions, candidateRankExpression, + e.parquetPath(datasetParticipantClusters), e.parquetPath(datasetOwnerParticipants)) + } rows, err := e.db.QueryContext(ctx, queryText, args...) if err != nil { @@ -116,7 +125,6 @@ func (e *DuckDBEngine) Explore(ctx context.Context, request ExploreRequest) (*Ex return nil, fmt.Errorf("iterate analytical entries: %w", err) } if len(response.Rows) == 0 && request.Page.Offset > 0 { - countArgs := args[:len(args)-2] if err := e.db.QueryRowContext(ctx, buildExploreCountSQL(conditions, candidateRankExpression), countArgs...).Scan(&response.TotalCount); err != nil { return nil, fmt.Errorf("count analytical entries beyond page: %w", err) } @@ -293,12 +301,17 @@ func buildExploreConditions(request ExploreRequest) (string, []any) { } } appendStringAnyOf(request.Context.MessageTypes, "lower(message_type) = lower(?)") + // CAST(? AS TIMESTAMP) pins the bound time to its UTC wall clock. The Go + // DuckDB driver binds time.Time as TIMESTAMP WITH TIME ZONE; left uncast, + // the comparison would coerce the naive-UTC occurred_at column to + // TIMESTAMPTZ on every row — a per-row ICU session-timezone conversion + // (see buildRelationshipsSQL for the measured cost of the same hazard). if request.Context.After != nil { - conditions = append(conditions, "occurred_at >= ?") + conditions = append(conditions, "occurred_at >= CAST(? AS TIMESTAMP)") args = append(args, request.Context.After.UTC()) } if request.Context.Before != nil { - conditions = append(conditions, "occurred_at < ?") + conditions = append(conditions, "occurred_at < CAST(? AS TIMESTAMP)") args = append(args, request.Context.Before.UTC()) } switch request.Context.Deletion { @@ -388,6 +401,136 @@ func buildExploreCountSQL(conditions, candidateRankExpression string) string { SELECT COUNT(*) FROM logical_entries` } +// exploreConditionsTouchParticipantLists reports whether buildExploreConditions +// renders predicates over the per-message participant list columns for this +// request. Those predicates force analytical_entries to assemble participant +// lists for the whole archive during filtering, so the two-phase listing fast +// path (which rescans the filtered population) would pay that cost twice; such +// requests keep the single-pass legacy query. +func exploreConditionsTouchParticipantLists(request ExploreRequest) bool { + return len(request.Context.ParticipantIDs) > 0 || len(request.Context.Domains) > 0 +} + +// buildExploreFastListingSQL builds the two-phase entry-row page query used +// when exploreConditionsTouchParticipantLists is false. Phase one selects the +// page (ORDER BY + LIMIT/OFFSET) from logical entries WITHOUT participant +// list columns, so the whole-archive per-message list aggregation inside +// analytical_entries is pruned; phase two rebuilds participant_ids and +// participant_labels for the ≤limit page rows only, from the same base tables +// with the same expressions the view uses: +// +// - a non-conversation entry is one message (its anchor), whose analytical +// participant list is its recipients plus its sender +// (message_participant_links in sqlAnalyticalEntries); +// - a conversation entry aggregates the message-level lists of the chat +// messages that pass the same filter conditions ("membership" re-applies +// them), plus the conversation's own participant rows. Per-message +// conversation-level lists are constant across a group, so the flattened +// concat in the legacy chat arm reduces to this union. +// +// The total count runs as its own aggregate over the filtered population: +// COUNT(*) OVER () on the page pipeline would materialize every pre-LIMIT +// row, and a scalar subquery on logical_entries would force DuckDB to +// materialize the doubly-referenced CTE with its string columns (both +// measured slower). Bind order: condition args (filtered), candidate-rank +// args (classified), limit, offset (page), condition args again (membership), +// condition args again (total). +// +// Output columns, ordering, and pagination are identical to buildExploreSQL; +// TestExploreListingFastPathMatchesLegacy pins the equivalence. +func buildExploreFastListingSQL(conditions, candidateRankExpression, clustersGlob, ownersGlob string) string { + return buildExploreFilteredClassifiedCTE(conditions, candidateRankExpression) + + exploreLogicalEntriesCTE(false) + fmt.Sprintf(` +), page AS ( + SELECT * FROM logical_entries + ORDER BY occurred_at DESC, source_id ASC, entry_key ASC + LIMIT ? OFFSET ? +), membership AS ( + SELECT source_id, conversation_id, message_id + FROM analytical_entries + WHERE (%s) AND (%s) +), page_messages AS ( + SELECT p.entry_key, p.anchor_message_id AS message_id + FROM page p WHERE p.entry_kind <> 'conversation' + UNION ALL + SELECT p.entry_key, m.message_id + FROM page p JOIN membership m + ON m.source_id = p.source_id AND m.conversation_id = p.conversation_id + WHERE p.entry_kind = 'conversation' +), page_participant_links AS ( + SELECT pm.entry_key, mr.participant_id + FROM page_messages pm JOIN message_recipients mr ON mr.message_id = pm.message_id + UNION ALL + SELECT pm.entry_key, msg.sender_id AS participant_id + FROM page_messages pm JOIN messages msg ON msg.id = pm.message_id + WHERE msg.sender_id IS NOT NULL + UNION ALL + SELECT p.entry_key, cp.participant_id + FROM page p JOIN conversation_participants cp ON cp.conversation_id = p.conversation_id + WHERE p.entry_kind = 'conversation' +), page_participant_facts AS ( + SELECT links.entry_key, + list_sort(list_distinct(list(links.participant_id))) AS participant_ids, + list_sort(list_distinct(list(%s))) AS participant_labels + FROM page_participant_links links + JOIN participants pt ON pt.id = links.participant_id + GROUP BY links.entry_key +), total AS ( + SELECT COUNT(*) FILTER (WHERE NOT is_chat) + + COUNT(DISTINCT (source_id, conversation_id)) FILTER (WHERE is_chat) AS total_count + FROM ( + SELECT source_id, conversation_id, + %s AS is_chat + FROM analytical_entries + WHERE %s + ) +), clusters AS ( + SELECT participant_id, canonical_id FROM read_parquet('%s') +), owners AS ( + SELECT DISTINCT participant_id FROM read_parquet('%s') +), canon AS ( + SELECT p.id AS participant_id, COALESCE(c.canonical_id, p.id) AS canonical_id + FROM participants p LEFT JOIN clusters c ON c.participant_id = p.id +), owner_canon AS ( + SELECT DISTINCT cn.canonical_id FROM owners o JOIN canon cn ON cn.participant_id = o.participant_id +), owner_participant_ids AS ( + SELECT DISTINCT cn.participant_id FROM canon cn + WHERE cn.canonical_id IN (SELECT canonical_id FROM owner_canon) +) +SELECT + p.entry_key, + p.entry_kind, + p.anchor_message_id, + p.conversation_id, + p.occurred_at, + p.source_id, + p.source_type, + p.source_identifier, + p.message_type, + p.conversation_type, + p.title, + p.preview, + CAST(COALESCE(to_json(f.participant_ids), '[]') AS VARCHAR) AS participant_ids, + CAST(COALESCE(to_json(f.participant_labels), '[]') AS VARCHAR) AS participant_labels, + p.strongest_matched_message_id, + p.message_count, + p.has_attachments, + p.attachment_count, + p.attachment_size, + p.deleted_from_source, + (SELECT total_count FROM total) AS total_count, + CASE WHEN NOT EXISTS (SELECT 1 FROM owners) THEN NULL + ELSE (SELECT MIN(pid) FROM UNNEST(COALESCE(f.participant_ids, []::BIGINT[])) AS u(pid) + WHERE pid NOT IN (SELECT participant_id FROM owner_participant_ids)) + END AS counterpart_participant_id +FROM page p LEFT JOIN page_participant_facts f ON f.entry_key = p.entry_key +ORDER BY p.occurred_at DESC, p.source_id ASC, p.entry_key ASC`, + conditions, sqlIsChatPredicate("message_type", "conversation_type"), + sqlAnalyticalEntriesParticipantLabel("pt"), + sqlIsChatPredicate("message_type", "conversation_type"), conditions, + clustersGlob, ownersGlob) +} + func buildExploreLogicalSQL(conditions string) string { return buildExploreLogicalSQLWithCandidateRank(conditions, "NULL::BIGINT") } @@ -434,7 +577,31 @@ WITH filtered AS ( } func buildExploreLogicalSQLWithCandidateRank(conditions, candidateRankExpression string) string { - return buildExploreFilteredClassifiedCTE(conditions, candidateRankExpression) + `, logical_entries AS ( + return buildExploreFilteredClassifiedCTE(conditions, candidateRankExpression) + + exploreLogicalEntriesCTE(true) +} + +// exploreLogicalEntriesCTE renders the logical_entries CTE (appended directly +// after buildExploreFilteredClassifiedCTE, left unclosed for the caller). +// withParticipantLists controls whether the three participant list columns +// are projected. They come from per-message list aggregation over the whole +// archive inside analytical_entries, which dominates listing latency; the +// Explore fast path omits them here and rebuilds them for the ≤limit page +// rows only (see buildExploreFastListingSQL). +func exploreLogicalEntriesCTE(withParticipantLists bool) string { + messageLists := "" + conversationLists := "" + if withParticipantLists { + messageLists = ` + participant_ids, + participant_labels, + list_sort(list_distinct(list_concat(participant_domains, conversation_participant_domains))) AS participant_domains,` + conversationLists = ` + list_sort(list_distinct(flatten(list(list_concat(participant_ids, conversation_participant_ids))))) AS participant_ids, + list_sort(list_distinct(flatten(list(list_concat(participant_labels, conversation_participant_labels))))) AS participant_labels, + list_sort(list_distinct(flatten(list(list_concat(participant_domains, conversation_participant_domains))))) AS participant_domains,` + } + return `, logical_entries AS ( SELECT 'source:' || CAST(source_id AS VARCHAR) || ':message:' || COALESCE(NULLIF(source_message_id, ''), CAST(message_id AS VARCHAR)) AS entry_key, entry_kind, @@ -447,10 +614,7 @@ func buildExploreLogicalSQLWithCandidateRank(conditions, candidateRankExpression message_type, conversation_type, COALESCE(NULLIF(subject, ''), NULLIF(conversation_title, ''), snippet, '') AS title, - snippet AS preview, - participant_ids, - participant_labels, - list_sort(list_distinct(list_concat(participant_domains, conversation_participant_domains))) AS participant_domains, + snippet AS preview,` + messageLists + ` CASE WHEN candidate_rank IS NOT NULL THEN message_id ELSE NULL END AS strongest_matched_message_id, 1::BIGINT AS message_count, (size_estimate + attachment_size)::BIGINT AS estimated_bytes, @@ -478,10 +642,7 @@ func buildExploreLogicalSQLWithCandidateRank(conditions, candidateRankExpression arg_max(message_type, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS message_type, arg_max(conversation_type, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS conversation_type, COALESCE(NULLIF(MAX(conversation_title), ''), 'Conversation') AS title, - arg_max(snippet, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS preview, - list_sort(list_distinct(flatten(list(list_concat(participant_ids, conversation_participant_ids))))) AS participant_ids, - list_sort(list_distinct(flatten(list(list_concat(participant_labels, conversation_participant_labels))))) AS participant_labels, - list_sort(list_distinct(flatten(list(list_concat(participant_domains, conversation_participant_domains))))) AS participant_domains, + arg_max(snippet, struct_pack(occurred_at := occurred_at, message_id := message_id)) AS preview,` + conversationLists + ` arg_min(message_id, struct_pack(candidate_rank := candidate_rank, message_id := message_id)) FILTER (WHERE candidate_rank IS NOT NULL) AS strongest_matched_message_id, COUNT(*)::BIGINT AS message_count, diff --git a/internal/query/explore_fastpath_test.go b/internal/query/explore_fastpath_test.go new file mode 100644 index 000000000..c7e182be3 --- /dev/null +++ b/internal/query/explore_fastpath_test.go @@ -0,0 +1,312 @@ +package query + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// buildFastPathEquivalenceEngine assembles an archive exercising every shape +// the two-phase listing fast paths must reproduce: emails with recipients and +// per-message display names, a participant-less message, calendar/meeting +// entries, source-deleted rows, direct and group chat conversations with +// senders, conversation participants and titles, phone-only and empty-label +// participants, owner participants with a clustered alias (counterpart +// resolution), and attachments across MIME families on both email and chat +// messages. +func buildFastPathEquivalenceEngine(t *testing.T) (*DuckDBEngine, fastPathFixtureIDs) { + t.Helper() + b := NewTestDataBuilder(t) + gmail := b.AddSource("owner@example.com") + chats := b.AddSourceWithType("+15550001111", "imessage") + + alice := b.AddParticipant("alice@example.com", "example.com", "Alice Adams") + bob := b.AddParticipant("bob@corp.example", "corp.example", "") + carol := b.AddPhoneParticipant("+15550002222", "Carol") + owner := b.AddParticipant("owner@example.com", "example.com", "Owner") + ownerAlias := b.AddPhoneParticipant("+15550001111", "") + b.AddOwnerParticipant(gmail, owner) + b.LinkCluster(owner, ownerAlias) + + base := time.Date(2026, 5, 1, 12, 0, 0, 0, time.UTC) + + email1 := b.AddMessage(MessageOpt{Subject: "Quarterly report", SentAt: base, SizeEstimate: 100, SourceID: gmail}) + b.AddFrom(email1, alice, "Alice Adams") + b.AddTo(email1, owner, "") + b.AddAttachmentWithMIME(1, email1, 2048, "report.pdf", "application/pdf") + b.AddAttachmentWithMIME(2, email1, 512, "notes.txt", "text/plain") + + email2 := b.AddMessage(MessageOpt{Snippet: "hello there", SentAt: base.Add(time.Hour), SourceID: gmail}) + b.AddFrom(email2, bob, "") + b.AddTo(email2, owner, "") + b.AddCc(email2, alice, "") + + deletedAt := base.Add(30 * time.Minute) + emailDeleted := b.AddMessage(MessageOpt{ + Subject: "Old thread", SentAt: base.Add(2 * time.Hour), SourceID: gmail, DeletedFromSourceAt: &deletedAt, + }) + b.AddFrom(emailDeleted, alice, "") + b.AddAttachmentWithMIME(3, emailDeleted, 9000, "archive.zip", "application/zip") + + b.AddMessage(MessageOpt{Subject: "Orphan draft", SentAt: base.Add(3 * time.Hour), SourceID: gmail}) + + event := b.AddMessage(MessageOpt{ + Subject: "Standup", MessageType: messageTypeCalendar, SentAt: base.Add(4 * time.Hour), SourceID: gmail, + }) + b.AddFrom(event, alice, "") + b.AddMessage(MessageOpt{ + Subject: "Weekly sync", MessageType: "meeting_transcript", SentAt: base.Add(5 * time.Hour), SourceID: gmail, + }) + + const directConv = int64(900) + chat1 := b.AddMessage(MessageOpt{ + Snippet: "yo", MessageType: messageTypeIMessage, ConversationType: "direct_chat", + ConversationID: directConv, SourceID: chats, SenderID: &carol, SentAt: base.Add(10 * time.Minute), + }) + chat2 := b.AddMessage(MessageOpt{ + Snippet: "hey!", MessageType: messageTypeIMessage, ConversationType: "direct_chat", + ConversationID: directConv, SourceID: chats, SenderID: &ownerAlias, IsFromMe: true, + SentAt: base.Add(20 * time.Minute), + }) + b.AddAttachmentWithMIME(4, chat2, 4096, "photo.jpg", "image/jpeg") + b.AddConversationParticipant(directConv, carol) + b.AddConversationParticipant(directConv, ownerAlias) + + const groupConv = int64(901) + group1 := b.AddMessage(MessageOpt{ + Snippet: "dinner?", MessageType: messageTypeIMessage, ConversationType: "group_chat", + ConversationID: groupConv, ConversationTitle: "Family", SourceID: chats, + SenderID: &carol, SentAt: base.Add(6 * time.Hour), + }) + b.AddMessage(MessageOpt{ + Snippet: "sure", MessageType: messageTypeIMessage, ConversationType: "group_chat", + ConversationID: groupConv, SourceID: chats, SenderID: &ownerAlias, IsFromMe: true, + SentAt: base.Add(6*time.Hour + time.Minute), + }) + b.AddConversationParticipant(groupConv, carol) + b.AddConversationParticipant(groupConv, ownerAlias) + b.AddConversationParticipant(groupConv, alice) + + engine := b.BuildEngine() + return engine, fastPathFixtureIDs{ + gmailSource: gmail, chatSource: chats, alice: alice, + email1: email1, email2: email2, chat1: chat1, chat2: chat2, group1: group1, + base: base, + } +} + +type fastPathFixtureIDs struct { + gmailSource int64 + chatSource int64 + alice int64 + email1 int64 + email2 int64 + chat1 int64 + chat2 int64 + group1 int64 + base time.Time +} + +// runExploreBothPaths executes the same request through the fast two-phase +// listing query and the legacy single-pass query on the same engine. +func runExploreBothPaths(t *testing.T, engine *DuckDBEngine, request ExploreRequest) (fast, legacy *ExploreResponse) { + t.Helper() + engine.exploreFastPathDisabled = false + fast, err := engine.Explore(context.Background(), request) + require.NoError(t, err) + engine.exploreFastPathDisabled = true + legacy, err = engine.Explore(context.Background(), request) + require.NoError(t, err) + engine.exploreFastPathDisabled = false + return fast, legacy +} + +func TestExploreListingFastPathMatchesLegacy(t *testing.T) { + engine, ids := buildFastPathEquivalenceEngine(t) + generation := int64(4) + after := ids.base.Add(30 * time.Minute) + before := ids.base.Add(5 * time.Hour) + + tests := []struct { + name string + request ExploreRequest + }{ + {name: "full listing", request: ExploreRequest{Page: PageSpec{Limit: 50}}}, + {name: "small page", request: ExploreRequest{Page: PageSpec{Limit: 3}}}, + {name: "offset page", request: ExploreRequest{Page: PageSpec{Limit: 3, Offset: 2}}}, + {name: "offset beyond end", request: ExploreRequest{Page: PageSpec{Limit: 3, Offset: 500}}}, + {name: "source filter", request: ExploreRequest{ + Context: Context{SourceIDs: []int64{ids.chatSource}}, Page: PageSpec{Limit: 50}, + }}, + {name: "message type filter", request: ExploreRequest{ + Context: Context{MessageTypes: []string{"email"}}, Page: PageSpec{Limit: 50}, + }}, + {name: "time range filter", request: ExploreRequest{ + Context: Context{After: &after, Before: &before}, Page: PageSpec{Limit: 50}, + }}, + {name: "deletion active", request: ExploreRequest{ + Context: Context{Deletion: DeletionActive}, Page: PageSpec{Limit: 50}, + }}, + {name: "deletion deleted", request: ExploreRequest{ + Context: Context{Deletion: DeletionDeleted}, Page: PageSpec{Limit: 50}, + }}, + {name: "no matches", request: ExploreRequest{ + Context: Context{MessageTypes: []string{"telegram"}}, Page: PageSpec{Limit: 50}, + }}, + {name: "full text candidates", request: ExploreRequest{ + Search: SearchSpec{ + Mode: SearchFullText, Query: "report", + CandidateMessageIDs: []int64{ids.email1, ids.chat1, ids.chat2}, + LexicalIndexRevision: "fts5:test", + }, + Page: PageSpec{Limit: 50}, + }}, + {name: "empty candidates", request: ExploreRequest{ + Search: SearchSpec{ + Mode: SearchFullText, Query: "nothing", + CandidateMessageIDs: []int64{}, LexicalIndexRevision: "fts5:test", + }, + Page: PageSpec{Limit: 50}, + }}, + {name: "semantic strongest match inside chat group", request: ExploreRequest{ + Search: SearchSpec{ + Mode: SearchSemantic, Query: "photos", + CandidateMessageIDs: []int64{ids.chat2, ids.chat1, ids.group1}, + VectorGeneration: &generation, + }, + Page: PageSpec{Limit: 50}, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fast, legacy := runExploreBothPaths(t, engine, tt.request) + assert.Equal(t, legacy, fast) + assert.NotNil(t, fast.Rows) + }) + } +} + +// TestExploreListingFastPathPagesTileFullListing pins that fast-path +// LIMIT/OFFSET pages concatenate into exactly the full listing, so +// offset-based cursors keep working across pages. +func TestExploreListingFastPathPagesTileFullListing(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + engine, _ := buildFastPathEquivalenceEngine(t) + full, err := engine.Explore(context.Background(), ExploreRequest{Page: PageSpec{Limit: 100}}) + require.NoError(err) + require.NotEmpty(full.Rows) + + var tiled []EntryRow + for offset := 0; ; offset += 3 { + page, err := engine.Explore(context.Background(), ExploreRequest{Page: PageSpec{Limit: 3, Offset: offset}}) + require.NoError(err) + if len(page.Rows) == 0 { + break + } + assert.Equal(full.TotalCount, page.TotalCount) + tiled = append(tiled, page.Rows...) + } + assert.Equal(full.Rows, tiled) +} + +// runSearchFilesBothPaths executes the same request through the fast +// two-phase file page query and the legacy single-pass query. +func runSearchFilesBothPaths(t *testing.T, engine *DuckDBEngine, request FileSearchRequest) (fast, legacy *FileSearchResponse) { + t.Helper() + engine.exploreFastPathDisabled = false + fast, err := engine.SearchFiles(context.Background(), request) + require.NoError(t, err) + engine.exploreFastPathDisabled = true + legacy, err = engine.SearchFiles(context.Background(), request) + require.NoError(t, err) + engine.exploreFastPathDisabled = false + return fast, legacy +} + +func TestSearchFilesFastPathMatchesLegacy(t *testing.T) { + engine, ids := buildFastPathEquivalenceEngine(t) + before := ids.base.Add(90 * time.Minute) + + tests := []struct { + name string + request FileSearchRequest + }{ + {name: "full listing", request: FileSearchRequest{Page: PageSpec{Limit: 50}}}, + {name: "small page", request: FileSearchRequest{Page: PageSpec{Limit: 2}}}, + {name: "offset page", request: FileSearchRequest{Page: PageSpec{Limit: 2, Offset: 1}}}, + {name: "filename ascending", request: FileSearchRequest{ + Sort: SortSpec{Field: "filename", Direction: "asc"}, Page: PageSpec{Limit: 50}, + }}, + {name: "size descending", request: FileSearchRequest{ + Sort: SortSpec{Field: "size", Direction: "desc"}, Page: PageSpec{Limit: 50}, + }}, + {name: "occurred ascending", request: FileSearchRequest{ + Sort: SortSpec{Field: "occurred_at", Direction: "asc"}, Page: PageSpec{Limit: 50}, + }}, + {name: "filename query", request: FileSearchRequest{ + FilenameQuery: "RepOrt", Page: PageSpec{Limit: 50}, + }}, + {name: "mime family filter", request: FileSearchRequest{ + MIMEFamilies: []FileMIMEFamily{FileMIMEImage, FileMIMEPDF}, Page: PageSpec{Limit: 50}, + }}, + {name: "explore source filter", request: FileSearchRequest{ + Explore: ExploreRequest{Context: Context{SourceIDs: []int64{ids.chatSource}}}, + Page: PageSpec{Limit: 50}, + }}, + {name: "explore time filter", request: FileSearchRequest{ + Explore: ExploreRequest{Context: Context{Before: &before}}, + Page: PageSpec{Limit: 50}, + }}, + {name: "explore deletion filter", request: FileSearchRequest{ + Explore: ExploreRequest{Context: Context{Deletion: DeletionActive}}, + Page: PageSpec{Limit: 50}, + }}, + {name: "no matches", request: FileSearchRequest{ + FilenameQuery: "does-not-exist", Page: PageSpec{Limit: 50}, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fast, legacy := runSearchFilesBothPaths(t, engine, tt.request) + assert.Equal(t, legacy, fast) + assert.NotNil(t, fast.Files) + }) + } +} + +// TestExploreParticipantContextKeepsLegacyPath pins the fork rule: participant +// and domain context filters render list_contains predicates whose evaluation +// already assembles participant lists archive-wide, so the two-phase rescan +// would pay that twice. +func TestExploreParticipantContextKeepsLegacyPath(t *testing.T) { + assert := assert.New(t) + assert.False(exploreConditionsTouchParticipantLists(ExploreRequest{})) + assert.False(exploreConditionsTouchParticipantLists(ExploreRequest{ + Context: Context{SourceIDs: []int64{1}, MessageTypes: []string{"email"}}, + })) + assert.True(exploreConditionsTouchParticipantLists(ExploreRequest{ + Context: Context{ParticipantIDs: []int64{7}}, + })) + assert.True(exploreConditionsTouchParticipantLists(ExploreRequest{ + Context: Context{Domains: []string{"example.com"}}, + })) +} + +// TestExploreParticipantFilterListingStillCorrect exercises the legacy path +// the fork preserves for participant-scoped listings. +func TestExploreParticipantFilterListingStillCorrect(t *testing.T) { + engine, ids := buildFastPathEquivalenceEngine(t) + response, err := engine.Explore(context.Background(), ExploreRequest{ + Context: Context{ParticipantIDs: []int64{ids.alice}}, + Page: PageSpec{Limit: 50}, + }) + require.NoError(t, err) + require.NotEmpty(t, response.Rows) + for _, row := range response.Rows { + assert.Contains(t, row.ParticipantIDs, ids.alice, "row %s must include the filtered participant", row.Key) + } +} diff --git a/internal/query/files.go b/internal/query/files.go index 4d116b0fe..a5c95cb6d 100644 --- a/internal/query/files.go +++ b/internal/query/files.go @@ -105,13 +105,23 @@ func (e *DuckDBEngine) SearchFiles(ctx context.Context, request FileSearchReques if err != nil { return nil, fmt.Errorf("read committed cache state: %w", err) } - population, args := filteredFilePopulationSQL(request.Explore, request.FilenameQuery, request.MIMEFamilies) + exploreConditions, exploreArgs := buildExploreConditions(request.Explore) + fileConditions, fileArgs := buildFileConditions(request.FilenameQuery, request.MIMEFamilies) limit := request.Page.Limit if limit == 0 { limit = 100 } + args := append(append([]any{}, exploreArgs...), fileArgs...) args = append(args, limit, request.Page.Offset) - rows, err := e.db.QueryContext(ctx, fileSearchSQL(population, order), args...) + var queryText string + if !e.exploreFastPathDisabled && !exploreConditionsTouchParticipantLists(request.Explore) { + queryText = buildFileSearchFastSQL(exploreConditions, fileConditions, order) + args = append(args, exploreArgs...) // total-count scan + args = append(args, fileArgs...) + } else { + queryText = fileSearchSQL(filePopulationSQL(exploreConditions, fileConditions), order) + } + rows, err := e.db.QueryContext(ctx, queryText, args...) if err != nil { return nil, fmt.Errorf("search analytical files: %w", err) } @@ -238,9 +248,19 @@ func filteredFilePopulationSQL( mimeFamilies []FileMIMEFamily, ) (string, []any) { conditions, args := buildExploreConditions(explore) - fileConditions := []string{"true"} + fileConditions, fileArgs := buildFileConditions(filenameQuery, mimeFamilies) + return filePopulationSQL(conditions, fileConditions), append(args, fileArgs...) +} + +// buildFileConditions renders the attachment-level predicates (filename +// substring, MIME family) applied on top of the explore conditions. The +// returned SQL references the filename and mime_family columns of the +// classified attachment population. +func buildFileConditions(filenameQuery string, mimeFamilies []FileMIMEFamily) (string, []any) { + conditions := []string{"true"} + var args []any if query := strings.TrimSpace(filenameQuery); query != "" { - fileConditions = append(fileConditions, "contains(lower(filename), lower(?))") + conditions = append(conditions, "contains(lower(filename), lower(?))") args = append(args, query) } if len(mimeFamilies) > 0 { @@ -249,9 +269,9 @@ func filteredFilePopulationSQL( parts[i] = "?" args = append(args, family) } - fileConditions = append(fileConditions, "mime_family IN ("+strings.Join(parts, ",")+")") + conditions = append(conditions, "mime_family IN ("+strings.Join(parts, ",")+")") } - return filePopulationSQL(conditions, strings.Join(fileConditions, " AND ")), args + return strings.Join(conditions, " AND "), args } func fileGroupExpressions(dimension string) (key, label, fromSuffix, whereSuffix string, err error) { @@ -304,7 +324,32 @@ func fileSearchOrder(sort SortSpec) (string, error) { } } -func filePopulationSQL(exploreConditions, fileConditions string) string { +// sqlFileMIMEFamilyExpr renders the attachment MIME-type → mime_family +// mapping. alias is the attachments table alias. Shared by the population +// CTE and the fast-path count scan so classifications cannot drift. +func sqlFileMIMEFamilyExpr(alias string) string { + mime := "lower(" + alias + ".mime_type)" + return `CASE + WHEN ` + mime + ` LIKE 'image/%' THEN 'image' + WHEN ` + mime + ` = 'application/pdf' THEN 'pdf' + WHEN ` + mime + ` LIKE 'audio/%' THEN 'audio' + WHEN ` + mime + ` LIKE 'video/%' THEN 'video' + WHEN ` + mime + ` LIKE 'text/%' THEN 'text' + WHEN ` + mime + ` IN ('application/msword', 'application/rtf', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.oasis.opendocument.text') THEN 'document' + WHEN ` + mime + ` IN ('application/zip', 'application/gzip', 'application/x-tar', + 'application/x-7z-compressed', 'application/x-rar-compressed') THEN 'archive' + ELSE 'other' + END` +} + +// fileFilteredCTE renders the selected → classified → filtered CTE chain +// shared by filePopulationSQL and buildFileSearchFastSQL: the analytical +// population narrowed by explore conditions, joined to attachments, with +// attachment-level predicates applied. The WITH clause is left open for the +// caller to append further CTEs. +func fileFilteredCTE(exploreConditions, fileConditions string) string { return ` WITH selected AS ( SELECT * FROM analytical_entries WHERE ` + exploreConditions + ` @@ -312,24 +357,16 @@ WITH selected AS ( SELECT a.attachment_id, a.message_id, COALESCE(a.size, 0)::BIGINT AS size, COALESCE(a.filename, '') AS filename, COALESCE(a.mime_type, '') AS mime_type, - CASE - WHEN lower(a.mime_type) LIKE 'image/%' THEN 'image' - WHEN lower(a.mime_type) = 'application/pdf' THEN 'pdf' - WHEN lower(a.mime_type) LIKE 'audio/%' THEN 'audio' - WHEN lower(a.mime_type) LIKE 'video/%' THEN 'video' - WHEN lower(a.mime_type) LIKE 'text/%' THEN 'text' - WHEN lower(a.mime_type) IN ('application/msword', 'application/rtf', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.oasis.opendocument.text') THEN 'document' - WHEN lower(a.mime_type) IN ('application/zip', 'application/gzip', 'application/x-tar', - 'application/x-7z-compressed', 'application/x-rar-compressed') THEN 'archive' - ELSE 'other' - END AS mime_family, + ` + sqlFileMIMEFamilyExpr("a") + ` AS mime_family, s.* FROM selected s JOIN attachments a ON a.message_id = s.message_id ), filtered AS ( SELECT * FROM classified WHERE ` + fileConditions + ` -), file_population AS ( +)` +} + +func filePopulationSQL(exploreConditions, fileConditions string) string { + return fileFilteredCTE(exploreConditions, fileConditions) + `, file_population AS ( SELECT *, list_sort(list_distinct(list_concat(participant_ids, conversation_participant_ids))) AS file_participant_ids, list_sort(list_distinct(list_concat(participant_domains, conversation_participant_domains))) AS file_participant_domains @@ -363,3 +400,78 @@ SELECT total_count FROM counted ORDER BY ` + order + ` LIMIT ? OFFSET ?` } + +// buildFileSearchFastSQL builds the two-phase file page query used when +// exploreConditionsTouchParticipantLists is false. Phase one orders and +// limits the filtered attachment population without referencing any +// participant list column, so the whole-archive per-message list assembly +// inside analytical_entries is pruned; phase two rebuilds the participant +// facts for the ≤limit page rows only from the base tables, with the same +// union the view's per-message lists concatenated with the conversation +// lists reduce to: recipients plus sender (message_participant_links in +// sqlAnalyticalEntries) plus the message's conversation participants. +// +// The total count runs as its own slim aggregate: COUNT(*) OVER () on the +// page pipeline would materialize every pre-LIMIT row with its string +// columns (measured 3x slower on a 208k-attachment archive). Bind order: +// explore condition args (selected), file condition args (filtered), limit, +// offset (page), explore condition args again and file condition args again +// (total). +// +// Output columns, ordering, and pagination are identical to fileSearchSQL; +// TestSearchFilesFastPathMatchesLegacy pins the equivalence. +func buildFileSearchFastSQL(exploreConditions, fileConditions, order string) string { + return fileFilteredCTE(exploreConditions, fileConditions) + `, page AS ( + SELECT * FROM filtered + ORDER BY ` + order + ` LIMIT ? OFFSET ? +), total AS ( + SELECT COUNT(*) AS total_count FROM ( + SELECT COALESCE(a.filename, '') AS filename, ` + sqlFileMIMEFamilyExpr("a") + ` AS mime_family + FROM (SELECT message_id FROM analytical_entries WHERE ` + exploreConditions + `) s + JOIN attachments a ON a.message_id = s.message_id + ) WHERE ` + fileConditions + ` +), page_ids AS ( + SELECT DISTINCT message_id, conversation_id FROM page +), page_links AS ( + SELECT pid.message_id, mr.participant_id + FROM page_ids pid JOIN message_recipients mr ON mr.message_id = pid.message_id + UNION ALL + SELECT pid.message_id, msg.sender_id AS participant_id + FROM page_ids pid JOIN messages msg ON msg.id = pid.message_id + WHERE msg.sender_id IS NOT NULL + UNION ALL + SELECT pid.message_id, cp.participant_id + FROM page_ids pid JOIN conversation_participants cp ON cp.conversation_id = pid.conversation_id +), page_facts AS ( + SELECT links.message_id, + list_sort(list_distinct(list(links.participant_id))) AS file_participant_ids, + list_sort(list_distinct(list(` + sqlAnalyticalEntriesParticipantLabel("pt") + `))) AS file_participant_labels, + list_sort(list_distinct(list(COALESCE(pt.domain, '')))) AS file_participant_domains + FROM page_links links + JOIN participants pt ON pt.id = links.participant_id + GROUP BY links.message_id +), enriched AS ( + SELECT page.*, f.file_participant_ids, f.file_participant_labels, f.file_participant_domains, + (SELECT total_count FROM total) AS total_count + FROM page LEFT JOIN page_facts f ON f.message_id = page.message_id +) +SELECT + attachment_id, + CASE WHEN lower(message_type) IN (` + TextMessageTypeSQLList + `) + OR (lower(message_type) IN ('', 'chat', 'text') AND lower(conversation_type) IN ('direct_chat', 'group_chat', 'channel', 'chat')) + THEN 'source:' || CAST(source_id AS VARCHAR) || ':conversation:' || CAST(conversation_id AS VARCHAR) || ':file:' || CAST(attachment_id AS VARCHAR) + ELSE 'source:' || CAST(source_id AS VARCHAR) || ':message:' || COALESCE(NULLIF(source_message_id, ''), CAST(message_id AS VARCHAR)) || ':file:' || CAST(attachment_id AS VARCHAR) END, + CASE WHEN lower(message_type) IN (` + TextMessageTypeSQLList + `) + OR (lower(message_type) IN ('', 'chat', 'text') AND lower(conversation_type) IN ('direct_chat', 'group_chat', 'channel', 'chat')) + THEN 'source:' || CAST(source_id AS VARCHAR) || ':conversation:' || CAST(conversation_id AS VARCHAR) + ELSE 'source:' || CAST(source_id AS VARCHAR) || ':message:' || COALESCE(NULLIF(source_message_id, ''), CAST(message_id AS VARCHAR)) END, + message_id, conversation_id, occurred_at, source_id, source_type, source_identifier, + COALESCE(NULLIF(subject, ''), NULLIF(conversation_title, ''), snippet, ''), + snippet, + filename, mime_type, mime_family, size, + CAST(COALESCE(to_json(file_participant_ids), '[]') AS VARCHAR), + CAST(COALESCE(to_json(file_participant_labels), '[]') AS VARCHAR), + CAST(COALESCE(to_json(file_participant_domains), '[]') AS VARCHAR), + total_count +FROM enriched ORDER BY ` + order +} diff --git a/internal/query/views.go b/internal/query/views.go index b4e1cee3d..61fe4b198 100644 --- a/internal/query/views.go +++ b/internal/query/views.go @@ -384,10 +384,20 @@ func createConvenienceViews(db *sql.DB) error { return nil } +// sqlAnalyticalEntriesParticipantLabel renders the canonical participant +// label used by every analytical participant list: display name, then phone +// number, then email address, then empty string. alias is the participants +// table alias. Any query that rebuilds participant labels outside the +// analytical_entries view (the explore/files page-enrichment fast paths) +// must use this so labels cannot drift from the view's. +func sqlAnalyticalEntriesParticipantLabel(alias string) string { + return "COALESCE(NULLIF(" + alias + ".display_name, ''), NULLIF(" + alias + ".phone_number, ''), " + alias + ".email_address, '')" +} + // sqlAnalyticalEntries normalizes every durable message-shaped source row. // Logical row-unit selection deliberately remains in Explore so Context can be // applied before chat conversations are aggregated. -const sqlAnalyticalEntries = ` +var sqlAnalyticalEntries = ` CREATE OR REPLACE VIEW analytical_entries AS WITH message_participant_links AS ( SELECT message_id, participant_id @@ -400,7 +410,7 @@ WITH message_participant_links AS ( SELECT link.message_id, list_sort(list_distinct(list(link.participant_id))) AS participant_ids, - list_sort(list_distinct(list(COALESCE(NULLIF(p.display_name, ''), NULLIF(p.phone_number, ''), p.email_address, '')))) AS participant_labels, + list_sort(list_distinct(list(` + sqlAnalyticalEntriesParticipantLabel("p") + `))) AS participant_labels, list_sort(list_distinct(list(COALESCE(p.domain, '')))) AS participant_domains FROM message_participant_links link JOIN participants p ON p.id = link.participant_id @@ -409,7 +419,7 @@ WITH message_participant_links AS ( SELECT cp.conversation_id, list_sort(list_distinct(list(cp.participant_id))) AS participant_ids, - list_sort(list_distinct(list(COALESCE(NULLIF(p.display_name, ''), NULLIF(p.phone_number, ''), p.email_address, '')))) AS participant_labels, + list_sort(list_distinct(list(` + sqlAnalyticalEntriesParticipantLabel("p") + `))) AS participant_labels, list_sort(list_distinct(list(COALESCE(p.domain, '')))) AS participant_domains FROM conversation_participants cp JOIN participants p ON p.id = cp.participant_id From 9484a82711cedde801a810bd36a3ee73ee039bc2 Mon Sep 17 00:00:00 2001 From: Wes McKinney <wesmckinn+git@gmail.com> Date: Tue, 21 Jul 2026 19:52:41 -0500 Subject: [PATCH 031/102] feat(web): overhaul archived HTML mail rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simple mail now renders theme-native (transparent frame, shell inks, matching color-scheme) while designed mail keeps a proper white canvas: 680px centered measure, 20/24px padding, hairline border, 6px radius. Designed-ness is detected on the raw message HTML (backgrounds, explicit container colors, multi-column layout tables) since sanitization strips every style carrier. The frame's script and styles move to same-origin static assets (web/public/archived-frame.{js,css}) because the daemon's shell CSP (script-src 'self'; style-src 'self') is inherited by sandboxed srcdoc frames and blocked the previous inline hash-authorized bridge and inline styles outright — the root cause of unstyled 1995-looking mail and frames stuck at 96px with inner scrollbars in production. The frame CSP admits exactly those two URLs and nothing else executable. Reading typography ships as low-specificity :where() defaults: system sans at 14px/1.55, readable links, bounded images, soft wrapping, and a 680px measure for simple mail and plain text. Blocked remote and unavailable inline images render as compact captioned placeholder blocks instead of raw alt text flowing as body copy. Long quote chains fold behind a native details/summary 'Show quoted text' toggle with muted left-hairline quote styling. The height bridge measures the root border box plus body overflow instead of documentElement.scrollHeight, whose viewport floor ratcheted frames tall; heights now shrink, reset per message, follow quote toggles, and account for horizontal overflow and the canvas hairline. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --- web/index.html | 5 + web/public/archived-frame.css | 174 ++++++++++++++++++ web/public/archived-frame.js | 68 +++++++ .../lib/components/reader/ContentFrame.svelte | 48 ++++- .../components/reader/ContentFrame.test.ts | 93 +++++++++- .../lib/components/reader/MessageCard.svelte | 10 +- .../lib/components/reader/MessageCard.test.ts | 6 +- .../lib/components/reader/inline-images.ts | 5 +- web/src/lib/content/frame-document.test.ts | 93 +++++++++- web/src/lib/content/frame-document.ts | 62 ++++--- web/src/lib/content/sanitize.test.ts | 120 +++++++++++- web/src/lib/content/sanitize.ts | 108 ++++++++++- web/tests/conversation-content-frame.spec.ts | 23 ++- 13 files changed, 757 insertions(+), 58 deletions(-) create mode 100644 web/public/archived-frame.css create mode 100644 web/public/archived-frame.js diff --git a/web/index.html b/web/index.html index a7d3fa852..87e9884e3 100644 --- a/web/index.html +++ b/web/index.html @@ -4,6 +4,11 @@ <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="description" content="Explore a local msgvault archive." /> + <!-- Static bridge + chrome for archived mail frames: preloading warms the + cache before the first message opens and keeps the files reachable in + the validated release graph (scripts/check-web-assets.mjs). --> + <link rel="preload" href="/archived-frame.js" as="script" /> + <link rel="preload" href="/archived-frame.css" as="style" /> <title>msgvault diff --git a/web/public/archived-frame.css b/web/public/archived-frame.css new file mode 100644 index 000000000..faeec6910 --- /dev/null +++ b/web/public/archived-frame.css @@ -0,0 +1,174 @@ +/* Chrome for archived mail frames (see web/src/lib/content/frame-document.ts). + * + * Served as a same-origin static asset because the daemon's shell CSP + * (style-src 'self') is inherited by sandboxed srcdoc frames and blocks every + * inline style. Rendering mode and scheme arrive as html[data-mode] / + * html[data-scheme] attributes: + * - canvas: designed mail on its own white, light canvas in both themes + * - themed: simple mail rendered transparently with shell inks + * Themed ink values mirror web/src/styles/theme-light.css / theme-dark.css. + */ + +html { + --mail-text: #20242a; + --mail-muted: #59626e; + --mail-link: #1a5fce; + --mail-border: #d8dade; + + color-scheme: light; +} + +html[data-mode='themed'][data-scheme='light'] { + --mail-text: #191b1f; + --mail-muted: #555a62; + --mail-link: #2257d6; + --mail-border: #d8dade; +} + +html[data-mode='themed'][data-scheme='dark'] { + --mail-text: #f2f3f5; + --mail-muted: #a4a8af; + --mail-link: #74acfe; + --mail-border: #3a3d42; + + color-scheme: dark; +} + +body { + margin: 0; + overflow-wrap: break-word; +} + +html[data-mode='canvas'] body { + max-width: 680px; + margin: 0 auto; + padding: 20px 24px; + background: #ffffff; +} + +html[data-mode='themed'] body { + /* Simple mail keeps a readable measure on wide panes; left-aligned so it + * reads as part of the thread rather than a centered artifact. */ + max-width: 680px; + padding: 2px 0; + background: transparent; +} + +/* Reading typography as low-specificity defaults: author styles in designed + * mail always win over :where() rules. */ +:where(body) { + color: var(--mail-text); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, + sans-serif; + font-size: 14px; + line-height: 1.55; +} + +:where(body > :first-child) { + margin-top: 0; +} + +:where(body > :last-child) { + margin-bottom: 0; +} + +:where(a) { + color: var(--mail-link); +} + +:where(pre) { + white-space: pre-wrap; +} + +:where(hr) { + border: 0; + border-top: 1px solid var(--mail-border); +} + +:where(table) { + max-width: 100%; +} + +img { + max-width: 100%; + height: auto; +} + +/* Quotes: muted ink behind a left hairline; long chains fold behind the + * details/summary toggle emitted by sanitize.ts. */ +:where(blockquote, [data-archived-quote]) { + margin: 8px 0; + padding: 0 0 0 12px; + border-left: 2px solid var(--mail-border); + color: var(--mail-muted); +} + +details[data-archived-quote-toggle] { + margin: 10px 0; +} + +details[data-archived-quote-toggle] > summary { + display: inline-block; + padding: 2px 10px; + border: 1px solid var(--mail-border); + border-radius: 10px; + color: var(--mail-muted); + cursor: pointer; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, + sans-serif; + font-size: 12px; + line-height: 1.4; + list-style: none; + user-select: none; +} + +details[data-archived-quote-toggle] > summary::-webkit-details-marker { + display: none; +} + +details[data-archived-quote-toggle] > summary:hover { + color: var(--mail-text); +} + +details[data-archived-quote-toggle]:not([open]) > summary [data-archived-quote-hide] { + display: none; +} + +details[data-archived-quote-toggle][open] > summary [data-archived-quote-show] { + display: none; +} + +/* Blocked remote images and unavailable inline images render as compact + * captioned blocks instead of raw alt text flowing as body copy. */ +[data-archived-image-placeholder] { + display: inline-flex; + max-width: 100%; + max-height: 80px; + box-sizing: border-box; + align-items: center; + gap: 8px; + padding: 6px 10px; + border: 1px solid var(--mail-border); + border-radius: 6px; + color: var(--mail-muted); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, + sans-serif; + font-size: 12px; + line-height: 1.4; + vertical-align: middle; +} + +[data-archived-image-placeholder] [data-archived-image-caption] { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +[data-archived-image-glyph] { + flex: none; + width: 14px; + height: 14px; + background: currentColor; + -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='2'/%3E%3Ccircle cx='8.5' cy='8.5' r='1.5'/%3E%3Cpath d='m21 15-5-5L5 21'/%3E%3C/svg%3E") center / contain no-repeat; + mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='2'/%3E%3Ccircle cx='8.5' cy='8.5' r='1.5'/%3E%3Cpath d='m21 15-5-5L5 21'/%3E%3C/svg%3E") center / contain no-repeat; +} diff --git a/web/public/archived-frame.js b/web/public/archived-frame.js new file mode 100644 index 000000000..5f57c6daa --- /dev/null +++ b/web/public/archived-frame.js @@ -0,0 +1,68 @@ +// Bridge for archived mail frames (see web/src/lib/content/frame-document.ts). +// +// Served as a same-origin static asset because the daemon's shell CSP +// (script-src 'self') is inherited by sandboxed srcdoc frames, which blocks +// every inline script — including a hash-authorized one. The frame's own CSP +// admits exactly this URL and nothing else executable. +// +// The channel name and limits mirror ARCHIVED_CONTENT_CHANNEL, +// MAX_ARCHIVED_SCROLL_DELTA, and MAX_ARCHIVED_FRAME_HEIGHT in +// frame-document.ts; a unit test keeps them in sync. +(() => { + 'use strict'; + const root = document.documentElement; + const nonce = root.dataset.bridgeNonce; + const channel = 'msgvault-archived-content'; + const MAX_SCROLL_DELTA = 10000; + const MAX_FRAME_HEIGHT = 65536; + let origin; + try { + const declared = root.dataset.bridgeOrigin || ''; + const parsed = new URL(declared); + if ((parsed.protocol === 'http:' || parsed.protocol === 'https:') && parsed.origin === declared) { + origin = parsed.origin; + } + } catch { + origin = undefined; + } + if (!nonce || !origin) return; + + const KEYS = new Set(['Escape', 'PageUp', 'PageDown', 'Home', 'End', 'ArrowUp', 'ArrowDown']); + addEventListener('keydown', (event) => { + if (KEYS.has(event.key)) { + parent.postMessage({ channel, nonce, type: 'key', key: event.key }, origin); + } + }); + addEventListener('wheel', (event) => { + if (Number.isFinite(event.deltaY) && Math.abs(event.deltaY) <= MAX_SCROLL_DELTA) { + parent.postMessage({ channel, nonce, type: 'scroll', deltaY: event.deltaY }, origin); + } + }, { passive: true }); + + // Height is measured from the root border box plus body overflow — never + // documentElement.scrollHeight, whose viewport floor would ratchet the + // frame: once the shell sizes it, a shorter document could never report a + // smaller value. Horizontal overflow (wide layout tables) gets a scrollbar + // allowance so the inner horizontal scrollbar never steals height and + // forces a vertical one. + const reportHeight = () => { + const body = document.body; + let height = Math.ceil(Math.max( + root.getBoundingClientRect().height, + body ? body.scrollHeight : 0 + )); + if (root.scrollWidth > root.clientWidth) height += 18; + height = Math.min(height, MAX_FRAME_HEIGHT); + if (height > 0) parent.postMessage({ channel, nonce, type: 'height', height }, origin); + }; + if (typeof ResizeObserver === 'function') { + const observer = new ResizeObserver(reportHeight); + observer.observe(root); + if (document.body) observer.observe(document.body); + } + addEventListener('load', reportHeight); + // Quote-chain details/summary toggles change content height without any + // other signal in some engines; toggle does not bubble, so capture it. + addEventListener('toggle', reportHeight, true); + reportHeight(); +})(); diff --git a/web/src/lib/components/reader/ContentFrame.svelte b/web/src/lib/components/reader/ContentFrame.svelte index 3f99f718b..9b6fae546 100644 --- a/web/src/lib/components/reader/ContentFrame.svelte +++ b/web/src/lib/components/reader/ContentFrame.svelte @@ -2,6 +2,7 @@ import { onDestroy, onMount, untrack } from 'svelte'; import type { APIClient } from '../../api/client'; + import type { FrameColorScheme } from '../../content/frame-document'; import { buildFrameDocument } from '../../content/frame-document'; import { sanitizeArchivedHTML } from '../../content/sanitize'; import { @@ -29,15 +30,22 @@ let host = $state(); let frame = $state(); - let frameDocument = $state<{ generation: number; srcdoc: string }>(); + let frameDocument = $state<{ generation: number; srcdoc: string; mode: 'canvas' | 'themed' }>(); let documentState = $state<'building' | 'loading' | 'ready' | 'failed'>('building'); let remoteImageCount = $state(0); let remoteImagesAllowed = $state(false); let frameHeight = $state(96); let nonce = $state(createFrameNonce()); + let colorScheme = $state(resolvedColorScheme()); let documentGeneration = 0; let messageIdentity = ''; let inlineController: AbortController | undefined; + let themeObserver: MutationObserver | undefined; + + function resolvedColorScheme(): FrameColorScheme { + if (typeof document === 'undefined') return 'light'; + return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light'; + } function invalidateDocument(): void { documentGeneration += 1; @@ -50,10 +58,15 @@ const currentMessageID = messageId; const currentHTML = html; const currentClient = client; + const currentScheme = colorScheme; const nextIdentity = `${currentMessageID}\u0000${currentHTML}`; const identityChanged = nextIdentity !== messageIdentity; messageIdentity = nextIdentity; if (identityChanged && remoteImagesAllowed) remoteImagesAllowed = false; + // A new message starts from the compact default height; the bridge + // reports the real content height as soon as the document loads. Reusing + // the previous message's height would leave a tall empty frame. + if (identityChanged) frameHeight = 96; const allowRemoteImages = identityChanged ? false : remoteImagesAllowed; untrack(invalidateDocument); const generation = documentGeneration; @@ -63,6 +76,7 @@ allowRemoteImages }); remoteImageCount = sanitized.remoteImages.length; + const mode = sanitized.designed ? 'canvas' as const : 'themed' as const; inlineController = new AbortController(); const signal = inlineController.signal; void resolveArchivedInlineImages({ @@ -75,11 +89,12 @@ html: resolvedHTML, nonce: buildNonce, targetOrigin: window.location.origin, - remoteImages: allowRemoteImages ? sanitized.remoteImages : [] + remoteImages: allowRemoteImages ? sanitized.remoteImages : [], + appearance: { mode, colorScheme: currentScheme } })).then((document) => { if (generation !== documentGeneration || signal.aborted) return; nonce = buildNonce; - frameDocument = { generation, srcdoc: document }; + frameDocument = { generation, srcdoc: document, mode }; documentState = 'loading'; }).catch((error) => { if (generation !== documentGeneration || signal.aborted) return; @@ -152,9 +167,17 @@ onHeight: (height) => { frameHeight = height; } }); - onMount(() => window.addEventListener('message', messageHandler)); + onMount(() => { + window.addEventListener('message', messageHandler); + themeObserver = new MutationObserver(() => { colorScheme = resolvedColorScheme(); }); + themeObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ['data-theme'] + }); + }); onDestroy(() => { window.removeEventListener('message', messageHandler); + themeObserver?.disconnect(); inlineController?.abort(); documentGeneration += 1; }); @@ -186,6 +209,7 @@ forwards Escape back out to the surrounding scroller. --> ', + '', + '

Tracked archived words

' + ].join(''), + attachments: [] + }], + has_before: false, + has_after: false, + total: 1 + } })); + await page.route('https://tracking.example/**', async (route) => { + sentinelRequests.push(route.request().url()); + await route.fulfill({ + contentType: 'image/png', + body: Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64') + }); + }); + + const explore = encodeURIComponent(JSON.stringify({ workspace: 'everything' })); + await page.goto(`/?feature=reader-security&explore=${explore}`); + const grid = page.getByRole('grid', { name: 'Everything results' }); + await expect(grid.getByText(row.title)).toBeVisible(); + await grid.focus(); + await page.keyboard.press('Enter'); + + // The archived body renders fully — raw HTML has been parsed, sanitized, + // and reassembled — yet the sender's host has seen nothing. + const frame = page.locator('[data-message-id="42"]').locator('iframe[title="Message body"]'); + await expect(frame.contentFrame().getByText('Tracked archived words')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Load 1 remote image' })).toBeVisible(); + expect(sentinelRequests).toEqual([]); + + // Explicit consent fetches exactly the blocked img URL; the iframe and + // stylesheet URLs the sanitizer removed stay unfetched forever. + await page.getByRole('button', { name: 'Load 1 remote image' }).click(); + await expect.poll(() => sentinelRequests.length).toBe(1); + await expect(frame.contentFrame().locator('img[src="https://tracking.example/pixel.gif"]')).toHaveCount(1); + expect(sentinelRequests).toEqual(['https://tracking.example/pixel.gif']); +}); From 0167c1214b601f5f88a234dd724670322e6a1478 Mon Sep 17 00:00:00 2001 From: Wes McKinney Date: Wed, 22 Jul 2026 14:23:54 -0500 Subject: [PATCH 050/102] fix: clear stale thread when navigating to a different conversation The reading pane kept the previous thread visible under the new title while a different conversation loaded, because loading only showed when messages were empty. The window-identity effect now clears messages at the exact point a different-target load starts; same-window expansions never reach that path, so no flicker. Regression tests cover delayed cross-conversation navigation and a late response from an abandoned conversation. Co-Authored-By: Claude Fable 5 --- .../components/reader/ConversationView.svelte | Bin 7984 -> 8311 bytes .../reader/ConversationView.test.ts | 69 ++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/web/src/lib/components/reader/ConversationView.svelte b/web/src/lib/components/reader/ConversationView.svelte index 697dd1bca133f3bd6bc9126c507f28ac773a8508..c848c2f45f71ebc01d0851bc87e5253f7df25a18 100644 GIT binary patch delta 293 zcmX|+F-`+P3`IMnq@Y8zUsNCk_6CF#L=;?LwByZMTI^-)&6IG5aF9scCG9|3v|Bwl4PJVJhqNb0k?X50qRq9(K|hvEy) zDjUq&8fBX1_y8TvMuuNn)v8e?Jk#T;8fBdYi}`BT2M} O_3``i`fIm3U;P1(b7&a= delta 12 TcmezFu)%JFALr(&T#rNnClCd8 diff --git a/web/src/lib/components/reader/ConversationView.test.ts b/web/src/lib/components/reader/ConversationView.test.ts index 53defdc08..c5c264d4e 100644 --- a/web/src/lib/components/reader/ConversationView.test.ts +++ b/web/src/lib/components/reader/ConversationView.test.ts @@ -148,6 +148,75 @@ describe('ConversationView', () => { expect(await screen.findByRole('article', { name: 'Message 99' })).toBeTruthy(); }); + it('hides the previous thread while a different conversation loads', async () => { + const pending = new Map void>(); + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const conversation = Number(new URL(request.url).pathname.split('/').at(-1)); + return await new Promise((resolve) => { + pending.set(conversation, resolve); + }); + }); + const client = createAPIClient(fetchFn); + const rendered = render(ConversationView, { + props: { client, conversationId: 7, anchorId: 2 } + }); + await waitFor(() => expect(pending.get(7)).toBeDefined()); + pending.get(7)!(Response.json({ + id: 7, anchor_id: 2, messages: [message(1), message(2)], + has_before: false, has_after: false, total: 2 + })); + await screen.findByRole('article', { name: 'Message 2' }); + + await rendered.rerender({ client, conversationId: 8, anchorId: 80 }); + await waitFor(() => expect(pending.get(8)).toBeDefined()); + + // The stale thread disappears immediately; loading is shown while B pends. + expect(screen.queryByRole('article', { name: 'Message 2' })).toBeNull(); + expect(screen.queryByRole('button', { name: /Expand message 1/ })).toBeNull(); + expect(screen.getByRole('status').textContent).toContain('Loading conversation'); + + pending.get(8)!(Response.json({ + id: 8, anchor_id: 80, messages: [{ ...message(9), id: 80 }], + has_before: false, has_after: false, total: 1 + })); + expect(await screen.findByRole('article', { name: 'Message 80' })).toBeTruthy(); + }); + + it('ignores a late response from an abandoned conversation after navigating away', async () => { + const pending = new Map void>(); + const fetchFn = vi.fn(async (input) => { + const request = input instanceof Request ? input : new Request(input); + const conversation = Number(new URL(request.url).pathname.split('/').at(-1)); + return await new Promise((resolve) => { + pending.set(conversation, resolve); + }); + }); + const client = createAPIClient(fetchFn); + const rendered = render(ConversationView, { + props: { client, conversationId: 7, anchorId: 2 } + }); + await waitFor(() => expect(pending.get(7)).toBeDefined()); + + await rendered.rerender({ client, conversationId: 8, anchorId: 80 }); + await waitFor(() => expect(pending.get(8)).toBeDefined()); + pending.get(8)!(Response.json({ + id: 8, anchor_id: 80, messages: [{ ...message(9), id: 80 }], + has_before: false, has_after: false, total: 1 + })); + await screen.findByRole('article', { name: 'Message 80' }); + + pending.get(7)!(Response.json({ + id: 7, anchor_id: 2, messages: [message(1), message(2)], + has_before: false, has_after: false, total: 2 + })); + await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(screen.getByRole('article', { name: 'Message 80' })).toBeTruthy(); + expect(screen.queryByRole('article', { name: 'Message 2' })).toBeNull(); + expect(screen.queryByRole('alert')).toBeNull(); + }); + it('aborts a superseded window load and ignores stale resolve or reject settlements', async () => { const pending = new Map Date: Wed, 22 Jul 2026 14:56:52 -0500 Subject: [PATCH 051/102] fix: paginate the relationships list with quiet infinite scroll The controller retains next_cursor and total_count and loads further pages automatically when the list scrolls near its end (same idiom as the timeline), deduping boundary rows and preserving row focus across appends. Pages replay a snapshot of the query context they came from; a context change resets the cursor and a generation guard drops late stale pages. Page failures keep loaded rows behind a slim banner instead of wiping the list; total_count feeds aria-rowcount. Co-Authored-By: Claude Fable 5 --- .../relationships/RelationshipList.svelte | 52 ++++- .../relationships/RelationshipList.test.ts | 63 ++++++ .../RelationshipsWorkspace.svelte | 4 + .../relationships/controller.svelte.test.ts | 189 ++++++++++++++++++ .../lib/relationships/controller.svelte.ts | 141 ++++++++++--- 5 files changed, 424 insertions(+), 25 deletions(-) diff --git a/web/src/lib/components/relationships/RelationshipList.svelte b/web/src/lib/components/relationships/RelationshipList.svelte index fca7b936e..6b651f4af 100644 --- a/web/src/lib/components/relationships/RelationshipList.svelte +++ b/web/src/lib/components/relationships/RelationshipList.svelte @@ -10,6 +10,9 @@ interface Props { rows: RelationshipRow[] | PersonSummary[] | DomainSummary[]; loading: boolean; + loadingMore?: boolean; + hasMore?: boolean; + totalCount?: number | null; error: string | null; degraded: 'cache_unavailable' | null; facet: RelationshipFacet; @@ -20,6 +23,7 @@ onFacetChange: (facet: RelationshipFacet) => void; onShowAllChange: (value: boolean) => void; onSelect: (target: string) => void; + onLoadMore?: () => void; onOpenEverything?: () => void; } @@ -34,6 +38,9 @@ let { rows, loading, + loadingMore = false, + hasMore = false, + totalCount = null, error, degraded, facet, @@ -44,9 +51,15 @@ onFacetChange, onShowAllChange, onSelect, + onLoadMore = undefined, onOpenEverything = undefined }: Props = $props(); + /* Quiet infinite scroll (same scroll-proximity idiom as + * RelationshipTimeline): once the remaining scroll runway shrinks below + * this, the next page loads with only the inline "Loading more…" line. */ + const LOAD_MORE_PROXIMITY_PX = 200; + let gridElement = $state(); let activeKey = $state(null); @@ -139,6 +152,14 @@ activeKey = view.key; onSelect(view.target); } + + // Fires on user scrolling and on the scrollIntoView calls moveTo makes, so + // keyboard navigation toward the end also pulls the next page in. + function handleScroll(): void { + if (!gridElement || !hasMore || loadingMore || loading) return; + const remaining = gridElement.scrollHeight - gridElement.scrollTop - gridElement.clientHeight; + if (remaining <= LOAD_MORE_PROXIMITY_PX) onLoadMore?.(); + }