Merge remote-tracking branch 'origin/master' into session-query-tool
# Conflicts: # docs/architecture.i18n.yaml # docs/cordis-catalog/services.md
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
## Reads
|
||||
|
||||
- `listSessions()` reads current persistence metadata, merges live records with live precedence, and returns cloned records in deterministic newest-first order.
|
||||
- `readSession(sessionId)` returns one complete detached raw log after the same core replay validation used by resume; it never enters the session into the live store.
|
||||
- `filterSessions(filters)` applies provider-independent session metadata and availability predicates to that same cloned logical corpus.
|
||||
- `filterEvents(sessionId, filters)` extracts first-party semantic documents and applies provider-independent metadata and literal-text predicates in ascending seq order.
|
||||
- `readTitleSnapshot(sessionId)` loads one live-preferred or persisted log and returns the cloned source header with its latest folded `session/title` event. `readTitle(sessionId)` is the title-only convenience view; it returns `undefined` when the known session has no title.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
import { Context, Service } from 'cordis'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { Session, type SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { foldSessionTitle } from '@deepseek-ai/dsh-session-title'
|
||||
import type { SessionTitleSnapshot } from '@deepseek-ai/dsh-session-title'
|
||||
import type {
|
||||
@@ -19,6 +19,7 @@ import type {
|
||||
SessionEventTraceRequest,
|
||||
SessionEventWindow,
|
||||
SessionLineageTrace,
|
||||
SessionLogSnapshot,
|
||||
SessionRecord,
|
||||
SessionResultFilter,
|
||||
SessionSearchExecContext,
|
||||
@@ -119,6 +120,21 @@ export abstract class SessionQueryService extends Service {
|
||||
return this._corpus.listSessions()
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and replay-validate one complete logical session log without making it live.
|
||||
* @param sessionId - live or persisted session id to read.
|
||||
* @returns cloned header and complete raw event log from one observation.
|
||||
* @throws when persistence, header compatibility, or replay validation fails.
|
||||
*/
|
||||
async readSession(sessionId: SessionId): Promise<SessionLogSnapshot> {
|
||||
const loaded = await this._corpus.load(sessionId)
|
||||
new Session(sessionId, loaded.events, loaded.header)
|
||||
return {
|
||||
session: structuredClone(loaded.header),
|
||||
events: loaded.events.map(event => structuredClone(event)),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the complete logical corpus with provider-independent predicates.
|
||||
* @param filters - ANDed session metadata and availability clauses.
|
||||
|
||||
@@ -40,6 +40,14 @@ export interface SessionSurfaceSnapshot {
|
||||
events: SurfaceEvent[]
|
||||
}
|
||||
|
||||
/** One validated detached observation of a logical session's complete raw log. */
|
||||
export interface SessionLogSnapshot {
|
||||
/** Cloned session header selected from the same observation as `events`. */
|
||||
session: SessionHeader
|
||||
/** Cloned contiguous raw events after persistence repair and replay validation. */
|
||||
events: SessionEvent[]
|
||||
}
|
||||
|
||||
/** Lightweight metadata for one event within a logical session. */
|
||||
export interface SessionEventRecord {
|
||||
/** Session that owns the event. */
|
||||
|
||||
@@ -105,6 +105,25 @@ function rejectUnknown<T>(reason: unknown): Promise<T> {
|
||||
}
|
||||
|
||||
describe('session-query exact reads', () => {
|
||||
it('returns a detached replay-valid full log and rejects a corrupt persisted seed', async () => {
|
||||
const valid = header('valid-log', 2)
|
||||
const corrupt = header('corrupt-log', 1)
|
||||
const validEvents = eventLog('valid')
|
||||
const corruptEvents = [{ ...eventLog('bad')[0]!, seq: 1 }]
|
||||
TestPersistence.reset([
|
||||
{ meta: valid, events: validEvents },
|
||||
{ meta: corrupt, events: corruptEvents },
|
||||
])
|
||||
const ctx = await liveContext()
|
||||
await ctx.plugin(TestPersistence)
|
||||
|
||||
const snapshot = await ctx.sessionQuery.readSession(valid.id)
|
||||
expect(snapshot).toEqual({ session: valid, events: validEvents })
|
||||
Object.assign(snapshot.events[0]!, { time: 999 })
|
||||
expect(TestPersistence.entries.get(valid.id)?.events[0]?.time).toBe(10)
|
||||
await expect(ctx.sessionQuery.readSession(corrupt.id)).rejects.toThrow('seed event at index 0 has seq 1')
|
||||
})
|
||||
|
||||
it('prefers a live owner that attaches while its persisted prefix is inspected', async () => {
|
||||
const shared = header('attach-during-inspect', 2)
|
||||
TestPersistence.reset([{ meta: shared, events: eventLog('persisted') }])
|
||||
|
||||
Reference in New Issue
Block a user