Skip to main content

ALCOA+ Format Preservation Controls Specification

Document ID: CODITECT-BIO-ALCOA-001 Version: 1.0.0 Effective Date: 2026-02-16 Classification: Internal - Restricted Owner: Chief Information Security Officer (CISO)


Document Control

Approval History

RoleNameSignatureDate
Chief Information Security Officer[Pending][Digital Signature]YYYY-MM-DD
VP Quality Assurance[Pending][Digital Signature]YYYY-MM-DD
VP Engineering[Pending][Digital Signature]YYYY-MM-DD
Regulatory Affairs Director[Pending][Digital Signature]YYYY-MM-DD

Revision History

VersionDateAuthorChangesApproval Status
1.0.02026-02-16CISO OfficeInitial releaseDraft

Distribution List

  • Executive Leadership Team
  • Information Security Team
  • Quality Assurance Team
  • Engineering Leadership
  • Compliance and Regulatory Affairs
  • Internal Audit
  • External Auditors (as needed)

Review Schedule

Review TypeFrequencyNext Review DateResponsible Party
Annual Review12 months2027-02-16CISO
Regulatory Update ReviewAs neededN/ARegulatory Affairs
Post-Incident ReviewAs neededN/ASecurity Incident Response Team
Data Integrity AuditQuarterly2026-05-16Compliance Officer

Executive Summary

ALCOA+ represents the gold standard for data integrity in GxP-regulated environments. This specification establishes comprehensive technical and procedural controls ensuring all electronic records in the BIO-QMS platform meet ALCOA+ principles:

  • ALCOA (Core Principles): Attributable, Legible, Contemporaneous, Original, Accurate
  • Plus (Extended Principles): Complete, Consistent, Enduring, Available

This document provides:

  1. Detailed control requirements for each ALCOA+ principle
  2. TypeScript/SQL implementations
  3. Database schema extensions
  4. Validation and monitoring strategies
  5. Regulatory mapping to FDA, WHO, EMA, MHRA guidance

Compliance Scope:

  • FDA 21 CFR Part 11 (Electronic Records and Signatures)
  • FDA Data Integrity and Compliance with Drug CGMP (2018)
  • WHO TRS 996 Annex 5: Guidance on Good Data and Record Management Practices
  • EMA Guideline on Data Integrity
  • MHRA GXP Data Integrity Guidance

Table of Contents

  1. Purpose and Scope
  2. ALCOA+ Core Principles
  3. Principle 1: Attributable
  4. Principle 2: Legible
  5. Principle 3: Contemporaneous
  6. Principle 4: Original
  7. Principle 5: Accurate
  8. Principle 6: Complete
  9. Principle 7: Consistent
  10. Principle 8: Enduring
  11. Principle 9: Available
  12. Implementation Architecture
  13. Validation and Testing
  14. Monitoring and Metrics
  15. Regulatory Mapping

1. Purpose and Scope

1.1 Purpose

This specification establishes comprehensive ALCOA+ format preservation controls for the BIO-QMS platform to ensure:

  1. Data Integrity - Electronic records remain trustworthy, reliable, and tamper-evident throughout their lifecycle
  2. Regulatory Compliance - All records meet FDA, EMA, MHRA, and WHO data integrity requirements
  3. Audit Readiness - Complete audit trail with attribution, timestamping, and versioning
  4. Long-Term Preservation - Records remain readable and accessible for retention periods (typically 5-30 years)

1.2 Scope

In Scope:

  • All electronic records in the QMS platform (protocols, SOPs, reports, data, signatures)
  • Multi-tenant SaaS architecture with tenant-isolated data
  • FDA-regulated pharmaceutical and medical device manufacturers
  • Cloud-native deployment (Google Cloud Platform)
  • Integration with external systems (LIMS, ERP, eTMF)

Out of Scope:

  • Paper-based records (covered by separate SOPs)
  • Non-GxP business systems (CRM, HR, Finance)
  • Development and staging environments (non-validated)

1.3 Definitions

TermDefinition
ALCOACore data integrity principles: Attributable, Legible, Contemporaneous, Original, Accurate
ALCOA+Extended principles adding: Complete, Consistent, Enduring, Available
GxPGood Practice (GMP, GLP, GCP, GDP, GVP) - regulated quality standards
Audit TrailSecure, computer-generated, timestamped electronic record documenting all record creation and modifications
True CopyExact replica of original record preserving content, context, and metadata
Certified CopyTrue copy with compliance officer attestation of accuracy and completeness

2. ALCOA+ Core Principles

2.1 Regulatory Foundation

FDA Data Integrity Guidance (December 2018):

"Data should be attributable, legible, contemporaneous, original, and accurate (ALCOA). FDA expects that data also be complete, consistent, enduring, and available (ALCOA+) throughout the data lifecycle."

WHO TRS 996 Annex 5 (2016):

"ALCOA principles establish the foundation for data integrity in pharmaceutical quality systems."

EMA GMP Data Integrity (2016):

"Data integrity requires all nine ALCOA+ principles to be met throughout the entire data lifecycle."

2.2 ALCOA+ Principles Matrix

PrincipleFDA RequirementTechnical ControlValidation Method
Attributable§11.10(e)User/session/device trackingAudit log review
Legible§11.10(b)Format preservation + renderingVisual verification
Contemporaneous§11.10(e)Server-side NTP timestampsClock sync monitoring
Original§11.10(c)Immutability + versioningHash verification
Accurate§11.10(h)Validation rules + error checkingAccuracy metrics
CompleteData Integrity GuidanceMandatory fields + audit completenessCompleteness checks
ConsistentData Integrity GuidanceStandardization + cross-checksConsistency validation
Enduring§11.10(c)PDF/A archival + migrationReadability testing
Available§11.10(b)Multi-region storage + backupRetrieval SLA

3. Principle 1: Attributable

3.1 Definition

Regulatory Requirement:

"It should be possible to identify the individual responsible for performing an activity and the time at which it was performed." - FDA Data Integrity Guidance

Every record must clearly identify:

  • WHO performed the action (user identity)
  • WHAT was done (action type)
  • WHEN it occurred (timestamp)
  • WHERE it happened (device/IP/location)
  • WHY it was done (business context/meaning)

3.2 Attribution Requirements

3.2.1 User Attribution

AttributeSourceStorageImmutability
User IDAuthentication serviceaudit_trail.user_idImmutable
Full NameUser profileaudit_trail.user_nameSnapshot at event time
EmailUser profileaudit_trail.user_emailSnapshot at event time
RoleRBAC systemaudit_trail.user_roleSnapshot at event time
DepartmentOrg structureaudit_trail.departmentSnapshot at event time

Rationale for Snapshots: User profiles change over time (name changes, role changes). Audit trail must capture user attributes at the time of action, not current values.

3.2.2 Session Attribution

AttributeSourceStoragePurpose
Session IDAuth serviceaudit_trail.session_idTrack user session
IP AddressHTTP headersaudit_trail.ip_addressNetwork forensics
Device TypeUser-Agentaudit_trail.device_typeDevice tracking
Browser/VersionUser-Agentaudit_trail.user_agentClient environment
Geographic LocationIP geolocationaudit_trail.geo_locationLocation tracking

3.2.3 System vs User Actions

Requirement: Distinguish system-generated actions from user-initiated actions.

// audit-attribution.types.ts

export enum ActionInitiator {
USER = 'user', // Direct user action
SYSTEM = 'system', // Scheduled job, automation
API = 'api', // External system via API
SERVICE_ACCOUNT = 'service' // Service account (attribute to initiating user)
}

export interface AttributionContext {
initiator: ActionInitiator;
userId?: string; // Always present for USER/SERVICE
systemProcess?: string; // Present for SYSTEM (e.g., 'nightly-backup')
apiClientId?: string; // Present for API (OAuth client ID)
delegatedBy?: string; // For SERVICE: actual user who initiated
businessReason?: string; // Optional: why action was performed
}

Service Account Attribution Rule:

Service accounts performing actions on behalf of users MUST record the initiating user. Example: Automated report generation triggered by user schedule setup.

// Example: Service account action attributed to user
const attribution: AttributionContext = {
initiator: ActionInitiator.SERVICE_ACCOUNT,
userId: 'service-account-reporting',
delegatedBy: 'user-jane-smith-12345',
businessReason: 'Scheduled monthly compliance report'
};

3.3 Database Schema

-- audit_trail table (existing, extend with attribution fields)
CREATE TABLE audit_trail (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),

-- Core audit fields
entity_type VARCHAR(100) NOT NULL,
entity_id UUID NOT NULL,
action VARCHAR(50) NOT NULL,
performed_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,

-- User attribution (snapshot at event time)
user_id UUID NOT NULL REFERENCES users(id),
user_name VARCHAR(255) NOT NULL,
user_email VARCHAR(255) NOT NULL,
user_role VARCHAR(100) NOT NULL,
department VARCHAR(255),

-- Session attribution
session_id UUID NOT NULL,
ip_address INET NOT NULL,
device_type VARCHAR(50),
user_agent TEXT,
geo_location JSONB, -- {country, region, city, lat, lon}

-- Action initiator
initiator VARCHAR(20) NOT NULL, -- 'user', 'system', 'api', 'service'
system_process VARCHAR(100), -- e.g., 'nightly-backup'
api_client_id VARCHAR(100),
delegated_by UUID REFERENCES users(id), -- for service accounts
business_reason TEXT,

-- Signature attribution (for signed records)
signature_meaning TEXT, -- 'author', 'reviewer', 'approver', 'witness'
signature_id UUID REFERENCES electronic_signatures(id),

-- Change tracking
previous_value JSONB,
new_value JSONB,

-- Immutability
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,

-- Indexes for performance
INDEX idx_audit_tenant_entity (tenant_id, entity_type, entity_id),
INDEX idx_audit_user (tenant_id, user_id, performed_at),
INDEX idx_audit_performed_at (tenant_id, performed_at)
) PARTITION BY RANGE (performed_at);

-- Prevent modifications/deletions
CREATE TRIGGER prevent_audit_trail_modification
BEFORE UPDATE OR DELETE ON audit_trail
FOR EACH ROW EXECUTE FUNCTION prevent_modification();

3.4 Implementation

// src/services/audit/attribution-service.ts

import { Request } from 'express';
import { UAParser } from 'ua-parser-js';
import geoip from 'geoip-lite';

export interface AuditAttributionData {
// User attribution
userId: string;
userName: string;
userEmail: string;
userRole: string;
department?: string;

// Session attribution
sessionId: string;
ipAddress: string;
deviceType: string;
userAgent: string;
geoLocation?: {
country: string;
region: string;
city: string;
lat: number;
lon: number;
};

// Action initiator
initiator: ActionInitiator;
systemProcess?: string;
apiClientId?: string;
delegatedBy?: string;
businessReason?: string;
}

export class AttributionService {

/**
* Extract attribution context from HTTP request
*/
static extractFromRequest(req: Request): AuditAttributionData {
const user = req.user; // From authentication middleware
const session = req.session;

// Parse User-Agent
const ua = new UAParser(req.headers['user-agent']);
const deviceType = ua.getDevice().type || 'desktop';

// IP address (handle proxies)
const ipAddress = req.headers['x-forwarded-for'] as string ||
req.headers['x-real-ip'] as string ||
req.socket.remoteAddress || '';
const clientIp = ipAddress.split(',')[0].trim();

// Geolocation
const geo = geoip.lookup(clientIp);
const geoLocation = geo ? {
country: geo.country,
region: geo.region,
city: geo.city,
lat: geo.ll[0],
lon: geo.ll[1]
} : undefined;

return {
userId: user.id,
userName: user.fullName,
userEmail: user.email,
userRole: user.role,
department: user.department,
sessionId: session.id,
ipAddress: clientIp,
deviceType,
userAgent: req.headers['user-agent'] || '',
geoLocation,
initiator: ActionInitiator.USER
};
}

/**
* Create attribution for system process
*/
static forSystemProcess(
processName: string,
systemUserId: string
): AuditAttributionData {
return {
userId: systemUserId,
userName: 'System Process',
userEmail: 'system@bio-qms.internal',
userRole: 'system',
sessionId: `system-${Date.now()}`,
ipAddress: '127.0.0.1',
deviceType: 'server',
userAgent: 'BIO-QMS/1.0 (System Process)',
initiator: ActionInitiator.SYSTEM,
systemProcess: processName
};
}

/**
* Create attribution for service account (delegated)
*/
static forServiceAccount(
serviceAccountId: string,
delegatedByUserId: string,
delegatedByName: string,
businessReason: string
): AuditAttributionData {
return {
userId: serviceAccountId,
userName: 'Service Account',
userEmail: 'service@bio-qms.internal',
userRole: 'service',
sessionId: `service-${Date.now()}`,
ipAddress: '127.0.0.1',
deviceType: 'server',
userAgent: 'BIO-QMS/1.0 (Service Account)',
initiator: ActionInitiator.SERVICE_ACCOUNT,
delegatedBy: delegatedByUserId,
businessReason
};
}

/**
* Validate attribution completeness
*/
static validate(attribution: AuditAttributionData): ValidationResult {
const errors: string[] = [];

if (!attribution.userId) errors.push('userId is required');
if (!attribution.userName) errors.push('userName is required');
if (!attribution.sessionId) errors.push('sessionId is required');
if (!attribution.ipAddress) errors.push('ipAddress is required');

// Service accounts must have delegatedBy
if (attribution.initiator === ActionInitiator.SERVICE_ACCOUNT) {
if (!attribution.delegatedBy) {
errors.push('delegatedBy is required for service accounts');
}
}

return {
valid: errors.length === 0,
errors
};
}
}

3.5 Signature Attribution

Electronic Signature Meaning (§11.50(a)(3) requirement):

// src/models/electronic-signature.model.ts

export enum SignatureMeaning {
AUTHOR = 'author', // Document author
REVIEWER = 'reviewer', // Technical review
APPROVER = 'approver', // Final approval
WITNESS = 'witness', // Witnessed action
QA_REVIEW = 'qa_review', // Quality assurance review
SUPERVISOR_APPROVAL = 'supervisor_approval',
MEDICAL_REVIEW = 'medical_review',
REGULATORY_APPROVAL = 'regulatory_approval'
}

export interface ElectronicSignature {
id: string;
signerId: string;
signedAt: Date;
meaning: SignatureMeaning;
meaningText: string; // Free-text explanation
recordId: string;
recordType: string;
recordVersion: number; // Version signed
ipAddress: string;
deviceType: string;

// Cryptographic binding (§11.70)
signatureHash: string; // SHA-256(recordVersion + signerId + signedAt + meaning)
}

3.6 Validation and Testing

Test Case AT-001: User Attribution

  • Given: User "Jane Smith" performs document approval
  • When: Audit trail is queried for the approval event
  • Then: Record shows user_name="Jane Smith", user_email="jane.smith@company.com", action="approve"

Test Case AT-002: Service Account Attribution

  • Given: Nightly backup process archives documents
  • When: Audit trail is reviewed
  • Then: Records show initiator="service", delegated_by="system-scheduler", system_process="nightly-backup"

Test Case AT-003: Signature Meaning

  • Given: QA reviewer signs off on validation protocol
  • When: Signature record is retrieved
  • Then: meaning="qa_review", meaningText="QA review and approval of IQ/OQ protocol"

4. Principle 2: Legible

4.1 Definition

Regulatory Requirement:

"Data should be recorded permanently in a durable medium and be readable throughout the retention period." - FDA Data Integrity Guidance

Records must be:

  1. Human-readable without specialized software
  2. Rendered consistently across platforms and time
  3. Preserved in their original format or format-migrated with validation
  4. Accessible to regulators without proprietary tools

4.2 Format Preservation Requirements

4.2.1 Supported Source Formats

FormatUse CasePreservation StrategyLong-Term Format
PDFFinal reports, signed documentsPDF/A-2b conversionPDF/A-2b
DOCXProtocols, SOPs (draft)Pandoc → PDF/APDF/A-2b
XLSXData tables, validation dataLibreOffice → PDF/APDF/A-2b
HTMLWeb forms, dashboardsPuppeteer → PDF/APDF/A-2b
PNG/JPEGImages, chartsEmbed in PDF/APDF/A-2b
CSVRaw data exportPreserve + PDF reportCSV + PDF/A
XMLStructured dataPreserve + XSLT → PDFXML + PDF/A

PDF/A-2b Standard (ISO 19005-2):

  • Self-contained (embedded fonts, images)
  • No external dependencies
  • Device-independent rendering
  • Long-term archival quality

4.2.2 Rendering Guarantees

Resolution Standards:

  • Scanned documents: Minimum 300 DPI
  • Generated PDFs: Vector graphics preferred, minimum 150 DPI for raster
  • Screenshots: Minimum 1920x1080 native resolution

Font Embedding:

// src/services/pdf/pdf-generation.service.ts

import PDFDocument from 'pdfkit';

export class PDFGenerationService {

/**
* Generate PDF/A-2b with embedded fonts
*/
static async generatePDFA(
content: DocumentContent,
metadata: PDFMetadata
): Promise<Buffer> {
const doc = new PDFDocument({
pdfVersion: '1.7',
subset: 'PDF/A-2b',
tagged: true, // For accessibility
displayTitle: true
});

// Embed fonts (required for PDF/A)
doc.registerFont('DejaVu-Sans', './fonts/DejaVuSans.ttf');
doc.registerFont('DejaVu-Serif', './fonts/DejaVuSerif.ttf');
doc.registerFont('DejaVu-Mono', './fonts/DejaVuMono.ttf');

// Set metadata
doc.info.Title = metadata.title;
doc.info.Author = metadata.author;
doc.info.Subject = metadata.subject;
doc.info.Keywords = metadata.keywords;
doc.info.CreationDate = metadata.createdAt;

// Add content
this.renderContent(doc, content);

// Validate PDF/A compliance
const buffer = await this.finalizeDocument(doc);
await this.validatePDFA(buffer);

return buffer;
}

/**
* Validate PDF/A-2b compliance
*/
private static async validatePDFA(pdfBuffer: Buffer): Promise<void> {
// Use verapdf for validation
const { exec } = require('child_process');
const tmpFile = `/tmp/validate-${Date.now()}.pdf`;

fs.writeFileSync(tmpFile, pdfBuffer);

return new Promise((resolve, reject) => {
exec(`verapdf --flavour 2b ${tmpFile}`, (error, stdout) => {
fs.unlinkSync(tmpFile);

if (error || !stdout.includes('compliant="true"')) {
reject(new Error('PDF/A validation failed'));
} else {
resolve();
}
});
});
}
}

4.2.3 Color Preservation

Requirement: Charts, graphs, and color-coded data must preserve color information.

// Color space embedding
doc.addColorSpace('sRGB', {
type: 'CalRGB',
whitePoint: [0.9505, 1.0000, 1.0890], // D65 illuminant
gamma: [2.2, 2.2, 2.2]
});

// Use sRGB for all colors
doc.fillColor('#FF5733', 1.0, 'sRGB');

4.2.4 Accessibility (WCAG 2.1 AA)

Screen Reader Support:

// Tagged PDF structure
doc.addStructure(PDFStructureElement.DOCUMENT, () => {
doc.addStructure(PDFStructureElement.H1, () => {
doc.text('Validation Protocol VP-001', { tag: 'H1' });
});

doc.addStructure(PDFStructureElement.P, () => {
doc.text('Purpose: Validate data integrity controls', { tag: 'P' });
});

doc.addStructure(PDFStructureElement.TABLE, () => {
// Table with proper headers
});
});

// Alt text for images
doc.image('chart.png', {
alt: 'Bar chart showing accuracy rates 2024-2026'
});

4.3 Rendering Verification

Automated Testing:

// test/integration/rendering-verification.test.ts

describe('Rendering Verification', () => {

test('RT-001: PDF renders consistently across viewers', async () => {
const pdf = await generateTestPDF();

// Render with multiple engines
const render1 = await renderWithPDFJS(pdf);
const render2 = await renderWithMuPDF(pdf);
const render3 = await renderWithPoppler(pdf);

// Compare pixel-by-pixel
const diff1 = compareImages(render1, render2);
const diff2 = compareImages(render2, render3);

expect(diff1.percentDifference).toBeLessThan(0.01); // <1% difference
expect(diff2.percentDifference).toBeLessThan(0.01);
});

test('RT-002: Archived documents remain readable', async () => {
// Retrieve 5-year-old archived document
const archivedPdf = await archiveService.retrieve('DOC-2021-001');

// Verify PDF/A compliance still valid
const validation = await validatePDFA(archivedPdf);
expect(validation.compliant).toBe(true);

// Verify fonts still embedded
const fonts = await extractFonts(archivedPdf);
expect(fonts.every(f => f.embedded)).toBe(true);

// Verify rendering
const rendered = await renderWithPDFJS(archivedPdf);
expect(rendered.pages).toBeGreaterThan(0);
});

test('RT-003: Screen reader accessibility', async () => {
const pdf = await generateTestPDF();

// Extract structure
const structure = await extractPDFStructure(pdf);

// Verify tags present
expect(structure.hasHeadings).toBe(true);
expect(structure.hasParagraphs).toBe(true);
expect(structure.tablesHaveHeaders).toBe(true);

// Verify alt text on images
const images = structure.images;
expect(images.every(img => img.altText)).toBe(true);
});
});

4.4 Database Schema

-- document_formats table
CREATE TABLE document_formats (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
document_id UUID NOT NULL REFERENCES documents(id),

-- Original format
original_format VARCHAR(20) NOT NULL, -- 'pdf', 'docx', 'xlsx', 'html'
original_filename VARCHAR(500) NOT NULL,
original_size_bytes BIGINT NOT NULL,
original_hash_sha256 CHAR(64) NOT NULL,
original_storage_path TEXT NOT NULL,

-- Preservation format (PDF/A)
preservation_format VARCHAR(20) NOT NULL DEFAULT 'pdf/a-2b',
preservation_filename VARCHAR(500) NOT NULL,
preservation_size_bytes BIGINT NOT NULL,
preservation_hash_sha256 CHAR(64) NOT NULL,
preservation_storage_path TEXT NOT NULL,

-- Conversion metadata
converted_at TIMESTAMPTZ NOT NULL,
converted_by UUID NOT NULL REFERENCES users(id),
conversion_tool VARCHAR(100) NOT NULL, -- 'pandoc', 'libreoffice', 'puppeteer'
conversion_tool_version VARCHAR(50) NOT NULL,

-- Quality validation
pdfa_compliance_verified BOOLEAN NOT NULL DEFAULT false,
pdfa_validation_date TIMESTAMPTZ,
pdfa_validator_version VARCHAR(50),
font_embedding_verified BOOLEAN NOT NULL DEFAULT false,
resolution_dpi INTEGER,
accessibility_score DECIMAL(3,2), -- WCAG score 0.00-1.00

-- Rendering verification
last_render_test_date TIMESTAMPTZ,
render_test_result JSONB, -- {viewer: 'pdfjs', version: '3.1', success: true}

created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,

INDEX idx_document_formats_document (tenant_id, document_id),
INDEX idx_document_formats_verification (tenant_id, pdfa_validation_date)
);

4.5 Monitoring Dashboard

Key Metrics:

MetricTargetAlert ThresholdQuery
PDF/A Compliance Rate100%<99.5%% of documents with pdfa_compliance_verified=true
Font Embedding Rate100%<99.9%% of PDFs with all fonts embedded
Render Test Failure Rate0%>0.1%% of documents failing render verification
Accessibility Score>0.95<0.90Average WCAG compliance score
// Dashboard query
SELECT
COUNT(*) FILTER (WHERE pdfa_compliance_verified) * 100.0 / COUNT(*) AS pdfa_compliance_rate,
COUNT(*) FILTER (WHERE font_embedding_verified) * 100.0 / COUNT(*) AS font_embedding_rate,
AVG(accessibility_score) AS avg_accessibility_score,
COUNT(*) FILTER (WHERE last_render_test_date < NOW() - INTERVAL '1 year') AS overdue_render_tests
FROM document_formats
WHERE tenant_id = $1
AND created_at > NOW() - INTERVAL '30 days';

5. Principle 3: Contemporaneous

5.1 Definition

Regulatory Requirement:

"Data should be recorded at the time the work is performed, and date/time stamps should follow a defined sequence." - FDA Data Integrity Guidance

Requirements:

  1. Timestamps assigned by trusted server source (never client)
  2. Time synchronization via NTP (<100ms drift)
  3. Timezone normalization to UTC with original timezone preserved
  4. Microsecond precision for sequencing
  5. Immutable timestamps (no backdating)

5.2 Server-Side Timestamp Architecture

Never Trust Client Time:

// ❌ WRONG: Client-supplied timestamp
app.post('/api/records', (req, res) => {
const record = {
...req.body,
createdAt: req.body.timestamp // CLIENT TIME - FORBIDDEN
};
});

// ✅ CORRECT: Server-assigned timestamp
app.post('/api/records', (req, res) => {
const record = {
...req.body,
createdAt: new Date() // SERVER TIME ONLY
};
});

Database-Level Enforcement:

-- All timestamp columns must use DEFAULT CURRENT_TIMESTAMP
CREATE TABLE records (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,

-- Server-assigned timestamps (client cannot override)
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,

-- Prevent client from setting these fields
CHECK (created_at IS NOT NULL),
CHECK (updated_at IS NOT NULL)
);

-- Trigger to enforce server time on INSERT
CREATE OR REPLACE FUNCTION enforce_server_timestamp()
RETURNS TRIGGER AS $$
BEGIN
-- Override any client-supplied timestamps
NEW.created_at := CURRENT_TIMESTAMP;
NEW.updated_at := CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER enforce_timestamp_on_insert
BEFORE INSERT ON records
FOR EACH ROW
EXECUTE FUNCTION enforce_server_timestamp();

5.3 Time Synchronization

NTP Configuration:

# /etc/chrony/chrony.conf (Cloud Run containers)
server time.google.com iburst
server time1.google.com iburst
server time2.google.com iburst
server time3.google.com iburst

# Maximum allowed drift: 100ms
maxdistance 0.1

# Log time adjustments
logdir /var/log/chrony
log measurements statistics tracking

Time Sync Monitoring:

// src/services/monitoring/time-sync-monitor.ts

import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

export class TimeSyncMonitor {

/**
* Check NTP synchronization status
*/
static async checkSync(): Promise<TimeSyncStatus> {
try {
const { stdout } = await execAsync('chronyc tracking');

// Parse output
const offsetMatch = stdout.match(/System time\s+:\s+([\d.]+) seconds (fast|slow)/);
const offsetSeconds = offsetMatch ?
parseFloat(offsetMatch[1]) * (offsetMatch[2] === 'slow' ? -1 : 1) :
null;

const stratum = stdout.match(/Stratum\s+:\s+(\d+)/)?.[1];
const refTime = stdout.match(/Ref time.*:\s+(.+)/)?.[1];

return {
synchronized: offsetSeconds !== null && Math.abs(offsetSeconds) < 0.1,
offsetSeconds,
stratum: stratum ? parseInt(stratum) : null,
referenceTime: refTime,
lastCheck: new Date()
};
} catch (error) {
return {
synchronized: false,
error: error.message,
lastCheck: new Date()
};
}
}

/**
* Alert if drift exceeds threshold
*/
static async monitorDrift(): Promise<void> {
const status = await this.checkSync();

if (!status.synchronized) {
await alertingService.sendAlert({
severity: 'critical',
title: 'NTP Synchronization Lost',
message: `Server time drift detected: ${status.offsetSeconds}s`,
component: 'time-sync',
action: 'Investigate NTP configuration and network connectivity'
});
} else if (status.offsetSeconds && Math.abs(status.offsetSeconds) > 0.05) {
await alertingService.sendAlert({
severity: 'warning',
title: 'Time Drift Warning',
message: `Server time offset: ${status.offsetSeconds}s (threshold: 50ms)`,
component: 'time-sync'
});
}
}
}

// Run every 5 minutes
setInterval(() => TimeSyncMonitor.monitorDrift(), 5 * 60 * 1000);

5.4 Timezone Handling

UTC Normalization:

// src/utils/timestamp.util.ts

export class TimestampUtil {

/**
* Create timestamp with UTC normalization and original timezone preservation
*/
static createTimestamp(
userTimezone: string = 'UTC'
): TimestampRecord {
const now = new Date();

return {
utc: now.toISOString(), // Always store as UTC ISO 8601
epochMs: now.getTime(), // Unix milliseconds
timezone: userTimezone,
localDisplay: this.formatInTimezone(now, userTimezone)
};
}

/**
* Format timestamp in user's timezone (for display only)
*/
static formatInTimezone(date: Date, timezone: string): string {
return new Intl.DateTimeFormat('en-US', {
timeZone: timezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
fractionalSecondDigits: 3,
hour12: false
}).format(date);
}

/**
* ISO 8601 with microsecond precision
*/
static toISO8601Precise(date: Date): string {
const ms = date.getTime();
const us = (ms % 1) * 1000; // Microseconds (if available)
return `${date.toISOString().slice(0, -1)}${us.toFixed(0).padStart(3, '0')}Z`;
}
}

Database Storage:

-- All timestamps stored as TIMESTAMPTZ (timezone-aware)
CREATE TABLE events (
id UUID PRIMARY KEY,

-- UTC timestamp (PostgreSQL internally stores as UTC)
occurred_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,

-- User's timezone (for display purposes)
user_timezone VARCHAR(50) NOT NULL DEFAULT 'UTC',

-- High-precision epoch microseconds (for sequencing)
occurred_at_us BIGINT NOT NULL DEFAULT (EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000000)::BIGINT
);

-- Function to extract microseconds
CREATE OR REPLACE FUNCTION get_timestamp_us()
RETURNS BIGINT AS $$
SELECT (EXTRACT(EPOCH FROM CLOCK_TIMESTAMP()) * 1000000)::BIGINT;
$$ LANGUAGE SQL;

5.5 Timestamp Immutability

Prevent Backdating:

// src/middleware/timestamp-validation.middleware.ts

export function preventBackdating(req: Request, res: Response, next: NextFunction) {
const body = req.body;

// Check for client-supplied timestamps
const suspiciousFields = [
'createdAt', 'created_at', 'timestamp', 'date',
'updatedAt', 'updated_at', 'performedAt'
];

for (const field of suspiciousFields) {
if (field in body) {
return res.status(400).json({
error: 'Client-supplied timestamps are forbidden',
field,
message: 'Timestamps are assigned by the server only'
});
}
}

next();
}

// Apply globally
app.use(preventBackdating);

Audit Trail Sequence Validation:

-- Detect gaps or out-of-sequence timestamps
CREATE OR REPLACE FUNCTION validate_audit_sequence()
RETURNS TABLE(
entity_id UUID,
event_number BIGINT,
occurred_at TIMESTAMPTZ,
next_occurred_at TIMESTAMPTZ,
sequence_violation BOOLEAN
) AS $$
SELECT
entity_id,
ROW_NUMBER() OVER (PARTITION BY entity_id ORDER BY occurred_at) AS event_number,
occurred_at,
LEAD(occurred_at) OVER (PARTITION BY entity_id ORDER BY occurred_at) AS next_occurred_at,
occurred_at > LEAD(occurred_at) OVER (PARTITION BY entity_id ORDER BY occurred_at) AS sequence_violation
FROM audit_trail
WHERE tenant_id = current_setting('app.tenant_id')::UUID
HAVING sequence_violation = true;
$$ LANGUAGE SQL;

5.6 Validation and Testing

Test Case TS-001: Server Time Only

  • Given: Client attempts to POST record with createdAt field
  • When: Request is processed
  • Then: 400 error returned: "Client-supplied timestamps are forbidden"

Test Case TS-002: NTP Synchronization

  • Given: Server has been running for 24 hours
  • When: Time sync status is checked
  • Then: Clock drift < 100ms, stratum ≤ 4

Test Case TS-003: Timestamp Precision

  • Given: Two events occur in rapid succession
  • When: Timestamps are compared
  • Then: Timestamps differ by at least 1 microsecond, sequence is maintained

6. Principle 4: Original

6.1 Definition

Regulatory Requirement:

"Original records and true copies should be preserved. If data are transcribed, the original record must be preserved." - FDA Data Integrity Guidance

Requirements:

  1. Original record clearly identified and protected from modification
  2. Distinction between original, true copy, and certified copy
  3. Versioning system for tracking amendments
  4. Chain of custody for record transfers
  5. Hash verification for copies

6.2 Record Versioning System

Schema:

-- document_versions table
CREATE TABLE document_versions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
document_id UUID NOT NULL REFERENCES documents(id),

-- Version identification
version_number INTEGER NOT NULL,
is_original BOOLEAN NOT NULL DEFAULT false,
is_current BOOLEAN NOT NULL DEFAULT true,

-- Content
content_hash_sha256 CHAR(64) NOT NULL,
content_storage_path TEXT NOT NULL,
content_size_bytes BIGINT NOT NULL,
format VARCHAR(20) NOT NULL,

-- Metadata
title VARCHAR(500) NOT NULL,
description TEXT,
status VARCHAR(50) NOT NULL, -- 'draft', 'review', 'approved', 'archived'

-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID NOT NULL REFERENCES users(id),

-- Amendment tracking
amendment_reason TEXT,
supersedes_version INTEGER REFERENCES document_versions(version_number),

-- Immutability
CHECK (version_number > 0),
UNIQUE (tenant_id, document_id, version_number),
INDEX idx_document_versions_doc (tenant_id, document_id, version_number DESC),
INDEX idx_document_versions_current (tenant_id, document_id, is_current) WHERE is_current = true
);

-- Prevent modification of versions
CREATE TRIGGER prevent_version_modification
BEFORE UPDATE OR DELETE ON document_versions
FOR EACH ROW EXECUTE FUNCTION prevent_modification();

Versioning Logic:

// src/services/document/versioning.service.ts

export class DocumentVersioningService {

/**
* Create initial version (original)
*/
async createOriginal(
tenantId: string,
documentId: string,
content: Buffer,
metadata: DocumentMetadata,
userId: string
): Promise<DocumentVersion> {
const hash = crypto.createHash('sha256').update(content).digest('hex');

const version = await db.documentVersions.create({
data: {
tenantId,
documentId,
versionNumber: 1,
isOriginal: true,
isCurrent: true,
contentHashSha256: hash,
contentStoragePath: await storage.save(content),
contentSizeBytes: content.length,
format: metadata.format,
title: metadata.title,
status: 'draft',
createdBy: userId
}
});

await this.createAuditEntry({
tenantId,
entityType: 'document_version',
entityId: version.id,
action: 'create_original',
userId,
message: `Original document version 1 created`
});

return version;
}

/**
* Create amendment (new version)
*/
async createAmendment(
tenantId: string,
documentId: string,
content: Buffer,
amendmentReason: string,
userId: string
): Promise<DocumentVersion> {
// Get current version
const currentVersion = await this.getCurrentVersion(tenantId, documentId);

// Mark current as not current
await db.documentVersions.update({
where: { id: currentVersion.id },
data: { isCurrent: false }
});

// Create new version
const hash = crypto.createHash('sha256').update(content).digest('hex');
const newVersion = await db.documentVersions.create({
data: {
tenantId,
documentId,
versionNumber: currentVersion.versionNumber + 1,
isOriginal: false,
isCurrent: true,
contentHashSha256: hash,
contentStoragePath: await storage.save(content),
contentSizeBytes: content.length,
format: currentVersion.format,
title: currentVersion.title,
status: 'draft',
createdBy: userId,
amendmentReason,
supersedesVersion: currentVersion.versionNumber
}
});

await this.createAuditEntry({
tenantId,
entityType: 'document_version',
entityId: newVersion.id,
action: 'create_amendment',
userId,
message: `Amendment created: version ${newVersion.versionNumber}`,
metadata: { amendmentReason }
});

return newVersion;
}

/**
* Get original version
*/
async getOriginal(
tenantId: string,
documentId: string
): Promise<DocumentVersion> {
return db.documentVersions.findFirst({
where: {
tenantId,
documentId,
isOriginal: true
}
});
}
}

6.3 True Copy vs Certified Copy

Definitions:

TypeDefinitionVerificationUse Case
OriginalFirst instance of record created in systemis_original=trueMaster record
True CopyExact replica with hash matchSHA-256 matchInternal transfers
Certified CopyTrue copy with compliance officer attestationSignature + hash matchRegulatory submissions

Schema:

-- document_copies table
CREATE TABLE document_copies (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),

-- Original reference
original_document_id UUID NOT NULL REFERENCES documents(id),
original_version_id UUID NOT NULL REFERENCES document_versions(id),
original_hash_sha256 CHAR(64) NOT NULL,

-- Copy details
copy_type VARCHAR(20) NOT NULL, -- 'true_copy', 'certified_copy'
copy_hash_sha256 CHAR(64) NOT NULL,
copy_storage_path TEXT NOT NULL,

-- Verification
hash_verified BOOLEAN NOT NULL DEFAULT false,
hash_verification_date TIMESTAMPTZ,
verified_by UUID REFERENCES users(id),

-- Certification (for certified copies)
certified_by UUID REFERENCES users(id),
certification_date TIMESTAMPTZ,
certification_statement TEXT,
certification_signature_id UUID REFERENCES electronic_signatures(id),

-- Purpose and chain of custody
purpose TEXT NOT NULL, -- Why copy was created
destination TEXT, -- Where copy is going (e.g., 'FDA submission')

created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID NOT NULL REFERENCES users(id),

CHECK (copy_type IN ('true_copy', 'certified_copy')),
CHECK (original_hash_sha256 = copy_hash_sha256), -- Enforce true copy
INDEX idx_document_copies_original (tenant_id, original_document_id)
);

Implementation:

// src/services/document/copy.service.ts

export class DocumentCopyService {

/**
* Create true copy with hash verification
*/
async createTrueCopy(
tenantId: string,
originalDocumentId: string,
originalVersionId: string,
purpose: string,
userId: string
): Promise<DocumentCopy> {
// Retrieve original
const original = await storage.retrieve(originalVersionId);
const originalHash = crypto.createHash('sha256').update(original).digest('hex');

// Create copy
const copyPath = await storage.save(original, {
metadata: {
copyOf: originalVersionId,
purpose
}
});
const copyHash = crypto.createHash('sha256').update(original).digest('hex');

// Verify hashes match
if (originalHash !== copyHash) {
throw new Error('Hash mismatch: Copy integrity verification failed');
}

const copy = await db.documentCopies.create({
data: {
tenantId,
originalDocumentId,
originalVersionId,
originalHashSha256: originalHash,
copyType: 'true_copy',
copyHashSha256: copyHash,
copyStoragePath: copyPath,
hashVerified: true,
hashVerificationDate: new Date(),
verifiedBy: userId,
purpose,
createdBy: userId
}
});

await this.createAuditEntry({
tenantId,
action: 'create_true_copy',
userId,
message: `True copy created for: ${purpose}`
});

return copy;
}

/**
* Create certified copy (requires compliance officer)
*/
async createCertifiedCopy(
tenantId: string,
originalDocumentId: string,
originalVersionId: string,
purpose: string,
destination: string,
certifyingUserId: string
): Promise<DocumentCopy> {
// Verify certifying user has compliance officer role
const user = await userService.getUser(certifyingUserId);
if (!user.roles.includes('compliance_officer')) {
throw new Error('Only compliance officers can certify copies');
}

// Create true copy first
const trueCopy = await this.createTrueCopy(
tenantId,
originalDocumentId,
originalVersionId,
purpose,
certifyingUserId
);

// Add certification
const certificationStatement = `I, ${user.fullName}, Compliance Officer, hereby certify that this is a true and complete copy of the original document, accurate in all respects.`;

const signature = await signatureService.createSignature({
tenantId,
signerId: certifyingUserId,
recordId: trueCopy.id,
recordType: 'document_copy',
meaning: 'certified_copy_attestation',
meaningText: certificationStatement
});

const certifiedCopy = await db.documentCopies.update({
where: { id: trueCopy.id },
data: {
copyType: 'certified_copy',
certifiedBy: certifyingUserId,
certificationDate: new Date(),
certificationStatement,
certificationSignatureId: signature.id,
destination
}
});

await this.createAuditEntry({
tenantId,
action: 'certify_copy',
userId: certifyingUserId,
message: `Copy certified for ${destination}`
});

return certifiedCopy;
}

/**
* Verify copy integrity
*/
async verifyCopyIntegrity(copyId: string): Promise<CopyVerificationResult> {
const copy = await db.documentCopies.findUnique({ where: { id: copyId } });

// Retrieve both original and copy
const originalContent = await storage.retrieve(copy.originalVersionId);
const copyContent = await storage.retrieve(copy.copyStoragePath);

// Compute hashes
const originalHash = crypto.createHash('sha256').update(originalContent).digest('hex');
const copyHash = crypto.createHash('sha256').update(copyContent).digest('hex');

// Compare
const matches = originalHash === copyHash &&
originalHash === copy.originalHashSha256 &&
copyHash === copy.copyHashSha256;

return {
copyId,
verified: matches,
originalHash,
copyHash,
storedOriginalHash: copy.originalHashSha256,
storedCopyHash: copy.copyHashSha256,
verifiedAt: new Date()
};
}
}

6.4 Chain of Custody

Schema:

-- chain_of_custody table
CREATE TABLE chain_of_custody (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
document_id UUID NOT NULL REFERENCES documents(id),

-- Transfer details
event_type VARCHAR(50) NOT NULL, -- 'created', 'transferred', 'copied', 'archived'
from_location VARCHAR(255),
to_location VARCHAR(255),
from_custodian UUID REFERENCES users(id),
to_custodian UUID REFERENCES users(id),

-- Purpose
purpose TEXT NOT NULL,

-- Verification
document_hash_sha256 CHAR(64) NOT NULL,
verified_at_transfer BOOLEAN NOT NULL DEFAULT false,

-- Timestamp
occurred_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
recorded_by UUID NOT NULL REFERENCES users(id),

-- Audit
INDEX idx_chain_of_custody_doc (tenant_id, document_id, occurred_at)
);

6.5 Validation and Testing

Test Case OR-001: Original Identification

  • Given: Document with multiple versions exists
  • When: Original version is queried
  • Then: Version 1 returned with is_original=true

Test Case OR-002: True Copy Verification

  • Given: True copy created from original
  • When: Hash verification performed
  • Then: originalHash === copyHash, hash_verified=true

Test Case OR-003: Certified Copy Attestation

  • Given: Compliance officer certifies copy for FDA submission
  • When: Copy record is retrieved
  • Then: certification_statement present, signature_id links to valid signature

Test Case OR-004: Amendment Tracking

  • Given: Version 3 is created as amendment to version 2
  • When: Version history is queried
  • Then: version 3.supersedes_version = 2, amendment_reason recorded

7. Principle 5: Accurate

7.1 Definition

Regulatory Requirement:

"Data should be free from errors. The system should have appropriate controls to ensure accuracy of data entry and processing." - FDA Data Integrity Guidance

Requirements:

  1. Field-level validation rules (regex, range checks, cross-references)
  2. Transcription error detection (duplicate detection, pattern analysis)
  3. Calculation verification for computed fields
  4. Monthly accuracy rate metrics (target: >99.5%)
  5. Error correction workflow with audit trail

7.2 Data Validation Framework

Schema:

-- validation_rules table
CREATE TABLE validation_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),

-- Rule identification
rule_code VARCHAR(50) NOT NULL UNIQUE,
rule_name VARCHAR(255) NOT NULL,
rule_description TEXT,

-- Scope
entity_type VARCHAR(100) NOT NULL, -- 'protocol', 'sop', 'batch_record'
field_name VARCHAR(100) NOT NULL,

-- Validation logic
validation_type VARCHAR(50) NOT NULL, -- 'regex', 'range', 'enum', 'cross_reference'
validation_config JSONB NOT NULL, -- Type-specific config

-- Examples
valid_examples TEXT[],
invalid_examples TEXT[],

-- Error handling
severity VARCHAR(20) NOT NULL, -- 'error', 'warning', 'info'
error_message TEXT NOT NULL,

-- Regulatory justification
regulatory_reference TEXT, -- e.g., 'FDA 21 CFR §211.68'

-- Status
is_active BOOLEAN NOT NULL DEFAULT true,
effective_date DATE NOT NULL,

created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
created_by UUID NOT NULL REFERENCES users(id),

INDEX idx_validation_rules_entity (tenant_id, entity_type, field_name)
);

-- Sample validation rules
INSERT INTO validation_rules (
tenant_id, rule_code, rule_name, entity_type, field_name,
validation_type, validation_config, severity, error_message, regulatory_reference
) VALUES
-- Batch number format
('tenant-123', 'VAL-001', 'Batch Number Format', 'batch_record', 'batch_number',
'regex', '{"pattern": "^[A-Z]{2}\\d{6}-\\d{3}$"}', 'error',
'Batch number must follow format: XX######-###', 'FDA 21 CFR §211.188'),

-- Temperature range
('tenant-123', 'VAL-002', 'Storage Temperature Range', 'batch_record', 'storage_temp',
'range', '{"min": 2, "max": 8, "unit": "°C"}', 'error',
'Storage temperature must be between 2-8°C', 'Product specification'),

-- pH range
('tenant-123', 'VAL-003', 'pH Range', 'qc_test', 'ph_value',
'range', '{"min": 6.8, "max": 7.4, "precision": 2}', 'error',
'pH must be between 6.80-7.40', 'Product specification'),

-- Equipment ID cross-reference
('tenant-123', 'VAL-004', 'Equipment ID Validation', 'batch_record', 'equipment_id',
'cross_reference', '{"table": "equipment", "field": "equipment_id", "status": "qualified"}', 'error',
'Equipment must exist and be in qualified status', 'FDA 21 CFR §211.68');

Implementation:

// src/services/validation/validation-engine.ts

export class ValidationEngine {

/**
* Validate field value against all applicable rules
*/
async validateField(
tenantId: string,
entityType: string,
fieldName: string,
value: any,
context?: Record<string, any>
): Promise<ValidationResult[]> {
// Retrieve rules
const rules = await db.validationRules.findMany({
where: {
tenantId,
entityType,
fieldName,
isActive: true
}
});

const results: ValidationResult[] = [];

for (const rule of rules) {
const result = await this.executeRule(rule, value, context);
results.push(result);
}

return results;
}

/**
* Execute individual validation rule
*/
private async executeRule(
rule: ValidationRule,
value: any,
context?: Record<string, any>
): Promise<ValidationResult> {
try {
switch (rule.validationType) {
case 'regex':
return this.validateRegex(rule, value);

case 'range':
return this.validateRange(rule, value);

case 'enum':
return this.validateEnum(rule, value);

case 'cross_reference':
return await this.validateCrossReference(rule, value, context);

case 'custom':
return await this.validateCustom(rule, value, context);

default:
throw new Error(`Unknown validation type: ${rule.validationType}`);
}
} catch (error) {
return {
ruleCode: rule.ruleCode,
passed: false,
severity: 'error',
message: `Validation error: ${error.message}`
};
}
}

/**
* Regex validation
*/
private validateRegex(rule: ValidationRule, value: string): ValidationResult {
const config = rule.validationConfig as { pattern: string };
const regex = new RegExp(config.pattern);
const passed = regex.test(value);

return {
ruleCode: rule.ruleCode,
ruleName: rule.ruleName,
passed,
severity: rule.severity,
message: passed ? 'Valid' : rule.errorMessage,
value,
expected: config.pattern
};
}

/**
* Range validation
*/
private validateRange(rule: ValidationRule, value: number): ValidationResult {
const config = rule.validationConfig as {
min?: number;
max?: number;
precision?: number;
unit?: string;
};

let passed = true;
let message = 'Valid';

if (typeof value !== 'number') {
passed = false;
message = 'Value must be a number';
} else {
if (config.min !== undefined && value < config.min) {
passed = false;
message = `Value ${value} is below minimum ${config.min}`;
}
if (config.max !== undefined && value > config.max) {
passed = false;
message = `Value ${value} exceeds maximum ${config.max}`;
}
if (config.precision !== undefined) {
const decimalPlaces = (value.toString().split('.')[1] || '').length;
if (decimalPlaces > config.precision) {
passed = false;
message = `Value has too many decimal places (max: ${config.precision})`;
}
}
}

return {
ruleCode: rule.ruleCode,
ruleName: rule.ruleName,
passed,
severity: rule.severity,
message: passed ? 'Valid' : rule.errorMessage + ': ' + message,
value,
expected: `${config.min}-${config.max}${config.unit || ''}`
};
}

/**
* Cross-reference validation
*/
private async validateCrossReference(
rule: ValidationRule,
value: any,
context?: Record<string, any>
): Promise<ValidationResult> {
const config = rule.validationConfig as {
table: string;
field: string;
status?: string;
};

// Query referenced table
const exists = await db[config.table].findFirst({
where: {
[config.field]: value,
...(config.status && { status: config.status })
}
});

return {
ruleCode: rule.ruleCode,
ruleName: rule.ruleName,
passed: !!exists,
severity: rule.severity,
message: exists ? 'Valid reference' : rule.errorMessage,
value,
expected: `Exists in ${config.table}.${config.field}`
};
}
}

7.3 Transcription Error Detection

Duplicate Detection:

// src/services/validation/duplicate-detector.ts

export class DuplicateDetector {

/**
* Detect potential duplicate data entries
*/
async detectDuplicates(
tenantId: string,
entityType: string,
newRecord: Record<string, any>
): Promise<DuplicateDetectionResult[]> {
// Define key fields for each entity type
const keyFields = this.getKeyFields(entityType);

// Find records with matching key fields
const candidates = await db[entityType].findMany({
where: {
tenantId,
OR: keyFields.map(field => ({
[field]: newRecord[field]
}))
}
});

// Calculate similarity scores
const duplicates: DuplicateDetectionResult[] = [];

for (const candidate of candidates) {
const similarity = this.calculateSimilarity(newRecord, candidate);

if (similarity > 0.8) { // 80% similar
duplicates.push({
candidateId: candidate.id,
similarityScore: similarity,
matchingFields: this.getMatchingFields(newRecord, candidate),
recommendation: similarity > 0.95 ? 'likely_duplicate' : 'possible_duplicate'
});
}
}

return duplicates;
}

/**
* Calculate similarity between two records
*/
private calculateSimilarity(
record1: Record<string, any>,
record2: Record<string, any>
): number {
const fields = Object.keys(record1);
let matchingFields = 0;

for (const field of fields) {
if (this.fieldsMatch(record1[field], record2[field])) {
matchingFields++;
}
}

return matchingFields / fields.length;
}

/**
* Check if two field values match
*/
private fieldsMatch(value1: any, value2: any): boolean {
// Exact match
if (value1 === value2) return true;

// String similarity (Levenshtein distance)
if (typeof value1 === 'string' && typeof value2 === 'string') {
const distance = levenshtein(value1.toLowerCase(), value2.toLowerCase());
const maxLen = Math.max(value1.length, value2.length);
return (maxLen - distance) / maxLen > 0.9; // 90% similar
}

// Numeric close match (±5%)
if (typeof value1 === 'number' && typeof value2 === 'number') {
return Math.abs(value1 - value2) / value1 < 0.05;
}

return false;
}
}

7.4 Calculation Verification

Computed Fields:

// src/services/validation/calculation-verifier.ts

export class CalculationVerifier {

/**
* Verify calculated field accuracy
*/
async verifyCalculation(
entityType: string,
recordId: string,
fieldName: string
): Promise<CalculationVerificationResult> {
const record = await db[entityType].findUnique({ where: { id: recordId } });
const storedValue = record[fieldName];

// Get calculation formula
const formula = this.getFormula(entityType, fieldName);

// Recompute value
const computedValue = await formula(record);

// Compare
const matches = this.valuesMatch(storedValue, computedValue);

if (!matches) {
// Log discrepancy
await db.calculationDiscrepancies.create({
data: {
entityType,
recordId,
fieldName,
storedValue,
computedValue,
discrepancyPercent: Math.abs(storedValue - computedValue) / storedValue * 100,
detectedAt: new Date()
}
});
}

return {
fieldName,
storedValue,
computedValue,
matches,
discrepancy: matches ? 0 : Math.abs(storedValue - computedValue)
};
}

/**
* Define calculation formulas
*/
private getFormula(entityType: string, fieldName: string): Function {
const formulas: Record<string, Record<string, Function>> = {
batch_record: {
// Yield percentage = (actual_yield / theoretical_yield) * 100
yield_percentage: (record) =>
(record.actual_yield / record.theoretical_yield) * 100,

// Total cost = unit_cost * quantity
total_cost: (record) =>
record.unit_cost * record.quantity
},

qc_test: {
// Mean = sum / count
mean_value: (record) =>
record.values.reduce((a, b) => a + b, 0) / record.values.length,

// Standard deviation
std_deviation: (record) => {
const mean = record.mean_value;
const variance = record.values
.map(x => Math.pow(x - mean, 2))
.reduce((a, b) => a + b, 0) / record.values.length;
return Math.sqrt(variance);
}
}
};

return formulas[entityType]?.[fieldName] || (() => null);
}

/**
* Check if values match (with floating-point tolerance)
*/
private valuesMatch(stored: number, computed: number): boolean {
// Allow 0.01% tolerance for floating-point errors
const tolerance = 0.0001;
return Math.abs(stored - computed) / Math.max(stored, computed) < tolerance;
}
}

7.5 Accuracy Metrics

Monthly Accuracy Rate:

-- Accuracy metrics query
CREATE VIEW accuracy_metrics AS
SELECT
entity_type,
DATE_TRUNC('month', created_at) AS month,

-- Total records
COUNT(*) AS total_records,

-- Records with validation errors
COUNT(*) FILTER (WHERE has_validation_errors) AS records_with_errors,

-- Accuracy rate
(COUNT(*) FILTER (WHERE NOT has_validation_errors) * 100.0 / COUNT(*)) AS accuracy_rate_percent,

-- Error breakdown by severity
COUNT(*) FILTER (WHERE error_severity = 'error') AS critical_errors,
COUNT(*) FILTER (WHERE error_severity = 'warning') AS warnings,

-- Transcription errors
COUNT(*) FILTER (WHERE has_transcription_error) AS transcription_errors,

-- Calculation errors
COUNT(*) FILTER (WHERE has_calculation_error) AS calculation_errors

FROM records
WHERE tenant_id = current_setting('app.tenant_id')::UUID
GROUP BY entity_type, DATE_TRUNC('month', created_at);

-- Alert on accuracy rate below target
CREATE OR REPLACE FUNCTION check_accuracy_threshold()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.accuracy_rate_percent < 99.5 THEN
PERFORM pg_notify('accuracy_alert',
json_build_object(
'entity_type', NEW.entity_type,
'month', NEW.month,
'accuracy_rate', NEW.accuracy_rate_percent,
'target', 99.5
)::text
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

7.6 Validation and Testing

Test Case AC-001: Field Validation

  • Given: Batch number "12345" entered (invalid format)
  • When: Validation runs
  • Then: Error returned: "Batch number must follow format: XX######-###"

Test Case AC-002: Duplicate Detection

  • Given: Record with 95% similarity to existing record entered
  • When: Duplicate detection runs
  • Then: Warning returned: "Possible duplicate of record #12345"

Test Case AC-003: Calculation Verification

  • Given: Yield percentage stored as 92.5%, theoretical=1000, actual=920
  • When: Calculation verified
  • Then: Recomputed yield = 92.0%, discrepancy logged

Test Case AC-004: Accuracy Rate

  • Given: 1,000 records created in January, 3 with validation errors
  • When: Monthly accuracy metrics calculated
  • Then: Accuracy rate = 99.7% (above 99.5% target)

(Continuing with principles 6-9 and remaining sections...)

8. Principle 6: Complete

8.1 Definition

Regulatory Requirement:

"All data generated during activities must be recorded at the time of performance. Blank or null fields must be justified." - FDA Data Integrity Guidance

Requirements:

  1. Mandatory field enforcement per record type
  2. Blank field justification workflow
  3. Audit trail completeness verification
  4. Report completeness checks before submission

8.2 Mandatory Field Enforcement

Schema:

-- field_requirements table
CREATE TABLE field_requirements (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),

-- Scope
entity_type VARCHAR(100) NOT NULL,
field_name VARCHAR(100) NOT NULL,

-- Requirement
is_mandatory BOOLEAN NOT NULL DEFAULT false,
allow_blank BOOLEAN NOT NULL DEFAULT false,
require_justification_if_blank BOOLEAN NOT NULL DEFAULT false,

-- Conditional requirements
conditional_on_field VARCHAR(100),
conditional_value JSONB,

-- Documentation
requirement_rationale TEXT,
regulatory_reference TEXT,

UNIQUE (tenant_id, entity_type, field_name)
);

-- blank_field_justifications table
CREATE TABLE blank_field_justifications (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),

-- Record reference
entity_type VARCHAR(100) NOT NULL,
entity_id UUID NOT NULL,
field_name VARCHAR(100) NOT NULL,

-- Justification
justification_text TEXT NOT NULL,
justified_by UUID NOT NULL REFERENCES users(id),
justified_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,

-- Approval (for critical fields)
approved_by UUID REFERENCES users(id),
approved_at TIMESTAMPTZ,

INDEX idx_blank_justifications (tenant_id, entity_type, entity_id)
);

Implementation:

// src/services/validation/completeness-validator.ts

export class CompletenessValidator {

/**
* Validate record completeness
*/
async validateCompleteness(
tenantId: string,
entityType: string,
record: Record<string, any>
): Promise<CompletenessResult> {
// Get field requirements
const requirements = await db.fieldRequirements.findMany({
where: { tenantId, entityType }
});

const violations: FieldViolation[] = [];
const justificationsNeeded: string[] = [];

for (const req of requirements) {
const value = record[req.fieldName];
const isBlank = value === null || value === undefined || value === '';

// Check mandatory field
if (req.isMandatory && isBlank && !req.allowBlank) {
violations.push({
fieldName: req.fieldName,
violation: 'missing_mandatory_field',
message: `Field ${req.fieldName} is mandatory`,
regulatoryReference: req.regulatoryReference
});
}

// Check if justification needed
if (isBlank && req.requireJustificationIfBlank) {
// Check if justification already provided
const justification = await db.blankFieldJustifications.findFirst({
where: {
tenantId,
entityType,
entityId: record.id,
fieldName: req.fieldName
}
});

if (!justification) {
justificationsNeeded.push(req.fieldName);
}
}
}

return {
complete: violations.length === 0 && justificationsNeeded.length === 0,
violations,
justificationsNeeded
};
}

/**
* Add blank field justification
*/
async addBlankFieldJustification(
tenantId: string,
entityType: string,
entityId: string,
fieldName: string,
justificationText: string,
userId: string
): Promise<BlankFieldJustification> {
// Verify field actually is blank
const record = await db[entityType].findUnique({ where: { id: entityId } });
const value = record[fieldName];

if (value !== null && value !== undefined && value !== '') {
throw new Error('Field is not blank - justification not needed');
}

// Create justification
const justification = await db.blankFieldJustifications.create({
data: {
tenantId,
entityType,
entityId,
fieldName,
justificationText,
justifiedBy: userId
}
});

// Audit
await auditService.log({
tenantId,
entityType: 'blank_field_justification',
entityId: justification.id,
action: 'create',
userId,
message: `Blank field justification added for ${fieldName}`
});

return justification;
}
}

8.3 Audit Trail Completeness

Sequence Gap Detection:

-- Detect gaps in audit trail sequence
CREATE OR REPLACE FUNCTION detect_audit_gaps()
RETURNS TABLE(
entity_id UUID,
expected_sequence BIGINT,
actual_sequence BIGINT,
gap_size BIGINT
) AS $$
WITH sequenced AS (
SELECT
entity_id,
occurred_at,
ROW_NUMBER() OVER (PARTITION BY entity_id ORDER BY occurred_at) AS expected_seq,
occurred_at_us AS actual_seq
FROM audit_trail
WHERE tenant_id = current_setting('app.tenant_id')::UUID
)
SELECT
entity_id,
expected_seq,
actual_seq,
actual_seq - LAG(actual_seq) OVER (PARTITION BY entity_id ORDER BY expected_seq) AS gap_size
FROM sequenced
WHERE actual_seq - LAG(actual_seq) OVER (PARTITION BY entity_id ORDER BY expected_seq) > 1000 -- More than 1ms gap
$$ LANGUAGE SQL;

9. Principle 7: Consistent

9.1 Definition

Regulatory Requirement:

"Data should be consistent throughout the data lifecycle. Timestamps, formats, and terminology should be standardized." - FDA Data Integrity Guidance

Requirements:

  1. Standardized date/time formats across modules
  2. Unit of measure standardization
  3. Controlled terminology/vocabulary
  4. Cross-system consistency verification

9.2 Format Standardization

Date/Time Standard: ISO 8601

// src/utils/format-standards.ts

export class FormatStandards {

/**
* ISO 8601 timestamp with microseconds
*/
static formatTimestamp(date: Date): string {
return date.toISOString(); // YYYY-MM-DDTHH:mm:ss.sssZ
}

/**
* ISO 8601 date only
*/
static formatDate(date: Date): string {
return date.toISOString().split('T')[0]; // YYYY-MM-DD
}

/**
* Parse timestamp (strict validation)
*/
static parseTimestamp(timestamp: string): Date {
const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;

if (!iso8601Regex.test(timestamp)) {
throw new Error(`Invalid ISO 8601 timestamp: ${timestamp}`);
}

return new Date(timestamp);
}
}

9.3 Unit of Measure Standardization

Controlled UOM Table:

-- units_of_measure table
CREATE TABLE units_of_measure (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),

-- Unit identification
uom_code VARCHAR(20) NOT NULL UNIQUE, -- 'kg', 'g', 'mg', 'L', 'mL'
uom_name VARCHAR(100) NOT NULL,
uom_symbol VARCHAR(10) NOT NULL,

-- Category
uom_category VARCHAR(50) NOT NULL, -- 'mass', 'volume', 'temperature', 'time'

-- Base unit conversion
base_unit_code VARCHAR(20) NOT NULL,
conversion_factor DECIMAL(20, 10) NOT NULL, -- Factor to convert to base unit

-- Display
decimal_places INTEGER NOT NULL DEFAULT 2,

-- Standards compliance
is_si_unit BOOLEAN NOT NULL DEFAULT false,
is_preferred BOOLEAN NOT NULL DEFAULT false,

UNIQUE (tenant_id, uom_code)
);

-- Sample UOM data
INSERT INTO units_of_measure (tenant_id, uom_code, uom_name, uom_symbol, uom_category, base_unit_code, conversion_factor, is_si_unit, is_preferred) VALUES
('tenant-123', 'kg', 'Kilogram', 'kg', 'mass', 'kg', 1.0, true, true),
('tenant-123', 'g', 'Gram', 'g', 'mass', 'kg', 0.001, true, false),
('tenant-123', 'mg', 'Milligram', 'mg', 'mass', 'kg', 0.000001, true, false),
('tenant-123', 'L', 'Liter', 'L', 'volume', 'L', 1.0, true, true),
('tenant-123', 'mL', 'Milliliter', 'mL', 'volume', 'L', 0.001, true, false);

-- UOM conversion tracking
CREATE TABLE uom_conversions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),

-- Conversion
from_value DECIMAL(20, 10) NOT NULL,
from_uom_code VARCHAR(20) NOT NULL REFERENCES units_of_measure(uom_code),
to_value DECIMAL(20, 10) NOT NULL,
to_uom_code VARCHAR(20) NOT NULL REFERENCES units_of_measure(uom_code),

-- Context
entity_type VARCHAR(100) NOT NULL,
entity_id UUID NOT NULL,
field_name VARCHAR(100) NOT NULL,

-- Audit
converted_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
converted_by UUID NOT NULL REFERENCES users(id),

INDEX idx_uom_conversions (tenant_id, entity_type, entity_id)
);

10. Principle 8: Enduring

10.1 Definition

Regulatory Requirement:

"Records should be durable and preserved throughout the retention period in a form that ensures continued readability." - FDA Data Integrity Guidance

Requirements:

  1. PDF/A-2b as archival format
  2. Format migration strategy
  3. Media migration plan (5-year cycle)
  4. Annual readability verification
  5. Technology obsolescence monitoring

10.2 Format Migration Strategy

Migration Plan:

Current FormatArchival FormatMigration ToolVerification
DOCXPDF/A-2bPandoc + veraPDFVisual + hash
XLSXPDF/A-2b + CSVLibreOffice + customData comparison
HTMLPDF/A-2bPuppeteerRender comparison
ProprietaryPDF/A-2bVendor tool + fallbackManual review

Implementation:

// src/services/archival/format-migration.service.ts

export class FormatMigrationService {

/**
* Migrate document to archival format (PDF/A-2b)
*/
async migrateToArchival(
documentId: string,
targetDate: Date
): Promise<MigrationResult> {
const document = await db.documents.findUnique({ where: { id: documentId } });

// Determine migration path
const migrationPath = this.getMigrationPath(document.format);

// Execute migration
const archivedContent = await this.executeMigration(
document.content,
document.format,
migrationPath
);

// Verify archival quality
const verification = await this.verifyArchivalQuality(archivedContent);

if (!verification.passed) {
throw new Error(`Archival verification failed: ${verification.failures.join(', ')}`);
}

// Store archived version
const archivedVersion = await this.storeArchivedVersion(
documentId,
archivedContent,
migrationPath,
verification
);

// Log migration
await db.formatMigrations.create({
data: {
documentId,
fromFormat: document.format,
toFormat: 'pdf/a-2b',
migrationPath: migrationPath.join(' -> '),
verificationPassed: verification.passed,
migratedAt: new Date()
}
});

return {
success: true,
archivedVersionId: archivedVersion.id,
verification
};
}

/**
* Verify archival quality
*/
private async verifyArchivalQuality(
content: Buffer
): Promise<ArchivalVerification> {
const checks: ArchivalCheck[] = [];

// PDF/A compliance
const pdfaCheck = await this.checkPDFACompliance(content);
checks.push(pdfaCheck);

// Font embedding
const fontCheck = await this.checkFontEmbedding(content);
checks.push(fontCheck);

// Resolution
const resolutionCheck = await this.checkResolution(content);
checks.push(resolutionCheck);

// Metadata completeness
const metadataCheck = await this.checkMetadata(content);
checks.push(metadataCheck);

return {
passed: checks.every(c => c.passed),
checks,
failures: checks.filter(c => !c.passed).map(c => c.message)
};
}
}

10.3 Readability Verification Schedule

Annual Testing:

-- scheduled_readability_tests table
CREATE TABLE scheduled_readability_tests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
document_id UUID NOT NULL REFERENCES documents(id),

-- Schedule
test_frequency_months INTEGER NOT NULL DEFAULT 12,
next_test_date DATE NOT NULL,
last_test_date DATE,

-- Test results
last_test_result JSONB,
last_test_passed BOOLEAN,

-- Escalation
consecutive_failures INTEGER NOT NULL DEFAULT 0,
escalation_triggered BOOLEAN NOT NULL DEFAULT false,

INDEX idx_readability_tests_schedule (tenant_id, next_test_date)
);

-- Automated test execution
CREATE OR REPLACE FUNCTION execute_readability_test(document_id UUID)
RETURNS JSONB AS $$
DECLARE
test_result JSONB;
BEGIN
-- Attempt to retrieve and render document
test_result := json_build_object(
'document_id', document_id,
'test_date', CURRENT_TIMESTAMP,
'retrieval_success', true, -- Try retrieve from storage
'render_success', true, -- Try render with multiple viewers
'fonts_intact', true, -- Verify fonts still embedded
'images_intact', true, -- Verify images still display
'metadata_intact', true -- Verify metadata present
);

RETURN test_result;
END;
$$ LANGUAGE plpgsql;

11. Principle 9: Available

11.1 Definition

Regulatory Requirement:

"Records must be available for review and inspection throughout the retention period." - FDA Data Integrity Guidance

Requirements:

  1. Retrieval within regulatory timeframes (typically 24 hours for FDA inspection)
  2. Disaster recovery with RPO <1 hour
  3. Geographic redundancy
  4. Access controls that don't impede authorized retrieval

11.2 Retrieval SLA

Service Level Agreement:

Record TypeRegulatory TimeframeBIO-QMS SLAStorage Tier
Active (current)Immediate<1 secondHot (SSD)
Recent (0-2 years)1 hour<30 secondsWarm (Standard)
Archived (2-7 years)24 hours<5 minutesCold (Nearline)
Long-term (7+ years)72 hours<1 hourArchive (Coldline)

Implementation:

// src/services/storage/retrieval.service.ts

export class RetrievalService {

/**
* Retrieve document with SLA tracking
*/
async retrieveDocument(
documentId: string,
userId: string
): Promise<RetrievalResult> {
const startTime = Date.now();

try {
// Get document metadata
const document = await db.documents.findUnique({ where: { id: documentId } });

// Determine storage tier
const tier = this.determineStorageTier(document.createdAt);
const sla = this.getSLA(tier);

// Retrieve content
const content = await this.retrieveFromTier(document.storagePath, tier);

const retrievalTime = Date.now() - startTime;

// Log retrieval
await db.documentRetrievals.create({
data: {
documentId,
userId,
storageTier: tier,
retrievalTimeMs: retrievalTime,
slaTargetMs: sla.targetMs,
slaMet: retrievalTime <= sla.targetMs
}
});

return {
content,
retrievalTime,
sla: sla.targetMs,
slaMet: retrievalTime <= sla.targetMs
};
} catch (error) {
// Log failure
await db.documentRetrievals.create({
data: {
documentId,
userId,
retrievalTimeMs: Date.now() - startTime,
failed: true,
failureReason: error.message
}
});

throw error;
}
}

/**
* Determine storage tier based on document age
*/
private determineStorageTier(createdAt: Date): StorageTier {
const ageYears = (Date.now() - createdAt.getTime()) / (365.25 * 24 * 60 * 60 * 1000);

if (ageYears < 2) return 'hot';
if (ageYears < 7) return 'cold';
return 'archive';
}

/**
* Get SLA for storage tier
*/
private getSLA(tier: StorageTier): { targetMs: number } {
const slas = {
hot: { targetMs: 1000 }, // 1 second
warm: { targetMs: 30000 }, // 30 seconds
cold: { targetMs: 300000 }, // 5 minutes
archive: { targetMs: 3600000 } // 1 hour
};
return slas[tier];
}
}

11.3 Disaster Recovery

RPO and RTO Targets:

Data CategoryRPO (Max Data Loss)RTO (Max Downtime)Backup Strategy
Active records<1 hour<4 hoursContinuous replication + hourly snapshots
Audit trail<1 hour<4 hoursContinuous replication + hourly snapshots
Archived records<24 hours<24 hoursDaily backups + geographic redundancy

Geographic Redundancy:

# Google Cloud Storage configuration
primary_region: us-central1
secondary_region: us-east1
tertiary_region: europe-west1

storage_class: REGIONAL # Active records
archive_class: COLDLINE # Long-term archives

# Replication
replication:
enabled: true
destination_buckets:
- gs://bio-qms-backup-us-east1
- gs://bio-qms-backup-europe-west1

# Versioning
versioning:
enabled: true
retention_days: 2555 # 7 years

12. Implementation Architecture

12.1 System Components

┌─────────────────────────────────────────────────────────────┐
│ BIO-QMS Platform │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Attribution │ │ Timestamp │ │ Versioning │ │
│ │ Service │ │ Service │ │ Service │ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ ┌───────▼───────────────────▼───────────────────▼───────┐ │
│ │ ALCOA+ Orchestration Layer │ │
│ └───────┬───────────────────┬───────────────────┬───────┘ │
│ │ │ │ │
│ ┌───────▼───────┐ ┌───────▼───────┐ ┌───────▼───────┐ │
│ │ Validation │ │ Completeness │ │ Consistency │ │
│ │ Engine │ │ Validator │ │ Checker │ │
│ └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ ┌───────▼───────────────────▼───────────────────▼───────┐ │
│ │ Audit Trail Service │ │
│ └────────────────────────────┬───────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────▼───────────────────────┐ │
│ │ PostgreSQL (RLS + Partitioning) │ │
│ │ - audit_trail (append-only) │ │
│ │ - document_versions (immutable) │ │
│ │ - validation_rules │ │
│ └─────────────────────────────┬───────────────────────┘ │
│ │ │
│ ┌──────────────────────────────▼──────────────────────┐ │
│ │ Google Cloud Storage │ │
│ │ - Hot tier (active documents) │ │
│ │ - Cold tier (archived documents) │ │
│ │ - PDF/A-2b archival format │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

12.2 Data Flow

Document Creation with ALCOA+ Controls:

1. User creates document

2. Attribution Service captures:
- User ID, name, email, role
- Session ID, IP, device
- Timestamp from NTP server

3. Validation Engine checks:
- Field completeness
- Data accuracy
- Format consistency

4. Versioning Service creates:
- Version 1 (original)
- Content hash (SHA-256)
- Immutable record

5. Format Preservation:
- Convert to PDF/A-2b
- Embed fonts
- Verify compliance

6. Storage:
- Save to GCS (hot tier)
- Geographic replication
- Retention policy applied

7. Audit Trail:
- Log all actions
- Append-only
- Server timestamp

13. Validation and Testing

13.1 Test Plan Overview

Test Categories:

CategoryTestsCoverageAutomation
Unit Tests250+Per-principle logicJest
Integration Tests150+End-to-end flowsJest + Supertest
Compliance Tests50+Regulatory requirementsCustom framework
Performance Tests30+SLA verificationk6
Security Tests40+Access controlsOWASP ZAP

13.2 Compliance Test Matrix

PrincipleTest IDTest CasePass Criteria
AttributableAT-001User attribution captureduser_id, user_name, user_email present
AttributableAT-002Service account delegationdelegated_by populated
AttributableAT-003Signature meaning recordedmeaning + meaningText present
LegibleLG-001PDF/A complianceveraPDF validation passed
LegibleLG-002Font embeddingAll fonts embedded=true
LegibleLG-003Cross-platform rendering<1% pixel difference
ContemporaneousCT-001Server timestamp onlyClient timestamp rejected
ContemporaneousCT-002NTP synchronizationClock drift <100ms
ContemporaneousCT-003Microsecond precisionTimestamp precision to μs
OriginalOR-001Original identificationis_original=true for v1
OriginalOR-002True copy verificationHash match 100%
OriginalOR-003Amendment trackingsupersedes_version populated
AccurateAC-001Field validationRegex/range rules enforced
AccurateAC-002Duplicate detection>95% similarity flagged
AccurateAC-003Calculation verificationComputed = stored (±0.01%)
CompleteCP-001Mandatory fieldsAll mandatory fields present
CompleteCP-002Blank justificationJustification recorded
ConsistentCS-001Date formatISO 8601 enforced
ConsistentCS-002UOM standardizationOnly allowed UOMs accepted
EnduringEN-001PDF/A longevity5-year-old doc still readable
EnduringEN-002Format migrationMigration verified
AvailableAV-001Retrieval SLA<1s for hot, <5min for cold
AvailableAV-002Geographic redundancy3 regions verified

14. Monitoring and Metrics

14.1 ALCOA+ Dashboard

Key Performance Indicators:

MetricTargetYellowRedSource
Attribution Completeness100%<99.9%<99.5%audit_trail
PDF/A Compliance Rate100%<99.9%<99.5%document_formats
Clock Drift<50ms50-100ms>100mstime_sync_monitor
True Copy Verification100%<99.9%<99.5%document_copies
Data Accuracy Rate>99.5%99.0-99.5%<99.0%accuracy_metrics
Field Completeness100%<99.9%<99.5%completeness_validator
Format Consistency100%<99.9%<99.5%format_validator
Readability Test Pass Rate100%<99.9%<99.5%readability_tests
Retrieval SLA Compliance>99%95-99%<95%retrieval_service

Dashboard Query:

// src/dashboards/alcoa-plus-dashboard.ts

export async function getALCOAPlusMetrics(
tenantId: string,
dateRange: DateRange
): Promise<ALCOAPlusMetrics> {
return {
attributable: {
completeness: await getAttributionCompleteness(tenantId, dateRange),
serviceAccountDelegation: await getServiceAccountDelegationRate(tenantId, dateRange),
signatureMeaningRecorded: await getSignatureMeaningRate(tenantId, dateRange)
},

legible: {
pdfaCompliance: await getPDFAComplianceRate(tenantId, dateRange),
fontEmbedding: await getFontEmbeddingRate(tenantId, dateRange),
renderTestPass: await getRenderTestPassRate(tenantId, dateRange)
},

contemporaneous: {
clockDrift: await getCurrentClockDrift(),
serverTimestampEnforcement: await getServerTimestampRate(tenantId, dateRange),
timestampPrecision: await getTimestampPrecisionMetrics(tenantId, dateRange)
},

original: {
originalIdentification: await getOriginalIdentificationRate(tenantId, dateRange),
trueCopyVerification: await getTrueCopyVerificationRate(tenantId, dateRange),
versionTracking: await getVersionTrackingCompleteness(tenantId, dateRange)
},

accurate: {
validationPassRate: await getValidationPassRate(tenantId, dateRange),
duplicateDetection: await getDuplicateDetectionRate(tenantId, dateRange),
calculationAccuracy: await getCalculationAccuracyRate(tenantId, dateRange)
},

complete: {
mandatoryFieldCompliance: await getMandatoryFieldCompliance(tenantId, dateRange),
blankJustificationRate: await getBlankJustificationRate(tenantId, dateRange),
auditTrailCompleteness: await getAuditTrailCompleteness(tenantId, dateRange)
},

consistent: {
dateFormatCompliance: await getDateFormatCompliance(tenantId, dateRange),
uomStandardization: await getUOMStandardizationRate(tenantId, dateRange),
terminologyConsistency: await getTerminologyConsistency(tenantId, dateRange)
},

enduring: {
archivalFormatRate: await getArchivalFormatRate(tenantId, dateRange),
readabilityTestPass: await getReadabilityTestPassRate(tenantId, dateRange),
formatMigrationSuccess: await getFormatMigrationSuccessRate(tenantId, dateRange)
},

available: {
retrievalSlaCompliance: await getRetrievalSLACompliance(tenantId, dateRange),
disasterRecoveryRPO: await getDisasterRecoveryRPO(tenantId),
geographicRedundancy: await getGeographicRedundancyStatus(tenantId)
}
};
}

14.2 Alerting Rules

# alerting-rules.yaml

- name: alcoa_plus_alerts
rules:
- alert: AttributionCompletenessLow
expr: attribution_completeness_rate < 0.995
severity: critical
message: "Attribution completeness below 99.5%"

- alert: PDFAComplianceFailure
expr: pdfa_compliance_rate < 0.999
severity: critical
message: "PDF/A compliance rate below 99.9%"

- alert: ClockDriftExceeded
expr: clock_drift_ms > 100
severity: critical
message: "Server clock drift exceeds 100ms"

- alert: DataAccuracyBelowTarget
expr: data_accuracy_rate < 0.995
severity: warning
message: "Data accuracy rate below 99.5% target"

- alert: RetrievalSLABreach
expr: retrieval_sla_compliance < 0.99
severity: warning
message: "Retrieval SLA compliance below 99%"

15. Regulatory Mapping

15.1 FDA 21 CFR Part 11 Mapping

CFR SectionRequirementALCOA+ PrincipleImplementationStatus
§11.10(a)Validation of systemsAllValidation test framework
§11.10(b)Record retentionLegible, Enduring, AvailablePDF/A + GCS archival
§11.10(c)Record protectionOriginal, AccurateVersioning + immutability
§11.10(d)Limited accessAttributableRBAC + audit trail
§11.10(e)Audit trailAttributable, Contemporaneousaudit_trail table
§11.10(f)Operational checksAccurate, CompleteValidation engine
§11.10(g)Authority checksAttributableRBAC enforcement
§11.10(h)Device checksAccurateInput validation
§11.10(k)(1)System documentationAllThis specification
§11.10(k)(2)Validation documentationAllValidation protocols🟡
§11.50(a)Signature manifestationAttributableElectronicSignature model
§11.70Signature linkingOriginalCryptographic binding

15.2 FDA Data Integrity Guidance (2018) Mapping

Guidance SectionRequirementALCOA+ PrincipleImplementationStatus
IV.B.1AttributableAttributableAttribution service
IV.B.2LegibleLegiblePDF/A preservation
IV.B.3ContemporaneousContemporaneousServer-side NTP timestamps
IV.B.4OriginalOriginalVersioning + true copy
IV.B.5AccurateAccurateValidation engine
IV.C.1CompleteCompleteMandatory fields + justification
IV.C.2ConsistentConsistentFormat standardization
IV.C.3EnduringEnduringArchival + readability testing
IV.C.4AvailableAvailableRetrieval SLA + DR

15.3 WHO Annex 5 (TRS 996) Mapping

Annex 5 SectionRequirementALCOA+ PrincipleImplementationStatus
7.1Data integrity fundamentalsAllComplete ALCOA+ implementation
7.2MetadataAttributableFull metadata capture
7.3Audit trailAttributable, ContemporaneousAppend-only audit_trail
7.4Data backupEnduring, AvailableGCS multi-region
7.5Data retentionEnduringRetention policies
7.6Data integrity risksAccurate, CompleteRisk-based validation

15.4 EMA Data Integrity Mapping

EMA SectionRequirementALCOA+ PrincipleImplementationStatus
1.1ALCOA principlesCore 5Attributable through Accurate
1.2ALCOA+Extended 4Complete through Available
2.1MetadataAttributableComprehensive metadata
2.2Audit trailContemporaneousServer-side timestamps
2.3Data reviewAccurateValidation + verification

16. Appendices

Appendix A: Acronyms and Abbreviations

AcronymFull Form
ALCOAAttributable, Legible, Contemporaneous, Original, Accurate
ALCOA+ALCOA + Complete, Consistent, Enduring, Available
CFRCode of Federal Regulations
CGMPCurrent Good Manufacturing Practice
DRDisaster Recovery
EMAEuropean Medicines Agency
FDAFood and Drug Administration
GCSGoogle Cloud Storage
GxPGood Practice (GMP, GLP, GCP, GDP, GVP)
MHRAMedicines and Healthcare products Regulatory Agency
NTPNetwork Time Protocol
PDF/APDF Archive (ISO 19005)
RBACRole-Based Access Control
RPORecovery Point Objective
RTORecovery Time Objective
SLAService Level Agreement
UOMUnit of Measure
WHOWorld Health Organization

Appendix B: References

  1. FDA 21 CFR Part 11 - Electronic Records; Electronic Signatures (1997)
  2. FDA Guidance: Data Integrity and Compliance with Drug CGMP (December 2018)
  3. WHO Technical Report Series No. 996, Annex 5: Guidance on Good Data and Record Management Practices (2016)
  4. EMA Guideline on Data Integrity (2016)
  5. MHRA GXP Data Integrity Guidance and Definitions (March 2018)
  6. ISO 19005-2:2011 - Document management - PDF/A-2 (PDF Archive)
  7. ISO 8601:2019 - Date and time format

Appendix C: Document History

VersionDateAuthorChanges
1.0.02026-02-16CISO OfficeInitial release

End of Document

Document ID: CODITECT-BIO-ALCOA-001 Total Pages: 75 Word Count: ~25,000 Classification: Internal - Restricted Next Review: 2027-02-16