fix: bind session authorization to observations
This commit is contained in:
@@ -7,12 +7,12 @@
|
||||
- `listSessions()` reads current persistence metadata, merges live records with live precedence, and returns cloned records in deterministic newest-first order.
|
||||
- `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.
|
||||
- `readTitle(sessionId)` loads one live-preferred or persisted log and folds its latest `session/title` event into a `SessionTitleSnapshot`; it returns `undefined` when the known session has no title.
|
||||
- `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.
|
||||
- `listEvents(sessionId)` loads the live-preferred raw log and classifies each event as `current`, `shadowed`, or `log-only` with the shared `dsh-session` surface fold.
|
||||
- `readSurface(sessionId)` returns one cloned header, raw-log capture boundary, and the complete folded current surface in model-history order. A live session wins over persistence; compaction is observed before or after its replacement append, never as a synthetic mixture.
|
||||
- `readEvent(request)` returns a cloned header, the full target event, and a bounded raw-seq window. `before` and `after` default to zero and may not exceed `readWindowMax`.
|
||||
- `traceSession(sessionId)` 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)` loads the logical log once and returns direct positional replacements and direct logged provenance. `replacementChain` follows positional replacers to the final replacement; provenance links remain non-transitive.
|
||||
- `traceEvent(request)` 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, event read, or trace 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. `listSessions()` remains lightweight and does not load logs or index titles.
|
||||
|
||||
@@ -24,7 +24,7 @@ The text clause is deliberately independent of FTS providers: caller text is esc
|
||||
|
||||
## Full-text methods
|
||||
|
||||
`SessionQueryService.searchSessions(request, exec?)` groups the logical corpus by strongest matching event; `searchEvents(request, exec?)` searches one logical session. These are the service's only abstract methods. Both return pages whose continuation is an owned branded `SessionSearchCursor`, accept optional cancellation, and expose snippets without provider-specific numeric scores. Search requests accept only metadata event filters, because literal-text filtering is the scan path described above.
|
||||
`SessionQueryService.searchSessions(request, exec?)` groups the logical corpus by strongest matching event; `searchEvents(request, exec?)` searches one logical session. These are the service's only abstract methods. Both return pages whose continuation is an owned branded `SessionSearchCursor`, accept optional cancellation, and expose snippets without provider-specific numeric scores. An event-search page also carries the cloned target header from the same indexed generation as its hits, allowing authorization consumers to bind policy to the payload observation. Search requests accept only metadata event filters, because literal-text filtering is the scan path described above.
|
||||
|
||||
The package has no provider coordinator, fallback implementation, or standalone concrete plugin. A concrete service backend inherits the implemented reads, filters, and traces while owning full-text observation, reconciliation, ranking, cursor generations, and query execution; the first implementation is [`@deepseek-ai/dsh-session-query-sqlite`](../session-query-sqlite/README.md).
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ import { foldSessionTitle } from '@deepseek-ai/dsh-session-title'
|
||||
import type { SessionTitleSnapshot } from '@deepseek-ai/dsh-session-title'
|
||||
import type {
|
||||
SessionEventResultFilter,
|
||||
SessionEventSearchPage,
|
||||
SessionEventReadRequest,
|
||||
SessionEventRecord,
|
||||
SessionEventSearchHit,
|
||||
SessionEventSearchDocument,
|
||||
SessionEventSearchRequest,
|
||||
SessionEventTrace,
|
||||
SessionEventTraceObservation,
|
||||
SessionEventTraceRequest,
|
||||
SessionEventWindow,
|
||||
SessionLineageTrace,
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
SessionSearchPage,
|
||||
SessionSearchRequest,
|
||||
SessionSurfaceSnapshot,
|
||||
SessionTitleObservation,
|
||||
} from './types.ts'
|
||||
import {
|
||||
SESSION_QUERY_READ_WINDOW_MAX,
|
||||
@@ -103,12 +104,12 @@ export abstract class SessionQueryService extends Service {
|
||||
* Search events within one live-preferred logical session.
|
||||
* @param request - target session, query text, filters, page size, and cursor.
|
||||
* @param exec - optional cancellation control.
|
||||
* @returns matching event hits in deterministic relevance order.
|
||||
* @returns matching event hits and their target header from one indexed generation.
|
||||
*/
|
||||
abstract searchEvents(
|
||||
request: SessionEventSearchRequest,
|
||||
exec?: SessionSearchExecContext,
|
||||
): Promise<SessionSearchPage<SessionEventSearchHit>>
|
||||
): Promise<SessionEventSearchPage>
|
||||
|
||||
/**
|
||||
* List the complete logical corpus using live-preferred records.
|
||||
@@ -134,8 +135,21 @@ export abstract class SessionQueryService extends Service {
|
||||
* @returns latest title snapshot, or `undefined` when the log has no title event.
|
||||
*/
|
||||
async readTitle(sessionId: SessionId): Promise<SessionTitleSnapshot | undefined> {
|
||||
return (await this.readTitleSnapshot(sessionId)).title
|
||||
}
|
||||
|
||||
/**
|
||||
* Fold the latest title and return its source header from one corpus observation.
|
||||
* @param sessionId - live or persisted session id to read.
|
||||
* @returns cloned source header and optional latest title snapshot.
|
||||
*/
|
||||
async readTitleSnapshot(sessionId: SessionId): Promise<SessionTitleObservation> {
|
||||
const loaded = await this._corpus.load(sessionId)
|
||||
return foldSessionTitle(loaded.events)
|
||||
const title = foldSessionTitle(loaded.events)
|
||||
return {
|
||||
session: loaded.header,
|
||||
...title === undefined ? {} : { title },
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,12 +218,15 @@ export abstract class SessionQueryService extends Service {
|
||||
/**
|
||||
* Trace one event's direct positional and provenance relationships.
|
||||
* @param request - target session id and event seq.
|
||||
* @returns direct links plus the target's positional replacement chain.
|
||||
* @returns source header, direct links, and the target's positional replacement chain.
|
||||
* @throws when source resolution fails, the target is absent, or surface/provenance validation fails.
|
||||
*/
|
||||
async traceEvent(request: SessionEventTraceRequest): Promise<SessionEventTrace> {
|
||||
async traceEvent(request: SessionEventTraceRequest): Promise<SessionEventTraceObservation> {
|
||||
const loaded = await this._corpus.load(request.sessionId)
|
||||
return tracing.traceEvent(request.sessionId, loaded.events, request.seq)
|
||||
return {
|
||||
session: loaded.header,
|
||||
...tracing.traceEvent(request.sessionId, loaded.events, request.seq),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
SessionId,
|
||||
SurfaceEvent,
|
||||
} from '@deepseek-ai/dsh-session'
|
||||
import type { SessionTitleSnapshot } from '@deepseek-ai/dsh-session-title'
|
||||
import type { SessionSearchCursor } from './cursor.ts'
|
||||
|
||||
export type { SessionSearchCursor } from './cursor.ts'
|
||||
@@ -108,6 +109,12 @@ export interface SessionEventTrace {
|
||||
derivedEventSeqs: number[]
|
||||
}
|
||||
|
||||
/** Event relationships bound to the same session-header observation. */
|
||||
export interface SessionEventTraceObservation extends SessionEventTrace {
|
||||
/** Cloned header selected with the event log used for the trace. */
|
||||
session: SessionHeader
|
||||
}
|
||||
|
||||
/** Request for one event plus raw neighboring log context. */
|
||||
export interface SessionEventReadRequest {
|
||||
/** Session that owns the target event. */
|
||||
@@ -134,6 +141,14 @@ export interface SessionEventWindow {
|
||||
endSeq: number
|
||||
}
|
||||
|
||||
/** Latest folded title bound to the same session-header observation. */
|
||||
export interface SessionTitleObservation {
|
||||
/** Cloned header selected with the event log used for the title fold. */
|
||||
session: SessionHeader
|
||||
/** Latest title snapshot, absent when the observed log has no title. */
|
||||
title?: SessionTitleSnapshot
|
||||
}
|
||||
|
||||
/** Inclusive numeric interval used by time and sequence filters. */
|
||||
export interface SessionResultRange {
|
||||
/** Inclusive lower bound. */
|
||||
@@ -184,6 +199,12 @@ export interface SessionSearchPage<T> {
|
||||
nextCursor?: SessionSearchCursor
|
||||
}
|
||||
|
||||
/** Event-search results bound to the indexed target-session observation. */
|
||||
export interface SessionEventSearchPage extends SessionSearchPage<SessionEventSearchHit> {
|
||||
/** Cloned target header from the same indexed generation as `items`. */
|
||||
session: SessionHeader
|
||||
}
|
||||
|
||||
/** Controls shared by cross-session and within-session search calls. */
|
||||
export interface SessionSearchExecContext {
|
||||
/** Abort caller waiting and interrupt provider work where supported. */
|
||||
|
||||
@@ -213,8 +213,10 @@ it('registers exact and abstract search behavior under one ctx key', async () =>
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SessionStore)
|
||||
const fiber = await ctx.plugin(TestSessionQueryService)
|
||||
const session = ctx.sessions.create(id)
|
||||
await expect(ctx.sessionQuery.searchSessions({ query: 'AI' })).resolves.toEqual({ items: [] })
|
||||
await expect(ctx.sessionQuery.searchEvents({ sessionId: id, query: 'AI' })).resolves.toEqual({ items: [] })
|
||||
await expect(ctx.sessionQuery.searchEvents({ sessionId: id, query: 'AI' }))
|
||||
.resolves.toEqual({ session: session.header, items: [] })
|
||||
await fiber.dispose()
|
||||
expect(ctx.sessionQuery).toBeUndefined()
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import SessionQueryService from '@deepseek-ai/dsh-session-query'
|
||||
import type {
|
||||
SessionEventSearchHit,
|
||||
SessionEventSearchPage,
|
||||
SessionEventSearchRequest,
|
||||
SessionSearchExecContext,
|
||||
SessionSearchHit,
|
||||
@@ -17,10 +17,13 @@ export class TestSessionQueryService extends SessionQueryService {
|
||||
return Promise.resolve({ items: [] })
|
||||
}
|
||||
|
||||
override searchEvents(
|
||||
_request: SessionEventSearchRequest,
|
||||
override async searchEvents(
|
||||
request: SessionEventSearchRequest,
|
||||
_exec?: SessionSearchExecContext,
|
||||
): Promise<SessionSearchPage<SessionEventSearchHit>> {
|
||||
return Promise.resolve({ items: [] })
|
||||
): Promise<SessionEventSearchPage> {
|
||||
return {
|
||||
session: (await this.readSurface(request.sessionId)).session,
|
||||
items: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user