Skip to main content

aion_context/
compliance.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Compliance Reporting Module
3//!
4//! Generates compliance reports for regulatory frameworks:
5//! - SOX (Sarbanes-Oxley) - Financial controls audit
6//! - HIPAA - Healthcare audit logs
7//! - GDPR - Data processing records
8//!
9//! Reports are generated in structured formats (JSON, Markdown, plain text)
10//! suitable for regulatory submission or conversion to PDF.
11
12use crate::operations::{show_file_info, verify_file, FileInfo, VerificationReport};
13use crate::{AionError, Result};
14use std::path::Path;
15
16/// Report output format
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ReportFormat {
19    /// Plain text format
20    Text,
21    /// Markdown format (can be converted to PDF)
22    Markdown,
23    /// JSON format for machine processing
24    Json,
25}
26
27/// Compliance framework type
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum ComplianceFramework {
30    /// Sarbanes-Oxley Act (financial controls)
31    Sox,
32    /// Health Insurance Portability and Accountability Act
33    Hipaa,
34    /// General Data Protection Regulation
35    Gdpr,
36    /// Generic audit report (all frameworks)
37    Generic,
38}
39
40impl std::fmt::Display for ComplianceFramework {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        match self {
43            Self::Sox => write!(f, "SOX"),
44            Self::Hipaa => write!(f, "HIPAA"),
45            Self::Gdpr => write!(f, "GDPR"),
46            Self::Generic => write!(f, "Generic Audit"),
47        }
48    }
49}
50
51/// Compliance report data
52#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
53pub struct ComplianceReport {
54    /// Report title
55    pub title: String,
56    /// Compliance framework
57    pub framework: String,
58    /// Report generation timestamp (ISO 8601)
59    pub generated_at: String,
60    /// File being reported on
61    pub file_path: String,
62    /// File ID
63    pub file_id: String,
64    /// Verification status
65    pub verification: VerificationSummary,
66    /// Version history summary
67    pub version_history: Vec<VersionSummary>,
68    /// Framework-specific sections
69    pub framework_sections: Vec<ReportSection>,
70}
71
72/// Verification summary for reports
73#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
74pub struct VerificationSummary {
75    /// Overall validity
76    pub is_valid: bool,
77    /// Structure check passed
78    pub structure_valid: bool,
79    /// Integrity hash check passed
80    pub integrity_valid: bool,
81    /// Hash chain check passed
82    pub hash_chain_valid: bool,
83    /// All signatures valid
84    pub signatures_valid: bool,
85    /// Number of temporal warnings
86    pub temporal_warning_count: usize,
87}
88
89/// Version summary for reports
90#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
91pub struct VersionSummary {
92    /// Version number
93    pub version: u64,
94    /// Author ID
95    pub author_id: u64,
96    /// Timestamp (ISO 8601)
97    pub timestamp: String,
98    /// Commit message
99    pub message: String,
100}
101
102/// Report section for framework-specific content
103#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
104pub struct ReportSection {
105    /// Section title
106    pub title: String,
107    /// Section content
108    pub content: String,
109}
110
111/// Generate a compliance report for the specified framework
112///
113/// # Arguments
114///
115/// * `path` - Path to the AION file
116/// * `framework` - Compliance framework to report for
117/// * `format` - Output format
118///
119/// # Returns
120///
121/// Formatted compliance report as a string
122///
123/// # Errors
124///
125/// Returns error if file cannot be read or verified
126pub fn generate_compliance_report(
127    path: &Path,
128    framework: ComplianceFramework,
129    format: ReportFormat,
130    registry: &crate::key_registry::KeyRegistry,
131) -> Result<String> {
132    // Gather file information
133    let file_info = show_file_info(path, registry)?;
134    let verification = verify_file(path, registry)?;
135
136    // Build report structure
137    let report = build_report(path, framework, &file_info, &verification)?;
138
139    // Format output
140    match format {
141        ReportFormat::Text => Ok(format_as_text(&report)),
142        ReportFormat::Markdown => Ok(format_as_markdown(&report)),
143        ReportFormat::Json => format_as_json(&report),
144    }
145}
146
147/// Build the compliance report structure
148fn build_report(
149    path: &Path,
150    framework: ComplianceFramework,
151    file_info: &FileInfo,
152    verification: &VerificationReport,
153) -> Result<ComplianceReport> {
154    let generated_at = chrono_timestamp();
155
156    let verification_summary = VerificationSummary {
157        is_valid: verification.is_valid,
158        structure_valid: verification.structure_valid,
159        integrity_valid: verification.integrity_hash_valid,
160        hash_chain_valid: verification.hash_chain_valid,
161        signatures_valid: verification.signatures_valid,
162        temporal_warning_count: verification.temporal_warnings.len(),
163    };
164
165    let version_history: Vec<VersionSummary> = file_info
166        .versions
167        .iter()
168        .map(|v| VersionSummary {
169            version: v.version_number,
170            author_id: v.author_id,
171            timestamp: format_timestamp_nanos(v.timestamp),
172            message: v.message.clone(),
173        })
174        .collect();
175
176    let framework_sections = match framework {
177        ComplianceFramework::Sox => build_sox_sections(file_info, verification),
178        ComplianceFramework::Hipaa => build_hipaa_sections(file_info, verification),
179        ComplianceFramework::Gdpr => build_gdpr_sections(file_info, verification),
180        ComplianceFramework::Generic => build_generic_sections(file_info, verification),
181    };
182
183    Ok(ComplianceReport {
184        title: format!("{framework} Compliance Report"),
185        framework: framework.to_string(),
186        generated_at,
187        file_path: path.display().to_string(),
188        file_id: format!("0x{:016x}", file_info.file_id),
189        verification: verification_summary,
190        version_history,
191        framework_sections,
192    })
193}
194
195// ============================================================================
196// SOX (Sarbanes-Oxley) Report Sections
197// ============================================================================
198
199fn build_sox_sections(
200    file_info: &FileInfo,
201    verification: &VerificationReport,
202) -> Vec<ReportSection> {
203    vec![
204        ReportSection {
205            title: "Internal Control Assessment".to_string(),
206            content: format!(
207                "This report documents the integrity controls for business rules file ID {}.\n\n\
208                 Control Objective: Ensure accuracy and completeness of automated business rules.\n\n\
209                 Control Activity: Cryptographic verification of all rule changes.\n\n\
210                 Test Results:\n\
211                 - Digital signatures verified: {}\n\
212                 - Hash chain integrity: {}\n\
213                 - Tamper detection: {}",
214                format!("0x{:016x}", file_info.file_id),
215                if verification.signatures_valid { "PASS" } else { "FAIL" },
216                if verification.hash_chain_valid { "PASS" } else { "FAIL" },
217                if verification.integrity_hash_valid { "PASS" } else { "FAIL" }
218            ),
219        },
220        ReportSection {
221            title: "Change Management Log".to_string(),
222            content: format!(
223                "Total versions recorded: {}\n\
224                 All changes cryptographically signed: {}\n\
225                 Audit trail completeness: {}\n\n\
226                 Each version entry contains:\n\
227                 - Unique version number\n\
228                 - Author identification\n\
229                 - Timestamp of change\n\
230                 - Digital signature (Ed25519)\n\
231                 - Cryptographic hash linking to previous version",
232                file_info.version_count,
233                if verification.signatures_valid { "Yes" } else { "No" },
234                if verification.hash_chain_valid { "Complete" } else { "Incomplete" }
235            ),
236        },
237        ReportSection {
238            title: "Management Assertion".to_string(),
239            content: "Based on the cryptographic verification performed, management can assert that:\n\n\
240                     1. All changes to business rules have been authorized and recorded\n\
241                     2. The audit trail has not been tampered with\n\
242                     3. Each change is attributable to a specific author\n\
243                     4. The chronological sequence of changes is preserved".to_string(),
244        },
245    ]
246}
247
248// ============================================================================
249// HIPAA Report Sections
250// ============================================================================
251
252fn build_hipaa_sections(
253    file_info: &FileInfo,
254    verification: &VerificationReport,
255) -> Vec<ReportSection> {
256    vec![
257        hipaa_audit_controls_section(file_info, verification),
258        hipaa_audit_log_entries_section(file_info),
259        hipaa_technical_safeguards_section(file_info, verification),
260    ]
261}
262
263fn hipaa_audit_controls_section(
264    file_info: &FileInfo,
265    verification: &VerificationReport,
266) -> ReportSection {
267    ReportSection {
268        title: "Access and Audit Controls (§164.312)".to_string(),
269        content: format!(
270            "HIPAA Security Rule Compliance Assessment\n\n\
271             § 164.312(b) - Audit Controls:\n\
272             - Audit trail implemented: Yes\n\
273             - Hardware/software/procedural mechanisms: Cryptographic signatures\n\
274             - Activity recording: {} versions recorded\n\
275             - Audit log integrity: {}\n\n\
276             § 164.312(c) - Integrity Controls:\n\
277             - Mechanism to authenticate ePHI: Ed25519 digital signatures\n\
278             - Integrity verification: {}\n\
279             - Unauthorized alteration detection: BLAKE3 hash chain",
280            file_info.version_count,
281            if verification.hash_chain_valid {
282                "Verified"
283            } else {
284                "Failed"
285            },
286            if verification.is_valid {
287                "PASS"
288            } else {
289                "FAIL"
290            }
291        ),
292    }
293}
294
295fn hipaa_audit_log_entries_section(file_info: &FileInfo) -> ReportSection {
296    ReportSection {
297        title: "Audit Log Entries".to_string(),
298        content: format!(
299            "Activity Type: Business Rule Modification\n\
300             Total Entries: {}\n\
301             Entry Authentication: Digital Signature (Ed25519)\n\
302             Timestamp Precision: Nanosecond\n\
303             Non-repudiation: Cryptographic proof of authorship\n\n\
304             Each audit entry contains:\n\
305             - User identification (Author ID)\n\
306             - Date and time of activity\n\
307             - Type of activity (version commit)\n\
308             - Cryptographic proof of entry integrity",
309            file_info.version_count
310        ),
311    }
312}
313
314fn hipaa_technical_safeguards_section(
315    file_info: &FileInfo,
316    verification: &VerificationReport,
317) -> ReportSection {
318    ReportSection {
319        title: "Technical Safeguards Summary".to_string(),
320        content: format!(
321            "Encryption: ChaCha20-Poly1305 (AEAD)\n\
322             Digital Signatures: Ed25519\n\
323             Hashing: BLAKE3\n\
324             Key Derivation: HKDF-SHA256\n\n\
325             Verification Status:\n\
326             - All {} signatures valid: {}\n\
327             - File integrity verified: {}\n\
328             - Temporal warnings: {}",
329            file_info.signatures.len(),
330            if verification.signatures_valid {
331                "Yes"
332            } else {
333                "No"
334            },
335            if verification.is_valid { "Yes" } else { "No" },
336            verification.temporal_warnings.len()
337        ),
338    }
339}
340
341// ============================================================================
342// GDPR Report Sections
343// ============================================================================
344
345fn build_gdpr_sections(
346    file_info: &FileInfo,
347    verification: &VerificationReport,
348) -> Vec<ReportSection> {
349    vec![
350        ReportSection {
351            title: "Record of Processing Activities (Article 30)".to_string(),
352            content: format!(
353                "Processing Activity: Automated Decision-Making Rule Management\n\n\
354                 Controller Reference: File ID {}\n\
355                 Purpose: Storage and versioning of business rules for automated processing\n\
356                 Categories of Processing: Rule creation, modification, verification\n\n\
357                 Technical Measures (Article 32):\n\
358                 - Encryption of data: ChaCha20-Poly1305\n\
359                 - Integrity verification: BLAKE3 hash chain\n\
360                 - Access attribution: Ed25519 digital signatures",
361                format!("0x{:016x}", file_info.file_id)
362            ),
363        },
364        ReportSection {
365            title: "Data Processing Log".to_string(),
366            content: format!(
367                "Total Processing Events: {}\n\
368                 First Event: {}\n\
369                 Latest Event: {}\n\n\
370                 Each processing event records:\n\
371                 - Data controller action (rule change)\n\
372                 - Timestamp of processing\n\
373                 - Identity of processor (Author ID)\n\
374                 - Cryptographic proof of processing integrity",
375                file_info.version_count,
376                file_info
377                    .versions
378                    .first()
379                    .map(|v| format_timestamp_nanos(v.timestamp))
380                    .unwrap_or_default(),
381                file_info
382                    .versions
383                    .last()
384                    .map(|v| format_timestamp_nanos(v.timestamp))
385                    .unwrap_or_default()
386            ),
387        },
388        ReportSection {
389            title: "Accountability Demonstration (Article 5(2))".to_string(),
390            content: format!(
391                "This record demonstrates compliance with GDPR accountability principle:\n\n\
392                 Integrity and Confidentiality (Article 5(1)(f)):\n\
393                 - Processing integrity verified: {}\n\
394                 - Unauthorized access detection: Hash chain verification\n\
395                 - Data protection: Authenticated encryption\n\n\
396                 Transparency:\n\
397                 - All processing activities logged\n\
398                 - Processing history retrievable\n\
399                 - Audit trail tamper-evident",
400                if verification.is_valid { "Yes" } else { "No" }
401            ),
402        },
403    ]
404}
405
406// ============================================================================
407// Generic Audit Report Sections
408// ============================================================================
409
410fn build_generic_sections(
411    file_info: &FileInfo,
412    verification: &VerificationReport,
413) -> Vec<ReportSection> {
414    vec![
415        generic_file_summary_section(file_info),
416        generic_verification_results_section(verification),
417        generic_crypto_methods_section(),
418    ]
419}
420
421fn generic_file_summary_section(file_info: &FileInfo) -> ReportSection {
422    ReportSection {
423        title: "File Summary".to_string(),
424        content: format!(
425            "File ID: 0x{:016x}\n\
426             Total Versions: {}\n\
427             Current Version: {}\n\
428             Total Signatures: {}",
429            file_info.file_id,
430            file_info.version_count,
431            file_info.current_version,
432            file_info.signatures.len()
433        ),
434    }
435}
436
437fn generic_verification_results_section(verification: &VerificationReport) -> ReportSection {
438    ReportSection {
439        title: "Verification Results".to_string(),
440        content: format!(
441            "Overall Status: {}\n\n\
442             Checks Performed:\n\
443             - Structure validation: {}\n\
444             - Integrity hash: {}\n\
445             - Hash chain: {}\n\
446             - Signatures: {}\n\n\
447             Temporal Warnings: {}",
448            if verification.is_valid {
449                "VALID"
450            } else {
451                "INVALID"
452            },
453            if verification.structure_valid {
454                "PASS"
455            } else {
456                "FAIL"
457            },
458            if verification.integrity_hash_valid {
459                "PASS"
460            } else {
461                "FAIL"
462            },
463            if verification.hash_chain_valid {
464                "PASS"
465            } else {
466                "FAIL"
467            },
468            if verification.signatures_valid {
469                "PASS"
470            } else {
471                "FAIL"
472            },
473            verification.temporal_warnings.len()
474        ),
475    }
476}
477
478fn generic_crypto_methods_section() -> ReportSection {
479    ReportSection {
480        title: "Cryptographic Methods".to_string(),
481        content: "Digital Signatures: Ed25519 (RFC 8032)\n\
482                 Encryption: ChaCha20-Poly1305 (RFC 8439)\n\
483                 Hashing: BLAKE3\n\
484                 Key Derivation: HKDF-SHA256 (RFC 5869)"
485            .to_string(),
486    }
487}
488
489// ============================================================================
490// Output Formatters
491// ============================================================================
492
493fn format_as_text(report: &ComplianceReport) -> String {
494    let mut output = String::new();
495    text_push_header(&mut output, report);
496    text_push_verification_summary(&mut output, &report.verification);
497    text_push_version_history(&mut output, &report.version_history);
498    text_push_framework_sections(&mut output, &report.framework_sections);
499    output.push_str(&format!("{}\n", "=".repeat(70)));
500    output.push_str(&format!("{:^70}\n", "END OF REPORT"));
501    output.push_str(&format!("{}\n", "=".repeat(70)));
502    output
503}
504
505fn text_push_header(output: &mut String, report: &ComplianceReport) {
506    output.push_str(&format!("{}\n", "=".repeat(70)));
507    output.push_str(&format!("{:^70}\n", report.title));
508    output.push_str(&format!("{}\n\n", "=".repeat(70)));
509    output.push_str(&format!("Generated: {}\n", report.generated_at));
510    output.push_str(&format!("File: {}\n", report.file_path));
511    output.push_str(&format!("File ID: {}\n", report.file_id));
512    output.push_str(&format!("\n{}\n\n", "-".repeat(70)));
513}
514
515fn text_push_verification_summary(output: &mut String, verification: &VerificationSummary) {
516    output.push_str("VERIFICATION SUMMARY\n");
517    output.push_str(&format!(
518        "  Overall Status: {}\n",
519        if verification.is_valid {
520            "VALID"
521        } else {
522            "INVALID"
523        }
524    ));
525    output.push_str(&format!(
526        "  Structure: {}\n",
527        if verification.structure_valid {
528            "OK"
529        } else {
530            "FAILED"
531        }
532    ));
533    output.push_str(&format!(
534        "  Integrity: {}\n",
535        if verification.integrity_valid {
536            "OK"
537        } else {
538            "FAILED"
539        }
540    ));
541    output.push_str(&format!(
542        "  Hash Chain: {}\n",
543        if verification.hash_chain_valid {
544            "OK"
545        } else {
546            "FAILED"
547        }
548    ));
549    output.push_str(&format!(
550        "  Signatures: {}\n",
551        if verification.signatures_valid {
552            "OK"
553        } else {
554            "FAILED"
555        }
556    ));
557    output.push_str(&format!("\n{}\n\n", "-".repeat(70)));
558}
559
560fn text_push_version_history(output: &mut String, history: &[VersionSummary]) {
561    output.push_str("VERSION HISTORY\n\n");
562    for v in history {
563        output.push_str(&format!(
564            "  Version {}: {} (Author {})\n",
565            v.version, v.message, v.author_id
566        ));
567        output.push_str(&format!("    Timestamp: {}\n\n", v.timestamp));
568    }
569    output.push_str(&format!("{}\n\n", "-".repeat(70)));
570}
571
572fn text_push_framework_sections(output: &mut String, sections: &[ReportSection]) {
573    for section in sections {
574        output.push_str(&format!("{}\n\n", section.title.to_uppercase()));
575        output.push_str(&format!("{}\n\n", section.content));
576        output.push_str(&format!("{}\n\n", "-".repeat(70)));
577    }
578}
579
580fn format_as_markdown(report: &ComplianceReport) -> String {
581    let mut output = String::new();
582    md_push_header(&mut output, report);
583    md_push_verification_summary(&mut output, &report.verification);
584    md_push_version_history(&mut output, &report.version_history);
585    md_push_framework_sections(&mut output, &report.framework_sections);
586    output.push_str("---\n\n");
587    output.push_str("*Report generated by AION v2 Compliance Reporting*\n");
588    output
589}
590
591fn md_push_header(output: &mut String, report: &ComplianceReport) {
592    output.push_str(&format!("# {}\n\n", report.title));
593    output.push_str(&format!("**Generated**: {}  \n", report.generated_at));
594    output.push_str(&format!("**File**: `{}`  \n", report.file_path));
595    output.push_str(&format!("**File ID**: `{}`\n\n", report.file_id));
596    output.push_str("---\n\n");
597}
598
599fn md_push_verification_summary(output: &mut String, verification: &VerificationSummary) {
600    output.push_str("## Verification Summary\n\n");
601    output.push_str("| Check | Status |\n");
602    output.push_str("|-------|--------|\n");
603    output.push_str(&format!(
604        "| Overall | {} |\n",
605        if verification.is_valid {
606            "✅ VALID"
607        } else {
608            "❌ INVALID"
609        }
610    ));
611    output.push_str(&format!(
612        "| Structure | {} |\n",
613        if verification.structure_valid {
614            "✅"
615        } else {
616            "❌"
617        }
618    ));
619    output.push_str(&format!(
620        "| Integrity | {} |\n",
621        if verification.integrity_valid {
622            "✅"
623        } else {
624            "❌"
625        }
626    ));
627    output.push_str(&format!(
628        "| Hash Chain | {} |\n",
629        if verification.hash_chain_valid {
630            "✅"
631        } else {
632            "❌"
633        }
634    ));
635    output.push_str(&format!(
636        "| Signatures | {} |\n",
637        if verification.signatures_valid {
638            "✅"
639        } else {
640            "❌"
641        }
642    ));
643    output.push_str("\n---\n\n");
644}
645
646fn md_push_version_history(output: &mut String, history: &[VersionSummary]) {
647    output.push_str("## Version History\n\n");
648    output.push_str("| Version | Author | Timestamp | Message |\n");
649    output.push_str("|---------|--------|-----------|--------|\n");
650    for v in history {
651        output.push_str(&format!(
652            "| {} | {} | {} | {} |\n",
653            v.version, v.author_id, v.timestamp, v.message
654        ));
655    }
656    output.push_str("\n---\n\n");
657}
658
659fn md_push_framework_sections(output: &mut String, sections: &[ReportSection]) {
660    for section in sections {
661        output.push_str(&format!("## {}\n\n", section.title));
662        output.push_str(&format!("{}\n\n", section.content));
663    }
664}
665
666fn format_as_json(report: &ComplianceReport) -> Result<String> {
667    serde_json::to_string_pretty(report).map_err(|e| AionError::InvalidFormat {
668        reason: format!("JSON serialization failed: {e}"),
669    })
670}
671
672// ============================================================================
673// Utility Functions
674// ============================================================================
675
676/// Get current timestamp in ISO 8601 format
677fn chrono_timestamp() -> String {
678    use std::time::{SystemTime, UNIX_EPOCH};
679
680    let duration = SystemTime::now()
681        .duration_since(UNIX_EPOCH)
682        .unwrap_or_default();
683
684    let secs = duration.as_secs();
685    // Simple ISO 8601 format without external dependency
686    format_unix_timestamp(secs)
687}
688
689/// Format Unix timestamp to ISO 8601
690fn format_unix_timestamp(secs: u64) -> String {
691    // Calculate date components from Unix timestamp
692    let days = secs / 86400;
693    let time_of_day = secs % 86400;
694
695    let hours = time_of_day / 3600;
696    let minutes = (time_of_day % 3600) / 60;
697    let seconds = time_of_day % 60;
698
699    // Days since 1970-01-01
700    let (year, month, day) = days_to_ymd(days);
701
702    format!("{year:04}-{month:02}-{day:02}T{hours:02}:{minutes:02}:{seconds:02}Z")
703}
704
705/// Convert nanoseconds timestamp to ISO 8601
706fn format_timestamp_nanos(nanos: u64) -> String {
707    format_unix_timestamp(nanos / 1_000_000_000)
708}
709
710/// Convert days since epoch to year/month/day
711fn days_to_ymd(days: u64) -> (u64, u64, u64) {
712    // Simplified calculation - good enough for reports
713    let mut remaining_days = days as i64;
714    let mut year = 1970u64;
715
716    loop {
717        let days_in_year = if is_leap_year(year) { 366 } else { 365 };
718        if remaining_days < days_in_year {
719            break;
720        }
721        remaining_days = remaining_days.saturating_sub(days_in_year);
722        year = year.saturating_add(1);
723    }
724
725    let days_in_months: [i64; 12] = if is_leap_year(year) {
726        [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
727    } else {
728        [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
729    };
730
731    let mut month = 1u64;
732    for days_in_month in days_in_months {
733        if remaining_days < days_in_month {
734            break;
735        }
736        remaining_days = remaining_days.saturating_sub(days_in_month);
737        month = month.saturating_add(1);
738    }
739
740    let day = (remaining_days as u64).saturating_add(1);
741
742    (year, month, day)
743}
744
745const fn is_leap_year(year: u64) -> bool {
746    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
747}
748
749#[cfg(test)]
750mod tests {
751    use super::*;
752
753    #[test]
754    fn test_format_unix_timestamp() {
755        // 2024-01-01 00:00:00 UTC
756        let ts = 1704067200u64;
757        let formatted = format_unix_timestamp(ts);
758        assert!(formatted.starts_with("2024-01-01"));
759    }
760
761    #[test]
762    fn test_days_to_ymd_epoch() {
763        let (y, m, d) = days_to_ymd(0);
764        assert_eq!((y, m, d), (1970, 1, 1));
765    }
766
767    #[test]
768    fn test_is_leap_year() {
769        assert!(is_leap_year(2000)); // Divisible by 400
770        assert!(is_leap_year(2024)); // Divisible by 4, not 100
771        assert!(!is_leap_year(2023)); // Not divisible by 4
772        assert!(!is_leap_year(1900)); // Divisible by 100, not 400
773    }
774
775    #[test]
776    fn test_compliance_framework_display() {
777        assert_eq!(ComplianceFramework::Sox.to_string(), "SOX");
778        assert_eq!(ComplianceFramework::Hipaa.to_string(), "HIPAA");
779        assert_eq!(ComplianceFramework::Gdpr.to_string(), "GDPR");
780    }
781}