fix(session-query): reject misplaced surface ops

This commit is contained in:
Hypatia May
2026-07-13 16:05:11 +08:00
parent 3d3789bf2d
commit 84e6f72ef5
12 changed files with 182 additions and 114 deletions

View File

@@ -28,7 +28,7 @@ await ctx.plugin(Invariants)
Session log (per session):
- **`seq` strictly increases** — the spine of replay equivalence.
- **surface provenance is valid** — `sourceEventSeqs` uses the shared `dsh-session` checker for type eligibility, nonempty unique earlier references, and complete replacement coverage.
- **surface metadata is valid** — `surfaceOp` and `sourceEventSeqs` use the shared `dsh-session` checker for type eligibility, structural shape, nonempty unique earlier references, and complete replacement coverage.
- **turns pair and nest** — `turn/start` opens a turn, `turn/end` closes the matching one; no overlapping turns.
- **steps nest in turns** — `step/start` opens a step in the open turn; `step/end` closes the matching step.
- **chunks belong to an open step** — `step/start` precedes its `assistant/chunk`s.

View File

@@ -26,8 +26,7 @@ import {
Session,
SessionId,
foldRequestHeader,
isSurfaceEligibleType,
validateSurfaceProvenance,
validateSurfaceMetadata,
} from '@deepseek-ai/dsh-session'
import type { SessionEvent, SurfaceEventType } from '@deepseek-ai/dsh-session'
@@ -131,9 +130,8 @@ function validateEvent(trace: SessionTrace, event: SessionEvent): SessionTraceTr
// SurfaceEvent's mandatory surfaceOp is too strict here — we need to
// CHECK whether surface metadata is present, not assume it.
const se = event as SessionEvent<SurfaceEventType>
if (!isSurfaceEligibleType(event.type) && se.surfaceOp !== undefined) {
throw new InvariantError(`${event.type} cannot carry surfaceOp (non-surface event)`)
}
const metadataViolation = validateSurfaceMetadata(event)
if (metadataViolation !== undefined) throw new InvariantError(metadataViolation.message)
// Fold this event into the tracked surface linked list, validating the
// replace contract as we go. `append` adds a tail node; `replace` shadows a
@@ -160,13 +158,13 @@ function validateEvent(trace: SessionTrace, event: SessionEvent): SessionTraceTr
}
}
const provenanceViolation = validateSurfaceProvenance(
const provenanceViolation = validateSurfaceMetadata(
event,
trace.knownSeqs,
shadowed,
)
if (provenanceViolation !== undefined) {
throw new InvariantError(provenanceViolation)
throw new InvariantError(provenanceViolation.message)
}
// Boundary/step-scoped events have explicit cases; every OTHER event type —