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}