Simulation Mode — UX Contract and Guarantees¶
Purpose¶
Simulation mode allows users to test their triggers and configurations without actually executing anything. This builds confidence and catches configuration errors before they matter.
Core Guarantee: Simulation shows exactly what WOULD happen without making it happen.
The Contract¶
What Simulation DOES¶
- Shows trigger evaluation — Would this trigger fire right now? Why or why not?
- Shows action sequence — What actions would execute, in what order?
- Shows recipients — Who would be notified? Who would receive access?
- Shows timing — When would each step happen?
- Shows documents affected — Which documents would be released?
- Produces a report — Downloadable summary of simulation results
What Simulation NEVER Does¶
- Never sends notifications — No emails, SMS, or push notifications
- Never grants access — No permissions changed
- Never decrypts documents — Content remains encrypted
- Never modifies state — Trigger state unchanged
- Never records to audit log — Simulation is not an auditable event
- Never contacts external systems — No API calls to recipients
User Interface Requirements¶
Always-Visible Simulation Banner¶
When in simulation mode, display prominently:
┌─────────────────────────────────────────────────────────────────────┐
│ 🔬 SIMULATION MODE │
│ │
│ This is a preview only. No actions will be taken. │
│ No notifications sent. No access changed. No data released. │
│ │
│ [Exit Simulation] │
└─────────────────────────────────────────────────────────────────────┘
Simulation Entry Point¶
┌─────────────────────────────────────────────────────────────────────┐
│ My Vault — Triggers │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Estate Release [Armed] │ │
│ │ Dead man's switch • 7-day check-in • 3-day grace │ │
│ │ │ │
│ │ [Edit] [Simulate] [Disable] │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Simulation Report¶
┌─────────────────────────────────────────────────────────────────────┐
│ 🔬 Simulation Report: Estate Release │
│ Generated: 2025-12-16 14:32:00 UTC │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ TRIGGER STATUS │
│ ───────────────────────────────────────────── │
│ Would fire now? NO │
│ Reason: Last check-in was 2 days ago. Next check-in due in 5 days.│
│ │
│ IF TRIGGER FIRED │
│ ───────────────────────────────────────────── │
│ │
│ Step 1: Challenge Window (7 days) │
│ • You would receive: Email, SMS, Push notification │
│ • Secondary contacts notified: John Smith, Jane Doe │
│ • You could abort during this window │
│ │
│ Step 2: Pending Execution (24 hours) │
│ • Final warning sent to all channels │
│ • Abort still available with typed confirmation │
│ │
│ Step 3: Execution │
│ • Notifications sent to: │
│ - john.executor@email.com (Executor) │
│ - jane.family@email.com (Family) │
│ │
│ • Access granted to: │
│ - John Smith: Full executor access │
│ - Jane Doe: View-only family access │
│ │
│ • Documents released: │
│ - Will_2024.pdf (Class B) │
│ - Account_List.pdf (Class B) │
│ - Letter_to_Children.pdf (Class B) │
│ │
│ Step 4: Release Complete │
│ • Reversal window: 7 days │
│ • After reversal window: Finalized (irreversible) │
│ │
│ ───────────────────────────────────────────── │
│ NOTHING HAS HAPPENED. This is only a simulation. │
│ ───────────────────────────────────────────── │
│ │
│ [Download Report] [Run Another Simulation] [Exit] │
│ │
└─────────────────────────────────────────────────────────────────────┘
Simulation Scenarios¶
Users can simulate different scenarios:
1. "What if I died today?"¶
Simulates death verification trigger with current date.
2. "What if I miss my next check-in?"¶
Simulates dead man's switch firing.
3. "What if [date] arrives?"¶
Simulates scheduled trigger at specific date.
4. "What if [event] happens?"¶
Simulates event-based trigger.
Privacy Considerations¶
Simulation Visibility¶
Simulation results may contain sensitive information: - Who receives what documents - Who has what access level - What documents exist
Rules: 1. Simulation requires full authentication 2. Simulation results are not cached server-side 3. Simulation results are not logged to audit trail 4. Downloaded reports are marked "CONFIDENTIAL - SIMULATION ONLY"
Sensitive Content Warning¶
Before showing simulation results:
┌─────────────────────────────────────────────────────────────────────┐
│ Privacy Notice │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ This simulation will show: │
│ • Who would receive your documents │
│ • What access levels would be granted │
│ • Which documents would be released │
│ │
│ Make sure you're in a private location before viewing. │
│ │
│ [Show Simulation Results] [Cancel] │
│ │
└─────────────────────────────────────────────────────────────────────┘
Implementation¶
Simulation Engine¶
interface SimulationResult {
triggerId: string;
simulatedAt: Date;
wouldFireNow: boolean;
fireReason: string | null;
noFireReason: string | null;
timeline: SimulationTimelineStep[];
notifications: SimulationNotification[];
accessGrants: SimulationAccessGrant[];
documentsReleased: SimulationDocument[];
warnings: string[]; // Configuration issues detected
}
interface SimulationTimelineStep {
step: number;
name: string;
description: string;
duration: string;
abortAvailable: boolean;
}
interface SimulationNotification {
recipient: string;
recipientEmail: string; // Partially masked: j***@email.com
channel: 'email' | 'sms' | 'push';
type: 'warning' | 'release' | 'access_granted';
timing: string;
}
interface SimulationAccessGrant {
recipientName: string;
role: string;
permissions: string[];
timing: string;
}
interface SimulationDocument {
name: string;
encryptionClass: 'A' | 'B' | 'C';
size: string;
willBeDecrypted: boolean;
}
Simulation Execution¶
async function simulateTrigger(
triggerId: string,
scenario: SimulationScenario
): Promise<SimulationResult> {
const trigger = await getTrigger(triggerId);
// Create a virtual copy of state for simulation
const virtualState = createVirtualState(trigger, scenario);
// Evaluate trigger conditions
const wouldFire = evaluateTriggerConditions(virtualState);
// If would fire, walk through execution steps
let timeline: SimulationTimelineStep[] = [];
let notifications: SimulationNotification[] = [];
let accessGrants: SimulationAccessGrant[] = [];
let documentsReleased: SimulationDocument[] = [];
if (wouldFire) {
// Simulate each phase
timeline = simulateTimeline(trigger);
notifications = simulateNotifications(trigger);
accessGrants = simulateAccessGrants(trigger);
documentsReleased = simulateDocumentRelease(trigger);
}
// Check for configuration warnings
const warnings = validateTriggerConfig(trigger);
return {
triggerId,
simulatedAt: new Date(),
wouldFireNow: wouldFire,
fireReason: wouldFire ? getFireReason(virtualState) : null,
noFireReason: wouldFire ? null : getNoFireReason(virtualState),
timeline,
notifications,
accessGrants,
documentsReleased,
warnings
};
}
Configuration Warnings¶
Simulation should catch configuration issues:
function validateTriggerConfig(trigger: Trigger): string[] {
const warnings: string[] = [];
// No recipients configured
if (trigger.actions.filter(a => a.type === 'notify').length === 0) {
warnings.push('No notification recipients configured. No one will be notified when this trigger fires.');
}
// No documents to release
if (trigger.actions.filter(a => a.type === 'release_documents').length === 0) {
warnings.push('No documents configured for release. This trigger will notify but not share any files.');
}
// Executor email not verified
const executor = getExecutor(trigger);
if (executor && !executor.emailVerified) {
warnings.push(`Executor email (${maskEmail(executor.email)}) has not been verified. They may not receive notifications.`);
}
// No secondary contacts for dead man's switch
if (trigger.type === 'dead_man_switch' && !trigger.config.secondary_contacts?.length) {
warnings.push('No secondary contacts configured. Consider adding someone who can confirm your status before the trigger fires.');
}
// Check-in interval very short
if (trigger.type === 'dead_man_switch' && trigger.config.check_interval_days < 3) {
warnings.push(`Check-in interval is only ${trigger.config.check_interval_days} days. This may cause accidental triggers during travel or illness.`);
}
// Class C documents can't be previewed
const classC = trigger.actions
.filter(a => a.type === 'release_documents')
.flatMap(a => a.documents)
.filter(d => d.encryptionClass === 'C');
if (classC.length > 0) {
warnings.push(`${classC.length} document(s) are Class C (zero-knowledge). Even you cannot preview their contents after upload.`);
}
return warnings;
}
Guarantees (Enforceable in Code)¶
G1: No Side Effects¶
// Simulation functions must not call any side-effect functions
// Enforced via code review and linting rules
// PROHIBITED in simulation code:
// - await sendEmail(...)
// - await sendSMS(...)
// - await grantAccess(...)
// - await updateTriggerState(...)
// - await auditLog.record(...)
// - await decrypt(...) // for Class B/C
G2: No Persistent State¶
// Simulation results are computed, not stored
// No database writes during simulation
// Virtual state is discarded after simulation
function simulateTrigger(triggerId: string, scenario: SimulationScenario) {
// All state is local to this function
const virtualState = { ...currentState }; // Copy, not reference
// Compute results
const result = computeSimulation(virtualState);
// virtualState is garbage collected
// No writes to database occurred
return result;
}
G3: Authentication Required¶
// Simulation requires authenticated user who owns the trigger
async function simulateTrigger(triggerId: string, userId: string) {
const trigger = await getTrigger(triggerId);
if (trigger.owner_id !== userId) {
throw new AuthorizationError('Cannot simulate trigger you do not own');
}
// Proceed with simulation
}
G4: Clear Visual Distinction¶
// UI must clearly distinguish simulation from reality
// Enforced via design system and component library
// SimulationBanner component is REQUIRED on all simulation screens
// SimulationBanner cannot be dismissed while in simulation mode
// All simulation output is styled differently (e.g., dashed borders, different background)
Testing Requirements¶
Unit Tests¶
- [ ] Simulation produces correct results for each trigger type
- [ ] Simulation never calls side-effect functions
- [ ] Simulation warns on configuration issues
- [ ] Simulation masks sensitive data appropriately
Integration Tests¶
- [ ] Full simulation flow for each trigger type
- [ ] Simulation results match what would actually happen
- [ ] Simulation does not modify database state
- [ ] Simulation report can be downloaded
Manual QA¶
- [ ] Simulation banner is always visible
- [ ] Privacy warning appears before results
- [ ] Results are clearly labeled as simulation
- [ ] "Nothing has happened" message is prominent
Edge Cases¶
Empty Configuration¶
If trigger has no actions configured, simulation should show:
This trigger has no actions configured.
If it fires, nothing will happen.
Consider adding notification recipients and documents to release.
All Class C Documents¶
If all documents are Class C (zero-knowledge), simulation should show:
All documents in this trigger are Class C (zero-knowledge).
Even we cannot preview their contents.
Your recipients will receive encrypted files that only they can decrypt
with keys you've shared with them separately.
Executor Not Configured¶
If no executor is configured:
⚠️ WARNING: No executor configured.
When this trigger fires, no one will receive access to your vault.
Add an executor to ensure your documents are delivered.
This document defines the simulation mode contract. Implementation must guarantee these behaviors. Simulation must never have side effects.