Browse Source

fix(attachment): type failure codes

Tianyi Cui 2 months ago
parent
commit
de17206051

+ 2 - 2
docs/subsystems/attachment.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write docs/subsystems/attachment.md
-attachment.md: c769d9e608b9e1ab12a5960ca2629a297853bf26
-attachment.zh.md: d07ea722656fafd93793850b8dd268cb14e6856b
+attachment.md: ff5a802b23b0111dff4481394772438f5d68feab
+attachment.zh.md: 6ca15c1a5b079463066e8c09b1f9dd26faed7158

+ 1 - 1
docs/subsystems/attachment.md

@@ -121,5 +121,5 @@ abstract saveImage(input: SaveImageAttachment): Promise<ImageAttachmentRef>
 abstract readImage(ref: ImageAttachmentRef, signal?: AbortSignal): Promise<StoredImageAttachment>
 ```
 
-Source: [`packages/attachment/attachment/src/index.ts:30`](../../packages/attachment/attachment/src/index.ts)
+Source: [`packages/attachment/attachment/src/index.ts:31`](../../packages/attachment/attachment/src/index.ts)
 <!-- END GENERATED cordis-surface -->

+ 1 - 1
docs/subsystems/attachment.zh.md

@@ -121,5 +121,5 @@ abstract saveImage(input: SaveImageAttachment): Promise<ImageAttachmentRef>
 abstract readImage(ref: ImageAttachmentRef, signal?: AbortSignal): Promise<StoredImageAttachment>
 ```
 
-Source: [`packages/attachment/attachment/src/index.ts:30`](../../packages/attachment/attachment/src/index.ts)
+Source: [`packages/attachment/attachment/src/index.ts:31`](../../packages/attachment/attachment/src/index.ts)
 <!-- END GENERATED cordis-surface -->

+ 2 - 2
packages/attachment/attachment/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/attachment/attachment/README.md
-README.md: 05c4bce5498f3c0bf172264be3e4b834ea0925e2
-README.zh.md: 91a454da09d32b0a87d02ca7ccb482e95c485a37
+README.md: 4fe608552492c33d2bd9acddce51ea1cf20acae4
+README.zh.md: a3093fc9dd1f926cb1c54831c6302eb7bbca25c5

+ 1 - 1
packages/attachment/attachment/README.md

@@ -4,7 +4,7 @@ English | [中文](README.zh.md)
 
 The durable attachment seam. `ctx.attachments` validates and durably commits immutable image bytes, then returns a serializable `ImageAttachmentRef`; consumers never persist browser paths, object URLs, provider URLs, or base64 in session events.
 
-Unsent composer images remain browser-owned temporary drafts. `validateImage` runs the same admission policy without persisting. `saveImages` owns batch count and aggregate-byte limits, validates every member before writing any member, then commits in order and returns references only after the complete batch succeeds. A later storage failure returns no partial references, although an earlier immutable content-addressed object may remain unreachable until reference-aware garbage collection exists. `isImageAdmissionError` distinguishes caller-correctable image-policy failures from storage faults so each protocol adapter can map its own error vocabulary. `saveImage` commits one accepted image before any model-visible session event is published, and `readImage` verifies the content-addressed object against its logged metadata. Callers may cancel `readImage`; implementations observe cancellation around backend and verification work and preserve it instead of translating it into a storage failure.
+Unsent composer images remain browser-owned temporary drafts. `validateImage` runs the same admission policy without persisting. `saveImages` owns batch count and aggregate-byte limits, validates every member before writing any member, then commits in order and returns references only after the complete batch succeeds. A later storage failure returns no partial references, although an earlier immutable content-addressed object may remain unreachable until reference-aware garbage collection exists. `AttachmentError.code` uses the closed `AttachmentErrorCode` string union. Its `ImageAdmissionErrorCode` subset marks caller-correctable image-input failures; `isImageAdmissionError` recognizes that subset at runtime so each protocol adapter can map its own error vocabulary. `saveImage` commits one accepted image before any model-visible session event is published, and `readImage` verifies the content-addressed object against its logged metadata. Callers may cancel `readImage`; implementations observe cancellation around backend and verification work and preserve it instead of translating it into a storage failure.
 
 ## Model Experience
 

+ 1 - 1
packages/attachment/attachment/README.zh.md

@@ -4,7 +4,7 @@
 
 持久附件服务边界。`ctx.attachments` 校验并持久提交不可变图片字节,随后返回可序列化的 `ImageAttachmentRef`;消费方绝不会在会话事件中持久保存浏览器路径、对象 URL、提供方 URL 或 base64。
 
-未发送的输入区图片仍是由浏览器持有的临时草稿。`validateImage` 运行相同的准入策略,但不执行持久化。`saveImages` 负责批次图片数量和总字节限制,先校验全部成员,再按顺序提交,并且只在完整批次成功后返回引用。后续存储失败不会返回部分引用,但较早写入的不可变内容寻址对象可能保持不可达,直至具备按引用感知的垃圾回收。`isImageAdmissionError` 区分可由调用方修正的图片策略失败与存储故障,使每个协议适配器可以映射自己的错误词汇。`saveImage` 会在发布任何模型可见的会话事件前提交一张已接受的图片,`readImage` 则根据已记录的元数据校验内容寻址对象。调用方可以取消 `readImage`;实现会在后端读取与校验工作的边界观察取消,并保留取消语义,而不会将其转换为存储失败。
+未发送的输入区图片仍是由浏览器持有的临时草稿。`validateImage` 运行相同的准入策略,但不执行持久化。`saveImages` 负责批次图片数量和总字节限制,先校验全部成员,再按顺序提交,并且只在完整批次成功后返回引用。后续存储失败不会返回部分引用,但较早写入的不可变内容寻址对象可能保持不可达,直至具备按引用感知的垃圾回收。`AttachmentError.code` 使用封闭的 `AttachmentErrorCode` 字符串联合类型。其 `ImageAdmissionErrorCode` 子集标记可由调用方修正的图片输入失败;`isImageAdmissionError` 在运行时识别该子集,使每个协议适配器可以映射自己的错误词汇。`saveImage` 会在发布任何模型可见的会话事件前提交一张已接受的图片,`readImage` 则根据已记录的元数据校验内容寻址对象。调用方可以取消 `readImage`;实现会在后端读取与校验工作的边界观察取消,并保留取消语义,而不会将其转换为存储失败。
 
 ## 模型体验
 

+ 32 - 15
packages/attachment/attachment/src/error.ts

@@ -1,5 +1,31 @@
 /** Attachment failure class. @module @deepseek-ai/dsh-attachment/error */
 
+const IMAGE_ADMISSION_ERROR_CODES = [
+  'TOO_MANY_IMAGES',
+  'IMAGES_TOO_LARGE',
+  'UNSUPPORTED_IMAGE_TYPE',
+  'INVALID_IMAGE_BASE64',
+  'INVALID_IMAGE',
+  'IMAGE_TYPE_MISMATCH',
+  'IMAGE_TOO_LARGE',
+  'IMAGE_TOO_MANY_PIXELS',
+] as const
+
+/** Caller-correctable attachment failure codes raised while admitting image input. */
+export type ImageAdmissionErrorCode = typeof IMAGE_ADMISSION_ERROR_CODES[number]
+
+/** Stable attachment failure codes used for protocol error routing. */
+export type AttachmentErrorCode =
+  | ImageAdmissionErrorCode
+  | 'INVALID_ATTACHMENT_REF'
+  | 'ATTACHMENT_CORRUPT'
+  | 'ATTACHMENT_WRITE_FAILED'
+  | 'ATTACHMENT_NOT_FOUND'
+  | 'ATTACHMENT_READ_FAILED'
+
+/** Runtime membership for structurally compatible errors crossing package boundaries. */
+const IMAGE_ADMISSION_ERROR_CODE_SET: ReadonlySet<string> = new Set(IMAGE_ADMISSION_ERROR_CODES)
+
 /**
  * Stable failures suitable for host RPC error mapping.
  *
@@ -11,39 +37,30 @@
  */
 export class AttachmentError extends Error {
   /** Stable machine-routing failure code. */
-  readonly code: string
+  readonly code: AttachmentErrorCode
 
   /**
    * @param message - human-readable failure description without raw bytes or host paths.
    * @param code - stable machine-routing code.
    * @param options - optional chained cause.
    */
-  constructor(message: string, code: string, options?: ErrorOptions) {
+  constructor(message: string, code: AttachmentErrorCode, options?: ErrorOptions) {
     super(message, options)
     this.name = 'AttachmentError'
     this.code = code
   }
 }
 
-/** Attachment failures caused by the caller's proposed image batch. */
-const IMAGE_ADMISSION_ERROR_CODES = new Set([
-  'TOO_MANY_IMAGES',
-  'IMAGES_TOO_LARGE',
-  'UNSUPPORTED_IMAGE_TYPE',
-  'INVALID_IMAGE',
-  'IMAGE_TYPE_MISMATCH',
-  'IMAGE_TOO_LARGE',
-  'IMAGE_TOO_MANY_PIXELS',
-])
-
 /**
  * Distinguish caller-correctable image admission failures from storage faults.
  * @param error - failure raised while validating or persisting an image batch.
  * @returns whether the caller can correct the proposed image content or batch.
  */
-export function isImageAdmissionError(error: unknown): error is AttachmentError {
+export function isImageAdmissionError(
+  error: unknown,
+): error is AttachmentError & { readonly code: ImageAdmissionErrorCode } {
   return error instanceof Error
     && 'code' in error
     && typeof error.code === 'string'
-    && IMAGE_ADMISSION_ERROR_CODES.has(error.code)
+    && IMAGE_ADMISSION_ERROR_CODE_SET.has(error.code)
 }

+ 1 - 0
packages/attachment/attachment/src/index.ts

@@ -11,6 +11,7 @@ import type {
 
 export { AttachmentId } from './brand.ts'
 export { AttachmentError, isImageAdmissionError } from './error.ts'
+export type { AttachmentErrorCode, ImageAdmissionErrorCode } from './error.ts'
 export type {
   AttachmentId as AttachmentIdType,
   ImageAttachmentLimits,

+ 2 - 1
packages/attachment/attachment/tests/index.spec.ts

@@ -97,8 +97,9 @@ describe('AttachmentStore.saveImages', () => {
 })
 
 describe('isImageAdmissionError', () => {
-  it('separates caller-correctable image policy failures from storage faults', () => {
+  it('separates caller-correctable image admission failures from storage faults', () => {
     expect(isImageAdmissionError(new AttachmentError('bad bytes', 'INVALID_IMAGE'))).toBe(true)
+    expect(isImageAdmissionError(new AttachmentError('bad base64', 'INVALID_IMAGE_BASE64'))).toBe(true)
     expect(isImageAdmissionError(new AttachmentError('too many', 'TOO_MANY_IMAGES'))).toBe(true)
     expect(isImageAdmissionError(Object.assign(new Error('foreign policy error'), { code: 'IMAGE_TOO_LARGE' }))).toBe(true)
     expect(isImageAdmissionError(new AttachmentError('corrupt object', 'ATTACHMENT_CORRUPT'))).toBe(false)