From a4dcff874e34702385d660ffadd20a7c56355cd6 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:18:04 +0800 Subject: [PATCH] fix(scripts): validate historical fixture layout with source codecs --- scripts/session-fixture-layout.spec.ts | 30 ++++++++++ scripts/session-fixture-layout.ts | 76 +++++++------------------- 2 files changed, 49 insertions(+), 57 deletions(-) diff --git a/scripts/session-fixture-layout.spec.ts b/scripts/session-fixture-layout.spec.ts index 7c69743393..358e6d44a2 100644 --- a/scripts/session-fixture-layout.spec.ts +++ b/scripts/session-fixture-layout.spec.ts @@ -106,6 +106,36 @@ describe('canonicalSessionFixture', () => { expect(canonicalSessionFixture(projected)).toBe(projected) }) + it.each([0, 1, 2])('preserves physically valid v%i bytes without requiring migration to current', (version) => { + const header = { type: 'session', version, id: 'historical', createdAt: 1, delegationDepth: 0, ...(version === 2 ? { isSeeded: false } : {}) } + const content = [ + JSON.stringify(header), + JSON.stringify({ type: 'user/message', data: { role: 'user', id: 'historical-user', source: { kind: 'user' }, content: [] }, surfaceOp: 'append' }), + '', + ].join('\n') + expect(canonicalSessionFixture(content)).toBe(content) + }) + + it.each([0, 1, 2])('rejects v%i sequence gaps and invalid provenance ranges with source line diagnostics', (version) => { + const header = JSON.stringify({ type: 'session', version, id: 'historical', createdAt: 1, delegationDepth: 0, ...(version === 2 ? { isSeeded: false } : {}) }) + expect(() => canonicalSessionFixture(`${header}\n{"type":"feedback/record","seq":3,"data":{"text":"gap"}}\n`, 'gap.jsonl')) + .toThrow(/gap\.jsonl: session snapshot line 2:.*seq/) + expect(() => canonicalSessionFixture(`${header}\n{"type":"feedback/record","data":{},"sourceEventSeqs":[[2,0]]}\n`, 'range.jsonl')) + .toThrow(/range\.jsonl: session snapshot line 2:/) + }) + + it.each([0, 1, 2])('finalizes the v%i source inherited cut', (version) => { + const header = { type: 'session', version, id: 'historical', createdAt: 1, delegationDepth: 0, ...(version === 2 ? { isSeeded: true } : { seedLength: 1 }) } + expect(() => canonicalSessionFixture(`${JSON.stringify(header)}\n`, 'cut.jsonl')) + .toThrow(/cut\.jsonl: session snapshot line 1:.*(?:inherited|seed)/) + }) + + it('refuses unsupported generation headers', () => { + const header = { type: 'session', version: 99, id: 'future', createdAt: 1, isSeeded: false, delegationDepth: 0 } + expect(() => canonicalSessionFixture(`${JSON.stringify(header)}\n`, 'future.jsonl')) + .toThrow(/future\.jsonl: session snapshot line 1:.*99/) + }) + it('fails loud on malformed records after a session header', () => { expect(() => canonicalSessionFixture(`${HEADER}\n{not-json}\n`, 'broken.jsonl')) .toThrow(/broken\.jsonl: session snapshot line 2 contains invalid JSON/) diff --git a/scripts/session-fixture-layout.ts b/scripts/session-fixture-layout.ts index dbc4414b9d..d220a30b77 100644 --- a/scripts/session-fixture-layout.ts +++ b/scripts/session-fixture-layout.ts @@ -11,6 +11,13 @@ import { } from '@deepseek-ai/dsh-session' import type { SessionLogOffset as SessionLogOffsetType } from '@deepseek-ai/dsh-session' import { sessionFormatCatalog } from '@deepseek-ai/dsh-session-format-catalog' +import { SessionFormatEventCollector, type SessionFormatArtifactDecoder, type SessionFormatCodec } from '@deepseek-ai/dsh-session-format' +import { releasedV0SessionFormatCodec, releasedV1SessionFormatCodec } from '@deepseek-ai/dsh-session-format-v0-to-v1' +import { releasedV2SessionFormatCodec } from '@deepseek-ai/dsh-session-format-v1-to-v2' + +const historicalCodecs: readonly SessionFormatCodec[] = [ + releasedV0SessionFormatCodec, releasedV1SessionFormatCodec, releasedV2SessionFormatCodec, +] /** Physical persistence artifacts validated by the WebWorker runtime fixture spec. */ const WEBWORKER_PHYSICAL_SESSION_FIXTURE_ROOT = @@ -58,51 +65,6 @@ function validationHeader(value: unknown): unknown { return header } -function validationRow(source: Readonly>): Record { - if (source.type !== 'request/header') return { ...source } - const data = source.data - if (data === null || typeof data !== 'object' || Array.isArray(data)) return { ...source } - const header = (data as Record).header - if (header === null || typeof header !== 'object' || Array.isArray(header)) return { ...source } - if ((header as Record).tools !== '{{tools}}') return { ...source } - return { - ...source, - data: { - ...data, - header: { ...header, tools: [] }, - }, - } -} - -function restoreRequestHeaderTokens( - events: readonly SessionEvent[], - rows: readonly Readonly>[], -): SessionEvent[] { - const sources = rows.filter(row => row.type === 'request/header') - let sourceIndex = 0 - return events.map((event) => { - if (event.type !== 'request/header') return event - const source = sources[sourceIndex] - sourceIndex += 1 - const sourceData = source?.data - const sourceHeader = sourceData !== null && typeof sourceData === 'object' && !Array.isArray(sourceData) - ? (sourceData as Record).header - : undefined - if (sourceHeader === null || typeof sourceHeader !== 'object' || Array.isArray(sourceHeader) - || (sourceHeader as Record).tools !== '{{tools}}') return event - return { - ...event, - data: { - ...event.data, - header: { - ...(event.data as unknown as { header: Record }).header, - tools: '{{tools}}', - }, - }, - } as SessionEvent - }) -} - function renderFixture(headerLine: string, events: readonly SessionEvent[]): string { return [ headerLine, @@ -191,29 +153,28 @@ function parseFixtureRows(content: string, headerValue: unknown): SessionEvent[] } }) } - let restore: ReturnType + const collector = new SessionFormatEventCollector() + let decoder: SessionFormatArtifactDecoder try { - restore = sessionFormatCatalog.createRestore(validationHeader(headerValue), { - recovery: 'strict', - validation: 'current', - }) + const version = (headerValue as Record).version + const codec = historicalCodecs.find(candidate => candidate.version === version) + if (codec === undefined) throw new Error(`unsupported session fixture version ${String(version)}`) + decoder = codec.createDecoder(validationHeader(headerValue), 'strict') } catch (error) { const detail = error instanceof Error ? error.message : String(error) throw new Error(`session snapshot line 1: ${detail}`, { cause: error }) } for (const [index, row] of rows.entries()) { try { - restore.decodeRow(validationRow(row)) + decoder.decodeRow(row, collector) } catch (error) { const detail = error instanceof Error ? error.message : String(error) throw new Error(`session snapshot line ${rowLines[index] ?? 1}: ${detail}`, { cause: error }) } } try { - return restoreRequestHeaderTokens( - [...restore.finish().events] as unknown as SessionEvent[], - rows, - ) + decoder.finish(collector) + return [...collector.values] as unknown as SessionEvent[] } catch (error) { const detail = error instanceof Error ? error.message : String(error) const line = fixtureDiagnosticLine(error, rowLines, eventLines) @@ -246,8 +207,9 @@ function withoutEnvelope(events: readonly SessionEvent[]): Array