Unified session query service
统一会话查询服务
Exact reads, semantic filters, relationship traces, and full-text search operate on the same live-preferred session corpus. Exposing full-text search under a second context key makes consumers and app compositions treat one capability as two services, even though the SQLite implementation is the only backend-specific part. The interface package already owns the shared record, filter, trace, search-request, cursor, an
English
Problem
Exact reads, semantic filters, relationship traces, and full-text search operate on the same live-preferred session corpus. Exposing full-text search under a second context key makes consumers and app compositions treat one capability as two services, even though the SQLite implementation is the only backend-specific part.
The interface package already owns the shared record, filter, trace, search-request, cursor, and error contracts. A provider registry or coordinator would add runtime selection semantics unsupported by any current consumer.
Decision
SessionQueryService is the single abstract service registered as ctx.sessionQuery. It concretely implements listing, title and event reads, surface reads, filtering, and relationship tracing through its backend-independent SessionCorpus. Its only abstract methods are searchSessions() and searchEvents().
SessionQuerySqlite extends that service and is the sole concrete backend. One mounted instance therefore exposes every operation through ctx.sessionQuery; its inherited exact operations use the shared corpus implementation, while its SQLite-owned lifecycle observes sources, reconciles the derived FTS index, ranks matches, and owns cursor generations. The interface package has no standalone concrete plugin, search-provider registry, or second context key.
SQLite reconciliation is one quiescent serialized state machine. It passes the caller's exact abort signal into durable snapshot listing and inspection, awaits each started backend operation itself, and checks cancellation after every await and before starting the next source or index operation. Cancellation therefore cannot release the serializer while an ignored or cooperative backend call is still cleaning up, and it cannot start a subsequent listing, inspection, reconciliation, or query after the signal is observed.
Backend configuration includes the inherited readWindowMax setting alongside its own index path, journal mode, page limits, and snippet limit. First-party apps that need session queries mount the SQLite backend and place its disposable index beside their configured persistence root.
This service topology supersedes the separate-key portion of the exact query decision and SQLite search decision; their corpus, query, tokenizer, reconciliation, and safety decisions remain in force.
Alternatives considered
- Keep
ctx.sessionQueryandctx.sessionSearchseparate — rejected because both expose operations over one logical corpus, force consumers to discover two keys, and let apps accidentally mount only a partial query surface. - Keep a concrete base service and let the SQLite plugin register or mutate two search methods — rejected because method availability would depend on plugin order and teardown, and the service would need a provider registration protocol for one implementation.
- Move every query implementation into the SQLite package — rejected because exact reads, filters, and traces require no index and are shared behavior that belongs with their provider-independent contracts.
Consequences
Consumers inject one service and can combine exact and full-text operations without a second capability lookup. A production composition must choose a concrete backend even when one consumer currently calls only inherited exact methods; tests may use a minimal subclass when backend behavior is outside their scope.
The unified object deliberately retains two internal observation strategies: exact operations read authoritative live/persisted sources per call, while full-text operations reconcile a disposable index. Sharing the context key does not make the derived index authoritative or couple exact-read availability to an FTS query.
Queued cancellation remains prompt. Cancellation during active asynchronous source observation waits for that started operation to settle, which makes rejection a quiescence boundary and preserves single-file execution for a following search. Synchronous SQLite statements remain non-preemptible and are bracketed by signal checks.
Unit coverage pins inherited and abstract behavior on one key, SQLite coverage exercises both operation families on the concrete backend, and the real Loader path verifies that one exported plugin registers the combined service.
中文
问题
精确读取、语义过滤、关系追踪与全文搜索都作用于同一个实时源优先的会话语料库。将全文搜索暴露在第二个上下文键下,会让消费方与应用组合把同一项查询功能视为两个服务,尽管只有 SQLite 实现是后端特有的部分。
接口包已经拥有共享的记录、过滤、追踪、搜索请求、游标与错误契约。提供方注册表或协调器会引入运行时选择语义,而目前没有任何消费方支持这种语义。
决策
SessionQueryService 是注册为 ctx.sessionQuery 的唯一抽象服务。它通过后端无关的 SessionCorpus 具体实现列表查询、标题与事件读取、表层读取、过滤和关系追踪。仅有 searchSessions() 与 searchEvents() 两个方法为抽象方法。
SessionQuerySqlite 扩展该服务,并且是唯一的具体后端。因此,一个挂载实例便可通过 ctx.sessionQuery 暴露全部操作;其继承的精确操作使用共享的语料库实现,而由 SQLite 管理的生命周期负责观察数据源、对齐派生 FTS 索引、对匹配项排序并管理游标代际。接口包不提供独立的具体插件、搜索提供方注册表或第二个上下文键。
SQLite 的对齐过程是一个具备静止性保证的串行状态机。它将调用方的原始中止信号传给持久化快照列表与检查操作,直接等待每个已经启动的后端操作,并在每次等待后以及启动下一个数据源或索引操作前检查是否已取消。因此,即使后端忽略取消或正在配合清理,串行器也不会提前释放;观察到中止信号后,也不会再启动后续的列表、检查、对齐或查询操作。
后端配置除了自身的索引路径、日志模式、分页限制与文本片段长度上限外,还包含继承的 readWindowMax 设置。需要会话查询的第一方应用挂载 SQLite 后端,并将其可丢弃索引放在已配置的持久化根目录旁。
这一服务拓扑取代了精确查询决策和 SQLite 搜索决策中关于分离上下文键的部分;其中关于语料库、查询、分词器、对齐与安全性的决策仍然有效。
已考虑的替代方案
- 保留相互独立的
ctx.sessionQuery与ctx.sessionSearch:不予采纳,因为二者都针对同一逻辑语料库提供操作,迫使消费方识别两个键,还可能让应用误挂载一组不完整的查询接口。 - 保留具体的基础服务,再由 SQLite 插件注册或修改两个搜索方法:不予采纳,因为方法是否可用将取决于插件顺序与资源释放时机,而且该服务需要为唯一的实现定义一套提供方注册协议。
- 将所有查询实现移入 SQLite 包:不予采纳,因为精确读取、过滤与追踪不需要索引,并且都属于应与提供方无关契约放在一起的共享行为。
后果
消费方只需注入一个服务,无需再次查找其他功能,便可组合精确操作与全文操作。生产环境的组合必须选择一个具体后端,即使当前某个消费方只调用继承的精确方法;如果后端行为不在测试范围内,测试可以使用最小子类。
统一后的对象有意保留两种内部观察策略:精确操作在每次调用时读取权威的实时源或持久化源,全文操作则使可丢弃索引与数据源对齐。共用上下文键不会让派生索引成为权威来源,也不会使精确读取的可用性依赖 FTS 查询。
排队阶段的取消仍会及时生效。在异步数据源观察已经开始后取消时,调用方会等待该操作完成清理后才收到拒绝;因此拒绝本身构成静止边界,并保证后续搜索仍按单一串行流程执行。同步 SQLite 语句无法在执行中被抢占,服务会在其前后检查中止信号。
单元测试在同一个键上同时固定继承实现与抽象方法的契约,SQLite 测试在具体后端上覆盖两类操作,真实 Loader 路径则验证单个导出的插件能够注册组合后的服务。