Skip to content

Evidence Preservation

When decisions are contested—whether by users, regulators, or in court—you need the complete picture. Vettly preserves evidence in a GDPR-compliant manner.

What Gets Preserved

Decision Records

Every decision automatically preserves:

  • Decision ID and timestamp
  • Policy version in effect
  • Provider scores and classifications
  • Action taken
  • Context metadata (user, session, IP)
  • Input content hash

Content Preservation

For flagged/blocked content, optionally preserve the actual content:

typescript
const vettly = new VettlyClient({
  apiKey: 'your-api-key',
  evidence: {
    preserveContent: true,
    preserveFor: ['flag', 'block'], // Only preserve non-allowed content
    storage: {
      type: 's3',
      bucket: 'evidence-storage',
      encryption: 'AES-256'
    }
  }
})

Context Snapshots

Preserve additional context at decision time:

typescript
const decision = await vettly.check({
  content: post.body,
  context: {
    userId: user.id,
    sessionId: session.id,
    ipAddress: request.ip,
    userAgent: request.headers['user-agent'],
    // Snapshot user state
    userContext: {
      accountAge: user.createdAt,
      previousViolations: user.violationCount,
      verificationStatus: user.verified
    },
    // Snapshot content context
    contentContext: {
      parentPostId: post.parentId,
      threadId: post.threadId,
      mentions: post.mentions
    }
  }
})

GDPR Compliance

Right to Access

Users can request their decision history:

typescript
// Generate user data export
const export = await vettly.exportUserData({
  userId: 'user_123',
  format: 'json',
  includeDecisions: true,
  includeAppeals: true
})

Right to Erasure

Delete user data while preserving anonymized records:

typescript
await vettly.deleteUserData({
  userId: 'user_123',
  reason: 'GDPR erasure request',
  options: {
    // Keep anonymized decision stats for reporting
    preserveAnonymizedStats: true,
    // Keep content hashes for verification
    preserveContentHashes: true,
    // Remove all PII
    removePersonalData: true
  }
})

Data Minimization

Only preserve what you need:

typescript
await vettly.configureEvidence({
  // Only preserve blocked content
  preserveFor: ['block'],

  // Auto-delete after retention period
  retention: {
    default: '30d',
    blocked: '90d',
    legalHold: 'indefinite'
  },

  // Exclude sensitive fields
  exclude: ['userAgent', 'ipAddress']
})

Retention Policies

By Action Type

typescript
await vettly.setRetentionPolicy({
  allow: '7d',      // Short retention for allowed content
  warn: '30d',      // Medium for warnings
  flag: '90d',      // Longer for flagged
  block: '365d',    // Longest for blocked
  appeal: '365d'    // Keep appealed decisions
})

By Category

typescript
await vettly.setRetentionPolicy({
  byCategory: {
    csam: '7y',           // Legal requirement
    terrorism: '5y',      // Regulatory requirement
    harassment: '90d',    // Standard retention
    spam: '30d'           // Short retention
  }
})

Override retention for litigation:

typescript
await vettly.setLegalHold({
  name: 'Case #12345 - Smith v. Platform',
  scope: {
    // Hold specific decisions
    decisionIds: ['dec_abc...', 'dec_def...'],
    // Or hold all decisions matching criteria
    filter: {
      userId: 'user_123',
      dateRange: {
        from: '2024-01-01',
        to: '2024-03-31'
      }
    }
  },
  expiry: '2026-12-31', // Or 'indefinite'
  reason: 'Litigation hold per legal counsel'
})

Storage Security

Encryption

All evidence is encrypted:

typescript
await vettly.configureEvidence({
  encryption: {
    atRest: 'AES-256',
    inTransit: 'TLS-1.3',
    keyRotation: '90d'
  }
})

Access Control

typescript
await vettly.setEvidenceAccess({
  roles: {
    'moderation-team': {
      canView: true,
      canExport: false,
      canDelete: false
    },
    'compliance-team': {
      canView: true,
      canExport: true,
      canDelete: false
    },
    'legal-team': {
      canView: true,
      canExport: true,
      canDelete: false,
      canSetLegalHold: true
    }
  }
})

Audit Logging

All access is logged:

typescript
const accessLog = await vettly.getEvidenceAccessLog({
  decisionId: 'dec_abc123',
  from: '2024-01-01'
})

// Returns:
[
  { action: 'viewed', user: 'admin@...', timestamp: '...', ip: '...' },
  { action: 'exported', user: 'legal@...', timestamp: '...', ip: '...' },
]

Verification

Content Integrity

Verify content hasn't changed since decision:

typescript
const verification = await vettly.verifyEvidence({
  decisionId: 'dec_abc123',
  currentContent: currentContentFromDb
})

// Returns:
{
  verified: true, // or false if content changed
  originalHash: 'abc123...',
  currentHash: 'abc123...',
  decision: { /* original decision */ }
}

Chain of Custody

Document evidence handling:

typescript
const chain = await vettly.getEvidenceChain('dec_abc123')

// Returns:
[
  { event: 'decision_made', timestamp: '...', hash: '...' },
  { event: 'content_preserved', timestamp: '...', location: 's3://...' },
  { event: 'legal_hold_set', timestamp: '...', user: 'legal@...', reason: '...' },
  { event: 'exported', timestamp: '...', user: 'legal@...', destination: '...' }
]

Complete Evidence Package

typescript
const package = await vettly.exportEvidencePackage({
  decisionId: 'dec_abc123',
  format: 'pdf', // or 'json'
  include: [
    'decision',
    'content',
    'context',
    'policyVersion',
    'providerResponse',
    'chainOfCustody',
    'verificationSignature'
  ]
})

Bulk Export

typescript
const export = await vettly.exportEvidencePackage({
  filter: {
    userId: 'user_123',
    dateRange: {
      from: '2024-01-01',
      to: '2024-03-31'
    }
  },
  format: 'json',
  destination: {
    type: 'download' // or 's3', 'email'
  }
})

Next Steps