session-snapshot-corpus-policy.ts 3.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /** Enforced current-writer majority and retained migration coverage. */
  2. import { SESSION_FORMAT_VERSION } from '@deepseek-ai/dsh-session'
  3. import type { SnapshotSessionFormatManifest } from '@deepseek-ai/dsh-session-snapshot'
  4. /** One owning scenario's selected parent and child generations. */
  5. export interface SnapshotCorpusScenarioGenerations {
  6. /** Corpus-relative profile/scenario key. */
  7. readonly key: string
  8. /** Highest selected generation for each contiguous role. */
  9. readonly selectedVersions: readonly number[]
  10. /** Explicit historical generation and the behavior it preserves. */
  11. readonly retained?: SnapshotSessionFormatManifest
  12. }
  13. /** Counts returned after the corpus policy accepts the inventory. */
  14. export interface SnapshotCorpusGenerationSummary {
  15. readonly currentRoles: number
  16. readonly retainedRoles: number
  17. readonly retainedScenarios: number
  18. }
  19. const MAX_RETAINED_ROLES = 10
  20. const REQUIRED_V0_COVERAGE = new Set([
  21. 'multi-hop',
  22. 'packed-row',
  23. 'retry-failure',
  24. 'shipped-profile',
  25. ])
  26. const REQUIRED_ADJACENT_COVERAGE = new Set(['adjacent-migration'])
  27. /**
  28. * Require a current-writer majority and bounded coverage of each released migration source.
  29. *
  30. * @param scenarios - Every owning top-level recorded-session scenario.
  31. * @returns Accepted current and retained role counts.
  32. */
  33. export function assertSnapshotCorpusPolicy(
  34. scenarios: readonly SnapshotCorpusScenarioGenerations[],
  35. ): SnapshotCorpusGenerationSummary {
  36. let currentRoles = 0
  37. let retainedRoles = 0
  38. let retainedScenarios = 0
  39. const coverageByVersion = new Map<number, Set<string>>()
  40. for (const scenario of scenarios) {
  41. if (scenario.selectedVersions.length === 0) {
  42. throw new Error(`${scenario.key}: scenario owns no selected Session role`)
  43. }
  44. const expectedVersion = scenario.retained?.version ?? SESSION_FORMAT_VERSION
  45. const mismatched = scenario.selectedVersions.find(version => version !== expectedVersion)
  46. if (mismatched !== undefined) {
  47. throw new Error(
  48. `${scenario.key}: selected Session generation v${mismatched} does not match expected v${expectedVersion}`,
  49. )
  50. }
  51. if (scenario.retained === undefined) {
  52. currentRoles += scenario.selectedVersions.length
  53. continue
  54. }
  55. if (!Number.isSafeInteger(scenario.retained.version)
  56. || scenario.retained.version < 0 || scenario.retained.version >= SESSION_FORMAT_VERSION) {
  57. throw new Error(`${scenario.key}: retained Session format must precede current v${SESSION_FORMAT_VERSION}`)
  58. }
  59. const allowedCoverage = scenario.retained.version === 0
  60. ? REQUIRED_V0_COVERAGE
  61. : REQUIRED_ADJACENT_COVERAGE
  62. if (scenario.retained.coverage.some(item => !allowedCoverage.has(item))) {
  63. throw new Error(
  64. `${scenario.key}: v${scenario.retained.version} retained coverage must be ${[...allowedCoverage].join(', ')}`,
  65. )
  66. }
  67. retainedRoles += scenario.selectedVersions.length
  68. retainedScenarios += 1
  69. const coverage = coverageByVersion.get(scenario.retained.version) ?? new Set<string>()
  70. coverageByVersion.set(scenario.retained.version, coverage)
  71. for (const item of scenario.retained.coverage) coverage.add(item)
  72. }
  73. for (let version = 0; version < SESSION_FORMAT_VERSION; version += 1) {
  74. const required = version === 0 ? REQUIRED_V0_COVERAGE : REQUIRED_ADJACENT_COVERAGE
  75. const coverage = coverageByVersion.get(version)
  76. const missing = [...required].filter(item => !coverage?.has(item))
  77. if (missing.length > 0) {
  78. throw new Error(`Session corpus lacks v${version} coverage: ${missing.join(', ')}`)
  79. }
  80. }
  81. if (retainedRoles > MAX_RETAINED_ROLES) {
  82. throw new Error(`Session corpus retains ${retainedRoles} historical roles; maximum is ${MAX_RETAINED_ROLES}`)
  83. }
  84. if (currentRoles <= retainedRoles) {
  85. throw new Error(
  86. `Session corpus requires a current majority; current=${currentRoles}, retained=${retainedRoles}`,
  87. )
  88. }
  89. return { currentRoles, retainedRoles, retainedScenarios }
  90. }