spec.ts 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. /**
  2. * The session-projcache domain declaration: one `sessions` table keyed by
  3. * {@link SessionId}, each record the full projection checkpoint for one
  4. * session (`key → {ver, seq, val}` rows). The spec object
  5. * is the single source of the domain's identity, version, and record schema;
  6. * the storage-domain routing decides the medium (the shipped composition's
  7. * json backend lands it at `<root>/session_projcache.json`, beside
  8. * `workspace.json`).
  9. * @module @deepseek-ai/dsh-session-projection-cache/src/spec
  10. */
  11. import { z } from 'zod'
  12. import { SessionId } from '@deepseek-ai/dsh-session'
  13. import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain'
  14. /**
  15. * One persisted checkpoint row (the RFC's `(sessionId, key, ver, seq, val)`
  16. * minus the two record keys). `val` is the unit's internal state — plain
  17. * JSON by the unit contract; `z.json()` enforces that at the durable
  18. * boundary. A row is never wrong, only possibly stale: `seq` says exactly
  19. * how stale, and a `ver` mismatch against the live unit's `stateVersion`
  20. * discards it at read time (never a migration).
  21. */
  22. export const checkpointRow = z.object({
  23. ver: z.number().int().nonnegative(),
  24. seq: z.number().int().gte(-1),
  25. val: z.json(),
  26. })
  27. /**
  28. * The stored-log identity a record is bound to: the immutable header fields
  29. * that distinguish one session lifecycle from another under the same id. A
  30. * session id names a slot, not a lifecycle — a deleted-then-recreated id, or
  31. * a persistence root swapped under a surviving cache, would otherwise let an
  32. * old row pass every watermark check and seed state folded from an unrelated
  33. * log. Reads validate this against the live header (listing) or the stored
  34. * header (cold read) before accepting any row.
  35. */
  36. export const checkpointIdentity = z.object({
  37. createdAt: z.number().int().nonnegative(),
  38. cwd: z.string().optional(),
  39. })
  40. /** The identity fields a record is bound to, inferred from {@link checkpointIdentity}. */
  41. export type CheckpointIdentity = z.infer<typeof checkpointIdentity>
  42. /**
  43. * One session's stored record: the log identity it was folded from plus its
  44. * checkpoint rows keyed by projection key. The whole record is replaced on
  45. * every write (whole-value discipline — the registry checkpoint is always
  46. * the complete per-session cut).
  47. */
  48. export const checkpointRecord = z.object({
  49. identity: checkpointIdentity,
  50. rows: z.record(z.string(), checkpointRow),
  51. })
  52. /** One stored per-session checkpoint record, inferred from {@link checkpointRecord}. */
  53. export type CheckpointRecord = z.infer<typeof checkpointRecord>
  54. /**
  55. * The session-projcache domain spec. Version bumps discard the whole medium
  56. * (cache semantics: a stale or unreadable cache costs a longer tail replay,
  57. * never a wrong value).
  58. */
  59. export const projectionCacheDomainSpec = defineDomain({
  60. name: 'session_projcache',
  61. version: 3,
  62. tables: { sessions: domainTable<SessionId, CheckpointRecord>(checkpointRecord) },
  63. })