fix(session-export): distinguish unsupported raw artifacts

SessionPersistence.readRaw previously used undefined for two unrelated states: a supported backend could not find the requested session, or the backend had no per-session artifact concept at all. The export endpoint consequently reported an existing SQLite-backed session as HTTP 404, which falsely diagnosed storage capability as session absence.

Make raw-artifact support an explicit backend capability. Unsupported backends now fail their inherited readRaw path loudly and the host answers 501 before reading, while undefined retains the single meaning of an absent artifact on a supporting backend. First-party backends, test providers, generated API catalogs, bilingual persistence docs, and export error contracts now state that distinction; focused tests cover both the 501 and the inherited rejection.
This commit is contained in:
Tianyi Cui
2026-08-11 15:04:35 +08:00
parent 7f14c7e165
commit e58cc13de4
24 changed files with 78 additions and 30 deletions

View File

@@ -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/persistence.md
persistence.md: fd694161ed8ae4c364de5c22d8eb06f1b0a91aec
persistence.zh.md: b616b282204e946e18e90271d1eaeb2d4ed70fc3
persistence.md: d63fbaa22adead19fa53ae717e7d589f823f885a
persistence.zh.md: 31cf598ec4e6d6acca1111b5d576d585a49d362a

View File

@@ -124,7 +124,7 @@ Replay/fork is therefore `ctx.sessions.create(id, { seed: seedEvents })`; resumi
## `SessionRawArtifact` — verbatim stored artifact text
A backend's own artifact text for one session, byte-identical to what it durably wrote (decoded from its physical encoding). `readRaw` returns it without reconstructing from parsed events, so backend-specific serialization (chunk packing, key order, line breaks) survives; backends without a per-session artifact, such as SQLite, inherit the `undefined` default.
A backend's own artifact text for one session, byte-identical to what it durably wrote (decoded from its physical encoding). `readRaw` returns it without reconstructing from parsed events, so backend-specific serialization (chunk packing, key order, line breaks) survives. Consumers first test `supportsRawArtifacts`: `false` means the backend does not provide this capability (for example SQLite), while `readRaw(...) === undefined` means a supported backend has no materialized artifact for that session.
```ts type-equiv
/** A backend's own raw artifact text for one session, verbatim. */
@@ -262,13 +262,15 @@ abstract locate(meta: SessionHeader): SessionLocation | undefined
* bytes the backend wrote (decoded from its physical encoding, e.g. a
* decompressed JSONL). The returned `content` is the raw text, not a
* reconstruction from parsed events, so it preserves backend-specific
* serialization (chunk packing, key order, line breaks). Backends without a
* per-session artifact (SQLite) inherit the `undefined` default.
* serialization (chunk packing, key order, line breaks). Callers first test
* {@link supportsRawArtifacts}; `undefined` then means only that the requested
* session has no materialized artifact.
* @param _id - the persisted session to read (unused by the default: no
* per-session artifact).
* @param signal - optional cancellation for backend read work.
* @returns the raw artifact plus its parsed header, or `undefined` when the
* session is absent or the backend owns no per-session artifact.
* session is absent.
* @throws when this backend does not expose per-session raw artifacts.
*/
readRaw(_id: SessionId, signal?: AbortSignal): Promise<SessionRawArtifact | undefined>

View File

@@ -124,7 +124,7 @@ interface CreateSessionOptions {
## `SessionRawArtifact`——逐字存储工件文本
后端为单个会话自持的工件文本,与其持久化写入的字节逐字一致(按物理编码解码)。`readRaw` 返回它而不从解析后事件重建因此后端特定的序列化chunk 打包、键序、换行)得以保留;没有每会话工件的后端(如 SQLite继承 `undefined` 默认
后端为单个会话自持的工件文本,与其持久化写入的字节逐字一致(按物理编码解码)。`readRaw` 返回它而不从解析后事件重建因此后端特定的序列化chunk 打包、键序、换行)得以保留。Consumer 须先检查 `supportsRawArtifacts``false` 表示后端不提供此能力(如 SQLite,而 `readRaw(...) === undefined` 表示受支持的后端没有该会话的已实体化工件
```ts type-equiv
/** A backend's own raw artifact text for one session, verbatim. */
@@ -262,13 +262,15 @@ abstract locate(meta: SessionHeader): SessionLocation | undefined
* bytes the backend wrote (decoded from its physical encoding, e.g. a
* decompressed JSONL). The returned `content` is the raw text, not a
* reconstruction from parsed events, so it preserves backend-specific
* serialization (chunk packing, key order, line breaks). Backends without a
* per-session artifact (SQLite) inherit the `undefined` default.
* serialization (chunk packing, key order, line breaks). Callers first test
* {@link supportsRawArtifacts}; `undefined` then means only that the requested
* session has no materialized artifact.
* @param _id - the persisted session to read (unused by the default: no
* per-session artifact).
* @param signal - optional cancellation for backend read work.
* @returns the raw artifact plus its parsed header, or `undefined` when the
* session is absent or the backend owns no per-session artifact.
* session is absent.
* @throws when this backend does not expose per-session raw artifacts.
*/
readRaw(_id: SessionId, signal?: AbortSignal): Promise<SessionRawArtifact | undefined>