# Events And Audit GovOPlaN uses a small kernel event contract first, not a broad command bus. Commands remain module-owned application-service methods or API endpoints until there is a concrete need for durable asynchronous command orchestration. Events are facts about completed work and are safe for audit, projections, optional module reactions, and operator diagnostics. ## Production Transport Decision The first production target is a **database outbox plus in-process immediate dispatch**: - Use `govoplan_core.core.events.PlatformEvent` for domain and platform events. - Use `EventBus` as the in-process dispatch contract for same-process module reactions that are safe to run inline. - Persist durable integration/workflow events through a database outbox before acknowledging the state change that produced them. - Drain the outbox through a small dispatcher process. The dispatcher may call in-process handlers in the same deployment first, but its storage contract is database-backed. - Treat Redis/Celery as worker/job infrastructure, not as the authoritative first event transport. A Celery dispatcher can consume the outbox later. - Keep the dispatch implementation pluggable behind the `PlatformEvent` envelope so a future message broker can be added without changing event producers. - Keep commands out of the kernel until workflows need retryable, durable, operator-visible command records. This keeps the first contract small, PostgreSQL-friendly, auditable, and recoverable after process crashes. It also avoids making Redis a correctness dependency for deployments that only need synchronous mail/tests or light background work. ## Dispatch Semantics Event producers should write their domain state and outbox event in the same database transaction wherever possible. Handlers must be idempotent because the outbox dispatcher can retry after a crash or timeout. Recommended first outbox columns: - `event_id`, `event_type`, `module_id` - `correlation_id`, `causation_id` - `payload`, `occurred_at` - `available_at`, `attempt_count`, `claimed_at`, `claim_token` - `processed_at`, `last_error` Inline `EventBus` handlers are allowed only for non-critical local reactions. Anything that must survive process failure, restart, package update, or worker redeployment belongs in the outbox. ## Trace IDs Every `PlatformEvent` has: - `event_id`: unique ID for that event. - `correlation_id`: stable ID for the whole request, workflow, or job. - `causation_id`: the event ID or external operation ID that caused this event. The FastAPI app factory creates an event context for every request. It accepts `X-Correlation-ID` or `X-Request-ID` when the value is a compact safe trace ID, otherwise it generates a new ID. Responses include `X-Correlation-ID`. Audit logging reads the current event context and stores trace IDs in `details._trace`. Callers can also pass explicit `correlation_id` and `causation_id` to `audit_event` or `audit_from_principal`. Admin and lifecycle code should use the compact operational detail shape documented in `govoplan-audit/docs/AUDIT_TRACE_CONTEXT.md`. The core `audit_operation_context` helper preserves `module_id`, `request_id`, `run_id`, `outcome`, and `_trace` while applying the shared audit redaction pass to additional detail values. ## Audit MVP Boundary `govoplan-audit` owns: - the `audit_log` table and audit API routes - audit route contribution through its module manifest - audit retention behavior in cooperation with policy/retention settings - future audit sink/export capability implementations `govoplan-core` owns: - `AuditEvent` and `AuditSink` protocol contracts - request and event trace context - the compatibility `audit_event` helper while routes are still migrating - retention orchestration that calls module capabilities Feature modules should record audit facts through a small audit API or future `audit.sink` capability. They should not import audit storage internals. ## Initial Domain Event Inventory Access: - `access.account.created` - `access.account.updated` - `access.membership.created` - `access.membership.updated` - `access.group.created` - `access.group.updated` - `access.role.created` - `access.role.updated` - `access.role.deleted` - `access.api_key.created` - `access.api_key.revoked` - `access.session.created` - `access.session.revoked` Tenancy: - `tenancy.tenant.created` - `tenancy.tenant.updated` - `tenancy.tenant.suspended` - `tenancy.tenant.reactivated` - `tenancy.tenant.delete_requested` - `tenancy.tenant.delete_blocked` - `tenancy.tenant.deleted` Policy: - `policy.system.updated` - `policy.tenant.updated` - `policy.user.updated` - `policy.group.updated` - `policy.campaign.updated` - `policy.effective_policy.changed` Files: - `files.file.uploaded` - `files.file.renamed` - `files.file.deleted` - `files.file.frozen` - `files.share.created` - `files.share.revoked` - `files.connector.imported` - `files.connector.access_denied` Mail: - `mail.profile.created` - `mail.profile.updated` - `mail.profile.credentials_rotated` - `mail.profile.tested` - `mail.message.sent` - `mail.message.send_failed` - `mail.imap.appended` - `mail.imap.append_failed` - `mail.mailbox.message_seen` Campaign: - `campaign.created` - `campaign.version.created` - `campaign.validated` - `campaign.built` - `campaign.reviewed` - `campaign.queued` - `campaign.send_started` - `campaign.recipient_attempted` - `campaign.recipient_delivered` - `campaign.recipient_failed` - `campaign.paused` - `campaign.resumed` - `campaign.cancelled` - `campaign.report.exported` ## Event Payload Rules - Payloads must be JSON-serializable. - Use stable IDs, not ORM objects. - Include tenant ID when tenant-scoped. - Include actor/principal references only as DTOs or primitive IDs. - Do not include secrets, raw message bodies, full recipient lists, or file content. - Put large evidence in owning module storage and reference it by ID.