errors.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. /**
  2. * Stable failures exposed by the session-persistence service and its handles,
  3. * including the format refusals shared by every backend: a stored log this
  4. * build cannot faithfully interpret is refused, never misread, and the
  5. * refusal points at the raw artifact when the backend keeps one per session.
  6. * @module @deepseek-ai/dsh-session-persistence/errors
  7. */
  8. import { SESSION_FORMAT_VERSION } from '@deepseek-ai/dsh-session'
  9. import type { SessionId } from '@deepseek-ai/dsh-session'
  10. /** The requested Session identity has no durable log visible to this caller. */
  11. export class SessionPersistenceNotFoundError extends Error {
  12. /** @param sessionId - absent durable Session identity. */
  13. constructor(readonly sessionId: SessionId) {
  14. super(`session "${sessionId}" not found`)
  15. this.name = 'SessionPersistenceNotFoundError'
  16. }
  17. }
  18. /** `create` targeted a Session identity that already exists in this backend. */
  19. export class SessionAlreadyExistsError extends Error {
  20. /** @param sessionId - the occupied durable Session identity. */
  21. constructor(readonly sessionId: SessionId) {
  22. super(`session "${sessionId}" already exists`)
  23. this.name = 'SessionAlreadyExistsError'
  24. }
  25. }
  26. /** A write open found the session already bound to an active write handle. */
  27. export class SessionAlreadyOwnedError extends Error {
  28. /** @param sessionId - the session whose write ownership is taken. */
  29. constructor(readonly sessionId: SessionId) {
  30. super(`session "${sessionId}" is already owned by an active write handle`)
  31. this.name = 'SessionAlreadyOwnedError'
  32. }
  33. }
  34. /** A mutation (`append`/`flush`) was called on a read handle. */
  35. export class SessionReadOnlyError extends Error {
  36. /**
  37. * @param sessionId - the session the read handle observes.
  38. * @param operation - the refused mutating operation name.
  39. */
  40. constructor(readonly sessionId: SessionId, operation: string) {
  41. super(`session "${sessionId}": ${operation} is not available on a read handle`)
  42. this.name = 'SessionReadOnlyError'
  43. }
  44. }
  45. /**
  46. * A write handle's ownership is permanently gone: its lease expired, a renewal
  47. * failed, or the durable ownership record no longer names this handle. The
  48. * handle never re-acquires ownership — close it and reopen for write.
  49. *
  50. * Declared for the cross-process lease layer; the shipped in-process backends
  51. * never throw it yet.
  52. */
  53. export class SessionOwnershipLostError extends Error {
  54. /** @param sessionId - the session whose write ownership this handle lost. */
  55. constructor(readonly sessionId: SessionId) {
  56. super(`session "${sessionId}": write ownership was lost; close this handle and reopen`)
  57. this.name = 'SessionOwnershipLostError'
  58. }
  59. }
  60. /** An operation was called on a handle after `close()` was called. */
  61. export class SessionHandleClosedError extends Error {
  62. /**
  63. * @param sessionId - the session the closed handle addressed.
  64. * @param operation - the refused operation name.
  65. */
  66. constructor(readonly sessionId: SessionId, operation: string) {
  67. super(`session "${sessionId}": ${operation} on a closed handle`)
  68. this.name = 'SessionHandleClosedError'
  69. }
  70. }
  71. /**
  72. * A backend-resolved, per-session local artifact location. Carried only by
  73. * refusal diagnostics ({@link SessionFormatUnsupportedError}) so a user can
  74. * find the raw log a build refused to interpret; it is not a consumer-facing
  75. * query — log access goes through a session handle's `read`.
  76. */
  77. export interface SessionLocation {
  78. /** Backend-specific artifact kind, for example `jsonl`. */
  79. readonly kind: string
  80. /** Absolute path to this session's backend-owned artifact. */
  81. readonly path: string
  82. }
  83. /** Durable session contents failed validation after a successful backend read. */
  84. export class SessionPersistenceCorruptionError extends Error {
  85. /**
  86. * @param message - stable corruption context.
  87. * @param options - original validation failure.
  88. */
  89. constructor(message: string, options: ErrorOptions) {
  90. super(message, options)
  91. this.name = 'SessionPersistenceCorruptionError'
  92. }
  93. }
  94. /**
  95. * The stored log is intact but this runtime cannot faithfully interpret it:
  96. * the header carries an unsupported format version, or an event's type is
  97. * unknown to this build. Distinct from {@link SessionPersistenceCorruptionError}
  98. * — nothing is damaged; the raw log remains readable at {@link location} when
  99. * the backend keeps one artifact per session.
  100. */
  101. export class SessionFormatUnsupportedError extends Error {
  102. /**
  103. * @param message - stable reason the log cannot be interpreted, already
  104. * including the raw-log path when one exists.
  105. * @param location - the backend's artifact location, when one exists.
  106. */
  107. constructor(message: string, readonly location?: SessionLocation) {
  108. super(message)
  109. this.name = 'SessionFormatUnsupportedError'
  110. }
  111. }
  112. /**
  113. * Direction-aware refusal text for a stored session whose format version this
  114. * build does not read. Shared by load-time checks and by backends that must
  115. * refuse BEFORE decoding version-dependent structure (a future format may not
  116. * satisfy this build's structural checks at all, and the user must see
  117. * "upgrade the harness", never "corrupt").
  118. * @param id - the stored session id, for message context.
  119. * @param version - the stored format version.
  120. * @returns the stable refusal text, without a raw-log path suffix.
  121. */
  122. export function sessionFormatVersionRefusal(id: string, version: number): string {
  123. return version > SESSION_FORMAT_VERSION
  124. ? `session "${id}" uses log format v${version}, but this harness reads only v${SESSION_FORMAT_VERSION}: the log was written by a newer harness — upgrade the harness to open it`
  125. : `session "${id}" uses log format v${version}, older than the supported v${SESSION_FORMAT_VERSION}, and this build ships no upgrade path for it`
  126. }