session-snapshot-corpus.corpus.ts 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /** Repository-wide ownership and storage invariants for the recorded-session corpus. */
  2. import { existsSync } from 'node:fs'
  3. import { lstat, readFile, readdir, realpath } from 'node:fs/promises'
  4. import { basename, dirname, join, relative, resolve } from 'node:path'
  5. import { expect, it } from 'vitest'
  6. import {
  7. assertSessionFixtureVersion,
  8. captureExpectedWorkspaceSnapshot,
  9. EMPTY_WORKSPACE_MARKER,
  10. parseSnapshotManifest,
  11. parseSessionFixtureName,
  12. redactSessionSnapshotIds,
  13. scrubSystemPrompts,
  14. scrubToolSchemas,
  15. sessionFixtureFiles,
  16. sessionFixtureNames,
  17. type SnapshotManifest,
  18. } from '@deepseek-ai/dsh-session-snapshot'
  19. import { assertSnapshotCorpusPolicy } from './session-snapshot-corpus-policy.ts'
  20. const repoRoot = resolve(import.meta.dirname, '..')
  21. const corpusRoot = join(repoRoot, 'snapshots')
  22. const profiles = ['acp', 'sdk', 'session', 'web'] as const
  23. const snapshotAdapters = [
  24. 'apps/web/tests/message-feedback-protocol.snapshot.ts',
  25. 'apps/web/tests/minimal-preset.snapshot.ts',
  26. 'apps/web/tests/preset-migration.snapshot.ts',
  27. 'snapshots/acp/acp.snapshot.ts',
  28. 'snapshots/sdk/sdk.snapshot.ts',
  29. 'snapshots/session/headless.snapshot.ts',
  30. ] as const
  31. interface Scenario {
  32. readonly key: string
  33. readonly profile: string
  34. readonly name: string
  35. readonly dir: string
  36. readonly manifest: SnapshotManifest & {
  37. composition: string
  38. recording: 'live' | 'authored'
  39. header: NonNullable<SnapshotManifest['header']>
  40. }
  41. }
  42. async function scenarios(): Promise<Scenario[]> {
  43. const result: Scenario[] = []
  44. for (const profile of profiles) {
  45. const root = join(corpusRoot, profile)
  46. for (const entry of await readdir(root, { withFileTypes: true })) {
  47. if (!entry.isDirectory()) continue
  48. const dir = join(root, entry.name)
  49. const path = join(dir, 'snapshot.yml')
  50. expect(existsSync(path), `${profile}/${entry.name}/snapshot.yml`).toBe(true)
  51. const manifest = parseSnapshotManifest(await readFile(path, 'utf8'), path)
  52. expect(manifest.scenario, `${profile}/${entry.name}: scenario`).toBe(entry.name)
  53. expect(manifest.profile, `${profile}/${entry.name}: profile`).toBe(profile === 'session' ? 'headless' : profile)
  54. expect(manifest.composition, `${profile}/${entry.name}: composition`).toBeTypeOf('string')
  55. expect(manifest.recording, `${profile}/${entry.name}: recording`).toMatch(/^(live|authored)$/)
  56. expect(manifest.header, `${profile}/${entry.name}: header`).toBeDefined()
  57. result.push({
  58. key: `${profile}/${entry.name}`,
  59. profile,
  60. name: entry.name,
  61. dir,
  62. manifest: {
  63. ...manifest,
  64. composition: manifest.composition as string,
  65. recording: manifest.recording as 'live' | 'authored',
  66. header: manifest.header as NonNullable<SnapshotManifest['header']>,
  67. },
  68. })
  69. }
  70. }
  71. return result
  72. }
  73. function referencedScenario(owner: Scenario, source: string): string {
  74. return source.includes('/') ? source : `${owner.profile}/${source}`
  75. }
  76. async function snapshotNamedTests(): Promise<string[]> {
  77. const files: string[] = []
  78. const visit = async (directory: string, relativeDir: string): Promise<void> => {
  79. for (const entry of await readdir(directory, { withFileTypes: true })) {
  80. if (entry.isDirectory()) {
  81. if (['dist', 'lib', 'node_modules'].includes(entry.name)) continue
  82. await visit(join(directory, entry.name), join(relativeDir, entry.name))
  83. } else if (entry.isFile() && /\.snapshot\.tsx?$/u.test(entry.name)) {
  84. files.push(join(relativeDir, entry.name).split(/[/\\]/u).join('/'))
  85. }
  86. }
  87. }
  88. for (const root of ['apps', 'native', 'packages', 'python', 'scripts', 'snapshots', 'website']) {
  89. await visit(join(repoRoot, root), root)
  90. }
  91. return files.sort()
  92. }
  93. it('reserves the snapshot test suffix for recorded-session adapters', async () => {
  94. expect(await snapshotNamedTests()).toEqual([...snapshotAdapters])
  95. })
  96. it('keeps every recorded session owned, pinned, redacted, and header-scrubbed', async () => {
  97. const all = await scenarios()
  98. const byKey = new Map(all.map(scenario => [scenario.key, scenario]))
  99. const pinByClass = new Map<string, Scenario>()
  100. for (const scenario of all) {
  101. if (scenario.manifest.header.pin !== true) continue
  102. const key = `${scenario.manifest.composition}/${scenario.manifest.header.class}`
  103. expect(pinByClass.has(key), `${key}: duplicate header pin`).toBe(false)
  104. pinByClass.set(key, scenario)
  105. }
  106. for (const scenario of all) {
  107. const { manifest, dir, key } = scenario
  108. const classKey = `${manifest.composition}/${manifest.header.class}`
  109. expect(pinByClass.has(classKey), `${key}: missing composition/header pin ${classKey}`).toBe(true)
  110. const localEntries = await readdir(dir)
  111. const localSessionNames = localEntries.filter(name => parseSessionFixtureName(name) !== undefined)
  112. if (manifest.session === undefined) {
  113. expect(localSessionNames.length, `${key}: owner Session fixture`).toBeGreaterThan(0)
  114. } else {
  115. expect(localSessionNames, `${key}: borrower must not own a Session fixture`).toEqual([])
  116. const target = resolve(dir, manifest.session.source)
  117. expect(existsSync(target), `${key}: session source`).toBe(true)
  118. const targetDir = await realpath(dirname(target))
  119. const sourceKey = relative(corpusRoot, targetDir).split(/[/\\]/).join('/')
  120. expect(byKey.has(sourceKey), `${key}: session source must name a corpus owner`).toBe(true)
  121. expect(byKey.get(sourceKey)?.manifest.session, `${key}: session source cannot chain through a borrower`).toBeUndefined()
  122. const [selectedParent] = sessionFixtureFiles(await readdir(targetDir))
  123. expect(basename(target), `${key}: session source must name the owner's selected parent generation`)
  124. .toBe(selectedParent?.name)
  125. }
  126. expect(existsSync(join(dir, 'replay.override.json')), `${key}: replay override presence`)
  127. .toBe(manifest.replay?.override === true)
  128. expect(existsSync(join(dir, 'workspace.expected')), `${key}: final workspace presence`)
  129. .toBe(manifest.workspace?.final === true)
  130. if (manifest.workspace?.final === true) {
  131. const expectedRoot = join(dir, 'workspace.expected')
  132. const expectedWorkspace = await captureExpectedWorkspaceSnapshot(expectedRoot)
  133. expect(existsSync(join(expectedRoot, EMPTY_WORKSPACE_MARKER)), `${key}: empty workspace marker`)
  134. .toBe(expectedWorkspace.length === 0)
  135. }
  136. expect(existsSync(join(dir, 'input.json')), `${key}: executable input metadata is ACP-only`)
  137. .toBe(scenario.profile === 'acp')
  138. if (scenario.profile !== 'acp') {
  139. expect(existsSync(join(dir, 'stdout.expected.jsonl')), `${key}: ACP transcript outside ACP`).toBe(false)
  140. }
  141. if (manifest.header.pin === true) {
  142. const promptSource = byKey.get(referencedScenario(scenario, manifest.header.systemPromptSource ?? scenario.name))
  143. const schemaSource = byKey.get(referencedScenario(scenario, manifest.header.toolSchemasSource ?? scenario.name))
  144. expect(promptSource, `${key}: system-prompt source`).toBeDefined()
  145. expect(schemaSource, `${key}: tool-schema source`).toBeDefined()
  146. expect(existsSync(join((promptSource as Scenario).dir, 'system-prompt.expected.md')), `${key}: system-prompt sidecar`).toBe(true)
  147. expect(existsSync(join((schemaSource as Scenario).dir, 'tool-schemas.expected.json')), `${key}: tool-schema sidecar`).toBe(true)
  148. for (const [field, source] of [
  149. ['system-prompt.expected.md', promptSource],
  150. ['tool-schemas.expected.json', schemaSource],
  151. ] as const) {
  152. const local = join(dir, field)
  153. if (!existsSync(local) || !(await lstat(local)).isSymbolicLink()) continue
  154. expect(await realpath(local), `${key}: ${field} symlink follows its manifest source`)
  155. .toBe(await realpath(join((source as Scenario).dir, field)))
  156. }
  157. }
  158. if (manifest.session !== undefined) continue
  159. const names = sessionFixtureNames(localEntries)
  160. const fixtures = await Promise.all(names.map(name => readFile(join(dir, name), 'utf8')))
  161. for (const name of localSessionNames) {
  162. assertSessionFixtureVersion(name, await readFile(join(dir, name), 'utf8'))
  163. }
  164. expect(redactSessionSnapshotIds(fixtures), `${key}: typed identity fixed point`).toEqual(fixtures)
  165. for (const [index, fixture] of fixtures.entries()) {
  166. expect(scrubSystemPrompts(fixture), `${key}/${names[index]}: system prompt must be a sidecar`).toBe(fixture)
  167. expect(scrubToolSchemas(fixture), `${key}/${names[index]}: tool schemas must be a sidecar`).toBe(fixture)
  168. }
  169. for (const index of manifest.header.childSystemPrompts ?? []) {
  170. expect(names[index], `${key}: child prompt index ${index}`).toBeDefined()
  171. expect(existsSync(join(dir, `system-prompt.${index}.expected.md`)), `${key}: child prompt sidecar ${index}`).toBe(true)
  172. }
  173. for (const index of manifest.header.childToolSchemas ?? []) {
  174. expect(names[index], `${key}: child schema index ${index}`).toBeDefined()
  175. expect(existsSync(join(dir, `tool-schemas.${index}.expected.json`)), `${key}: child schema sidecar ${index}`).toBe(true)
  176. }
  177. }
  178. })
  179. it('keeps a current-writer majority plus bounded declared historical migration coverage', async () => {
  180. const owners = (await scenarios()).filter(scenario => scenario.manifest.session === undefined)
  181. const inventory = await Promise.all(owners.map(async scenario => ({
  182. key: scenario.key,
  183. selectedVersions: sessionFixtureFiles(await readdir(scenario.dir)).map(file => file.version),
  184. ...(scenario.manifest.sessionFormat === undefined
  185. ? {}
  186. : { retained: scenario.manifest.sessionFormat }),
  187. })))
  188. expect(assertSnapshotCorpusPolicy(inventory)).toMatchObject({
  189. retainedRoles: 9,
  190. retainedScenarios: 7,
  191. })
  192. })