mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
docs(session-projection): sync persist removal, cache root, and catalogs
- subsystem docs: drop the removed persist flag from the ProjectionDefinition type block (both languages). - config-catalog regenerated (cache requires sessionProjections/sessions, config gains root) and the zh side synced by hand; doc graphs regenerated. - Agent Notes: the per-session cache note records the owned root tree and no-persistence design; the storage-root proposal's link to it is corrected (two levels up).
This commit is contained in:
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-08-19-projection-cache-per-session-files.md
|
||||
2026-08-19-projection-cache-per-session-files.md: 1b5b0f3d3df95fd8193e6674ca09f0806bd82db6
|
||||
2026-08-19-projection-cache-per-session-files.zh.md: adf4ed1261680f11366b38225885a3bce7e71bc4
|
||||
2026-08-19-projection-cache-per-session-files.md: 62ba71bf1f33461ab0e43fa87d3177bd6649ef28
|
||||
2026-08-19-projection-cache-per-session-files.zh.md: e3af483711953fd5c1c471c105725eca8e0c300c
|
||||
|
||||
+9
-9
@@ -10,20 +10,20 @@ The persisted projection cache was one global `session_projcache.json` — a `se
|
||||
|
||||
## Decision
|
||||
|
||||
The cache becomes one `projection_cache.json` per session, stored inside the session's own persistence directory. The location comes from the persistence seam — `sessionPersistence.locate(meta)` — so the persistence backend owns the session-directory layout (the jsonl backend places the file beside the session log); the cache service never imports a backend's path helpers. The cache service keeps every other responsibility: checkpoint fold, write policy (turn/end + disposal mandatory, count/interval throttle), fail-soft durability, and the cold-read ladder.
|
||||
The cache becomes one `projection_cache.json` per session, stored under the cache's own storage root — `<root>/<session-id>/projection_cache.json` (the session id is a code-generated string, used directly as the directory name). The cache owns its directory tree and never consults the persistence layer: no `locate`, no dependency on which backend is mounted. The cache service keeps every other responsibility: checkpoint fold, write policy (turn/end + disposal mandatory, count/interval throttle), fail-soft durability, and the listing read.
|
||||
|
||||
Reading a cache row is now one file read, so `cachedSnapshot(meta)` is async; `coldSnapshot` takes the session header (it needs the header to locate the file — the stored log's header remains the identity witness). A persistence backend without a per-session directory (e.g. sqlite) disables the durable cache: writes no-op and cold reads fall to the full-log rung.
|
||||
Reading a cache row is one file read, so `cachedSnapshot(meta)` is async. The cache no longer runs a cold-refold ladder (that would require reading the session log, which belongs to the persistence layer); a consumer that needs a guaranteed cold snapshot refolds from the log itself. Session directories and cache files are created owner-only (`0o700`/`0o600`) via `@deepseek-ai/dsh-atomic-write`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Per-session write isolation: each throttled write replaces only that session's small file, removing the global write amplification.
|
||||
- Per-session write isolation: each throttled write replaces only that session's small file, removing the global write amplification. Writes to one cache file serialize, so a newer cut never lands before an older one; plugin disposal drains in-flight writes.
|
||||
- Listing pays N small file reads instead of one big load; a session without a cache file simply lacks the projection column.
|
||||
- No migration: the cache is derived data, never an authority. An obsolete global cache (or any earlier format) is never read — the first cold read refolds from the log and writes the current format.
|
||||
- The cache file is bound to the same log lifecycle as before: the stored `{createdAt, cwd}` identity guards against a recreated id or a swapped store.
|
||||
- No migration: the cache is derived data, never an authority. An obsolete cache (any earlier format) is never read — the first cold read refolds from the log and writes the current format.
|
||||
- The cache file is bound to the same log lifecycle as before: the stored `{createdAt, cwd}` identity guards against a recreated id.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Keep the global sessions table.** Preserves one-load listing and a synchronous `cachedSnapshot`, but keeps the global write amplification and single-file blast radius that motivated the change.
|
||||
- **One storage-domain unit per session in the storage root** (flat `session_projcache_<id>.json`). Rejected: unit names must match `[a-z0-9_]` (session ids cannot), and the files would sit outside the session's own directory, scattering the storage root instead of living beside the log.
|
||||
- **Replicate the session-directory layout inside the cache.** Rejected: the persistence backend already owns that layout through `locate`; duplicating the path helpers couples the cache to a backend's internals.
|
||||
- **Reuse `@deepseek-ai/dsh-atomic-write`** (the zero-dependency `writeFileAtomic` with `mode`/`dirMode`). Adopted: the cache writes through it with `{ mode: 0o600, dirMode: 0o700 }`, keeping the session tree owner-only like the jsonl backend's own 0o700 directories, instead of exporting a second atomic-write primitive from the storage-json backend plugin.
|
||||
- **Keep the global sessions table.** Preserves one-load listing, but keeps the global write amplification and single-file blast radius that motivated the change.
|
||||
- **Resolve the path through `sessionPersistence.locate(meta)`** (the file beside the session log). Rejected: the cache would have to guess "beside the log" from a log artifact path (`dirname` + fixed filename), coupling the cache to the persistence service and to a backend's layout. The cache keeps its own tree instead.
|
||||
- **Replicate the session-directory layout inside the cache.** Rejected: the persistence backend already owns that layout; the cache does not need the log's project/session directory structure for its own derived files.
|
||||
- **Use the existing `@deepseek-ai/dsh-atomic-write`.** Adopted: it is the repo's single atomic whole-file write primitive (owner-only modes included); exporting a second one from the storage-json backend was rejected.
|
||||
|
||||
+9
-9
@@ -10,20 +10,20 @@ Status: implemented
|
||||
|
||||
## Decision
|
||||
|
||||
缓存改为每会话一个 `projection_cache.json`,存放在该会话自己的持久化目录内。位置来自持久化 seam——`sessionPersistence.locate(meta)`——因此会话目录布局由持久化后端所有(jsonl 后端将其放在会话日志旁);缓存服务绝不 import 后端的路径 helper。缓存服务保留其余全部职责:检查点折叠、写策略(turn/end + dispose 强制点、count/interval 节流)、fail-soft 持久化与冷读阶梯。
|
||||
缓存改为每会话一个 `projection_cache.json`,存放在缓存自己的存储根下——`<root>/<session-id>/projection_cache.json`(session id 是代码生成的字符串,直接用作目录名)。缓存拥有自己的目录树,绝不咨询持久化层:没有 `locate`、不依赖挂载的是哪个后端。缓存服务保留其余全部职责:检查点折叠、写策略(turn/end + dispose 强制点、count/interval 节流)、fail-soft 持久化与列表读。
|
||||
|
||||
读取缓存行现在是一次文件读取,因此 `cachedSnapshot(meta)` 变为异步;`coldSnapshot` 改为接收会话 header(定位文件需要 header——存储日志的 header 仍是身份见证)。没有每会话目录的持久化后端(如 sqlite)会禁用持久缓存:写入变为 no-op,冷读落到全量日志那一级。
|
||||
读取缓存行现在是一次文件读取,因此 `cachedSnapshot(meta)` 变为异步。缓存不再运行冷重折叠阶梯(那需要读取会话日志,属于持久化层的职责);需要保证冷快照的消费方自行从日志重折叠。会话目录与缓存文件经 `@deepseek-ai/dsh-atomic-write` 以仅属主权限(`0o700`/`0o600`)创建。
|
||||
|
||||
## Consequences
|
||||
|
||||
- 每会话写入隔离:每次节流写入只替换该会话的小文件,消除全局写放大。
|
||||
- 每会话写入隔离:每次节流写入只替换该会话的小文件,消除全局写放大。同一缓存文件的写入被串行化,新切面绝不会先于旧切面落盘;插件释放时会排空在途写入。
|
||||
- 列表读取从一次大加载变为 N 次小文件读取;没有缓存文件的会话只是缺少投影列。
|
||||
- 无需迁移:缓存是派生数据,绝非权威。过时的全局缓存(或任何更早格式)从不被读取——首次冷读从日志重折叠并写出当前格式。
|
||||
- 缓存文件仍绑定同一日志生命周期:存储的 `{createdAt, cwd}` 身份防止被重建的 id 或替换的存储误导。
|
||||
- 无需迁移:缓存是派生数据,绝非权威。过时的缓存(任何更早格式)从不被读取——首次冷读从日志重折叠并写出当前格式。
|
||||
- 缓存文件仍绑定同一日志生命周期:存储的 `{createdAt, cwd}` 身份防止被重建的 id 误导。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **保留全局 sessions 表。** 保留一次加载式列表与同步 `cachedSnapshot`,但保留了促成此改动的全局写放大与单文件爆炸半径。
|
||||
- **存储根目录下每会话一个 storage-domain unit**(扁平 `session_projcache_<id>.json`)。未采用:unit 名必须匹配 `[a-z0-9_]`(会话 id 做不到),且文件会落在会话目录之外,散落在存储根目录而不是日志旁。
|
||||
- **在缓存内复刻会话目录布局。** 未采用:持久化后端已通过 `locate` 拥有该布局;复刻路径 helper 会把缓存耦合到后端的内部实现。
|
||||
- **复用 `@deepseek-ai/dsh-atomic-write`**(零依赖的 `writeFileAtomic`,带 `mode`/`dirMode`)。已采纳:缓存以 `{ mode: 0o600, dirMode: 0o700 }` 经其写入,使会话目录树与 jsonl 后端自己的 0o700 目录保持一致、仅属主可读,而不是从 storage-json 后端插件再导出一套原子写原语。
|
||||
- **保留全局 sessions 表。** 保留一次加载式列表,但保留了促成此改动的全局写放大与单文件爆炸半径。
|
||||
- **经 `sessionPersistence.locate(meta)` 解析路径**(文件放在会话日志旁)。未采用:缓存得从日志 artifact 路径"猜"日志旁边(`dirname` + 固定文件名),把缓存耦合到持久化服务与后端的布局。缓存改用自有目录树。
|
||||
- **在缓存内复刻会话目录布局。** 未采用:持久化后端已拥有该布局;缓存为自己派生的文件不需要日志的 project/session 目录结构。
|
||||
- **使用现有 `@deepseek-ai/dsh-atomic-write`。** 采纳:它是仓库唯一的原子整文件写入原语(含仅属主权限);从 storage-json 后端再导出一套并行实现被否决。
|
||||
|
||||
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-28-storage-root-and-derived-medium-recovery.md
|
||||
2026-07-28-storage-root-and-derived-medium-recovery.md: 9650b43a2493a36658a410e93cc76ec0050974ae
|
||||
2026-07-28-storage-root-and-derived-medium-recovery.zh.md: 0d944f017ce3187c1c1238893c8b83423f615d20
|
||||
2026-07-28-storage-root-and-derived-medium-recovery.md: c06226c34518a6971a313cc5b7e78c9750a90a17
|
||||
2026-07-28-storage-root-and-derived-medium-recovery.zh.md: 6082bc78729f2270840bb7e07860af227370f632
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ The persisted projection cache ([note](2026-07-27-session-projection-and-command
|
||||
|
||||
**Where the files actually live (root mismatch closed; resolve-once residual still open).** The shared base defaults the session store to the global harness home (`$DSH_HOME/sessions`, default `~/.dsh/sessions`), while the shipped Web overlay used to give the json backend the relative root `./.storages`: `workspace.json` and `session_projcache.json` landed under `<launch dir>/.storages/` — two launches from different directories shared their sessions yet saw different workspace registries and different projection caches, and the cache exists precisely to serve the cross-session cold listing, which missed for every session last cached under another launch directory. That mismatch is now closed: the overlay anchors `storage-json.root` to `$DSH_HOME/storages` with the same `!!js` expression the session root uses (`apps/cli/config/web.cordis.yml`). The residual hazard: `JsonStorageBackend` still never resolves its root — each unit open joins the path against whatever `process.cwd()` is at that moment (packages/storage/storage-json/src/index.ts); the shipped overlay root is already absolute and unaffected, but any relative root (bare Loader boots, tests) still splits on a later cwd change — the exact hazard the JSONL session backend resolves-once to prevent ("later process.cwd() changes cannot split one backend across roots", packages/session/session-persistence-jsonl/src/index.ts).
|
||||
|
||||
**How recovery works today.** Inside a healthy medium the cache is fully self-healing by design: a `stateVersion`-mismatched row is discarded and refolded, a log shrunk below a row's watermark is detected by the anchored restore floor and answered with one full re-read, and every background write is fail-soft. But at the *medium* level there is no recovery at all: a truncated, hand-edited, or version-bumped `session_projcache.json` fails `openJsonUnit` with `malformed-medium`/`version-mismatch` (packages/storage/storage-json/src/format.ts), a schema-drifted record fails domain open with `invalid-record` (packages/storage/storage-domain/src/index.ts), the rejection propagates through `SessionProjectionCache[Service.init]`, and under the CLI's fail-loud boot the assembly refuses to start. A file whose entire content is rebuildable from session logs can brick boot. This contradicts the cache package's own stated stance ("a stale or unreadable cache costs a longer tail replay, never a wrong value") and the cache domain spec's JSDoc ("version bumps discard the whole medium"), which today describes an aspiration, not the implementation. Partially superseded for the projection cache: [the per-session cache files note](../implemented/architecture/2026-08-19-projection-cache-per-session-files.md) removed the global `session_projcache` domain, so the cache half of this proposal (recovery on that domain) no longer applies; the `workspace.json` half remains current. The same fail-loud path is *correct* for `workspace.json` — workspace records are authoritative, not derivable — so the missing concept is a per-domain declaration of authority, not a global behavior change.
|
||||
**How recovery works today.** Inside a healthy medium the cache is fully self-healing by design: a `stateVersion`-mismatched row is discarded and refolded, a log shrunk below a row's watermark is detected by the anchored restore floor and answered with one full re-read, and every background write is fail-soft. But at the *medium* level there is no recovery at all: a truncated, hand-edited, or version-bumped `session_projcache.json` fails `openJsonUnit` with `malformed-medium`/`version-mismatch` (packages/storage/storage-json/src/format.ts), a schema-drifted record fails domain open with `invalid-record` (packages/storage/storage-domain/src/index.ts), the rejection propagates through `SessionProjectionCache[Service.init]`, and under the CLI's fail-loud boot the assembly refuses to start. A file whose entire content is rebuildable from session logs can brick boot. This contradicts the cache package's own stated stance ("a stale or unreadable cache costs a longer tail replay, never a wrong value") and the cache domain spec's JSDoc ("version bumps discard the whole medium"), which today describes an aspiration, not the implementation. Partially superseded for the projection cache: [the per-session cache files note](../../implemented/architecture/2026-08-19-projection-cache-per-session-files.md) removed the global `session_projcache` domain, so the cache half of this proposal (recovery on that domain) no longer applies; the `workspace.json` half remains current. The same fail-loud path is *correct* for `workspace.json` — workspace records are authoritative, not derivable — so the missing concept is a per-domain declaration of authority, not a global behavior change.
|
||||
|
||||
## Proposal
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ Status: proposed
|
||||
|
||||
**文件到底存在哪(根错位已收口,resolve-once 残余仍开放)。** 共享 base 将会话存储默认为全局 harness home(`$DSH_HOME/sessions`,默认 `~/.dsh/sessions`),而出厂 Web overlay 曾给 json 后端相对根 `./.storages`:`workspace.json` 和 `session_projcache.json` 落在 `<启动目录>/.storages/` 下——从两个不同目录启动,会话相同,工作区注册表和投影缓存却各是一份,而缓存存在的意义恰恰是跨会话冷列表,凡上次在别的启动目录下缓存过的会话全部 miss。这一错位已消除:overlay 现以与会话根同一段 `!!js` 表达式把 `storage-json.root` 锚定到 `$DSH_HOME/storages`(`apps/cli/config/web.cordis.yml`)。残余隐患:`JsonStorageBackend` 仍从不 resolve 根——每次打开 unit 都把路径 join 到当时的 `process.cwd()` 上(packages/storage/storage-json/src/index.ts);出厂 overlay 的根已是绝对路径不受影响,但任何相对根(裸 Loader 启动、测试)仍会被后续 cwd 变化劈开,JSONL 会话后端用「构造时 resolve 一次」防住的正是它("later process.cwd() changes cannot split one backend across roots",packages/session/session-persistence-jsonl/src/index.ts)。
|
||||
|
||||
**现在是怎么恢复的。** 在健康介质内部,缓存按设计完全自愈:`stateVersion` 不匹配的行被丢弃重折,日志缩短到行水位以下由带锚的 restore floor 检出并以一次全量重读回答,每次后台写都是 fail-soft。但在*介质*层面完全没有恢复:被截断、被手改或版本被 bump 的 `session_projcache.json` 会让 `openJsonUnit` 以 `malformed-medium`/`version-mismatch` 失败(packages/storage/storage-json/src/format.ts),schema 漂移的记录让域 open 以 `invalid-record` 失败(packages/storage/storage-domain/src/index.ts),拒绝一路穿过 `SessionProjectionCache[Service.init]`,在 CLI 的 fail-loud 启动下整个组装拒绝启动。一个内容完全可从会话日志重建的文件能把启动搞死。这与缓存包自己声明的立场("a stale or unreadable cache costs a longer tail replay, never a wrong value")和缓存域 spec 的 JSDoc("version bumps discard the whole medium")相矛盾——后者今天描述的是愿望而非实现。投影缓存半边已被[每会话缓存文件 note](../implemented/architecture/2026-08-19-projection-cache-per-session-files.md) 部分取代:全局 `session_projcache` domain 已移除,本提案的缓存恢复半边不再适用;`workspace.json` 半边仍然有效。同一条 fail-loud 路径对 `workspace.json` 却是*正确*的——工作区记录是权威数据,不可派生——所以缺的概念是按域声明权威性,而不是全局改行为。
|
||||
**现在是怎么恢复的。** 在健康介质内部,缓存按设计完全自愈:`stateVersion` 不匹配的行被丢弃重折,日志缩短到行水位以下由带锚的 restore floor 检出并以一次全量重读回答,每次后台写都是 fail-soft。但在*介质*层面完全没有恢复:被截断、被手改或版本被 bump 的 `session_projcache.json` 会让 `openJsonUnit` 以 `malformed-medium`/`version-mismatch` 失败(packages/storage/storage-json/src/format.ts),schema 漂移的记录让域 open 以 `invalid-record` 失败(packages/storage/storage-domain/src/index.ts),拒绝一路穿过 `SessionProjectionCache[Service.init]`,在 CLI 的 fail-loud 启动下整个组装拒绝启动。一个内容完全可从会话日志重建的文件能把启动搞死。这与缓存包自己声明的立场("a stale or unreadable cache costs a longer tail replay, never a wrong value")和缓存域 spec 的 JSDoc("version bumps discard the whole medium")相矛盾——后者今天描述的是愿望而非实现。投影缓存半边已被[每会话缓存文件 note](../../implemented/architecture/2026-08-19-projection-cache-per-session-files.md) 部分取代:全局 `session_projcache` domain 已移除,本提案的缓存恢复半边不再适用;`workspace.json` 半边仍然有效。同一条 fail-loud 路径对 `workspace.json` 却是*正确*的——工作区记录是权威数据,不可派生——所以缺的概念是按域声明权威性,而不是全局改行为。
|
||||
|
||||
## 提案
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ flowchart LR
|
||||
cfg --> plugin_dsh_base_llm_pi_ai
|
||||
plugin_dsh_base_session_persistence_jsonl["session-persistence-jsonl<br/>@deepseek-ai/dsh-session-persistence-jsonl"]
|
||||
cfg --> plugin_dsh_base_session_persistence_jsonl
|
||||
plugin_dsh_base_session_projection_cache["session-projection-cache<br/>@deepseek-ai/dsh-session-projection-cache"]
|
||||
cfg --> plugin_dsh_base_session_projection_cache
|
||||
plugin_dsh_base_attachment_local["attachment-local<br/>@deepseek-ai/dsh-attachment-local"]
|
||||
cfg --> plugin_dsh_base_attachment_local
|
||||
plugin_dsh_base_session_query_sqlite["session-query-sqlite<br/>@deepseek-ai/dsh-session-query-sqlite"]
|
||||
@@ -186,6 +188,7 @@ flowchart LR
|
||||
| `credentials` | `@deepseek-ai/dsh-credentials-local` |
|
||||
| `llm-pi-ai` | `@deepseek-ai/dsh-llm-pi-ai` |
|
||||
| `session-persistence-jsonl` | `@deepseek-ai/dsh-session-persistence-jsonl` |
|
||||
| `session-projection-cache` | `@deepseek-ai/dsh-session-projection-cache` |
|
||||
| `attachment-local` | `@deepseek-ai/dsh-attachment-local` |
|
||||
| `session-query-sqlite` | `@deepseek-ai/dsh-session-query-sqlite` |
|
||||
| `session-projection` | `@deepseek-ai/dsh-session-projection` |
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/config-catalog.md
|
||||
config-catalog.md: bd15d4c0b548526acd9adb578ba10e6a37f94410
|
||||
config-catalog.zh.md: 190500df89c97b68e3fff201e90f51436250ab07
|
||||
config-catalog.md: b5c487120f1272ddb1da29d8ed1230a5c1f3d0a9
|
||||
config-catalog.zh.md: 435ba990e69f2efc157edc793568cd889e639d0a
|
||||
|
||||
@@ -1698,7 +1698,7 @@ Source: [`packages/session/session-persistence-sqlite/src/index.ts:70`](../packa
|
||||
|
||||
## `@deepseek-ai/dsh-session-projection-cache`
|
||||
|
||||
Requires: `storageDomain` · `sessionProjections` · `sessionPersistence` · `sessions`
|
||||
Requires: `sessionProjections` · `sessions`
|
||||
|
||||
```ts config-catalog
|
||||
/**
|
||||
@@ -1708,6 +1708,8 @@ Requires: `storageDomain` · `sessionProjections` · `sessionPersistence` · `se
|
||||
* disposal) are policy, not tunables, and always fire.
|
||||
*/
|
||||
export interface Config {
|
||||
/** Directory holding one `<session-id>/projection_cache.json` per session. */
|
||||
root: string
|
||||
/** Committed events per session that force a durable checkpoint write between mandatory points. */
|
||||
writeEveryEvents: number
|
||||
/** Longest time (milliseconds) a dirty checkpoint may stay unwritten between mandatory points. */
|
||||
@@ -1715,7 +1717,7 @@ export interface Config {
|
||||
}
|
||||
```
|
||||
|
||||
Source: [`packages/session/session-projection-cache/src/index.ts:42`](../packages/session/session-projection-cache/src/index.ts)
|
||||
Source: [`packages/session/session-projection-cache/src/index.ts:48`](../packages/session/session-projection-cache/src/index.ts)
|
||||
|
||||
<a id="deepseek-aidsh-session-query-sqlite"></a>
|
||||
|
||||
|
||||
@@ -1700,7 +1700,7 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
|
||||
|
||||
## `@deepseek-ai/dsh-session-projection-cache`
|
||||
|
||||
需要:`storageDomain` · `sessionProjections` · `sessionPersistence` · `sessions`
|
||||
需要:`sessionProjections` · `sessions`
|
||||
|
||||
```ts config-catalog
|
||||
/**
|
||||
@@ -1710,6 +1710,8 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
|
||||
* disposal) are policy, not tunables, and always fire.
|
||||
*/
|
||||
export interface Config {
|
||||
/** Directory holding one `<session-id>/projection_cache.json` per session. */
|
||||
root: string
|
||||
/** Committed events per session that force a durable checkpoint write between mandatory points. */
|
||||
writeEveryEvents: number
|
||||
/** Longest time (milliseconds) a dirty checkpoint may stay unwritten between mandatory points. */
|
||||
@@ -1717,7 +1719,7 @@ export interface Config {
|
||||
}
|
||||
```
|
||||
|
||||
来源:[`packages/session/session-projection-cache/src/index.ts:42`](../packages/session/session-projection-cache/src/index.ts)
|
||||
来源:[`packages/session/session-projection-cache/src/index.ts:48`](../packages/session/session-projection-cache/src/index.ts)
|
||||
|
||||
<a id="deepseek-aidsh-session-query-sqlite"></a>
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/session-projection.md
|
||||
session-projection.md: 66543b6bfff666435acb8c00dd2f87d5fb0bccb4
|
||||
session-projection.zh.md: 4d99c208a8c54424655ec0b4238dbed37c75603b
|
||||
session-projection.md: f17e70d3dd4a4d89381bbe2be1428647a612ff9a
|
||||
session-projection.zh.md: cc6a4edf1ba394cf71abfa64ccff6667566f5d9d
|
||||
|
||||
@@ -27,8 +27,6 @@ interface ProjectionDefinition<
|
||||
key: K
|
||||
/** Validates persisted state before it seeds a fold. */
|
||||
stateSchema: ZodType<S>
|
||||
/** Persist a host-only unit. Client-visible units are always persisted. */
|
||||
persist?: boolean
|
||||
/**
|
||||
* State for the empty log.
|
||||
* @returns the initial state.
|
||||
@@ -114,7 +112,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp
|
||||
|
||||
### `ctx.sessionProjectionCache` — `SessionProjectionCache`
|
||||
|
||||
The persisted projection cache service. Checkpoints live sessions on a throttled write-behind (count/interval triggers from Config) plus two mandatory points — `turn/end` and session disposal (the live-to-cold moment) — and serves the cold-read ladder: cached file, persistence `readFrom` tail, registry `restore`, durable write-back. Every durable write is fail-soft: failures log a warning and the cache self-heals on the next write or cold read. A persistence backend without a per-session directory (e.g. sqlite) disables the durable cache: writes no-op, cold reads fall to the full-log rung.
|
||||
The persisted projection cache service. Checkpoints live sessions on a throttled write-behind (count/interval triggers from Config) plus two mandatory points — `turn/end` and session disposal (the live-to-cold moment) — and serves the cached rows for a session header. Every durable write is fail-soft: failures log a warning and the cache self-heals on the next write. The cache owns its directory tree and never consults the persistence layer.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
@@ -122,9 +120,7 @@ The persisted projection cache service. Checkpoints live sessions on a throttled
|
||||
* (version-matching keys only), each cut carried with its watermark so a
|
||||
* client value store can seed under its higher-seq-wins rule — as stale as
|
||||
* the last durable checkpoint but never wrong, and never from an unrelated
|
||||
* log (the caller's header is the identity witness). Fresher paths (the
|
||||
* history tail baseline, {@link coldSnapshot}) supersede these values
|
||||
* whenever a session is actually opened.
|
||||
* log (the caller's header is the identity witness).
|
||||
* @param meta - the listed session's header (identity witness; no log read).
|
||||
* @returns the cut (`asOfSeq` = lowest served-row watermark), or
|
||||
* `undefined` when no usable row exists for this lifecycle.
|
||||
@@ -141,26 +137,11 @@ async cachedSnapshot(meta: SessionHeader): Promise<ProjectionSnapshot | undefine
|
||||
* @returns resolution after durability and event emission.
|
||||
*/
|
||||
async write(session: Session): Promise<void>
|
||||
|
||||
/**
|
||||
* Cold-read one persisted session's projections with zero full-log load:
|
||||
* cached rows + a persistence `readFrom` tail from the registry's restore
|
||||
* floor, refolded by the registry and written back (fail-soft) so the next
|
||||
* cold read starts closer. A cache row invalidated by a shrunk log
|
||||
* (crash-repair truncation) triggers one full re-read from seq 0 — the
|
||||
* ladder's slow rung, still no crash. Rejects when the session has no
|
||||
* persisted log (`not found` from the persistence seam).
|
||||
* @param meta - the persisted session whose projections are read (locates
|
||||
* the cache file and witnesses the stored log identity).
|
||||
* @param signal - optional cancellation for the persistence reads.
|
||||
* @returns the snapshot cut at the stored log end.
|
||||
*/
|
||||
async coldSnapshot(meta: SessionHeader, signal?: AbortSignal): Promise<ProjectionSnapshot>
|
||||
```
|
||||
|
||||
Types: [Session](session.md) · [SessionHeader](persistence.md)
|
||||
|
||||
Source: [`packages/session/session-projection-cache/src/index.ts:78`](../../packages/session/session-projection-cache/src/index.ts)
|
||||
Source: [`packages/session/session-projection-cache/src/index.ts:80`](../../packages/session/session-projection-cache/src/index.ts)
|
||||
|
||||
<a id="ctxsessionprojections--sessionprojectionregistry"></a>
|
||||
|
||||
|
||||
@@ -27,8 +27,6 @@ interface ProjectionDefinition<
|
||||
key: K
|
||||
/** Validates persisted state before it seeds a fold. */
|
||||
stateSchema: ZodType<S>
|
||||
/** Persist a host-only unit. Client-visible units are always persisted. */
|
||||
persist?: boolean
|
||||
/**
|
||||
* State for the empty log.
|
||||
* @returns the initial state.
|
||||
@@ -114,7 +112,7 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp
|
||||
|
||||
### `ctx.sessionProjectionCache` — `SessionProjectionCache`
|
||||
|
||||
The persisted projection cache service. Checkpoints live sessions on a throttled write-behind (count/interval triggers from Config) plus two mandatory points — `turn/end` and session disposal (the live-to-cold moment) — and serves the cold-read ladder: cached file, persistence `readFrom` tail, registry `restore`, durable write-back. Every durable write is fail-soft: failures log a warning and the cache self-heals on the next write or cold read. A persistence backend without a per-session directory (e.g. sqlite) disables the durable cache: writes no-op, cold reads fall to the full-log rung.
|
||||
The persisted projection cache service. Checkpoints live sessions on a throttled write-behind (count/interval triggers from Config) plus two mandatory points — `turn/end` and session disposal (the live-to-cold moment) — and serves the cached rows for a session header. Every durable write is fail-soft: failures log a warning and the cache self-heals on the next write. The cache owns its directory tree and never consults the persistence layer.
|
||||
|
||||
```ts cordis-catalog
|
||||
/**
|
||||
@@ -122,9 +120,7 @@ The persisted projection cache service. Checkpoints live sessions on a throttled
|
||||
* (version-matching keys only), each cut carried with its watermark so a
|
||||
* client value store can seed under its higher-seq-wins rule — as stale as
|
||||
* the last durable checkpoint but never wrong, and never from an unrelated
|
||||
* log (the caller's header is the identity witness). Fresher paths (the
|
||||
* history tail baseline, {@link coldSnapshot}) supersede these values
|
||||
* whenever a session is actually opened.
|
||||
* log (the caller's header is the identity witness).
|
||||
* @param meta - the listed session's header (identity witness; no log read).
|
||||
* @returns the cut (`asOfSeq` = lowest served-row watermark), or
|
||||
* `undefined` when no usable row exists for this lifecycle.
|
||||
@@ -141,26 +137,11 @@ async cachedSnapshot(meta: SessionHeader): Promise<ProjectionSnapshot | undefine
|
||||
* @returns resolution after durability and event emission.
|
||||
*/
|
||||
async write(session: Session): Promise<void>
|
||||
|
||||
/**
|
||||
* Cold-read one persisted session's projections with zero full-log load:
|
||||
* cached rows + a persistence `readFrom` tail from the registry's restore
|
||||
* floor, refolded by the registry and written back (fail-soft) so the next
|
||||
* cold read starts closer. A cache row invalidated by a shrunk log
|
||||
* (crash-repair truncation) triggers one full re-read from seq 0 — the
|
||||
* ladder's slow rung, still no crash. Rejects when the session has no
|
||||
* persisted log (`not found` from the persistence seam).
|
||||
* @param meta - the persisted session whose projections are read (locates
|
||||
* the cache file and witnesses the stored log identity).
|
||||
* @param signal - optional cancellation for the persistence reads.
|
||||
* @returns the snapshot cut at the stored log end.
|
||||
*/
|
||||
async coldSnapshot(meta: SessionHeader, signal?: AbortSignal): Promise<ProjectionSnapshot>
|
||||
```
|
||||
|
||||
Types: [Session](session.md) · [SessionHeader](persistence.md)
|
||||
|
||||
Source: [`packages/session/session-projection-cache/src/index.ts:78`](../../packages/session/session-projection-cache/src/index.ts)
|
||||
Source: [`packages/session/session-projection-cache/src/index.ts:80`](../../packages/session/session-projection-cache/src/index.ts)
|
||||
|
||||
<a id="ctxsessionprojections--sessionprojectionregistry"></a>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user