fix(session-persistence): address preparation review feedback
This commit is contained in:
@@ -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 packages/session-query/session-query/README.md
|
||||
README.md: df97333be3b2c2cf71dd8c9287959bcbd83a5063
|
||||
README.zh.md: 649d06cdc7c3a61f9f2459466bc9fdc3a42554e6
|
||||
README.md: b75c1f23264cfa7c9323970b3c1a77ddcfe69be0
|
||||
README.zh.md: e1b9727ff892047c56d006e02906f3270fb11293
|
||||
|
||||
@@ -17,7 +17,7 @@ English | [中文](README.zh.md)
|
||||
- `traceSession(sessionId, signal?)` reads the corpus once and returns immediate-to-outward ancestors plus deterministic recursive descendant trees. `complete: false` identifies the first missing parent; a target-connected cycle fails with `SESSION_QUERY_INVALID_LINEAGE`.
|
||||
- `traceEvent(request, signal?)` loads the logical log once and returns its cloned source header with direct positional replacements and direct logged provenance. `replacementChain` follows positional replacers to the final replacement; provenance links remain non-transitive.
|
||||
|
||||
Persistence is optional and may mount or unmount dynamically. Cross-corpus listing and lineage tracing fail with `SESSION_QUERY_PERSISTENCE_FAILED` while mounted persistence is unreadable. A title read, event trace, or event read targeting a known live session does not consult persistence, so durable backend health cannot make current in-memory state unreadable. Persisted title and event operations list before loading and reject a metadata mismatch rather than combining inconsistent observations. Lineage-trace cancellation is passed to persisted listing; event-trace and event-read cancellation is passed to persisted listing and inspection. Each waits for the started backend call to settle, then rejects with the signal's exact reason even when the backend ignored that signal. A pre-aborted known-live title read, event trace, or event read rejects before folding or snapshotting without consulting persistence. A batch title observation performs one metadata listing, inspects its unique persisted ids with at most `persistedInspectConcurrency` workers, and preserves each title's own observed header for downstream authorization. Cancellation starts no queued inspections and rejects only after already-started workers settle. `listSessions()` remains lightweight and does not load logs or index titles.
|
||||
Persistence is optional and may mount or unmount dynamically. Cross-corpus listing and lineage tracing fail with `SESSION_QUERY_PERSISTENCE_FAILED` while mounted persistence is unreadable; a successfully read durable record that fails Session validation reports `SESSION_QUERY_CORRUPT_SESSION` instead. A title read, event trace, or event read targeting a known live session does not consult persistence, so durable backend health cannot make current in-memory state unreadable. Persisted title and event operations list before loading and reject a metadata mismatch rather than combining inconsistent observations. Lineage-trace cancellation is passed to persisted listing; event-trace and event-read cancellation is passed to persisted listing and inspection. Each waits for the started backend call to settle, then rejects with the signal's exact reason even when the backend ignored that signal. A pre-aborted known-live title read, event trace, or event read rejects before folding or snapshotting without consulting persistence. A batch title observation performs one metadata listing, inspects its unique persisted ids with at most `persistedInspectConcurrency` workers, and preserves each title's own observed header for downstream authorization. Cancellation starts no queued inspections and rejects only after already-started workers settle. `listSessions()` remains lightweight and does not load logs or index titles.
|
||||
|
||||
## Filtering and extraction
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
- `traceSession(sessionId, signal?)` 只读取一次语料库,返回从直接父级向外的祖先,以及确定性的递归后代树。`complete: false` 标识第一个缺失父级;与目标相连的循环会以 `SESSION_QUERY_INVALID_LINEAGE` 失败。
|
||||
- `traceEvent(request, signal?)` 只加载一次逻辑日志,返回其克隆源 header、直接位置替换和直接已记录来源信息。`replacementChain` 沿位置替换者跟踪到最终替换;来源链接仍不传递。
|
||||
|
||||
持久化是可选的,可动态挂载或卸载。已挂载持久化无法读取时,跨语料库列表和血缘跟踪以 `SESSION_QUERY_PERSISTENCE_FAILED` 失败。针对已知实时会话的标题读取、事件跟踪或事件读取不会查询持久化,因此持久化后端的健康状态无法使当前内存状态变得不可读。持久化标题和事件操作在加载前先执行列表查询,并在元数据不匹配时拒绝,而不会组合不一致的观察。血缘跟踪的取消信号会传递给持久化列表查询;事件跟踪和事件读取的取消信号会传递给持久化列表查询和检查。每项操作都会等待已启动的后端调用结算,然后使用信号的精确原因拒绝,即使后端忽略了该信号。针对已知实时会话且预先中止的标题读取、事件跟踪或事件读取会在 fold 或快照之前拒绝,且不查询持久化。批量标题观察执行一次元数据列表查询,使用最多 `persistedInspectConcurrency` 个 worker 检查唯一持久化 id,并保留每个标题自己观察到的 header,供下游授权使用。取消不会启动已排队检查,且只在已启动 worker 结算后拒绝。`listSessions()` 仍保持轻量,不加载日志或索引标题。
|
||||
持久化是可选的,可动态挂载或卸载。已挂载持久化无法读取时,跨语料库列表和血缘跟踪以 `SESSION_QUERY_PERSISTENCE_FAILED` 失败;已经成功读取、但无法通过 Session 校验的持久化记录则以 `SESSION_QUERY_CORRUPT_SESSION` 失败。针对已知实时会话的标题读取、事件跟踪或事件读取不会查询持久化,因此持久化后端的健康状态无法使当前内存状态变得不可读。持久化标题和事件操作在加载前先执行列表查询,并在元数据不匹配时拒绝,而不会组合不一致的观察。血缘跟踪的取消信号会传递给持久化列表查询;事件跟踪和事件读取的取消信号会传递给持久化列表查询和检查。每项操作都会等待已启动的后端调用结算,然后使用信号的精确原因拒绝,即使后端忽略了该信号。针对已知实时会话且预先中止的标题读取、事件跟踪或事件读取会在 fold 或快照之前拒绝,且不查询持久化。批量标题观察执行一次元数据列表查询,使用最多 `persistedInspectConcurrency` 个 worker 检查唯一持久化 id,并保留每个标题自己观察到的 header,供下游授权使用。取消不会启动已排队检查,且只在已启动 worker 结算后拒绝。`listSessions()` 仍保持轻量,不加载日志或索引标题。
|
||||
|
||||
## 过滤与提取
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface Config {
|
||||
/** Stable machine-routable failure taxonomy for session reads, traces, and search. */
|
||||
export type SessionQueryErrorCode =
|
||||
| 'SESSION_QUERY_ABORTED'
|
||||
| 'SESSION_QUERY_CORRUPT_SESSION'
|
||||
| 'SESSION_QUERY_EVENT_NOT_FOUND'
|
||||
| 'SESSION_QUERY_INDEX_FAILED'
|
||||
| 'SESSION_QUERY_INVALID_CONFIG'
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import type { Context, Fiber } from 'cordis'
|
||||
import type { Session, SessionEvent, SessionHeader, SessionId } from '@deepseek-ai/dsh-session'
|
||||
import type SessionPersistence from '@deepseek-ai/dsh-session-persistence'
|
||||
import SessionPersistence, { SessionPersistenceCorruptionError } from '@deepseek-ai/dsh-session-persistence'
|
||||
import type { SessionRecord } from './types.ts'
|
||||
import { SessionQueryError } from './config.ts'
|
||||
import { assertSessionHeadersCompatible } from './sources.ts'
|
||||
@@ -274,6 +274,13 @@ async function inspectPersisted(
|
||||
return await persistence.inspect(sessionId, signal)
|
||||
} catch (error: unknown) {
|
||||
if (signal?.aborted) signal.throwIfAborted()
|
||||
if (error instanceof SessionPersistenceCorruptionError) {
|
||||
throw new SessionQueryError(
|
||||
`stored session "${sessionId}" is corrupt: ${errorMessage(error)}`,
|
||||
'SESSION_QUERY_CORRUPT_SESSION',
|
||||
{ cause: error },
|
||||
)
|
||||
}
|
||||
throw new SessionQueryError(
|
||||
`failed to inspect session "${sessionId}": ${errorMessage(error)}`,
|
||||
'SESSION_QUERY_PERSISTENCE_FAILED',
|
||||
|
||||
@@ -23,6 +23,10 @@ const SAFE_SESSION_QUERY_FAILURES = {
|
||||
code: 'SESSION_QUERY_ABORTED',
|
||||
message: 'session query was cancelled',
|
||||
},
|
||||
SESSION_QUERY_CORRUPT_SESSION: {
|
||||
code: 'SESSION_QUERY_CORRUPT_SESSION',
|
||||
message: 'session event history is corrupt',
|
||||
},
|
||||
SESSION_QUERY_EVENT_NOT_FOUND: {
|
||||
code: 'SESSION_QUERY_EVENT_NOT_FOUND',
|
||||
message: 'session event was not found',
|
||||
|
||||
@@ -141,6 +141,26 @@ describe('tool-session-query with the real SQLite provider', () => {
|
||||
}),
|
||||
surfaceOp: 'append',
|
||||
},
|
||||
{
|
||||
type: 'user/message',
|
||||
seq: 2,
|
||||
time: -124,
|
||||
data: createUserMessage({
|
||||
content: [{ type: 'text', text: 'pre-epoch fractional needle' }],
|
||||
source: { kind: 'user' },
|
||||
}),
|
||||
surfaceOp: 'append',
|
||||
},
|
||||
{
|
||||
type: 'user/message',
|
||||
seq: 3,
|
||||
time: -123,
|
||||
data: createUserMessage({
|
||||
content: [{ type: 'text', text: 'pre-epoch fractional needle' }],
|
||||
source: { kind: 'user' },
|
||||
}),
|
||||
surfaceOp: 'append',
|
||||
},
|
||||
])
|
||||
|
||||
const caller = ctx.sessions.create(SessionId('fractional-caller'), {
|
||||
@@ -184,5 +204,27 @@ describe('tool-session-query with the real SQLite provider', () => {
|
||||
expect(emptySameMillisecond.isError).toBe(false)
|
||||
expect(emptySameMillisecond.content.map(block => block.type === 'text' ? block.text : '').join('\n'))
|
||||
.toContain('No prior event matches found.')
|
||||
|
||||
const preEpochLower = await execute({
|
||||
session_id: persisted,
|
||||
query: 'pre-epoch fractional needle',
|
||||
time_from: '1969-12-31T23:59:59.87600001Z',
|
||||
})
|
||||
expect(preEpochLower.isError).toBe(false)
|
||||
const preEpochLowerText = preEpochLower.content
|
||||
.map(block => block.type === 'text' ? block.text : '').join('\n')
|
||||
expect(preEpochLowerText).toContain('seq 3')
|
||||
expect(preEpochLowerText).not.toContain('seq 2')
|
||||
|
||||
const preEpochUpper = await execute({
|
||||
session_id: persisted,
|
||||
query: 'pre-epoch fractional needle',
|
||||
time_to: '1969-12-31T19:59:59.8769999-04:00',
|
||||
})
|
||||
expect(preEpochUpper.isError).toBe(false)
|
||||
const preEpochUpperText = preEpochUpper.content
|
||||
.map(block => block.type === 'text' ? block.text : '').join('\n')
|
||||
expect(preEpochUpperText).toContain('seq 2')
|
||||
expect(preEpochUpperText).not.toContain('seq 3')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user