Files
cal.diy/packages/features/booking-audit
b74dd3227c perf: Optimize DB calls and avoid N+1 queries in BookingAuditViewer (#26544)
* Integrate creation/rescheduling booking audit

* fix: add missing hostUserUuid to booking audit test data

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix-ci

* feat: enhance booking audit with seat reference

- Added support for seat reference in booking audit actions.
- Updated localization for booking creation to include seat information.
- Modified relevant services to pass attendee seat ID during booking creation.

* fix: update test data to match schema requirements

- Add seatReferenceUid: null to default mock audit log data
- Add seatReferenceUid: null to multiple audit logs test case
- Convert SEAT_RESCHEDULED test data to use numeric timestamps instead of ISO strings

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* Allow nullish seatReferenceUid

* feat: enhance booking audit to support rescheduledBy information

- Updated booking audit actions to include rescheduledBy details, allowing tracking of who rescheduled a booking.
- Refactored related services to accommodate the new rescheduledBy parameter in booking events.
- Adjusted type definitions and function signatures to reflect the changes in the booking audit context.

* Avoid possible run time issue

* Fix imoport path

* fix failing test due to merge from main\

* Pass useruuid

* Refactor getDisplayTitle method signatures to use _params for consistency across audit action services. Update IAuditActionService to include dbStore in GetDisplayTitleParams for improved data handling during title retrieval.

* fix: extend bulk-fetching optimization to actor and impersonator enrichment

- Collect actor userUuids (for USER type actors) in collectDataRequirements
- Collect impersonator UUIDs from log.context.impersonatedBy
- Update enrichActorInformation to use dbStore.getUserByUuid() for USER actors
- Update enrichImpersonator to use dbStore.getUserByUuid()
- Update tests to verify findByUuids is called with correct aggregated UUIDs

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* feat: Add infrastructure for no-show audit integration

- Add Prisma migrations for SYSTEM source and NO_SHOW_UPDATED audit action
- Add NoShowUpdatedAuditActionService with array-based attendeesNoShow schema
- Update BookingAuditActionServiceRegistry to include NO_SHOW_UPDATED
- Update BookingAuditTaskConsumer and BookingAuditViewerService
- Add AttendeeRepository methods for no-show queries
- Update IAuditActionService interface with values array support
- Update locales with no-show audit translation keys

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: Add NO_SHOW_UPDATED to BookingAuditAction and SYSTEM to ActionSource types

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: Remove HOST_NO_SHOW_UPDATED and ATTENDEE_NO_SHOW_UPDATED from BookingAuditAction type to match Prisma schema

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: Update BookingAuditActionSchema to use NO_SHOW_UPDATED instead of HOST_NO_SHOW_UPDATED and ATTENDEE_NO_SHOW_UPDATED

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* refactor: Remove deprecated no-show audit services and unify to NoShowUpdatedAuditActionService

- Delete HostNoShowUpdatedAuditActionService and AttendeeNoShowUpdatedAuditActionService
- Update BookingAuditProducerService.interface.ts to use queueNoShowUpdatedAudit
- Update BookingAuditTaskerProducerService.ts to use queueNoShowUpdatedAudit
- Update BookingEventHandlerService.ts to use onNoShowUpdated
- Add integration tests for NoShowUpdatedAuditActionService

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: Add data migration step for deprecated no-show enum values

Addresses Cubic AI review feedback (confidence 9/10): The migration now
includes an UPDATE statement to convert existing records using the
deprecated 'host_no_show_updated' or 'attendee_no_show_updated' enum
values to the new unified 'no_show_updated' value before the type cast.
This prevents migration failures if any existing data uses the old values.

Co-Authored-By: unknown <>

* fix: Use CASE expression in USING clause for enum migration

Fixes PostgreSQL error 'unsafe use of new value of enum type' by avoiding
the ADD VALUE statement and instead using a CASE expression in the ALTER
TABLE USING clause to convert deprecated enum values (host_no_show_updated,
attendee_no_show_updated) to the new unified value (no_show_updated) during
the type conversion.

Co-Authored-By: unknown <>

* fix: Remove dbStore usage from audit action services after merge

- Update CreatedAuditActionService to use userRepository.findByUuid directly
- Remove unused dbStore parameter from RescheduledAuditActionService
- Sync test file with prepare-for-no-show-audit branch

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: Restore dbStore bulk-fetching optimization pattern

- Add dbStore parameter and getDataRequirements method to IAuditActionService interface
- Update CreatedAuditActionService to use dbStore.getUserByUuid() for bulk-fetched data
- Update RescheduledAuditActionService to accept dbStore parameter
- Update BookingAuditViewerService to:
  - Collect data requirements from all audit logs
  - Bulk-fetch users with findByUuids() before enrichment
  - Pass dbStore to action services and enrichment methods
- Update tests to verify findByUuids is called for bulk-fetching optimization

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: Remove duplicate fireBookingEvents method in RecurringBookingService

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* revert: Remove formatting-only changes in audit action services

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* feat: Make getDataRequirements required and fetch rescheduled logs early

- Make getDataRequirements a required method in IAuditActionService interface
- Add getDataRequirements to all action services that didn't have it
- Fetch rescheduled logs early to include their data requirements in bulk fetch
- Update ReassignmentAuditActionService to use dbStore pattern instead of repository
- Update NoShowUpdatedAuditActionService to use dbStore pattern
- Update tests to use new dbStore pattern
- Handle invalid data gracefully in collectDataRequirements

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* Remve unncessary variable

* feat: Implement Actor Strategy pattern for bulk-fetching attendees and credentials

- Add ActorStrategies.ts with strategy pattern for all 5 actor types
- Extend EnrichmentDataStore with StoredAttendee and StoredCredential types
- Add findByIds method to CredentialRepository for bulk-fetching
- Update BookingAuditViewerService to use strategy pattern:
  - Replace switch statement in enrichActorInformation with strategy dispatch
  - Update collectDataRequirements to collect attendeeIds and credentialIds
  - Update buildEnrichmentDataStore to bulk-fetch in parallel
- Update tests to use findByIds mock and reflect graceful handling of missing data

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: Restore error-throwing for actors with missing required IDs

- USER actor now throws error when userUuid is missing
- ATTENDEE actor now throws error when attendeeId is missing
- Updated tests to expect hasError: true for fallback logs

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: Restore original test assertions, only update mocks for bulk methods

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* test: Add contract verification tests for getDataRequirements

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* refactor: Clean up imports and improve type definitions in BookingAuditTaskConsumer and BookingAuditViewerService

- Consolidated import statements for better readability
- Enhanced type definitions for clarity and consistency
- Updated method signatures to use destructured parameters for improved maintainability

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* test: Split contract verification tests into separate files per action service

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* test: Rename contract test files to .test.ts and merge into existing test file

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* feat: Add runtime validation to EnrichmentDataStore for undeclared data access

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* refactor: Make declaredRequirements required and add ensureFetched helper

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* refactor: Simplify EnrichmentDataStore with constructor + fetch() pattern

- Remove unused userIds and bookingUids from DataRequirements
- Remove unused getUserById and getBookingByUid methods
- Refactor constructor to accept requirements and repositories
- Add fetch() method that fetches data and populates maps
- Use Map keys as source of truth for declared requirements
- Update getters to throw when accessing undeclared data
- Add comprehensive unit tests for EnrichmentDataStore
- Update BookingAuditViewerService to use new API
- Update contractVerification.ts to remove unused tracking

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* refactor: Clean up imports and improve type definitions in BookingAuditTaskConsumer and BookingAuditViewerService

- Consolidated import statements for better readability
- Enhanced type definitions for clarity and consistency
- Updated method signatures to use destructured parameters for improved maintainability

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* fix: Include contextRequirements in mergeDataRequirements and add accessedData assertions

- Fix bug where contextRequirements (impersonator UUIDs) were not merged
- Add accessedData assertions to CreatedAuditActionService.test.ts
- Add accessedData assertions to ReassignmentAuditActionService.test.ts

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-02-06 16:08:38 +05:30
..

Booking Audit System

Overview

The Booking Audit System tracks all actions and changes related to bookings in Cal.com. The architecture is built around two core tables (AuditActor and BookingAudit) that work together to maintain a complete, immutable audit trail.

Database Architecture

Core Tables

  1. AuditActor: Stores information about entities that perform actions on bookings. Maintains historical records even after users are deleted.
  2. BookingAudit: Stores audit records for all booking-related actions.

Key Design Decisions

AuditActor Table:

  • Uses UUID primary keys for distributed system compatibility
  • Soft references to User (via userUuid) and Attendee (via attendeeId) without foreign key constraints
  • Audit trail persists independently even after users/attendees are deleted
  • Unique constraints prevent duplicate audit actors
  • Supports actor types: USER, GUEST, ATTENDEE, SYSTEM, APP
  • Identity fields (email, phone, name) can be anonymized when source records are deleted

BookingAudit Table:

  • Uses UUID v7 primary keys for time-sortable IDs
  • bookingUid stored as plain string (no foreign key) to preserve audit trail after booking deletion
  • onDelete: Restrict prevents actor deletion if audit records exist
  • Explicit timestamp field represents business event time (may differ from createdAt if processed asynchronously)
  • operationId required field for correlating audit logs from a single user action across different audit types (BookingAudit, UserAudit, etc.)
  • JSON data field stores action-specific contextual data
  • Indexed for efficient queries by bookingUid, actorId, timestamp, and operationId

Protecting the Audit Trail:

  • Database rejects deletion of AuditActor records with associated BookingAudit records
  • When a User is deleted, their AuditActor record persists with userUuid set to null

Actor Types

  • USER: Registered Cal.com users
  • GUEST: Non-registered users (typically booking guests)
  • ATTENDEE: Guests who have an Attendee record associated with a booking
  • SYSTEM: Automated system actions

Source and Actor Design Pattern

The booking audit system uses two complementary fields:

Source: The Channel

source identifies how the action was initiated:

  • WEBAPP: Cal.com web application
  • API_V1: API v1 endpoint
  • API_V2: API v2 endpoint
  • WEBHOOK: External webhook (e.g., Stripe)
  • SYSTEM: Background job (e.g., Tasker's task, trigger.dev job for automatic no-show detection)
  • UNKNOWN: Source cannot be determined

Actor: The Entity

actor identifies who or what performed the action:

  • User Actor: Registered Cal.com user
  • Guest Actor: Non-registered guest
  • Attendee Actor: Attendee associated with a booking
  • System Actor: Automated system action (generic or named for specific webhooks/services)

This separation enables clear compliance trails, easier debugging, better analytics, and security by distinguishing user-initiated vs automated actions.

Audit Actions

The system tracks various booking actions including:

  • CREATED: Initial booking creation
  • RESCHEDULED: Booking time/date changed
  • ACCEPTED: Booking request approved
  • CANCELLED: Booking cancelled
  • REJECTED: Booking request declined
  • RESCHEDULE_REQUESTED: Request to reschedule
  • ATTENDEE_ADDED: New attendee added
  • ATTENDEE_REMOVED: Attendee removed
  • REASSIGNMENT: Booking reassigned to different host
  • LOCATION_CHANGED: Meeting location updated
  • NO_SHOW_UPDATED: Host or attendee no-show status changed
  • SEAT_BOOKED: Seat reserved in group booking
  • SEAT_RESCHEDULED: Seat rescheduled in group booking

Data Structure Pattern

Most audit actions track changes using a consistent structure:

  • Each field tracks both old and new values: { old: T | null, new: T }
  • old: Previous value (null if field didn't exist before)
  • new: New value after the change
  • Complete before/after state captured in every record

Exception: The CREATED action captures initial booking state at creation using a flat object with initial values.

Schema Versioning

The audit system uses per-action versioning. Each action maintains its own schema version independently.

Benefits:

  • Update one action's schema without affecting others
  • Old records handled via discriminated unions (no migration required)
  • Strongly-typed schemas for input and storage

Storage Structure: Version stored separately from audit data: { version, data: {} }

Table Relationships

AuditActor (1) ──────< (many) BookingAudit
   ↑
   │ (soft reference, no FK)
   ├──────────── User (via userUuid)
   │ (soft reference, no FK)
   └──────────── Attendee (via attendeeId)

Relationship Details:

  1. AuditActor → BookingAudit: One-to-Many with FK constraint and onDelete: Restrict
  2. AuditActor → User: Soft reference (no FK) - nullable to preserve audits after user deletion
  3. AuditActor → Attendee: Soft reference (no FK) - nullable to preserve audits after attendee deletion

Indexing Strategy

AuditActor Table:

  • Indexed on email, userUuid, attendeeId for fast lookups
  • Indexed on pseudonymizedAt for compliance cleanup jobs

BookingAudit Table:

  • Indexed on bookingUid (primary query pattern)
  • Indexed on actorId (secondary query pattern)
  • Indexed on timestamp (time-based sorting and filtering)
  • Indexed on operationId (correlating multi-booking operations)

Special Actors

SYSTEM Actor:

  • Fixed UUID representing automated actions
  • Used for automated status changes, system-generated meeting URLs, scheduled operations
  • Single instance across the entire system
  • No userUuid, attendeeId, email, or phone

Design Principles

1. Immutability

Audit records are append-only. Once created, they are never modified or deleted. This ensures complete historical accuracy and a tamper-proof audit trail.

2. Historical Preservation

Actor information is preserved even after source records are deleted. AuditActor records persist with anonymized identity fields. The audit trail remains complete and queryable even after user/attendee deletion.

3. Flexibility

The JSON data field provides schema flexibility for action-specific context without database schema changes. Backward compatible with versioning.

4. Traceability

Every action is fully traceable with who (actor), what (action), when (timestamp), and contextual data.

5. Integrity

Database constraints ensure data quality through foreign keys, onDelete: Restrict protection, unique constraints, and strategic indexes.

6. Reality Over Enforcement

The audit system records actual state, not expected state. It captures what actually happened without enforcing business rules:

  • Store actual values from the database
  • Record all actions, including anomalies
  • Business logic layer enforces rules; audit layer records faithfully

Benefits:

  • Shows real system behavior including anomalies
  • Easier debugging
  • No silent failures

7. Compliance & Data Privacy

GDPR & HIPAA Compliance:

  • AuditActor records persist with PII fields nullified when users are deleted
  • Maintains immutable audit trail as required by HIPAA §164.312(b)
  • GDPR Article 17 compliance through anonymization: userUuid, email, phone, name set to null
  • Application-level logic handles retention policies

8. Queue Privacy

Zero PII in Queue: The audit system works with third-party queue providers without exposing PII:

  • UserActor: Only userUuid is queued
  • AttendeeActor: Only attendeeId is queued
  • GuestActor: Actor record created before queueing; only actorId is queued

Benefits:

  • Safe to use third-party queue providers
  • GDPR and HIPAA compliant
  • Audit trail remains complete even if queue is compromised

Service Architecture

BookingEventHandlerService is the primary entry point for tracking booking changes. It:

  1. Receives booking events from various parts of the application
  2. Queues audit tasks via BookingAuditProducerService
  3. Handles other side effects such as webhooks and notifications

Queue Payload Structure:

  • bookingUid: String identifier for the booking
  • actor: ID-only actor object (userUuid, attendeeId, or actorId)
  • organizationId: Number (for feature flag checks)
  • action: Enum value (e.g., "CREATED", "CANCELLED")
  • operationId: Required string for correlating related audit logs
  • data: Action-specific data
  • timestamp: Number (milliseconds since epoch)
  • source: Action source (API_V1, API_V2, WEBAPP, WEBHOOK, SYSTEM, UNKNOWN)

BookingAuditTaskConsumer processes audit records:

  • Validates queue payload structure
  • Resolves actor IDs to AuditActor records
  • Routes to appropriate action service for data validation and formatting
  • Creates immutable audit records in BookingAudit table

Operation ID

The operationId field correlates audit logs that result from a single user action affecting multiple bookings or across different audit types (BookingAudit, UserAudit, etc.).

Benefits:

  • Easy to identify all audits from a single user action
  • Track bulk vs individual operations
  • Improved debugging and understanding of action scope
  • Indexed field for fast lookups

Summary

The Booking Audit System provides a robust, scalable architecture for tracking all booking-related actions:

  • Complete Audit Trail: Every action tracked with full context
  • Historical Preservation: Data retained even after deletions through PII anonymization
  • Flexible Schema: JSON data supports evolution without migrations
  • Strong Integrity: Database constraints ensure data quality
  • Performance: Strategic indexes, UUID v7 for time-sortable IDs
  • HIPAA & GDPR Compliant: Immutable audit records, anonymized actors
  • Reality-Based Recording: Captures actual state for debugging
  • Independent Audit Trail: Persists after booking deletion
  • Operation Correlation: Links related audit logs across different audit types

This architecture supports compliance requirements, debugging, analytics, and provides transparency for users and administrators.