// Package evidence defines the narrow internal screenshot contract shared by HTTP and storage. package evidence import ( "context" "errors" "io" "time" "cmbuyer/admin/internal/deviceauth" ) const ( KindSKUPanelGate1 = "SKU_PANEL_GATE_1" PrivacyInternalRaw = "INTERNAL_RAW" PNGContentType = "image/png" MaxFileBytes int64 = 10 << 20 MaxImageSide = 8192 MaxImagePixels = 16_777_216 ) var ( ErrInvalid = errors.New("invalid evidence") ErrConflict = errors.New("evidence upload key conflict") ErrNotFound = errors.New("evidence not found") ErrTooLarge = errors.New("evidence file too large") ) type UploadMetadata struct { UploadKey string TaskID string AttemptID string Kind string PrivacyTier string SHA256 string CapturedAt time.Time } // StagedFile contains only server-generated state. Multipart filenames and client paths never enter this type. type StagedFile struct { Path string SHA256 string ByteSize int64 ContentType string Width int Height int } type Asset struct { ID string `json:"asset_id"` TaskID string `json:"task_id"` AttemptID string `json:"attempt_id"` Kind string `json:"kind"` PrivacyTier string `json:"privacy_tier"` SHA256 string `json:"sha256"` ByteSize int64 `json:"byte_size"` ContentType string `json:"content_type"` Width int `json:"width_px"` Height int `json:"height_px"` CapturedAt time.Time `json:"captured_at"` UploadedByDeviceID string `json:"-"` StorageKey string `json:"-"` CreatedAt time.Time `json:"-"` } // Store separates bounded multipart staging from metadata commit so field order cannot weaken validation. type Store interface { Stage(io.Reader, string) (StagedFile, error) Discard(StagedFile) Commit(context.Context, deviceauth.Principal, UploadMetadata, StagedFile) (Asset, bool, error) Open(context.Context, string) (Asset, io.ReadSeekCloser, error) }