@@ -418,10 +418,47 @@ export type ScreeningResult =
418418 | { readonly status : 'blocked' ; readonly screened_at : string ; readonly categories : [ string , ...string [ ] ] } ;
419419
420420// ---------------------------------------------------------------------------
421- // Attachment record (persisted metadata in TaskRecord)
421+ // Attachment record (persisted metadata in TaskRecord) — discriminated union
422+ // keyed on screening.status ensures that passed records always have storage fields.
422423// ---------------------------------------------------------------------------
423424
424- export interface AttachmentRecord {
425+ interface BaseAttachmentRecord {
426+ readonly attachment_id : string ;
427+ readonly type : AttachmentType ;
428+ readonly content_type : string ;
429+ readonly filename : string ;
430+ readonly source_url ?: string ;
431+ readonly token_estimate ?: number ;
432+ }
433+
434+ export interface PendingAttachmentRecord extends BaseAttachmentRecord {
435+ readonly screening : { readonly status : 'pending' } ;
436+ readonly s3_key ?: string ;
437+ readonly s3_version_id ?: string ;
438+ readonly size_bytes ?: number ;
439+ readonly checksum_sha256 ?: string ;
440+ }
441+
442+ export interface PassedAttachmentRecord extends BaseAttachmentRecord {
443+ readonly screening : { readonly status : 'passed' ; readonly screened_at : string } ;
444+ readonly s3_key : string ;
445+ readonly s3_version_id : string ;
446+ readonly size_bytes : number ;
447+ readonly checksum_sha256 : string ;
448+ }
449+
450+ export interface BlockedAttachmentRecord extends BaseAttachmentRecord {
451+ readonly screening : { readonly status : 'blocked' ; readonly screened_at : string ; readonly categories : [ string , ...string [ ] ] } ;
452+ readonly s3_key ?: string ;
453+ readonly s3_version_id ?: string ;
454+ readonly size_bytes ?: number ;
455+ readonly checksum_sha256 ?: string ;
456+ }
457+
458+ export type AttachmentRecord = PendingAttachmentRecord | PassedAttachmentRecord | BlockedAttachmentRecord ;
459+
460+ /** Parameters for creating an AttachmentRecord — accepts the union of all fields. */
461+ export type CreateAttachmentRecordParams = {
425462 readonly attachment_id : string ;
426463 readonly type : AttachmentType ;
427464 readonly content_type : string ;
@@ -433,22 +470,23 @@ export interface AttachmentRecord {
433470 readonly source_url ?: string ;
434471 readonly checksum_sha256 ?: string ;
435472 readonly token_estimate ?: number ;
436- }
437-
438- /** Parameters for creating an AttachmentRecord with cross-field invariant validation. */
439- export type CreateAttachmentRecordParams = AttachmentRecord ;
473+ } ;
440474
441475/**
442476 * Factory function enforcing cross-field invariants on AttachmentRecord construction.
443- * Validates that required fields are present based on screening status and type .
477+ * Returns the appropriate discriminated union variant based on screening status.
444478 */
445479export function createAttachmentRecord ( params : CreateAttachmentRecordParams ) : AttachmentRecord {
446480 if ( params . screening . status === 'passed' ) {
447481 if ( ! params . s3_key || ! params . s3_version_id || ! params . checksum_sha256 || ! params . size_bytes ) {
448482 throw new Error ( 'Passed screening requires s3_key, s3_version_id, checksum_sha256, and size_bytes' ) ;
449483 }
484+ return params as PassedAttachmentRecord ;
485+ }
486+ if ( params . screening . status === 'blocked' ) {
487+ return params as BlockedAttachmentRecord ;
450488 }
451- return params ;
489+ return params as PendingAttachmentRecord ;
452490}
453491
454492// ---------------------------------------------------------------------------
0 commit comments