DSH / Atlas
2026-09-09implementedarchitecture

Deprecate synchronous reads of arbitrary Session events

弃用对会话任意位置事件的同步读取

Synchronous access to arbitrary event positions makes consumers depend on the complete Session event sequence being immediately available in memory. The storage direction is to stop retaining that complete sequence in memory. Once historical events require storage I/O, the runtime cannot preserve the same synchronous read guarantee without retaining the history or blocking on storage. New callers increase that depend

English

Problem

Synchronous access to arbitrary event positions makes consumers depend on the complete Session event sequence being immediately available in memory. The storage direction is to stop retaining that complete sequence in memory. Once historical events require storage I/O, the runtime cannot preserve the same synchronous read guarantee without retaining the history or blocking on storage.

New callers increase that dependency even when they read only one old event. Repeated history scans after resume also make ordinary domain logic depend on historical storage instead of the state it actually needs.

Decision

All operations that synchronously read arbitrary positions or ranges of Session event history are deprecated, including Session.eventAt(), Session.snapshotEvents(), and Session.ownEvents(). Existing logic may remain unmigrated for now, but new calls are prohibited. New aliases or wrappers that expose the same synchronous historical access are prohibited as well.

The three methods carry this rule in @deprecated JSDoc. This is an API-use decision; the current Session implementation still retains the complete event sequence in memory.

Repository test files, including scripts/**/*.spec.{ts,tsx}, may call these three readers to inspect emitted events and exercise Session history behavior. The test-file lint override allows snapshotEvents, eventAt, and ownEvents; all other deprecated names remain errors. This allowance also covers unrelated declarations with the same three names under the current linter. It does not apply to production source or non-test repository scripts.

State needed after resume

Design durable event fields and Session projections together so each domain can reconstruct the state its consumers need. Restore that state during resume, then maintain it incrementally from newly committed events. After resume, ordinary logic reads the projection or processes the delivered current event instead of looking back through historical events. Reading already-maintained projection state synchronously does not require arbitrary access to the event log.

Historical content presented on demand uses explicit asynchronous pagination and progressive loading, with each read limited to the requested window. Loading the complete sequence behind a synchronous helper preserves the dependency this decision removes.

Operations that require complete history

Fork and a small number of operations may genuinely need a complete historical sequence or inherited prefix. Their need for those records remains valid and requires an explicit storage read. It does not require the whole sequence to remain resident or grant an exception for new calls to deprecated synchronous readers. Each such consumer must establish why its result requires the complete sequence rather than projected state or a limited historical window.

Alternatives considered

Keep synchronous single-event reads while deprecating only full snapshots. A single requested event may also be absent from memory. Restricting the result size does not remove the storage dependency, and helpers such as ownEvents() retain the same assumption for a suffix.

Keep the complete sequence resident to preserve the read APIs. This lets consumer convenience dictate Session memory retention and prevents the intended storage design. Projections preserve required state, while explicit historical reads preserve access to the records themselves.

Require every existing caller to migrate immediately. Existing logic may defer migration under this decision. Preventing new dependencies bounds the remaining work without making every existing consumer part of the same change.

Disable deprecation lint throughout test files or exempt production readers by name. Tests only need the three reader names; other deprecated APIs must remain errors. Production code retains the prohibition on new synchronous reads.

Add a separate test-only reader API. The three existing readers already expose the observations these tests need. Wrapping them adds an API and production-import checks without changing those observations.

Consequences

New domain behavior must make its event data and projected state sufficient for resumed execution. User-requested history may still load progressively, and genuine full-history operations still have a storage path to design. This decision does not claim that resume or fork already avoids loading the complete log.

Calls outside test files carry line-scoped typescript/no-deprecated waivers that identify deferred migration or delegation between deprecated readers. Remove a waiver when its deprecated call is removed; copying a waiver to a new production call violates this policy. The executable lint check accepts test reads and existing waived reads, rejects unwaived production reads, and rejects unrelated deprecated APIs in tests. Documentation checks verify the source-equivalent API declarations and bilingual records.

Related decisions

The session immutability decision continues to own event ownership and freezing; this decision supersedes its recommendation to use synchronous historical readers. The required projection reader decision continues to own missing-state failures and typed state reads. The recallable compaction proposal retains its recall design while replacing its proposed synchronous history access with paged reads.

中文

问题

同步访问任意事件位置,使消费方依赖完整会话事件序列在内存中随时可用。存储的演进方向是不再将完整序列保存在内存中。历史事件一旦需要存储 I/O,运行时若不继续保留历史或阻塞等待存储,就无法维持相同的同步读取保证。

即使只读取一个旧事件,新增调用也会加深这种依赖。恢复后反复扫描历史,还会使普通领域逻辑依赖历史存储,而不是它真正需要的状态。

决策

弃用所有同步读取会话事件历史任意位置或区间的操作,包括 Session.eventAt()、Session.snapshotEvents() 和 Session.ownEvents()。现有逻辑可以暂不迁移,但禁止新增调用。同样禁止新增暴露相同同步历史访问能力的别名或包装层。

这三个方法通过 @deprecated JSDoc 声明该规则。这是 API 使用决策;当前 Session 实现仍在内存中保留完整事件序列。

仓库测试文件(包括 scripts/**/*.spec.{ts,tsx})可以调用这三个读取方法,以检查已发出的事件并验证 Session 历史行为。测试文件 lint override 允许 snapshotEvents、eventAt 和 ownEvents,其他弃用名称仍报错。在当前 linter 下,此豁免也覆盖使用这三个名称的其他声明。它不适用于生产源码或非测试仓库脚本。

恢复后需要的状态

结合设计持久事件字段与会话投影,使每个领域都能重建其消费方所需的状态。在恢复期间还原这些状态,随后通过新提交的事件增量维护。恢复后,普通逻辑读取投影或处理当前收到的事件,而不是回头查找历史事件。同步读取已经维护好的投影状态,不需要任意访问事件日志。

按需展示的历史内容采用显式异步分页和渐进式加载,每次读取限制在请求的窗口内。在同步辅助方法背后加载完整序列,仍保留了本决策要消除的依赖。

确实需要完整历史的操作

fork 等少数操作可能确实需要完整历史序列或继承前缀。它们对这些记录的需求仍然成立,需要通过显式存储读取来满足。这不要求完整序列常驻内存,也不构成新增已弃用同步读取调用的例外。每个此类消费方都必须说明,为什么其结果需要完整序列,而不能只使用投影状态或局部历史窗口。

曾考虑的替代方案

只弃用完整快照,保留同步单事件读取。 单个被请求的事件也可能不在内存中。限制返回结果大小并不能消除存储依赖,ownEvents() 等辅助方法对后缀序列也保留着相同假设。

为保留读取 API 而让完整序列常驻内存。 这会让消费方的便利性决定会话的内存保留方式,阻碍预期的存储设计。投影保留所需状态,显式历史读取则保留对记录本身的访问能力。

要求立即迁移所有现有调用方。 本决策允许现有逻辑推迟迁移。禁止新增依赖可以限制剩余工作规模,无需将每个现有消费方都纳入同一次修改。

在整个测试文件中禁用弃用 lint,或按名称豁免生产代码中的读取方法。 测试只需要这三个读取方法名;其他弃用 API 必须继续报错。生产代码仍禁止新增同步读取。

添加单独的测试专用读取 API。 三个现有读取方法已经提供这些测试所需的观察结果。包装它们会增加一个 API 及生产代码导入检查,却不会改变这些观察结果。

后果

新增领域行为必须使其事件数据与投影状态足以支持恢复后的执行。用户请求的历史仍可渐进式加载,确实需要完整历史的操作仍需设计存储读取路径。本决策不表示恢复或 fork 已经能够避免加载完整日志。

测试文件之外的调用带有逐行的 typescript/no-deprecated 豁免,说明暂缓迁移或已弃用读取方法之间的委托。删除已弃用调用时应一并删除其豁免;将豁免复制到新增生产调用违反本策略。实际执行 lint 的检查允许测试读取和已有豁免调用,拒绝未豁免的生产读取,并拒绝测试中其他已弃用 API。文档检查校验与源码一致的 API 声明及双语记录。

相关决策

会话不可变性决策继续负责事件所有权与冻结;本决策取代其中对同步历史读取方法的使用建议。投影读取必需性决策继续负责缺失状态时的失败规则与类型化状态读取。可回溯压缩提案保留其回溯设计,并将其中拟议的同步历史访问替换为分页读取。