declare global { interface UserActivityEventBase { tenant_store_id: string; user_id: string; actor_role: 'USER' | 'ADMIN' | 'SUPERVISOR' | 'MANAGER' | 'PRINTER'; actor_full_name: string; actor_ip?: string; event_id: string; schema_version: 1; ts: string; } interface UserLoggedInEvent extends UserActivityEventBase { event: 'User Logged In'; /** * How the session was established. `'magic-link'` is a PASSWORDLESS * primary login — never fold it into `'password'`, which would put a * false method in the audit trail. The 2FA branch still resolves to * `'totp'`/`'recovery'` as it does for every other primary method. */ method: 'password' | 'totp' | 'refresh' | 'social' | 'recovery' | 'magic-link'; } interface UserLoggedOutEvent extends UserActivityEventBase { event: 'User Logged Out'; } interface UserPasswordChangedEvent extends UserActivityEventBase { event: 'User Password Changed'; target_user_id: string; } interface UserSuspendedEvent extends UserActivityEventBase { event: 'User Suspended'; target_user_id: string; reason: string; } interface TwoFactorEnrolledEvent extends UserActivityEventBase { event: 'Two-Factor Enrolled'; } interface TwoFactorDisabledEvent extends UserActivityEventBase { event: 'Two-Factor Disabled'; } interface TwoFactorResetEvent extends UserActivityEventBase { event: 'Two-Factor Reset'; target_user_id: string; } interface TwoFactorRecoveryCodesGeneratedEvent extends UserActivityEventBase { event: 'Two-Factor Recovery Codes Generated'; count: number; trigger: 'enrollment' | 'regenerate'; } interface StorePaletteChangedEvent extends UserActivityEventBase { event: 'Store Palette Changed'; before: Record; after: Record; } interface StoreSettingsUpdatedEvent extends UserActivityEventBase { event: 'Store Settings Updated'; section: string; before: Record; after: Record; } interface PlanChangedEvent extends UserActivityEventBase { event: 'Plan Changed'; from_tier: string; to_tier: string; } interface InvoiceCreatedEvent extends UserActivityEventBase { event: 'Invoice Created'; invoice_id: string; total: number; currency: string; type: string; } interface OrderCreatedEvent extends UserActivityEventBase { event: 'Order Created'; order_id: string; total: number; currency: string; } interface OrderCancelledEvent extends UserActivityEventBase { event: 'Order Cancelled'; order_id: string; reason: string; } /** An operator replaced an order's content under optimistic concurrency. */ interface OrderEditedEvent extends UserActivityEventBase { event: 'Order Edited'; order_id: string; /** Server-recomputed totals, in the order's own currency. */ old_total: number; new_total: number; /** Count of lines added / removed / quantity-or-price changed. */ lines_added: number; lines_removed: number; lines_modified: number; } /** An operator processed a full or partial return. */ interface OrderReturnedEvent extends UserActivityEventBase { event: 'Order Returned'; order_id: string; return_id: string; /** Credited amount — the discounted `Return.total`. */ total: number; /** Number of original order lines involved. */ line_count: number; reason: ReturnReason; /** True when at least one line came back as `damaged` (no restock). */ has_damaged: boolean; /** NC lifecycle at the moment the return committed. */ nc_status: ReturnCreditNoteStatus; } interface PriceListChange { list_id: number; from: number; to: number; } interface ProductPriceChangedEvent extends UserActivityEventBase { event: 'Product Price Changed'; product_id: string; from_price: number; to_price: number; currency: string; changes?: PriceListChange[]; } interface CustomerCreatedEvent extends UserActivityEventBase { event: 'Customer Created'; customer_id: string; } /** * One staff-side change to a single `Customer.marketing` channel. * * ⚠️ `to` is the literal `false`, not `boolean`, and that is load-bearing. * Staff may CLEAR a consent flag and may never grant one — a customer's * consent can only ever come from the customer. Typing the literal makes * the compiler refuse an emission that records a grant, so the rule is * enforced at every call site rather than restated in a comment. Widening * this to `boolean` would be a deliberate change to that policy, which is * exactly the kind of decision that should cost a types publish. * * `from` is `null` when the channel had never been set, which is a * different fact from `false` (explicitly opted out before) and must stay * distinguishable in an audit record. */ interface CustomerConsentChange { channel: 'adds' | 'email' | 'phone' | 'sms' | 'whatsapp'; from: boolean | null; to: false; } interface CustomerEditedEvent extends UserActivityEventBase { event: 'Customer Edited'; customer_id: string; fields_changed: string[]; /** * Present only when a staff-side write touched `Customer.marketing`. * Absent — not an empty array — when the edit left consent alone, so * "no consent change" and "consent examined and unchanged" do not have * to be told apart by a reader. * * `who` and `when` are already carried by `UserActivityEventBase` * (`actor_full_name`, `actor_role`, `ts`); this adds only the per- * channel before/after the base cannot express. */ consent_changes?: CustomerConsentChange[]; } interface CashDrawerOpenedEvent extends UserActivityEventBase { event: 'Cash Drawer Opened'; cash_id: string; opening_balance: number; currency: string; } interface CashDrawerClosedEvent extends UserActivityEventBase { event: 'Cash Drawer Closed'; cash_id: string; final_balance: number; discrepancy: number; currency: string; } interface TenantImpersonatedEvent extends UserActivityEventBase { event: 'Tenant Impersonated'; target_store_id: string; reason: string; } interface SecretRotatedEvent extends UserActivityEventBase { event: 'Secret Rotated'; secret_name: string; } interface UserCreatedEvent extends UserActivityEventBase { event: 'User Created'; target_user_id: string; target_user_roles: string[]; } interface UserUpdatedEvent extends UserActivityEventBase { event: 'User Updated'; target_user_id: string; fields_changed: string[]; } interface ProductCreatedEvent extends UserActivityEventBase { event: 'Product Created'; product_id: string; name: string; } interface ProductUpdatedEvent extends UserActivityEventBase { event: 'Product Updated'; product_id: string; fields_changed: string[]; } interface StockIncomeCreatedEvent extends UserActivityEventBase { event: 'Stock Income Created'; product_id: string; quantity: number; cost: number; currency: string; supplier_id?: string; } /** * A manual, audited stock adjustment: shrinkage, breakage, a physical count * correction, or stock found after being written off. Distinct from * `Stock Income Created`, which records a PURCHASE — an adjustment buys * nothing and sells nothing, it reconciles the books to reality. * * BE-emitted from the mutating handler, exactly like `Stock Income Created`. * Never add it to `UI_ONLY_USER_ACTIVITY_VARIANTS`: the FE must not be able * to POST a stock adjustment into the audit trail without one having * happened. */ interface StockAdjustedEvent extends UserActivityEventBase { event: 'Stock Adjusted'; product_id: string; /** * SIGNED change applied to on-hand: negative for shrinkage/breakage/a * count that came out short, positive for found stock/a count that came * out long. Signed here even though the underlying ledger row always * carries a positive `quantity` — the ledger encodes direction in which * partition the row lands in, and an audit reader has no partition to * look at. */ quantity_delta: number; reason: StockAdjustmentReason; /** * The operator's free-text justification, if given. Mirrors * `adjustmentNote` on the ledger row and carries the same prohibition: * no personal data, ever. This feed is append-only and anti-erasure. */ note?: string; /** * Groups every adjustment emitted by one physical stock-count session's * finalise step, mirroring `stocktakeId` on the ledger rows. Absent when * the operator adjusted a single product directly. */ stocktake_id?: string; /** * The physical count the operator actually entered, which is the EVIDENCE * an adjustment is defended with — `quantity_delta` alone says what * changed but not what was observed. Present only alongside * `stocktake_id`; absent for a direct adjustment, where nothing was * counted. * * It lives on the audit event and NOT on the ledger row on purpose: the * row records a movement, this records the act that justified it, and * this feed is the one with multi-year retention and anti-erasure. */ counted_quantity?: number; /** Unit cost the adjustment is valued at — the product's cost at write time. */ cost: number; currency: string; } interface CategoryCreatedEvent extends UserActivityEventBase { event: 'Category Created'; category_id: string; name: string; } interface CategoryUpdatedEvent extends UserActivityEventBase { event: 'Category Updated'; category_id: string; fields_changed: string[]; } interface BrandCreatedEvent extends UserActivityEventBase { event: 'Brand Created'; brand_id: string; name: string; } interface BrandUpdatedEvent extends UserActivityEventBase { event: 'Brand Updated'; brand_id: string; fields_changed: string[]; } interface SupplierCreatedEvent extends UserActivityEventBase { event: 'Supplier Created'; supplier_id: string; name: string; } interface SupplierUpdatedEvent extends UserActivityEventBase { event: 'Supplier Updated'; supplier_id: string; fields_changed: string[]; } interface SupplierInvoiceCreatedEvent extends UserActivityEventBase { event: 'Supplier Invoice Created'; supplier_id: string; supplier_invoice_id: string; total: number; currency: string; } interface SupplierAccountCreatedEvent extends UserActivityEventBase { event: 'Supplier Account Created'; supplier_id: string; account_id: string; currency: string; } interface SupplierAccountUpdatedEvent extends UserActivityEventBase { event: 'Supplier Account Updated'; supplier_id: string; account_id: string; fields_changed: string[]; } interface AccountCreatedEvent extends UserActivityEventBase { event: 'Account Created'; customer_id: string; account_id: string; currency: string; amount: number; } interface AccountDeletedEvent extends UserActivityEventBase { event: 'Account Deleted'; customer_id: string; account_id: string; } interface BasketUpdatedEvent extends UserActivityEventBase { event: 'Basket Updated'; /** * ⚠️ OPTIONAL because a walk-in POS ticket has no customer at all. * * The audit must still fire for one: an operator touching an anonymous * counter ticket is MORE worth auditing than one touching a named * customer's cart, so dropping the record would put the least * attributable writes in the store outside the audit trail entirely. * * ⚠️ Never substitute the cart id here to satisfy a required field. A * field that reads as one kind of id and holds another is undetectable * downstream — exactly the defect that had cart socket frames carrying * a cart id under `customerId`. */ customer_id?: string; items_count: number; total: number; } /** * A DISCOUNT was granted or withdrawn on a cart — a per-line cut, or a coupon * applied or removed. * * ⚠️ **Distinct from `BasketUpdatedEvent` on purpose.** That event carries * `{ items_count, total }`, which makes a 90% cut indistinguishable from a * price change in the trail. It already answers WHO (`user_id`, * `actor_role`, `actor_full_name`, `actor_ip` ride on the base); what it * cannot answer is WHAT and BY HOW MUCH, and those are the two questions a * discount-abuse review is made of. * * ⚠️ Together with `Order.coupons` this is what makes **employee * self-redemption detectable**. Neither is sufficient alone: the order record * says a redemption happened, this says who granted it. A per-user `discount` * permission decides who *may*; only this says who *did*. */ interface BasketDiscountGrantedEvent extends UserActivityEventBase { event: 'Basket Discount Granted'; /** Which verb — a withdrawal is as worth auditing as a grant. */ verb: 'setLineDiscount' | 'applyCoupon' | 'removeCoupon'; /** * The grant's unit. Absent on `removeCoupon`, which withdraws rather than * grants and therefore has no terms of its own. */ type?: 'percent' | 'amount'; /** The GRANT, in the unit `type` names — NOT money. See `amount`. */ value?: number; /** * The MONEY the grant actually took, in the cart's currency, after every * clamp. * * ⚠️ Recorded ALONGSIDE `value` rather than instead of it, because the two * answer different review questions and neither implies the other: `value` * is what the operator chose, `amount` is what it cost. A 15% grant on a * large ticket and a 15% grant on a small one are the same decision and * wildly different money. */ amount?: number; /** The coupon code, on the two coupon verbs. Absent for a line discount. */ code?: string; /** The line the cut was applied to, on `setLineDiscount`. */ line_id?: string; /** ⚠️ Optional for the same reason as `BasketUpdatedEvent.customer_id`: a * walk-in POS ticket has no customer, and an operator discounting an * anonymous counter ticket is MORE worth auditing, not less. */ customer_id?: string; } interface BasketDeletedEvent extends UserActivityEventBase { event: 'Basket Deleted'; /** Optional for the same reason as `BasketUpdatedEvent.customer_id`: * deleting a walk-in ticket is the same event with no customer. */ customer_id?: string; } interface CashDrawerMovementEvent extends UserActivityEventBase { event: 'Cash Drawer Movement'; cash_id: string; direction: 'income' | 'outcome'; amount: number; currency: string; concept: string; } interface ClockedInEvent extends UserActivityEventBase { event: 'Clocked In'; shift_id: string; source: ClockEventSource; geohash?: string; } interface ClockedOutEvent extends UserActivityEventBase { event: 'Clocked Out'; shift_id: string; total_minutes: number; overtime_minutes?: number; } interface PaymentCreatedEvent extends UserActivityEventBase { event: 'Payment Created'; payment_id: string; provider: 'mercadopago' | 'stripe'; amount: number; currency: string; } interface PaymentLinkedEvent extends UserActivityEventBase { event: 'Payment Linked'; payment_id: string; source: 'mp' | 'stripe' | 'mp_movement'; target_type: 'customer' | 'order' | 'account'; target_id: string; } interface PaymentUnlinkedEvent extends UserActivityEventBase { event: 'Payment Unlinked'; payment_id: string; source: 'mp' | 'stripe' | 'mp_movement'; } interface PaymentLinkageUpdatedEvent extends UserActivityEventBase { event: 'Payment Linkage Updated'; payment_id: string; before: Record; after: Record; } interface NotificationReadEvent extends UserActivityEventBase { event: 'Notification Read'; notification_id?: string; bulk: boolean; read_count?: number; } interface LogDeletedEvent extends UserActivityEventBase { event: 'Log Deleted'; log_mode: string; deleted_count: number; } interface PlanCreatedEvent extends UserActivityEventBase { event: 'Plan Created'; tier: string; name: string; } interface StoreMaintenanceToggledEvent extends UserActivityEventBase { event: 'Store Maintenance Toggled'; enabled: boolean; reason?: string; } interface PlatformMaintenanceToggledEvent extends UserActivityEventBase { event: 'Platform Maintenance Toggled'; enabled: boolean; reason?: string; } interface TenantCreatedEvent extends UserActivityEventBase { event: 'Tenant Created'; target_store_id: string; name: string; } type LiteralScope = 'GLOBAL' | 'APP' | 'PLATFORM' | 'WEB' | `APP#${string}` | `WEB#${string}`; interface LiteralUpdatedEvent extends UserActivityEventBase { event: 'Literal Updated'; scope: LiteralScope; key: string; before: string; after: string; } interface SupportTicketCreatedEvent extends UserActivityEventBase { event: 'Support Ticket Created'; ticket_id: string; subject: string; } interface SupportTicketUpdatedEvent extends UserActivityEventBase { event: 'Support Ticket Updated'; ticket_id: string; fields_changed: string[]; } interface AuditTrailViewedEvent extends UserActivityEventBase { event: 'Audit Trail Viewed'; scope: 'tenant' | 'platform'; filters?: Record; } interface ReportViewedEvent extends UserActivityEventBase { event: 'Report Viewed'; report_id: string; report_name: string; } interface CustomerPiiViewedEvent extends UserActivityEventBase { event: 'Customer PII Viewed'; customer_id: string; fields_revealed: string[]; } interface CashDrawerUiOpenedEvent extends UserActivityEventBase { event: 'Cash Drawer UI Opened'; cash_id: string; } interface CashDrawerUiClosedEvent extends UserActivityEventBase { event: 'Cash Drawer UI Closed'; cash_id: string; } interface ExportInitiatedEvent extends UserActivityEventBase { event: 'Export Initiated'; format: 'csv' | 'pdf' | 'xlsx'; entity_type: string; row_count: number; } interface ImpersonationUiStartedEvent extends UserActivityEventBase { event: 'Impersonation UI Started'; target_store_id: string; return_to?: string; } interface ImpersonationUiEndedEvent extends UserActivityEventBase { event: 'Impersonation UI Ended'; target_store_id: string; } interface PaymentViewedEvent extends UserActivityEventBase { event: 'Payment Viewed'; payment_id: string; source?: string; linked_status?: 'linked' | 'unlinked'; } interface InvoiceViewedEvent extends UserActivityEventBase { event: 'Invoice Viewed'; invoice_id: string; fiscal_status?: string; } interface CustomerDetailViewedEvent extends UserActivityEventBase { event: 'Customer Detail Viewed'; customer_id: string; } interface SupplierAccountViewedEvent extends UserActivityEventBase { event: 'Supplier Account Viewed'; supplier_id: string; } interface SearchPerformedEvent extends UserActivityEventBase { event: 'Search Performed'; scope: 'customers' | 'audit' | 'suppliers' | 'invoices' | 'payments'; query_hash?: string; result_count?: number; } interface ActionDeniedEvent extends UserActivityEventBase { event: 'Action Denied'; attempted_action: string; resource_type: string; resource_id?: string; reason: 'permission' | 'subscription' | 'maintenance'; } interface TwoFactorChallengeShownEvent extends UserActivityEventBase { event: 'Two-Factor Challenge Shown'; method: 'password' | 'social'; provider?: string; } interface TwoFactorCodeValidationFailedEvent extends UserActivityEventBase { event: 'Two-Factor Code Validation Failed'; method: 'totp' | 'recovery'; attempt_number?: number; } interface TwoFactorEnrollmentStartedEvent extends UserActivityEventBase { event: 'Two-Factor Enrollment Started'; } interface TwoFactorRecoveryCodesRevealedEvent extends UserActivityEventBase { event: 'Two-Factor Recovery Codes Revealed'; code_count: number; } interface TwoFactorResetInitiatedEvent extends UserActivityEventBase { event: 'Two-Factor Reset Initiated'; target_user_id: string; initiator_role: string; } interface IntegrationTokenRefreshedEvent extends UserActivityEventBase { event: 'Integration Token Refreshed'; provider: 'mercadopago' | 'gmail' | 'mercadolibre'; outcome: 'refreshed' | 'disconnected' | 'skipped' | 'error'; trigger: 'operator-single' | 'operator-global'; detail?: string; } interface WaitlistConvertedEvent extends UserActivityEventBase { event: 'Waitlist Converted'; } interface PlatformConfigUpdatedEvent extends UserActivityEventBase { event: 'Platform Config Updated'; key: string; kind: 'setting' | 'flag'; scope: 'app' | 'landing' | 'storefront'; before: string | number | boolean; after: string | number | boolean; } interface MlChannelStatusChangedEvent extends UserActivityEventBase { event: 'ML Channel Status Changed'; provider: 'mercadolibre'; product_id: string; ml_item_id: string; from_status: ProductChannelStatus; to_status: ProductChannelStatus; } interface PrintRuleCreatedEvent extends UserActivityEventBase { event: 'Print Rule Created'; use_case: PrintUseCase; agent_id: string; printer_id: string; } interface PrintRuleEditedEvent extends UserActivityEventBase { event: 'Print Rule Edited'; use_case: PrintUseCase; agent_id: string; printer_id: string; fields_changed: string[]; } interface PrintRuleDeletedEvent extends UserActivityEventBase { event: 'Print Rule Deleted'; use_case: PrintUseCase; agent_id: string; printer_id: string; } interface WebhookCreatedEvent extends UserActivityEventBase { event: 'Webhook Created'; webhook_id: string; url: string; events: import('./webhook').WebhookEventType[]; } interface WebhookUpdatedEvent extends UserActivityEventBase { event: 'Webhook Updated'; webhook_id: string; fields_changed: string[]; } interface WebhookDeletedEvent extends UserActivityEventBase { event: 'Webhook Deleted'; webhook_id: string; url: string; } interface PrinterActiveToggledEvent extends UserActivityEventBase { event: 'Printer Active Toggled'; agent_id: string; printer_id: string; active: boolean; } interface PrinterRawFormatsUpdatedEvent extends UserActivityEventBase { event: 'Printer Raw Formats Updated'; agent_id: string; printer_id: string; raw_formats: ('zpl' | 'escpos')[]; } /** * A MANAGER edit to a PLAN TEMPLATE's own fields via * `PATCH /platform/billing/plans/{tier}` — the catalog row, not anybody's * subscription. * * ⚠️ Deliberately NOT `Plan Changed`, which models a TENANT moving between * subscription tiers. Reusing that variant here would make a catalog edit * indistinguishable from a customer upgrade in the audit feed, and the two * have different actors, different blast radius and different retention * interest. `before`/`after` follow `StoreSettingsUpdatedEvent`'s shape. */ interface PlanTemplateUpdatedEvent extends UserActivityEventBase { event: 'Plan Template Updated'; tier: string; before: Record; after: Record; } interface SupplierAccountDeletedEvent extends UserActivityEventBase { event: 'Supplier Account Deleted'; supplier_id: string; account_id: string; } interface CustomerPasswordResetInitiatedEvent extends UserActivityEventBase { event: 'Customer Password Reset Initiated'; customer_id: string; email_sent: boolean; } interface UserSessionsRevokedEvent extends UserActivityEventBase { event: 'User Sessions Revoked'; target_user_id: string; revoked_count: number; } interface UserPasswordResetInitiatedByOperatorEvent extends UserActivityEventBase { event: 'User Password Reset Initiated by Operator'; target_user_id: string; email_sent: boolean; } interface AgentCommandDispatchedEvent extends UserActivityEventBase { event: 'Agent Command Dispatched'; agent_id: string; command: import('./socket').AgentCommand; command_id: string; destructive: boolean; } type UserActivityEvent = UserLoggedInEvent | UserLoggedOutEvent | UserPasswordChangedEvent | UserSuspendedEvent | TwoFactorEnrolledEvent | TwoFactorDisabledEvent | TwoFactorResetEvent | TwoFactorRecoveryCodesGeneratedEvent | StorePaletteChangedEvent | StoreSettingsUpdatedEvent | PlanChangedEvent | InvoiceCreatedEvent | OrderCreatedEvent | OrderCancelledEvent | OrderEditedEvent | OrderReturnedEvent | ProductPriceChangedEvent | CustomerCreatedEvent | CustomerEditedEvent | CashDrawerOpenedEvent | CashDrawerClosedEvent | TenantImpersonatedEvent | SecretRotatedEvent | UserCreatedEvent | UserUpdatedEvent | ProductCreatedEvent | ProductUpdatedEvent | StockIncomeCreatedEvent | StockAdjustedEvent | CategoryCreatedEvent | CategoryUpdatedEvent | BrandCreatedEvent | BrandUpdatedEvent | SupplierCreatedEvent | SupplierUpdatedEvent | SupplierInvoiceCreatedEvent | SupplierAccountCreatedEvent | SupplierAccountUpdatedEvent | AccountCreatedEvent | AccountDeletedEvent | BasketUpdatedEvent | BasketDiscountGrantedEvent | BasketDeletedEvent | CashDrawerMovementEvent | PaymentCreatedEvent | PaymentLinkedEvent | PaymentUnlinkedEvent | PaymentLinkageUpdatedEvent | NotificationReadEvent | LogDeletedEvent | PlanCreatedEvent | StoreMaintenanceToggledEvent | PlatformMaintenanceToggledEvent | TenantCreatedEvent | LiteralUpdatedEvent | SupportTicketCreatedEvent | SupportTicketUpdatedEvent | AuditTrailViewedEvent | ReportViewedEvent | CustomerPiiViewedEvent | CashDrawerUiOpenedEvent | CashDrawerUiClosedEvent | ExportInitiatedEvent | ImpersonationUiStartedEvent | ImpersonationUiEndedEvent | PaymentViewedEvent | InvoiceViewedEvent | CustomerDetailViewedEvent | SupplierAccountViewedEvent | SearchPerformedEvent | ActionDeniedEvent | TwoFactorChallengeShownEvent | TwoFactorCodeValidationFailedEvent | TwoFactorEnrollmentStartedEvent | TwoFactorRecoveryCodesRevealedEvent | TwoFactorResetInitiatedEvent | IntegrationTokenRefreshedEvent | WaitlistConvertedEvent | PlatformConfigUpdatedEvent | MlChannelStatusChangedEvent | PrintRuleCreatedEvent | PrintRuleEditedEvent | PrintRuleDeletedEvent | WebhookCreatedEvent | WebhookUpdatedEvent | WebhookDeletedEvent | PrinterActiveToggledEvent | PrinterRawFormatsUpdatedEvent | PlanTemplateUpdatedEvent | SupplierAccountDeletedEvent | CustomerPasswordResetInitiatedEvent | UserSessionsRevokedEvent | UserPasswordResetInitiatedByOperatorEvent | AgentCommandDispatchedEvent | ClockedInEvent | ClockedOutEvent; } /** * Canonical whitelist of UI-only `UserActivityEvent` variant names. Imported by * the api side (`POST /audit/user-activity`) to gate the FE-ingest endpoint: * any `event` value NOT in this set originates from a BE mutating handler and * must be rejected to prevent the FE from spoofing audit emissions. */ export declare const UI_ONLY_USER_ACTIVITY_VARIANTS: readonly ["Audit Trail Viewed", "Report Viewed", "Customer PII Viewed", "Cash Drawer UI Opened", "Cash Drawer UI Closed", "Export Initiated", "Impersonation UI Started", "Impersonation UI Ended", "Payment Viewed", "Invoice Viewed", "Customer Detail Viewed", "Supplier Account Viewed", "Search Performed", "Action Denied", "Two-Factor Challenge Shown", "Two-Factor Code Validation Failed", "Two-Factor Enrollment Started", "Two-Factor Recovery Codes Revealed", "Two-Factor Reset Initiated"]; export type UiOnlyUserActivityVariant = (typeof UI_ONLY_USER_ACTIVITY_VARIANTS)[number]; /** * Valid per-entity timeline entity types for the user-activity audit feed. * MUST stay in sync with the BE `VALID_ENTITY_TYPES` const in * `sinfactura/api/stacks/helpers/userActivity/query.ts`, which Zod-enums the * `entityType` query param. (That const moved out of * `stacks/lambdas/userActivity/_get.ts`, which still exists but no longer * holds it.) The BE const carries `satisfies readonly UserActivityEntityType[]`, * so widening this union is safe but narrowing it breaks the api build. * * `printer` and `printer_agent` are distinct types because their ids have * different uniqueness scope: `agentId` is unique store-wide, but `printerId` * is unique only WITHIN an agent — two agents can each report a printer under * the same id. A `printer` row must therefore key on the composite * `(agentId, printerId)` pair, never on `printerId` alone. */ export type UserActivityEntityType = 'order' | 'invoice' | 'supplier_invoice' | 'payment' | 'account' | 'customer' | 'supplier' | 'product' | 'user' | 'target_store' | 'brand' | 'category' | 'cash' | 'ticket' | 'report' | 'notification' | 'printer' | 'printer_agent' | 'attendance_shift';