Skip to main content

domain/
document.rs

1//! Document intake, safety review, storage, and retention values.
2//!
3//! ## Operator-summary
4//!
5//! This module supports the document-review queue for vaccine proofs, waivers, medical
6//! records, photos, incident evidence, and provider/customer uploads. It can reduce labor
7//! by routing files through classification, virus scan, PII redaction, extraction, storage,
8//! supersession, and reviewer status instead of making staff manually inspect each upload
9//! before it appears in a workflow.
10//!
11//! It must not automate live compliance clearance, medical acceptance, incident resolution,
12//! customer disclosure, provider writes, or retention/destruction decisions. The authoritative
13//! source facts remain the immutable stored object, hash, original metadata, source route,
14//! scan/redaction results, extraction evidence, reviewer decision, and audit trail. Review
15//! gates protect pets, customers, and staff by keeping unscanned, unredacted, failed,
16//! unverified, superseded, or rejected documents out of compliance, messaging, and safety
17//! decisions until the appropriate staff review is complete.
18//!
19//! Documents carry vaccine proofs, waivers, medical records, incident evidence, and other source
20//! artifacts that staff and agents rely on. The domain separates received/extracted facts from
21//! verified facts, records virus/PII handling state, and keeps storage references explicit so
22//! automation cannot treat unreviewed uploads as compliance truth.
23
24use bon::Builder;
25use nutype::nutype;
26#[allow(unused_imports)]
27use serde::{Deserialize, Serialize};
28use std::fmt;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
31/// Document classification used to route vaccine, waiver, medical, photo, and incident evidence.
32pub enum Classification {
33    /// Immunization proof that can satisfy compliance only after scan and reviewer checks pass.
34    VaccineProof,
35    /// Signed waiver artifact retained as customer consent evidence for staff review.
36    Waiver,
37    /// Pet or facility image that may support identity, care notes, or customer communication.
38    Photo,
39    /// Veterinary or medical record that requires review before influencing care decisions.
40    MedicalRecord,
41    /// Incident attachment preserved as safety and audit evidence for manager follow-up.
42    IncidentEvidence,
43    /// Non-dog, non-cat pet handled by exception policy.
44    Other,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48/// Source route through which a document entered the review and storage pipeline.
49pub enum Source {
50    /// Customer-submitted file that enters quarantine, scan, and reviewer queues before use.
51    CustomerUpload,
52    /// Paper document digitized by staff and tied to source metadata for auditability.
53    StaffScan,
54    /// Staff-uploaded file attached to an operational record with reviewer accountability.
55    StaffUpload,
56    /// Email attachment captured from an inbox before classification, scan, and review.
57    EmailIngest,
58    /// File discovered through provider polling and reconciled against source-system authority.
59    ProviderPoll,
60    /// File announced by provider webhook and retained with webhook provenance for audit.
61    ProviderWebhook,
62    /// Legacy file imported during migration with provenance preserved for cleanup review.
63    MigrationImport,
64    /// Source route is unknown, so staff should verify document origin before trusting it in workflows.
65    Unknown,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69/// Normalized lifecycle states used to reconcile source-system data with domain workflows.
70pub enum Status {
71    /// Stored but not yet scanned or extracted, so it cannot support compliance decisions.
72    Received,
73    /// Rejected during quarantine and blocked from staff-visible evidence flows.
74    QuarantinedRejected,
75    /// OCR or metadata extraction is running before reviewer-ready facts exist.
76    Extracting,
77    /// Extraction failed and requires staff review before the document can provide facts.
78    ExtractionFailed,
79    /// Scan and extraction evidence is ready but still needs human approval.
80    AwaitingReview,
81    /// Reviewer-approved document evidence may now support compliance or care workflows.
82    Verified,
83    /// Reviewer rejected the file, blocking it from compliance and customer messaging.
84    Rejected,
85    /// Newer evidence replaced this document while the old audit trail remains retained.
86    Superseded,
87    /// Retained historical document no longer participates in active workflows.
88    Archived,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
92/// Virus-scan outcome used before documents may become staff-visible evidence.
93pub enum VirusScanStatus {
94    /// Scan request is pending, so the file remains blocked from trusted evidence use.
95    Pending,
96    /// Virus scan passed, allowing the document to continue toward extraction and review.
97    Passed,
98    /// Virus scan failed, keeping the document quarantined from staff and automation.
99    Failed,
100    /// File type cannot be scanned by the supported path and needs manual handling.
101    Unsupported,
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
105/// PII redaction state used before document content is exposed to agents or reports.
106pub enum PiiRedactionStatus {
107    /// Redaction is unnecessary for this document before staff or agent use.
108    NotRequired,
109    /// Redaction is pending, so extracted content must stay out of agent/report surfaces.
110    Pending,
111    /// Sensitive content has been redacted for safe staff, agent, or report use.
112    Redacted,
113    /// Redaction failed, so document content remains blocked until manual review.
114    Failed,
115}
116
117#[nutype(
118    sanitize(trim),
119    validate(not_empty, len_char_max = 255),
120    derive(
121        Debug,
122        Clone,
123        PartialEq,
124        Eq,
125        PartialOrd,
126        Ord,
127        Hash,
128        Serialize,
129        Deserialize
130    )
131)]
132pub struct FileName(String);
133
134/// MIME type reported for a document before extraction, virus scanning, or storage policy decisions.
135#[nutype(
136    sanitize(trim),
137    validate(not_empty, len_char_max = 160),
138    derive(
139        Debug,
140        Clone,
141        PartialEq,
142        Eq,
143        PartialOrd,
144        Ord,
145        Hash,
146        Serialize,
147        Deserialize
148    )
149)]
150pub struct MimeType(String);
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
153/// Non-zero document size used to reject empty uploads before extraction or review.
154pub struct ContentLengthBytes(u64);
155
156impl ContentLengthBytes {
157    /// Rejects unusable document input before extraction, storage, or reviewer queues use it.
158    pub const fn try_new(value: u64) -> Result<Self, ContentLengthError> {
159        if value == 0 {
160            return Err(ContentLengthError::EmptyObject);
161        }
162        Ok(Self(value))
163    }
164
165    /// Returns the checked value for storage, reporting, or adapter output.
166    pub const fn get(self) -> u64 {
167        self.0
168    }
169}
170
171impl<'de> Deserialize<'de> for ContentLengthBytes {
172    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
173    where
174        D: serde::Deserializer<'de>,
175    {
176        Self::try_new(u64::deserialize(deserializer)?).map_err(serde::de::Error::custom)
177    }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
181/// Validation error for document size constraints.
182pub enum ContentLengthError {
183    #[error("document storage evidence must not point at an empty object")]
184    /// Signals that object was blank or missing during document validation.
185    EmptyObject,
186}
187
188#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
189/// SHA-256 digest used to detect duplicate, tampered, or drifted document payloads.
190pub struct Sha256Digest(String);
191
192impl Sha256Digest {
193    /// Validates and creates the document value.
194    pub fn try_new(value: impl Into<String>) -> Result<Self, Sha256DigestError> {
195        let value = value.into().trim().to_ascii_lowercase();
196        if value.len() != 64 || !value.chars().all(|ch| ch.is_ascii_hexdigit()) {
197            return Err(Sha256DigestError::InvalidSha256Hex);
198        }
199        Ok(Self(value))
200    }
201
202    /// Returns the owned inner string for storage or outbound mapping.
203    pub fn into_inner(self) -> String {
204        self.0
205    }
206}
207
208impl fmt::Debug for Sha256Digest {
209    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
210        formatter.write_str("Sha256Digest(<redacted>)")
211    }
212}
213
214impl<'de> Deserialize<'de> for Sha256Digest {
215    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
216    where
217        D: serde::Deserializer<'de>,
218    {
219        Self::try_new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
220    }
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
224/// Validation error for document hash formatting.
225pub enum Sha256DigestError {
226    #[error("document content hashes must be 64 lowercase/uppercase hexadecimal sha256 characters")]
227    /// Signals that sha256 hex could not be parsed or accepted during document validation.
228    InvalidSha256Hex,
229}
230
231#[nutype(
232    sanitize(trim),
233    validate(not_empty, len_char_max = 160),
234    derive(
235        Debug,
236        Clone,
237        PartialEq,
238        Eq,
239        PartialOrd,
240        Ord,
241        Hash,
242        Serialize,
243        Deserialize
244    )
245)]
246pub struct StorageBucket(String);
247
248/// Storage key for the immutable document object used as review or compliance evidence.
249#[nutype(
250    sanitize(trim),
251    validate(not_empty, len_char_max = 500),
252    derive(
253        Debug,
254        Clone,
255        PartialEq,
256        Eq,
257        PartialOrd,
258        Ord,
259        Hash,
260        Serialize,
261        Deserialize
262    )
263)]
264pub struct StorageKey(String);
265
266/// Optional object-version marker for document retention and supersession audits.
267#[nutype(
268    sanitize(trim),
269    validate(not_empty, len_char_max = 160),
270    derive(
271        Debug,
272        Clone,
273        PartialEq,
274        Eq,
275        PartialOrd,
276        Ord,
277        Hash,
278        Serialize,
279        Deserialize
280    )
281)]
282pub struct StorageVersion(String);
283
284#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
285/// Storage pointer to the immutable object that backs a reviewed or source document.
286pub struct StorageRef {
287    /// Bucket preserved with the stored document so reviewers can audit intake, extraction, and retention.
288    pub bucket: StorageBucket,
289    /// Key preserved with the stored document so reviewers can audit intake, extraction, and retention.
290    pub key: StorageKey,
291    /// Version preserved with the stored document so reviewers can audit intake, extraction, and retention.
292    pub version: StorageVersion,
293}
294
295#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
296/// Original uploaded file metadata preserved for audit, extraction, and staff review.
297pub struct OriginalFile {
298    /// Filename preserved with the stored document so reviewers can audit intake, extraction, and retention.
299    pub filename: FileName,
300    /// Mime type preserved with the stored document so reviewers can audit intake, extraction, and retention.
301    pub mime_type: MimeType,
302    /// Content length preserved with the stored document so reviewers can audit intake, extraction, and retention.
303    pub content_length: ContentLengthBytes,
304    /// Sha256 preserved with the stored document so reviewers can audit intake, extraction, and retention.
305    pub sha256: Sha256Digest,
306}