session-fixture-layout.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. /** Canonical packed-row and envelope projection helpers for repository session fixtures. */
  2. import { deepStrictEqual } from 'node:assert'
  3. import { execFileSync } from 'node:child_process'
  4. import { existsSync, readFileSync } from 'node:fs'
  5. import { resolve } from 'node:path'
  6. import { packChunkRuns, type SessionEvent } from '@deepseek-ai/dsh-session'
  7. import { parseSessionLog } from '@deepseek-ai/dsh-llm-replay'
  8. /** Physical persistence artifacts validated by the WebWorker runtime fixture spec. */
  9. const WEBWORKER_PHYSICAL_SESSION_FIXTURE_ROOT =
  10. 'packages/experimental/webworker-runtime/tests/fixtures/vfs-example/home/sessions/'
  11. /** Installed-runtime snapshots that preserve the JSONL writer's physical encoding. */
  12. const PYTHON_RUNTIME_PHYSICAL_SESSION_FIXTURE_ROOT =
  13. 'scripts/snapshots/python-sdk-single-exe/'
  14. /** One repository session fixture and its canonical projected representation. */
  15. export interface SessionFixtureLayout {
  16. /** Repository-relative path with `/` separators. */
  17. path: string
  18. /** Current fixture bytes decoded as UTF-8. */
  19. source: string
  20. /** Canonical projected fixture bytes. */
  21. canonical: string
  22. }
  23. /**
  24. * Whether a repository JSONL preserves physical persistence encoding rather
  25. * than the logical event projection owned by this script.
  26. * @param path - Repository-relative path with `/` separators.
  27. * @returns True for physical WebWorker and installed-runtime session logs.
  28. */
  29. export function isPhysicalSessionFixture(path: string): boolean {
  30. if (path.startsWith(WEBWORKER_PHYSICAL_SESSION_FIXTURE_ROOT)) {
  31. return path.endsWith('/session.jsonl')
  32. }
  33. return path.startsWith(PYTHON_RUNTIME_PHYSICAL_SESSION_FIXTURE_ROOT)
  34. && /\/session(?:\.\d+)?\.jsonl$/.test(path)
  35. }
  36. function isSessionHeader(value: unknown): boolean {
  37. return value !== null && typeof value === 'object' && (value as { type?: unknown }).type === 'session'
  38. }
  39. function renderFixture(headerLine: string, events: readonly SessionEvent[]): string {
  40. return [
  41. headerLine,
  42. ...packChunkRuns(events).map((stored) => {
  43. const record = stored as unknown as Record<string, unknown>
  44. delete record.seq
  45. delete record.time
  46. delete record.seq0
  47. delete record.time0
  48. return JSON.stringify(record)
  49. }),
  50. '',
  51. ].join('\n')
  52. }
  53. function withoutEnvelope(events: readonly SessionEvent[]): Array<Omit<SessionEvent, 'seq' | 'time'>> {
  54. return events.map((event) => {
  55. const { seq: _seq, time: _time, ...projected } = event
  56. return projected
  57. })
  58. }
  59. /**
  60. * Canonicalize one JSONL document when its first record is a session header.
  61. * The header line remains byte-identical; body records decode to logical events,
  62. * re-encode with {@link packChunkRuns}, and omit storage sequence/time envelopes.
  63. * Non-session JSONL returns undefined.
  64. *
  65. * @param content - JSONL source text.
  66. * @param label - path-like diagnostic label.
  67. * @returns Canonical text for a session fixture, otherwise undefined.
  68. */
  69. export function canonicalSessionFixture(content: string, label = '<session-fixture>'): string | undefined {
  70. const headerLine = content.split(/\r?\n/).find(line => line.trim().length > 0)
  71. if (headerLine === undefined) return undefined
  72. let headerValue: unknown
  73. try {
  74. headerValue = JSON.parse(headerLine) as unknown
  75. } catch {
  76. return undefined
  77. }
  78. if (!isSessionHeader(headerValue)) return undefined
  79. let events
  80. try {
  81. events = parseSessionLog(content)
  82. } catch (error) {
  83. const detail = error instanceof Error ? error.message : String(error)
  84. throw new Error(`${label}: ${detail}`, { cause: error })
  85. }
  86. const canonical = renderFixture(headerLine, events)
  87. const decoded = parseSessionLog(canonical)
  88. try {
  89. deepStrictEqual(withoutEnvelope(decoded), withoutEnvelope(events))
  90. } catch (error) {
  91. throw new Error(`${label}: packed snapshot rewrite changed the event payload stream`, { cause: error })
  92. }
  93. if (renderFixture(headerLine, decoded) !== canonical) {
  94. throw new Error(`${label}: packed rewrite is not idempotent`)
  95. }
  96. return canonical
  97. }
  98. /**
  99. * Discover tracked and unignored untracked JSONL files through Git.
  100. *
  101. * @param root - repository root.
  102. * @returns Stable repository-relative paths.
  103. */
  104. function discoverJsonlFiles(root: string): string[] {
  105. return execFileSync(
  106. 'git',
  107. ['ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', '*.jsonl'],
  108. { cwd: root, encoding: 'utf8' },
  109. ).split('\0')
  110. .filter(path => path.length > 0 && existsSync(resolve(root, path)))
  111. .sort()
  112. }
  113. /**
  114. * Inspect every repository JSONL whose first record is a session header.
  115. *
  116. * @param root - repository root.
  117. * @returns Session fixtures with current and canonical text.
  118. */
  119. export function inspectSessionFixtureLayouts(root: string): SessionFixtureLayout[] {
  120. return discoverJsonlFiles(root).flatMap((path) => {
  121. if (isPhysicalSessionFixture(path)) return []
  122. const source = readFileSync(resolve(root, path), 'utf8')
  123. const canonical = canonicalSessionFixture(source, path)
  124. return canonical === undefined ? [] : [{ path, source, canonical }]
  125. })
  126. }