{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-04T00:05:00.828Z","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","content":"You are Henrik Aalto, Chief Information Security Officer at Tidewick Commerce. Joined from a cloud provider's security team. Several enterprise deals depend on passing security reviews.\n\nYour area: Account security for merchants' staff and shoppers: sign-ins, sessions, multi-factor authentication, API tokens and service accounts, access to sensitive data, audit trails.\n\nYou are new, and you want to understand your part of the business quickly. You do not know the data model and you do not write SQL. Your analyst, Noah Fischer, will spend this week on your questions and send you a memo on Friday. The analyst has only a few warehouse reports per night, so tell them what matters most to you.\n\nWrite a short note to Noah Fischer:\n\n- 3 to 5 questions in your own words, broad and open, as a newly hired leader would ask;\n- what you need the answers for.\n\nDo not name tables or columns, and do not prescribe the analysis.\n"}
{"type":"queue-operation","operation":"dequeue","timestamp":"2026-10-04T00:05:00.828Z","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36"}
{"parentUuid":null,"isSidechain":false,"promptId":"a7686f2b-07af-42da-876a-81e6fadde1e9","type":"user","message":{"role":"user","content":"You are Henrik Aalto, Chief Information Security Officer at Tidewick Commerce. Joined from a cloud provider's security team. Several enterprise deals depend on passing security reviews.\n\nYour area: Account security for merchants' staff and shoppers: sign-ins, sessions, multi-factor authentication, API tokens and service accounts, access to sensitive data, audit trails.\n\nYou are new, and you want to understand your part of the business quickly. You do not know the data model and you do not write SQL. Your analyst, Noah Fischer, will spend this week on your questions and send you a memo on Friday. The analyst has only a few warehouse reports per night, so tell them what matters most to you.\n\nWrite a short note to Noah Fischer:\n\n- 3 to 5 questions in your own words, broad and open, as a newly hired leader would ask;\n- what you need the answers for.\n\nDo not name tables or columns, and do not prescribe the analysis.\n"},"uuid":"8fd7e123-6075-49c5-87d8-61297e73f850","timestamp":"2026-10-04T00:05:00.838Z","permissionMode":"default","promptSource":"sdk","userType":"external","entrypoint":"sdk-cli","cwd":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","version":"2.1.275","gitBranch":"HEAD"}
{"parentUuid":"8fd7e123-6075-49c5-87d8-61297e73f850","isSidechain":false,"attachment":{"type":"environment","snapshot":{"workingDirectory":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","isWorktree":false,"isGitRepo":false,"additionalWorkingDirectories":[],"platform":"darwin","shell":"zsh","osVersion":"Darwin 27.0.0"}},"type":"attachment","uuid":"94099b80-4adc-495c-83e1-7a0325904ca6","timestamp":"2026-10-04T00:05:00.837Z","rendered":[{"content":"<system-reminder>\n# Environment\nYou have been invoked in the following environment: \n - Primary working directory: /private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1\n - Is a git repository: false\n - Platform: darwin\n - Shell: zsh\n - OS Version: Darwin 27.0.0\n</system-reminder>"}],"userType":"external","entrypoint":"sdk-cli","cwd":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","version":"2.1.275","gitBranch":"HEAD"}
{"parentUuid":"94099b80-4adc-495c-83e1-7a0325904ca6","isSidechain":false,"attachment":{"type":"model","identity":{"modelId":"claude-sonnet-5","marketingName":"Sonnet 5","knowledgeCutoff":"January 2026"},"text":"You are powered by the model named Sonnet 5. The exact model ID is claude-sonnet-5. Assistant knowledge cutoff is January 2026."},"type":"attachment","uuid":"b3bdda39-eadf-47dc-b78f-30696aeaab18","timestamp":"2026-10-04T00:05:00.837Z","rendered":[{"content":"<system-reminder>\nYou are powered by the model named Sonnet 5. The exact model ID is claude-sonnet-5. Assistant knowledge cutoff is January 2026.\n</system-reminder>"}],"userType":"external","entrypoint":"sdk-cli","cwd":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","version":"2.1.275","gitBranch":"HEAD"}
{"type":"atis-latch","atis":"","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36"}
{"parentUuid":"b3bdda39-eadf-47dc-b78f-30696aeaab18","isSidechain":false,"attachment":{"type":"session_context","context":{"userEmail":"The user's email address is [redacted: e-mail address]. Use it only to identify the user, such as for authorship, attribution, or filtering their own work. Never send it to an unrelated service, such as in a request header, URL, or payload, unless the user explicitly asks."}},"type":"attachment","uuid":"f17e70bc-1f5b-413a-bbad-507d59df0115","timestamp":"2026-10-04T00:05:00.843Z","rendered":[{"content":"<system-reminder>\nAs you answer the user's questions, you can use the following context:\n# userEmail\nThe user's email address is [redacted: e-mail address]. Use it only to identify the user, such as for authorship, attribution, or filtering their own work. Never send it to an unrelated service, such as in a request header, URL, or payload, unless the user explicitly asks.\n\nIMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.\n</system-reminder>"}],"userType":"external","entrypoint":"sdk-cli","cwd":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","version":"2.1.275","gitBranch":"HEAD"}
{"parentUuid":"f17e70bc-1f5b-413a-bbad-507d59df0115","isSidechain":false,"attachment":{"type":"date","date":"2026-10-04"},"type":"attachment","uuid":"9665363f-a066-442f-8b03-1f6ceb76574b","timestamp":"2026-10-04T00:05:00.843Z","rendered":[{"content":"<system-reminder>\nToday's date is 2026-10-04.\n</system-reminder>"}],"userType":"external","entrypoint":"sdk-cli","cwd":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","version":"2.1.275","gitBranch":"HEAD"}
{"parentUuid":"9665363f-a066-442f-8b03-1f6ceb76574b","isSidechain":false,"attachment":{"type":"prompt_snapshot","systemPrompt":["# Tidewick Commerce: company briefing\n\n**Tidewick Commerce** is an all-in-one commerce platform for mid-size merchants. It provides:\n\n- storefront and checkout;\n- payments;\n- inventory and fulfillment;\n- subscriptions;\n- a light B2B CRM;\n- a helpdesk;\n- marketing tools.\n\nTidewick was founded in 2018 in Amsterdam. It has offices in London and Austin and about 400 employees.\n\n## Merchants\n\nAbout 120 merchants run their business on Tidewick, and each merchant is a tenant of the platform. A handful of large merchants generate most of the volume, with a long tail of small ones behind them.\n\nThe merchants work in these verticals:\n\n- apparel and footwear;\n- consumer electronics;\n- beauty;\n- home and living;\n- sports and outdoor;\n- food and coffee subscriptions;\n- pet supplies;\n- a few B2B wholesalers (industrial supplies, office equipment).\n\nMerchants are mostly based in the UK, the Netherlands, Germany, France, the Nordics and the US. Their shoppers come from about 40 countries.\n\n## How Tidewick makes money\n\nMerchants pay a monthly platform plan. Tidewick also keeps a processing fee on every payment it settles for them; payouts show this as gross, fee and net.\n\n## The data\n\n- Production runs on one multi-tenant database: every business table carries the merchant's `tenant_id`.\n- The production database is replicated nightly into the analytics warehouse.\n- Tidewick moved to this database at the start of 2023, so history starts on 2023-01-01.\n- Timestamps are stored in UTC.\n- Amounts are in the merchant's currency. `exchange_rates` has daily rates from EUR.\n- The full table catalog is in `schema.md`.\n\n## Today\n\nToday is **Monday, 2026-01-05**. The warehouse holds complete data through 2025-12-31.\n\nTidewick has just hired several new leaders. Each of them wants to understand their part of the business quickly and relies on the analytics team to do so.\n\nThe analytics team works through the warehouse, as described in `handbook.md`.\n\n\n# Analytics handbook\n\nThis handbook explains how the analytics team at Tidewick turns questions into numbers.\n\n## The people\n\n- **Leaders** (CEO, CFO, department heads) bring broad questions about their area.\n- **Analysts** each work for one leader. An analyst:\n  - turns the leader's questions into concrete report requests;\n  - reads the printed reports and follows up;\n  - writes a short memo for the leader at the end of the week.\n- **Data engineers** turn each report request into one SQL query. They can check the query against the schema as often as they like before submitting it.\n\n## The warehouse is expensive\n\nThe warehouse is shared and metered, and every executed query is billed to the department. We therefore run reports as an overnight batch:\n\n- An analyst has **3 report slots per night** and the week has **4 nights**.\n- The memo is due on Friday morning.\n- A slot is spent whenever a request is submitted, whether it runs, fails or is bounced by review.\n- Unused slots do not carry over.\n- Checking a query against the schema (a \"dry run\") is free. It runs the query against an empty copy of the warehouse schema, so it catches errors and shows the output columns, but it never reads or shows any data.\n\nThink before you request. Make one request answer one question well.\n\n## One request, one report\n\nEach request produces one printed report:\n\n- **Title and question.** Every report has a title and the analyst's question it answers.\n- **One grain.** A report is one table at a single grain: every row is the same kind of thing (a country, a week, a merchant, a carrier and month). The columns are measures or attributes of that thing.\n- **The page has a width.** A report has at most 12 columns, and about 10 is comfortable.\n- **The page has a length.** At most 50 rows are printed. The report notes how many rows the query returned in total.\n- **Readable columns.** Order the rows so the important ones come first, and give columns readable names.\n\nDense reports are good. For example, one row per country and month can carry these columns:\n\n- orders;\n- revenue;\n- refund rate;\n- share of late deliveries;\n- tickets per 100 orders;\n- the same month last year.\n\nData engineering reviews every request before it runs and **bounces bundled ones**, where several unrelated questions are packed into one report. Reports wider than 12 columns are bounced too. A bounced request still uses its slot.\n\nIf you have two questions, request two reports.\n\n## House SQL standards (for data engineers)\n\n- Write **one SELECT statement**. Do not end it with a semicolon.\n- Use **standard SQL** wherever possible.\n- **Dates:** never use `CURRENT_DATE` or `now()`. The reporting date is 2026-01-05 and the data ends on 2025-12-31.\n- **Deterministic ordering:** always finish with an ORDER BY whose tie-breaks make the order unique.\n- **Tenants:** filter or group by `tenant_id` when a question is about particular merchants. Join `tenants` for merchant names.\n- **Money:** amounts are in the currency given next to them (usually a `currency_code` column; a merchant's main currency is `tenants.default_currency_code`).\n  - Convert amounts before adding them up across merchants that use different currencies.\n  - `exchange_rates` holds a rate from EUR (`base_code`) for every day; weekends carry the previous business day's rate.\n- **Rates:** round rates and averages for the page.\n- **Catalog:** the table catalog (`schema.md`) lists every table, column, foreign key and the allowed codes of status columns.\n\n\n# Table catalog\n\n# Tidewick Commerce data warehouse: table catalog\n\nThe production database, replicated nightly. Every business table carries the merchant's\n`tenant_id`. Timestamps are UTC. Money columns are in the row's `currency_code` (merchants sell\nin their own currency); `exchange_rates` has daily EUR reference rates.\nStatus columns reference small lookup tables; their codes are listed with each table.\nAll tables live in one schema: refer to them by name only (`shipments`, not `sales.shipments`).\nThe sections below only group the tables by business area.\n\n## Area: analytics\n\n### web_sessions\nVisitor analytics sessions, distinct from sessions (auth). Anonymous traffic still produces a session with a null user_id.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `anonymous_id` uuid (Client-generated visitor identifier used to stitch sessions before the user authenticates.)\n- `started_at` timestamp not null\n- `ended_at` timestamp\n- `landing_page` varchar(2048)\n- `referrer` varchar(2048)\n- `utm_source` varchar(255)\n- `utm_medium` varchar(255)\n- `utm_campaign` varchar(255)\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `is_bounce` boolean not null\n- `page_view_count` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### events\nThe primary product analytics fact table. Each row is one tracked event with a free-form event_name and schemaless JSON properties.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `web_session_id` bigint -> web_sessions (Nullable link to the analytics web session this event occurred in; server-side events may have none.)\n- `event_name` varchar(64) not null\n- `properties` json not null\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### page_views\nIndividual page hits within a web session, used for path and dwell-time analysis.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `web_session_id` bigint not null -> web_sessions\n- `url` varchar(2048) not null\n- `path` varchar(2048)\n- `title` varchar(255)\n- `referrer` varchar(2048)\n- `duration_seconds` integer\n- `viewed_at` timestamp not null\n- `created_at` timestamp not null\n\n### device_profiles\nBrowser/device fingerprints observed per tenant, optionally linked to a user once authenticated.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `fingerprint` varchar(255) not null\n- `device_type` varchar(64)\n- `os` varchar(64)\n- `os_version` varchar(64)\n- `browser` varchar(64)\n- `browser_version` varchar(64)\n- `screen_width` integer\n- `screen_height` integer\n- `first_seen_at` timestamp not null\n- `last_seen_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, fingerprint)\n\n### feature_usage\nPer-user, per-day product feature adoption counters feeding engagement and stickiness metrics.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `feature_key` varchar(64) not null (Stable identifier of the product feature being measured (e.g. bulk_export, dashboard_share).)\n- `usage_count` integer not null\n- `usage_day` date not null\n- `last_used_at` timestamp\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n- unique: (tenant_id, user_id, feature_key, usage_day)\n\n### experiments\nServer-side product experiments (feature experiments), distinct from ab_tests which are message-level tests.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `key` varchar(64) not null\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `hypothesis` varchar(4000)\n- `status` varchar(32) not null one of: draft, running, paused, completed, archived\n- `primary_metric` varchar(255)\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, key)\n\n### experiment_variants\nVariants (arms) of an experiment; exactly one may be flagged is_control, with allocation_pct summing across arms.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `experiment_id` bigint not null -> experiments\n- `key` varchar(64) not null\n- `name` varchar(255) not null\n- `is_control` boolean not null\n- `allocation_pct` decimal(5,2) not null\n- `config` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (experiment_id, key)\n\n### experiment_assignments\nRecords which variant a user (or anonymous visitor) was bucketed into for an experiment.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `experiment_id` bigint not null -> experiments\n- `variant_id` bigint not null -> experiment_variants\n- `user_id` bigint -> users\n- `anonymous_id` uuid\n- `assigned_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (experiment_id, user_id)\n\n### funnels\nOrdered conversion-funnel definitions evaluated over the events stream.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `is_active` boolean not null\n- `window_days` integer\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### funnel_steps\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `funnel_id` bigint not null -> funnels\n- `step_order` integer not null\n- `name` varchar(255) not null\n- `event_name` varchar(64) not null\n- `filters` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (funnel_id, step_order)\n\n### cohorts\nNamed user cohorts, dynamic (rule-based) or static, used for retention and segmentation analysis.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `definition` json not null\n- `is_dynamic` boolean not null\n- `member_count` integer not null\n- `computed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### cohort_members\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `cohort_id` bigint not null -> cohorts\n- `user_id` bigint not null -> users\n- `added_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (cohort_id, user_id)\n\n### metrics_daily\nPre-aggregated daily metric values per tenant; one row per (tenant, metric_key, day) for fast dashboard reads.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `metric_key` varchar(64) not null\n- `metric_date` date not null\n- `metric_value` decimal(20,6) not null\n- `dimensions` json not null (Optional breakdown labels (e.g. {\"channel\":\"organic\"}) when a metric is sliced; empty object for the headline total.)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, metric_key, metric_date)\n\n### kpi_snapshots\nPeriodic captures of headline KPIs (MRR, active users, conversion rate) with the prior-period value for trend deltas.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `kpi_key` varchar(64) not null\n- `period_grain` varchar(64) not null\n- `period_start` date not null\n- `period_end` date not null\n- `kpi_value` decimal(20,6) not null\n- `previous_value` decimal(20,6)\n- `metadata` json not null\n- `captured_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, kpi_key, period_grain, period_start)\n\n## Area: audit\n\n### audit_log\nCentral append-only audit trail of meaningful mutations and security actions. The audited row is referenced polymorphically via entity_schema/entity_table/entity_id (no FK).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `action` varchar(32) not null one of: create, update, delete, login, logout, export, access_denied\n- `actor_user_id` bigint -> users\n- `actor_type` varchar(64) not null (Principal kind that performed the action: user, api_key, service_account, or system.)\n- `entity_schema` varchar(64)\n- `entity_table` varchar(64)\n- `entity_id` bigint (Primary-key id of the audited row in entity_schema.entity_table; intentionally not a foreign key.)\n- `summary` varchar(4000)\n- `metadata` json not null\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `request_id` varchar(64)\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### change_history\nColumn-level before/after JSON diffs for an audited row, usually linked to an audit_log entry.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `audit_log_id` bigint -> audit_log\n- `actor_user_id` bigint -> users\n- `entity_schema` varchar(64) not null\n- `entity_table` varchar(64) not null\n- `entity_id` bigint not null\n- `changed_columns` varchar(255)[] not null\n- `before_data` json not null\n- `after_data` json not null\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### access_log\nAppend-only record of reads of sensitive/PII data, for compliance and breach forensics.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `actor_user_id` bigint -> users\n- `entity_schema` varchar(64) not null\n- `entity_table` varchar(64) not null\n- `entity_id` bigint\n- `access_kind` varchar(64) not null\n- `field_names` varchar(255)[] not null\n- `purpose` varchar(64)\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### data_exports\ndata_exports — GDPR / data-portability export jobs. Tracks request through completion; the produced artifact lives in object storage (file_url).\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `requested_by_user_id` bigint -> users\n- `subject_user_id` bigint -> users\n- `status` varchar(64) not null\n- `format` varchar(64) not null\n- `file_url` varchar(2048)\n- `row_count` bigint\n- `requested_at` timestamp not null\n- `completed_at` timestamp\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id)\n\n### consent_records\nAppend-only per-user consent grants and withdrawals by purpose; the latest row per (user, purpose) is the effective consent state.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `purpose` varchar(64) not null\n- `status` varchar(32) not null one of: granted, withdrawn, expired\n- `source` varchar(64)\n- `policy_version` varchar(64) (Version of the privacy policy / terms the user consented to at the time.)\n- `ip_address` varchar(45)\n- `granted_at` timestamp\n- `withdrawn_at` timestamp\n- `expires_at` timestamp\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### compliance_events\ncompliance_events — flagged compliance incidents / policy violations needing review (suspicious access, retention overrun, failed DSAR SLA, ...).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `event_type` varchar(64) not null\n- `severity` varchar(64) not null\n- `detected_by_user_id` bigint -> users\n- `entity_schema` varchar(64)\n- `entity_table` varchar(64)\n- `entity_id` bigint\n- `details` json not null\n- `resolved` boolean not null\n- `resolved_at` timestamp\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### data_subject_requests\nGDPR/CCPA data-subject requests (access, erasure, rectification, portability) tracked through their processing lifecycle.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `subject_user_id` bigint -> users\n- `subject_email` varchar(254)\n- `request_type` varchar(64) not null (Kind of DSAR: access, erasure, rectification, portability, or restriction.)\n- `status` varchar(64) not null\n- `assigned_to_user_id` bigint -> users\n- `data_export_id` bigint -> data_exports\n- `notes` varchar(4000)\n- `received_at` timestamp not null\n- `due_at` timestamp\n- `completed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id)\n\n### retention_policies\nPer-entity data-retention rules (retention_days + action_on_expiry) driving automated purges.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `entity_schema` varchar(64) not null\n- `entity_table` varchar(64) not null\n- `retention_days` integer not null\n- `action_on_expiry` varchar(64) not null\n- `is_active` boolean not null\n- `last_run_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, entity_schema, entity_table)\n\n### legal_holds\nActive legal/investigation holds that suspend retention-driven deletion for a subject or entity scope.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `reason` varchar(4000)\n- `retention_policy_id` bigint -> retention_policies\n- `subject_user_id` bigint -> users\n- `entity_schema` varchar(64)\n- `entity_table` varchar(64)\n- `entity_id` bigint\n- `placed_by_user_id` bigint -> users\n- `is_active` boolean not null\n- `placed_at` timestamp not null\n- `released_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### audit_log_archive\nCold archive of aged-out audit_log rows; same shape as audit_log, written once by the archival job. source_audit_log_id preserves the original id.\n- `id` bigint not null\n- `source_audit_log_id` bigint\n- `tenant_id` bigint not null -> tenants\n- `action` varchar(32) not null one of: create, update, delete, login, logout, export, access_denied\n- `actor_user_id` bigint\n- `actor_type` varchar(64) not null\n- `entity_schema` varchar(64)\n- `entity_table` varchar(64)\n- `entity_id` bigint\n- `summary` varchar(4000)\n- `metadata` json not null\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `request_id` varchar(64)\n- `occurred_at` timestamp not null\n- `archived_at` timestamp not null\n- `created_at` timestamp not null\n\n## Area: billing\n\n### billing_profiles\nCustomer billing entity per tenant: legal name, tax id, billing email, net payment terms. One marked is_default per tenant.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `legal_name` varchar(255) not null\n- `billing_email` varchar(254)\n- `tax_id` varchar(64)\n- `net_terms_days` integer not null (Payment terms in days (Net-0/Net-30/Net-60) before an invoice is past due.)\n- `default_currency_code` varchar(3) not null -> currencies\n- `is_default` boolean not null\n- `metadata` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n\n### payment_methods\nStored payment instruments. Only a gateway token plus brand/last4 are kept; raw card numbers are never stored.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `billing_profile_id` bigint -> billing_profiles\n- `kind` varchar(32) not null one of: card, bank_transfer, paypal, wallet, wire, credit\n- `gateway` varchar(64) not null\n- `gateway_token` varchar(255) not null\n- `brand` varchar(255)\n- `last4` varchar(4)\n- `exp_month` smallint\n- `exp_year` smallint\n- `is_default` boolean not null\n- `billing_address_id` bigint -> addresses\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (gateway, gateway_token)\n\n### payments\nMoney-in captures against an order and/or invoice. amount_refunded tracks cumulative refunds for partial-refund accounting.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint -> orders\n- `invoice_id` bigint -> invoices\n- `payment_method_id` bigint -> payment_methods\n- `status` varchar(32) not null one of: pending, authorized, captured, failed, voided, refunded, partially_refunded\n- `amount` decimal(14,4) not null\n- `amount_refunded` decimal(14,4) not null (Running total of amounts refunded against this payment; must never exceed amount.)\n- `currency_code` varchar(3) not null -> currencies\n- `gateway` varchar(64) not null\n- `gateway_charge_id` varchar(255)\n- `authorized_at` timestamp\n- `captured_at` timestamp\n- `failure_code` varchar(64)\n- `metadata` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### payment_attempts\nEach gateway round-trip for a payment; a payment may need several attempts before it captures.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `payment_id` bigint not null -> payments\n- `attempt_number` integer not null\n- `status` varchar(32) not null one of: pending, authorized, captured, failed, voided, refunded, partially_refunded\n- `amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `gateway_response` json not null\n- `error_code` varchar(64)\n- `error_message` varchar(4000)\n- `attempted_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (payment_id, attempt_number)\n\n### payment_refunds\nRefunds reversing all or part of a captured payment, tracked through the refund_status lifecycle.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `payment_id` bigint not null -> payments\n- `status` varchar(32) not null one of: pending, succeeded, failed, cancelled\n- `amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `reason` varchar(4000)\n- `gateway_refund_id` varchar(255)\n- `processed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### invoices\nBilling documents (one-off or subscription). amount_due drives dunning; public_id is the opaque external identifier.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `billing_profile_id` bigint -> billing_profiles\n- `user_id` bigint -> users\n- `order_id` bigint -> orders\n- `subscription_id` bigint -> subscriptions\n- `invoice_number` varchar(64) not null\n- `status` varchar(32) not null one of: draft, open, paid, void, uncollectible, past_due\n- `currency_code` varchar(3) not null -> currencies\n- `subtotal` decimal(14,4) not null\n- `tax_total` decimal(14,4) not null\n- `discount_total` decimal(14,4) not null\n- `total` decimal(14,4) not null\n- `amount_paid` decimal(14,4) not null\n- `amount_due` decimal(14,4) not null (Outstanding balance still owed; reaches 0 when the invoice is fully paid.)\n- `issued_at` timestamp\n- `due_at` timestamp\n- `paid_at` timestamp\n- `voided_at` timestamp\n- `notes` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id); (tenant_id, invoice_number)\n\n### invoice_lines\nLine items on an invoice; optionally tied to a subscription_item, with the service period it covers.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `invoice_id` bigint not null -> invoices\n- `subscription_item_id` bigint -> subscription_items\n- `description` varchar(4000) not null\n- `quantity` decimal(14,4) not null\n- `unit_price` decimal(14,4) not null\n- `line_total` decimal(14,4) not null\n- `tax_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `period_start` timestamp\n- `period_end` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### credit_notes\nCredit memos issued against invoices for returns, goodwill, or corrections.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `invoice_id` bigint -> invoices\n- `billing_profile_id` bigint -> billing_profiles\n- `credit_note_number` varchar(64) not null\n- `currency_code` varchar(3) not null -> currencies\n- `subtotal` decimal(14,4) not null\n- `tax_total` decimal(14,4) not null\n- `total` decimal(14,4) not null\n- `reason` varchar(4000)\n- `issued_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, credit_note_number)\n\n### credit_note_lines\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `credit_note_id` bigint not null -> credit_notes\n- `invoice_line_id` bigint -> invoice_lines\n- `description` varchar(4000) not null\n- `quantity` decimal(14,4) not null\n- `unit_price` decimal(14,4) not null\n- `line_total` decimal(14,4) not null\n- `tax_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### payouts\nSettlements of collected funds out to the tenant; net = gross minus fees.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `currency_code` varchar(3) not null -> currencies\n- `gross_amount` decimal(14,4) not null\n- `fee_amount` decimal(14,4) not null\n- `net_amount` decimal(14,4) not null\n- `status` varchar(64) not null\n- `gateway_payout_id` varchar(255)\n- `arrival_date` date\n- `paid_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### payout_items\nThe individual payments settled within a payout; each payment appears in at most one payout.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `payout_id` bigint not null -> payouts\n- `payment_id` bigint not null -> payments\n- `amount` decimal(14,4) not null\n- `fee_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (payment_id)\n\n### ledger_accounts\nledger_accounts — the tenant's chart of accounts for the double-entry ledger. type is the accounting class (asset/liability/revenue/...).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `account_type` varchar(64) not null\n- `currency_code` varchar(3) not null -> currencies\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, code)\n\n### ledger_entries\nImmutable append-only double-entry ledger lines. Postings sharing an entry_group form one balanced journal entry.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ledger_account_id` bigint not null -> ledger_accounts\n- `entry_group` uuid not null (Groups the debit/credit rows of a single balanced journal entry (their amounts must net to zero).)\n- `direction` varchar(32) not null one of: debit, credit\n- `amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `payment_id` bigint -> payments\n- `invoice_id` bigint -> invoices\n- `description` varchar(4000)\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### plans\nSubscription products. code is the stable per-tenant key; default_interval sets the billing cadence.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `default_interval` varchar(32) not null one of: day, week, month, quarter, year\n- `interval_count` integer not null\n- `trial_period_days` integer not null\n- `is_active` boolean not null\n- `is_public` boolean not null\n- `metadata` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### plan_features\nFeature entitlements/limits per plan; limit_value NULL means unlimited.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `plan_id` bigint not null -> plans\n- `feature_key` varchar(64) not null\n- `limit_value` bigint (Quota for the feature (e.g. seats, api_calls); NULL means unlimited.)\n- `is_enabled` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (plan_id, feature_key)\n\n### plan_prices\nPrice points for a plan, one per currency and billing interval.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `plan_id` bigint not null -> plans\n- `currency_code` varchar(3) not null -> currencies\n- `billing_interval` varchar(32) not null one of: day, week, month, quarter, year\n- `interval_count` integer not null\n- `amount` decimal(14,4) not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (plan_id, currency_code, billing_interval, interval_count)\n\n### subscriptions\nA user's recurring commitment to a plan. current_period_* bound the next invoice; cancel_at_period_end defers cancellation.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `plan_id` bigint not null -> plans\n- `billing_profile_id` bigint -> billing_profiles\n- `payment_method_id` bigint -> payment_methods\n- `status` varchar(32) not null one of: trialing, active, past_due, canceled, paused, incomplete\n- `currency_code` varchar(3) not null -> currencies\n- `quantity` integer not null\n- `trial_ends_at` timestamp\n- `current_period_start` timestamp\n- `current_period_end` timestamp\n- `cancel_at_period_end` boolean not null (When true the subscription cancels at current_period_end rather than immediately.)\n- `canceled_at` timestamp\n- `started_at` timestamp not null\n- `ended_at` timestamp\n- `metadata` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id)\n\n### subscription_items\nLine items / add-ons of a subscription; unit_amount snapshots the price at the time of subscription.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `subscription_id` bigint not null -> subscriptions\n- `plan_price_id` bigint -> plan_prices\n- `description` varchar(4000)\n- `quantity` integer not null\n- `unit_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `is_metered` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### usage_records\nMetered usage reported for a subscription_item, aggregated into consumption-based invoice lines.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `subscription_item_id` bigint not null -> subscription_items\n- `quantity` decimal(14,4) not null\n- `unit` varchar(64)\n- `action` varchar(64) not null\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### dunning_attempts\nRetry sequence chasing payment on a past-due invoice; next_attempt_at schedules the following retry.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `invoice_id` bigint not null -> invoices\n- `subscription_id` bigint -> subscriptions\n- `payment_id` bigint -> payments\n- `attempt_number` integer not null\n- `outcome` varchar(64) not null\n- `next_attempt_at` timestamp\n- `attempted_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (invoice_id, attempt_number)\n\n### tax_registrations\nTenant tax IDs per jurisdiction (VAT/GST/sales tax) governing how tax is charged in a region.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `billing_profile_id` bigint -> billing_profiles\n- `country_code` varchar(2) not null\n- `jurisdiction` varchar(64)\n- `tax_type` varchar(64) not null\n- `registration_number` varchar(64) not null (The official tax/VAT registration number issued by the jurisdiction.)\n- `is_active` boolean not null\n- `valid_from` date\n- `valid_until` date\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, country_code, tax_type)\n\n### wallets\nStored-credit balances per user and currency (prepaid credit, refunds-to-credit, promo balance).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `currency_code` varchar(3) not null -> currencies\n- `balance` decimal(14,4) not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (user_id, currency_code)\n\n## Area: catalog\n\n### brands\nManufacturer/brand records that products are grouped under.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `description` varchar(4000)\n- `logo_url` varchar(2048)\n- `website_url` varchar(2048)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### categories\nHierarchical product taxonomy; parent_id is a self-reference to the parent category.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `parent_id` bigint -> categories\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `description` varchar(4000)\n- `sort_position` integer not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### category_closure\nTransitive closure of the category tree (ancestor, descendant, depth) for fast subtree queries.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ancestor_id` bigint not null -> categories\n- `descendant_id` bigint not null -> categories\n- `depth` integer not null (Number of edges between ancestor and descendant; 0 is the self-row.)\n- `created_at` timestamp not null\n- unique: (ancestor_id, descendant_id)\n\n### products\nProduct master records; public_id is the opaque external identifier exposed via API/URL.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `brand_id` bigint -> brands\n- `primary_category_id` bigint -> categories\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `description` varchar(4000)\n- `status` varchar(32) not null one of: draft, active, archived\n- `is_visible` boolean not null\n- `published_at` timestamp\n- `seo_title` varchar(255)\n- `seo_description` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (public_id); (tenant_id, slug)\n\n### product_variants\nSellable SKUs of a product; sku is unique per tenant. Reference price only — authoritative pricing lives in the pricing schema.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `sku` varchar(64) not null\n- `barcode` varchar(64)\n- `name` varchar(255)\n- `sort_position` integer not null\n- `price_amount` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `weight_grams` decimal(14,4)\n- `is_default` boolean not null (Exactly one variant per product may be the default (enforced by a partial unique index).)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (public_id); (tenant_id, sku)\n\n### product_categories\nproduct_categories — junction products×categories (a product can sit in many categories beyond its primary one).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `category_id` bigint not null -> categories\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- unique: (product_id, category_id)\n\n### attributes\nDefinitions of product/variant attributes (e.g. Color, Size); kind drives value interpretation.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `kind` varchar(32) not null one of: text, number, boolean, enum, color, dimension\n- `is_variant_defining` boolean not null (True when this attribute differentiates variants (Color, Size) rather than being descriptive.)\n- `is_filterable` boolean not null\n- `unit` varchar(64)\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, code)\n\n### attribute_values\nAllowed values for enum-kind attributes.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `attribute_id` bigint not null -> attributes\n- `value_code` varchar(255) not null\n- `label` varchar(255)\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (attribute_id, value_code)\n\n### variant_attribute_values\nJunction binding a variant to its attribute/value pairs (the combination that makes it unique).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `attribute_id` bigint not null -> attributes\n- `attribute_value_id` bigint -> attribute_values\n- `value_text` varchar(4000)\n- `created_at` timestamp not null\n- unique: (variant_id, attribute_id)\n\n### product_media\nImages, video, and documents attached to a product and optionally a specific variant.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `variant_id` bigint -> product_variants\n- `kind` varchar(32) not null one of: image, video, document, model_3d\n- `url` varchar(2048) not null\n- `alt_text` varchar(4000)\n- `sort_position` integer not null\n- `is_primary` boolean not null\n- `width_px` integer\n- `height_px` integer\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### collections\nMerchandising groupings of products; may be manual or rule-driven (is_automated).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `description` varchar(4000)\n- `is_automated` boolean not null\n- `rules` json\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### collection_products\ncollection_products — junction collections×products.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `collection_id` bigint not null -> collections\n- `product_id` bigint not null -> products\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- unique: (collection_id, product_id)\n\n### tags\ntags — free-form labels applied to products.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, slug)\n\n### product_tags\nproduct_tags — junction products×tags.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `tag_id` bigint not null -> tags\n- `created_at` timestamp not null\n- unique: (product_id, tag_id)\n\n### product_reviews\nCustomer reviews of products, rated 1-5 stars; one review per user per product.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `user_id` bigint -> users\n- `rating` integer not null\n- `title` varchar(255)\n- `body` varchar(65535)\n- `is_verified_purchase` boolean not null (True when the reviewer is known to have purchased the product.)\n- `is_approved` boolean not null\n- `helpful_count` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (product_id, user_id)\n\n### review_votes\nreview_votes — helpfulness votes cast by users on a review.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `review_id` bigint not null -> product_reviews\n- `user_id` bigint not null -> users\n- `is_helpful` boolean not null\n- `created_at` timestamp not null\n- unique: (review_id, user_id)\n\n### product_relations\nDirectional cross-sell/upsell/related links between two products.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `related_product_id` bigint not null -> products\n- `relation_kind` varchar(64) not null\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (product_id, related_product_id, relation_kind)\n\n### product_bundles\nHeader for a bundle/kit sold as a single offering, composed of variant components.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint -> products\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `description` varchar(4000)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, slug)\n\n### bundle_items\nComponent variants and quantities that make up a product bundle.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `bundle_id` bigint not null -> product_bundles\n- `variant_id` bigint not null -> product_variants\n- `quantity` integer not null\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (bundle_id, variant_id)\n\n## Area: comms\n\n### notification_templates\nReusable, per-tenant notification bodies keyed by code and channel; rendered into notifications and email/sms logs.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `channel` varchar(64) not null\n- `subject` varchar(255)\n- `body` varchar(65535) not null\n- `locale` varchar(5)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, code)\n\n### notifications\nPer-user notifications with a queued→sent→delivered→read lifecycle. template_id is null when composed ad hoc.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `template_id` bigint -> notification_templates\n- `channel` varchar(64) not null\n- `status` varchar(32) not null one of: queued, sent, delivered, read, failed\n- `title` varchar(255) not null\n- `body` varchar(65535)\n- `data` json not null\n- `sent_at` timestamp\n- `delivered_at` timestamp\n- `read_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### notification_preferences\nPer-user opt-in matrix across channels and categories; an absent row means the tenant default applies.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `channel` varchar(64) not null\n- `category` varchar(255) not null\n- `is_enabled` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (user_id, channel, category)\n\n### message_threads\nIn-app conversation threads between a tenant and a user. last_message_at is denormalized for inbox sorting.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `subject` varchar(255)\n- `channel` varchar(64) not null\n- `is_closed` boolean not null\n- `last_message_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### thread_messages\nthread_messages — individual messages within a thread. direction marks whether the message is inbound (from the user) or outbound (from the tenant).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `thread_id` bigint not null -> message_threads\n- `sender_user_id` bigint -> users\n- `direction` varchar(32) not null one of: inbound, outbound (inbound = sent by the user toward the tenant; outbound = sent by the tenant toward the user.)\n- `body` varchar(65535) not null\n- `attachments` json not null\n- `read_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### webhooks\nSubscriber HTTP endpoints that receive signed event callbacks. Only the hash of the signing secret is stored.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `created_by_user_id` bigint -> users\n- `name` varchar(255) not null\n- `target_url` varchar(2048) not null\n- `secret_hash` varchar(255) not null (Hash of the HMAC signing secret used to sign delivery payloads; the plaintext secret is shown once at creation.)\n- `event_types` varchar(64)[] not null\n- `is_active` boolean not null\n- `last_delivered_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### webhook_deliveries\nAppend-only log of each webhook delivery attempt, including HTTP response_code and retry attempt number.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `webhook_id` bigint not null -> webhooks\n- `event_type` varchar(64) not null\n- `status` varchar(32) not null one of: pending, success, failed, retrying\n- `attempt` integer not null (1-based retry counter; multiple rows per logical event when a delivery is retried.)\n- `response_code` integer\n- `request_body` json\n- `response_body` varchar(65535)\n- `duration_ms` integer\n- `delivered_at` timestamp\n- `created_at` timestamp not null\n\n### email_log\nImmutable record of every outbound email; recipient_email is denormalized since the user or address may be off-platform.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `template_id` bigint -> notification_templates\n- `recipient_email` varchar(254) not null\n- `from_email` varchar(254)\n- `subject` varchar(255)\n- `provider` varchar(64)\n- `provider_message_id` varchar(255)\n- `status` varchar(32) not null one of: pending, success, failed, retrying\n- `error_message` varchar(4000)\n- `sent_at` timestamp\n- `created_at` timestamp not null\n\n### sms_log\nsms_log — IMMUTABLE record of each outbound SMS. (Intentionally uncommented at table level — see CONVENTIONS.md on deliberate comment gaps.)\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `template_id` bigint -> notification_templates\n- `recipient_phone` varchar(32) not null\n- `from_number` varchar(32)\n- `body` varchar(65535) not null\n- `provider` varchar(64)\n- `provider_message_id` varchar(255)\n- `status` varchar(32) not null one of: pending, success, failed, retrying\n- `error_message` varchar(4000)\n- `segments` integer not null\n- `sent_at` timestamp\n- `created_at` timestamp not null\n\n### push_tokens\nDevice push-notification tokens per user; deactivated rather than deleted when a device unregisters.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `platform` varchar(64) not null\n- `token` varchar(255) not null\n- `device_name` varchar(255)\n- `is_active` boolean not null\n- `last_used_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, token)\n\n### unsubscribes\nSuppression list of addresses/channels that must be excluded from further sends; user_id may be null for off-platform addresses.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `channel` varchar(64) not null\n- `address` varchar(255) not null\n- `category` varchar(255) not null\n- `reason` varchar(4000)\n- `unsubscribed_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, channel, address, category)\n\n### contact_channels\nVerified contact endpoints (email/phone) per user with a verification lifecycle; one primary per kind via a partial unique index.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `kind` varchar(64) not null\n- `channel_address` varchar(255) not null\n- `is_primary` boolean not null\n- `is_verified` boolean not null\n- `verified_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (user_id, kind, channel_address)\n\n## Area: crm\n\n### accounts\nThe tenant's own B2B customer companies. Tenant-scoped; distinct from tenants (the platform's customers).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `legal_name` varchar(255)\n- `domain` varchar(255)\n- `industry` varchar(255)\n- `employee_count` integer\n- `annual_revenue` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `owner_user_id` bigint -> users (Internal sales rep (users) who owns the account relationship.)\n- `phone` varchar(32)\n- `website` varchar(2048)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### contacts\nPeople who work at a CRM account. primary_email is denormalized; the full set lives in contact_emails.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `account_id` bigint -> accounts\n- `owner_user_id` bigint -> users\n- `first_name` varchar(255)\n- `last_name` varchar(255)\n- `primary_email` varchar(254)\n- `phone` varchar(32)\n- `job_title` varchar(255)\n- `department` varchar(255)\n- `is_primary` boolean not null\n- `do_not_contact` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n\n### contact_emails\nAll email addresses for a contact; at most one flagged is_primary per contact.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `contact_id` bigint not null -> contacts\n- `email` varchar(254) not null\n- `label` varchar(255) not null\n- `is_primary` boolean not null\n- `verified_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (contact_id, email)\n\n### leads\nUnqualified inbound interest before it becomes an account/contact; converts forward once qualified.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `owner_user_id` bigint -> users\n- `first_name` varchar(255)\n- `last_name` varchar(255)\n- `email` varchar(254)\n- `phone` varchar(32)\n- `company` varchar(255)\n- `job_title` varchar(255)\n- `status` varchar(32) not null one of: new, contacted, qualified, unqualified, converted\n- `source` varchar(64)\n- `score` integer not null (Lead score (0+); higher means more sales-ready.)\n- `converted_account_id` bigint -> accounts\n- `converted_contact_id` bigint -> contacts\n- `converted_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### pipelines\nNamed sales pipelines per tenant; at most one is the default. Stages live in opportunity_stages.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `is_default` boolean not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### opportunity_stages\nOrdered lookup of stages within a pipeline. position sets funnel order; win_probability drives weighted forecasts.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `pipeline_id` bigint not null -> pipelines\n- `name` varchar(255) not null\n- `sort_position` integer not null\n- `win_probability` decimal(5,4) not null (Default close probability for opportunities in this stage, 0..1.)\n- `is_won` boolean not null\n- `is_lost` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (pipeline_id, sort_position); (pipeline_id, name)\n\n### opportunities\nDeals in flight: an account being worked through a pipeline stage with an expected amount.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `account_id` bigint not null -> accounts\n- `pipeline_id` bigint not null -> pipelines\n- `stage_id` bigint not null -> opportunity_stages\n- `primary_contact_id` bigint -> contacts\n- `owner_user_id` bigint -> users\n- `name` varchar(255) not null\n- `amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `probability` decimal(5,4)\n- `source` varchar(64)\n- `is_closed` boolean not null\n- `is_won` boolean not null\n- `expected_close_date` date\n- `closed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### deals\nClosed-won contracts realized from an opportunity, holding the booked contract value and term.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `account_id` bigint not null -> accounts\n- `opportunity_id` bigint -> opportunities\n- `owner_user_id` bigint -> users\n- `name` varchar(255) not null\n- `contract_value` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `signed_at` date\n- `starts_at` date\n- `ends_at` date\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### deal_line_items\nProducts sold on a deal, priced per unit in the deal currency.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `deal_id` bigint not null -> deals\n- `product_id` bigint -> products\n- `description` varchar(4000)\n- `quantity` integer not null\n- `unit_price` decimal(14,4) not null\n- `discount_pct` decimal(5,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### activities\nLogged interactions (calls, emails, meetings) against an account, contact, or opportunity.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `kind` varchar(32) not null one of: call, email, meeting, note, task\n- `account_id` bigint -> accounts\n- `contact_id` bigint -> contacts\n- `opportunity_id` bigint -> opportunities\n- `owner_user_id` bigint -> users\n- `subject` varchar(255)\n- `body` varchar(65535)\n- `occurred_at` timestamp not null\n- `duration_minutes` integer\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### crm_notes\ncrm_notes — free-text notes attached to a CRM entity (polymorphic target).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `account_id` bigint -> accounts\n- `contact_id` bigint -> contacts\n- `opportunity_id` bigint -> opportunities\n- `author_user_id` bigint -> users\n- `body` varchar(65535) not null\n- `is_pinned` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### tasks\nFollow-up to-dos assigned to reps, optionally linked to a CRM entity.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `assignee_user_id` bigint -> users\n- `account_id` bigint -> accounts\n- `contact_id` bigint -> contacts\n- `opportunity_id` bigint -> opportunities\n- `title` varchar(255) not null\n- `description` varchar(4000)\n- `priority` varchar(64) not null\n- `is_done` boolean not null\n- `due_at` timestamp\n- `completed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### account_relationships\nParent/subsidiary and other directed links between accounts within a tenant.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `parent_account_id` bigint not null -> accounts\n- `child_account_id` bigint not null -> accounts\n- `relationship_type` varchar(64) not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (parent_account_id, child_account_id, relationship_type)\n\n### contact_lists\ncontact_lists — static, manually-curated lists of contacts.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `owner_user_id` bigint -> users\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### contact_list_members\ncontact_list_members — junction of contacts into static lists.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `contact_list_id` bigint not null -> contact_lists\n- `contact_id` bigint not null -> contacts\n- `added_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- unique: (contact_list_id, contact_id)\n\n## Area: geo\n\n### countries\nISO-3166 country reference. Global, shared by all tenants.\n- `id` bigint not null\n- `iso2` varchar(2) not null\n- `iso3` varchar(3) not null\n- `name` varchar(255) not null\n- `phone_code` varchar(32)\n- `continent` varchar(64)\n- unique: (iso2); (iso3)\n\n### regions\n- `id` bigint not null\n- `country_id` bigint not null -> countries\n- `code` varchar(64)\n- `name` varchar(255) not null\n\n### cities\n- `id` bigint not null\n- `region_id` bigint -> regions\n- `country_id` bigint not null -> countries\n- `name` varchar(255) not null\n- `latitude` decimal(9,6)\n- `longitude` decimal(9,6)\n\n### currencies\nISO-4217 currencies. The code column (e.g. USD) is the FK target used by all monetary tables.\n- `id` bigint not null\n- `code` varchar(3) not null\n- `name` varchar(255) not null\n- `symbol` varchar(64)\n- `minor_unit` smallint not null (Number of decimal places (2 for USD, 0 for JPY).)\n- unique: (code)\n\n### exchange_rates\nDaily FX rates between currency pairs, used to normalize multi-currency revenue.\n- `id` bigint not null\n- `base_code` varchar(3) not null -> currencies\n- `quote_code` varchar(3) not null -> currencies\n- `rate` decimal(12,6) not null\n- `as_of` date not null\n- unique: (base_code, quote_code, as_of)\n\n### calendar_days\nCalendar dimension: one row per day from 2023-01-01 to 2026-12-31 with week, month and quarter attributes. Join to it to build complete time series (days, weeks or months without activity still appear).\n- `calendar_date` date not null (The day.)\n- `year_num` integer not null (Calendar year.)\n- `quarter_num` smallint not null (Calendar quarter, 1-4.)\n- `month_num` smallint not null (Calendar month, 1-12.)\n- `month_name` varchar(9) not null (English month name, e.g. 'November'.)\n- `month_label` varchar(7) not null (Year and month as 'YYYY-MM'.)\n- `day_of_month` smallint not null (Day of the month, 1-31.)\n- `day_of_year` smallint not null (Day of the year, 1-366.)\n- `iso_year` integer not null (ISO 8601 week-numbering year (differs from year_num around New Year).)\n- `iso_week` smallint not null (ISO 8601 week number, 1-53.)\n- `iso_day_of_week` smallint not null (ISO day of the week: 1 = Monday ... 7 = Sunday.)\n- `day_name` varchar(9) not null (English day name, e.g. 'Monday'.)\n- `is_weekend` boolean not null (Saturday or Sunday.)\n- `week_start` date not null (Monday of the ISO week.)\n- `month_start` date not null (First day of the month.)\n- `quarter_start` date not null (First day of the quarter.)\n\n### locales\n- `id` bigint not null\n- `code` varchar(5) not null\n- `name` varchar(255) not null\n- `native_name` varchar(255)\n- unique: (code)\n\n### timezones\n- `id` bigint not null\n- `name` varchar(255) not null\n- `utc_offset_minutes` integer not null\n- unique: (name)\n\n### addresses\nPostal addresses owned by a tenant and optionally a user. At most one default address per user.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `label` varchar(255)\n- `line1` varchar(255) not null\n- `line2` varchar(255)\n- `city` varchar(255)\n- `region_id` bigint -> regions\n- `country_id` bigint not null -> countries\n- `postal_code` varchar(64)\n- `latitude` decimal(9,6)\n- `longitude` decimal(9,6)\n- `is_default` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### address_validations\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `address_id` bigint not null -> addresses\n- `provider` varchar(64) not null\n- `is_valid` boolean not null\n- `response` json\n- `validated_at` timestamp not null\n\n### postal_zones\n- `id` bigint not null\n- `country_id` bigint not null -> countries\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- unique: (country_id, code)\n\n## Area: identity\n\n### tenants\nCustomer organizations on the platform; the root of multi-tenancy. Every tenant-scoped row hangs off a tenant_id.\n- `id` bigint not null\n- `public_id` uuid not null\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `status` varchar(32) not null one of: trial, active, past_due, suspended, churned\n- `default_currency_code` varchar(3) not null -> currencies (ISO-4217 default currency.)\n- `default_locale` varchar(5) not null -> locales\n- `industry` varchar(255)\n- `employee_count` integer\n- `trial_ends_at` timestamp\n- `signup_source` varchar(64)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (public_id); (slug)\n\n### tenant_settings\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `setting_key` varchar(64) not null\n- `setting_value` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, setting_key)\n\n### users\nEnd users and staff belonging to a tenant. Email is unique per tenant, not globally.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `email` varchar(254) not null\n- `full_name` varchar(255)\n- `status` varchar(32) not null one of: active, invited, suspended, deactivated\n- `is_staff` boolean not null (True for internal/admin operators of the tenant, as opposed to customer end-users.)\n- `locale` varchar(5) -> locales\n- `timezone` varchar(64)\n- `password_hash` varchar(255)\n- `last_login_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (public_id); (tenant_id, email)\n\n### user_profiles\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `avatar_url` varchar(2048)\n- `bio` varchar(4000)\n- `phone` varchar(32)\n- `job_title` varchar(255)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (user_id)\n\n### user_preferences\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `theme` varchar(64) not null\n- `email_opt_in` boolean not null\n- `marketing_opt_in` boolean not null\n- `locale_override` varchar(5)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (user_id)\n\n### roles\nPer-tenant RBAC roles. is_system marks built-in roles that cannot be deleted.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `is_system` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### permissions\nGlobal catalog of permission codes (e.g. orders.refund). Shared across all tenants.\n- `id` bigint not null\n- `code` varchar(64) not null\n- `description` varchar(4000)\n- `category` varchar(255)\n- unique: (code)\n\n### role_permissions\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `role_id` bigint not null -> roles\n- `permission_id` bigint not null -> permissions\n- `created_at` timestamp not null\n- unique: (role_id, permission_id)\n\n### user_roles\nAssignment of roles to users. granted_by_user_id records who made the grant.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `role_id` bigint not null -> roles\n- `granted_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- unique: (user_id, role_id)\n\n### teams\nteams — hierarchical sub-org groupings.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `parent_team_id` bigint -> teams\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### team_members\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `team_id` bigint not null -> teams\n- `user_id` bigint not null -> users\n- `team_role` varchar(64) not null\n- `created_at` timestamp not null\n- unique: (team_id, user_id)\n\n### api_keys\nProgrammatic API credentials for a tenant. Only the hash and a short prefix are stored.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `prefix` varchar(64) not null\n- `hashed_key` varchar(64) not null\n- `status` varchar(32) not null one of: active, revoked, expired\n- `created_by_user_id` bigint -> users\n- `last_used_at` timestamp\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id); (prefix)\n\n### personal_access_tokens\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `name` varchar(255) not null\n- `hashed_token` varchar(255) not null\n- `scopes` varchar(64)[] not null\n- `last_used_at` timestamp\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- unique: (hashed_token)\n\n### sessions\nAuthenticated browser/app sessions. revoked_at non-null means force-logged-out.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `token_hash` varchar(255) not null\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `started_at` timestamp not null\n- `last_seen_at` timestamp not null\n- `expires_at` timestamp not null\n- `revoked_at` timestamp\n- unique: (token_hash)\n\n### oauth_accounts\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `provider` varchar(64) not null\n- `provider_account_id` varchar(255) not null\n- `access_token_hash` varchar(255)\n- `created_at` timestamp not null\n- unique: (provider, provider_account_id)\n\n### mfa_devices\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `kind` varchar(64) not null\n- `secret_hash` varchar(255) not null\n- `confirmed_at` timestamp\n- `created_at` timestamp not null\n\n### invitations\nPending invitations to join a tenant; consumed when status becomes accepted.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `email` varchar(254) not null\n- `role_id` bigint -> roles\n- `invited_by_user_id` bigint -> users\n- `status` varchar(32) not null one of: pending, accepted, expired, revoked\n- `token_hash` varchar(255) not null\n- `expires_at` timestamp not null\n- `accepted_at` timestamp\n- `created_at` timestamp not null\n- unique: (token_hash)\n\n### password_resets\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `token_hash` varchar(255) not null\n- `expires_at` timestamp not null\n- `used_at` timestamp\n- `created_at` timestamp not null\n- unique: (token_hash)\n\n### service_accounts\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### login_attempts\nAudit of authentication attempts, successful and failed, for security analytics.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `email` varchar(254)\n- `succeeded` boolean not null\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `occurred_at` timestamp not null\n\n## Area: inventory\n\n### warehouses\nPhysical fulfillment sites for a tenant, each anchored to a postal address; one is flagged as the default.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `address_id` bigint -> addresses\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `is_default` boolean not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### stock_locations\nBins, shelves and zones inside a warehouse. Stock is counted at this granularity, not at the warehouse level.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `warehouse_id` bigint not null -> warehouses\n- `code` varchar(64) not null\n- `name` varchar(255)\n- `kind` varchar(64) not null\n- `is_pickable` boolean not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (warehouse_id, code)\n\n### suppliers\nVendors that goods are purchased from on purchase orders.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `contact_email` varchar(254)\n- `contact_phone` varchar(32)\n- `lead_time_days` integer\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### supplier_products\nPer-supplier sourcing of a variant: their SKU and unit cost; is_preferred marks the default source.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `supplier_id` bigint not null -> suppliers\n- `variant_id` bigint not null -> product_variants\n- `supplier_sku` varchar(64)\n- `unit_cost` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `min_order_qty` integer\n- `lead_time_days` integer\n- `is_preferred` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (supplier_id, variant_id)\n\n### stock_items\nLive on-hand quantity of a variant in a single location; one row per (variant, location). The movements ledger is its history.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `stock_location_id` bigint not null -> stock_locations\n- `quantity` integer not null\n- `reserved_quantity` integer not null (Portion of quantity earmarked by open reservations and not available to sell.)\n- `reorder_point` integer (Replenishment trigger: at or below this on-hand level the variant is flagged for reorder.)\n- `reorder_quantity` integer\n- `last_counted_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (variant_id, stock_location_id)\n\n### stock_movements\nImmutable append-only ledger of inventory quantity changes; quantity is signed (in positive, out negative) and the running sum reproduces stock_items.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `stock_location_id` bigint not null -> stock_locations\n- `lot_id` bigint -> lots\n- `kind` varchar(32) not null one of: receipt, shipment, adjustment, transfer, return, cycle_count\n- `quantity` integer not null\n- `reference_type` varchar(64)\n- `reference_id` bigint (Loose pointer to the source document (PO line, order, adjustment) identified by reference_type; not a foreign key.)\n- `note` varchar(4000)\n- `created_by_user_id` bigint -> users\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### stock_reservations\nSoft holds that earmark stock for an order so it is not double-sold; order_id references orders by id but has no FK (forward module).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `stock_location_id` bigint not null -> stock_locations\n- `order_id` bigint -> orders\n- `quantity` integer not null\n- `expires_at` timestamp\n- `released_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### purchase_orders\nReplenishment orders sent to a supplier and delivered to a warehouse; public_id is the opaque external identifier.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `supplier_id` bigint not null -> suppliers\n- `warehouse_id` bigint -> warehouses\n- `po_number` varchar(64) not null\n- `status` varchar(32) not null one of: draft, submitted, partially_received, received, cancelled\n- `currency_code` varchar(3) not null -> currencies\n- `expected_total` decimal(14,4) not null (Denormalized order total; the authoritative figure is the sum over purchase_order_lines.)\n- `notes` varchar(4000)\n- `ordered_at` timestamp\n- `expected_at` timestamp\n- `received_at` timestamp\n- `created_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id); (tenant_id, po_number)\n\n### purchase_order_lines\nLine items on a purchase order: variant, quantity and agreed unit cost, with received quantity accruing as goods arrive.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `purchase_order_id` bigint not null -> purchase_orders\n- `variant_id` bigint not null -> product_variants\n- `quantity_ordered` integer not null\n- `quantity_received` integer not null\n- `unit_cost` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (purchase_order_id, variant_id)\n\n### lots\nBatch/lot tracking for a variant, carrying manufacture and expiry dates for FEFO picking and recalls.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `supplier_id` bigint -> suppliers\n- `lot_code` varchar(64) not null\n- `manufactured_at` date\n- `expires_at` date\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, variant_id, lot_code)\n\n### serial_numbers\nserial_numbers — individually tracked units (one row per physical unit), optionally grouped under a lot and located in a stock location.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `lot_id` bigint -> lots\n- `stock_location_id` bigint -> stock_locations\n- `serial` varchar(64) not null\n- `status` varchar(64) not null\n- `received_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, serial)\n\n### inbound_receipts\nGoods-receipt headers recording a physical delivery into a warehouse, usually against a purchase order.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `purchase_order_id` bigint -> purchase_orders\n- `warehouse_id` bigint not null -> warehouses\n- `receipt_number` varchar(64) not null\n- `carrier` varchar(64)\n- `tracking_number` varchar(64)\n- `received_by_user_id` bigint -> users\n- `received_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, receipt_number)\n\n### receipt_lines\nreceipt_lines — the quantities of each variant actually received on a receipt, put away into a specific location (and optionally a lot).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `inbound_receipt_id` bigint not null -> inbound_receipts\n- `purchase_order_line_id` bigint -> purchase_order_lines\n- `variant_id` bigint not null -> product_variants\n- `stock_location_id` bigint -> stock_locations\n- `lot_id` bigint -> lots\n- `quantity_received` integer not null\n- `quantity_rejected` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### stock_adjustments\nManual corrections to on-hand stock (cycle counts, shrinkage, damage); quantity_delta is signed and a reason is required.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `stock_location_id` bigint not null -> stock_locations\n- `lot_id` bigint -> lots\n- `quantity_delta` integer not null\n- `reason` varchar(4000) not null\n- `note` varchar(4000)\n- `adjusted_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n## Area: marketing\n\n### email_templates\nReusable email subject/body templates used by campaigns and per-recipient sends.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `subject` varchar(255) not null\n- `body_html` varchar(65535)\n- `body_text` varchar(65535)\n- `from_name` varchar(255)\n- `from_email` varchar(254)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### segments\nAudience definitions; the selection logic lives in the rules jsonb and is materialized into segment_members.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `rules` json not null\n- `is_dynamic` boolean not null (True if membership is recomputed from rules on a schedule; false for static lists.)\n- `member_count` integer not null\n- `last_refreshed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### segment_members\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `segment_id` bigint not null -> segments\n- `user_id` bigint not null -> users\n- `added_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (segment_id, user_id)\n\n### landing_pages\nlanding_pages — campaign destination pages. Referenced by campaigns and utm_links, so defined ahead of them.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `slug` varchar(255) not null\n- `title` varchar(255) not null\n- `url` varchar(2048)\n- `content_html` varchar(65535)\n- `is_published` boolean not null\n- `published_at` timestamp\n- `view_count` bigint not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### campaigns\nMarketing campaigns; may target a segment, render via a template, drive to a landing page, and link a pricing promotion.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `channel` varchar(64) not null\n- `status` varchar(32) not null one of: draft, scheduled, sending, sent, paused, archived\n- `segment_id` bigint -> segments\n- `email_template_id` bigint -> email_templates\n- `landing_page_id` bigint -> landing_pages\n- `promotion_id` bigint -> promotions\n- `budget` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `scheduled_at` timestamp\n- `started_at` timestamp\n- `ended_at` timestamp\n- `created_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### campaign_messages\nOne row per recipient send; send_status tracks the delivery and engagement lifecycle.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `campaign_id` bigint not null -> campaigns\n- `user_id` bigint not null -> users\n- `email_template_id` bigint -> email_templates\n- `status` varchar(32) not null one of: queued, sent, delivered, opened, clicked, bounced, complained, failed, unsubscribed\n- `recipient_email` varchar(254)\n- `sent_at` timestamp\n- `delivered_at` timestamp\n- `opened_at` timestamp\n- `clicked_at` timestamp\n- `bounced_at` timestamp\n- `unsubscribed_at` timestamp\n- `error_message` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### ab_tests\nA/B test definitions; winner_variant_id records the chosen variant once the experiment completes.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `campaign_id` bigint -> campaigns\n- `name` varchar(255) not null\n- `hypothesis` varchar(4000)\n- `status` varchar(32) not null one of: draft, running, paused, completed, archived\n- `goal_metric` varchar(255)\n- `started_at` timestamp\n- `ended_at` timestamp\n- `winner_variant_id` bigint\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### ab_variants\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ab_test_id` bigint not null -> ab_tests\n- `name` varchar(255) not null\n- `is_control` boolean not null\n- `traffic_weight` decimal(5,4) not null (Fraction of eligible traffic routed to this variant; weights across a test should sum to ~1.)\n- `config` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (ab_test_id, name)\n\n### ab_assignments\nSticky assignment of a user to one variant within an A/B test; converted_at records goal completion.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ab_test_id` bigint not null -> ab_tests\n- `ab_variant_id` bigint not null -> ab_variants\n- `user_id` bigint not null -> users\n- `assigned_at` timestamp not null\n- `converted_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (ab_test_id, user_id)\n\n### attributions\nTouch attributions crediting a campaign with a conversion; order_id is set for purchase conversions.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `campaign_id` bigint -> campaigns\n- `order_id` bigint -> orders\n- `model` varchar(64) not null\n- `touch_point` varchar(64)\n- `weight` decimal(5,4) not null (Fractional credit for this touch under the attribution model (e.g. linear, last_touch).)\n- `attributed_revenue` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### referrals\nReferral codes owned by a referring user, with optional reward and redemption caps.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `referrer_user_id` bigint not null -> users\n- `code` varchar(64) not null\n- `reward_amount` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `max_redemptions` integer\n- `redemption_count` integer not null\n- `is_active` boolean not null\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### referral_redemptions\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `referral_id` bigint not null -> referrals\n- `referred_user_id` bigint not null -> users\n- `order_id` bigint -> orders\n- `reward_amount` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `redeemed_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (referral_id, referred_user_id)\n\n### loyalty_programs\nLoyalty: program defs -> per-user accounts (balance) -> immutable earn/burn transaction ledger.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `points_per_currency` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `is_active` boolean not null\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### loyalty_accounts\nPer-user point balance within a loyalty program; points_balance is the current redeemable total.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `loyalty_program_id` bigint not null -> loyalty_programs\n- `user_id` bigint not null -> users\n- `points_balance` bigint not null\n- `lifetime_points` bigint not null\n- `tier` varchar(64)\n- `enrolled_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (loyalty_program_id, user_id)\n\n### loyalty_transactions\nImmutable earn/burn ledger of loyalty point movements; append-only, never updated.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `loyalty_account_id` bigint not null -> loyalty_accounts\n- `user_id` bigint not null -> users\n- `direction` varchar(64) not null\n- `points` bigint not null\n- `reason` varchar(4000)\n- `order_id` bigint -> orders\n- `balance_after` bigint (Account points_balance immediately after this entry was applied.)\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### utm_links\nTracked links carrying UTM parameters, optionally tied to a campaign and landing page.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `campaign_id` bigint -> campaigns\n- `landing_page_id` bigint -> landing_pages\n- `slug` varchar(255) not null\n- `target_url` varchar(2048) not null\n- `utm_source` varchar(255)\n- `utm_medium` varchar(255)\n- `utm_campaign` varchar(255)\n- `utm_term` varchar(255)\n- `utm_content` varchar(255)\n- `click_count` bigint not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n## Area: ops\n\n### feature_flags\nFeature toggle and rollout definitions, addressed by a tenant-unique key. kind distinguishes boolean toggles from multivariate and percentage rollouts.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `key` varchar(64) not null\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `kind` varchar(32) not null one of: boolean, multivariate, percentage_rollout\n- `is_enabled` boolean not null\n- `default_value` json not null (Fallback value served when no rule or override matches; jsonb to support multivariate payloads, not just booleans.)\n- `rollout_percentage` integer\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, key)\n\n### feature_flag_rules\nOrdered targeting rules for a flag; the first rule whose jsonb conditions match decides the served value.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `feature_flag_id` bigint not null -> feature_flags\n- `sort_position` integer not null\n- `conditions` json not null\n- `serve_value` json not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (feature_flag_id, sort_position)\n\n### feature_flag_overrides\nExplicit per-user or tenant-wide overrides that bypass rule evaluation for a flag.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `feature_flag_id` bigint not null -> feature_flags\n- `user_id` bigint -> users\n- `override_value` json not null\n- `reason` varchar(4000)\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### settings\nTyped key/value configuration per tenant. value_type tags how the jsonb value should be interpreted by the application.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `setting_key` varchar(64) not null\n- `setting_value` json not null\n- `value_type` varchar(64) not null (Logical type hint for value (string/number/boolean/json/date); the column itself is always jsonb.)\n- `is_secret` boolean not null\n- `description` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, setting_key)\n\n### jobs\nBackground job definitions registered per tenant; job_runs records each execution.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `queue` varchar(64) not null\n- `handler` varchar(64) not null\n- `payload` json not null\n- `max_attempts` integer not null\n- `is_enabled` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### job_runs\nImmutable record of a single background job execution, including status, attempt number and timing.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `job_id` bigint not null -> jobs\n- `status` varchar(32) not null one of: queued, running, succeeded, failed, cancelled, retrying\n- `attempt` integer not null\n- `queued_at` timestamp not null\n- `started_at` timestamp\n- `finished_at` timestamp\n- `duration_ms` integer\n- `error_message` varchar(4000)\n- `result_payload` json\n- `created_at` timestamp not null\n\n### integrations\nThird-party service connections for a tenant (e.g. ERP, shipping, accounting), with current status and config.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `provider` varchar(64) not null\n- `name` varchar(255) not null\n- `status` varchar(32) not null one of: connected, disconnected, error, syncing\n- `config` json not null\n- `connected_by_user_id` bigint -> users\n- `connected_at` timestamp\n- `last_synced_at` timestamp\n- `last_error` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, provider, name)\n\n### integration_syncs\nImmutable record of a single data-sync run against an integration.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `integration_id` bigint not null -> integrations\n- `status` varchar(32) not null one of: queued, running, succeeded, failed, cancelled, retrying\n- `direction` varchar(64) not null\n- `records_processed` integer not null\n- `records_failed` integer not null\n- `started_at` timestamp\n- `finished_at` timestamp\n- `error_message` varchar(4000)\n- `created_at` timestamp not null\n\n### files\nMetadata for files uploaded to object storage; the actual blob lives in storage_bucket/storage_key, not in the database.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `filename` varchar(255) not null\n- `content_type` varchar(64) not null\n- `byte_size` bigint not null\n- `storage_bucket` varchar(255) not null\n- `storage_key` varchar(2048) not null\n- `checksum` varchar(255)\n- `uploaded_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (public_id); (storage_bucket, storage_key)\n\n### file_versions\nVersion chain for a file; is_current flags the active version (one per file, enforced by partial unique index).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `file_id` bigint not null -> files\n- `version` integer not null\n- `byte_size` bigint not null\n- `storage_key` varchar(2048) not null\n- `checksum` varchar(255)\n- `is_current` boolean not null\n- `created_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (file_id, version)\n\n### import_batches\nBulk data-import runs with row-level progress counters and a terminal status.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `entity_type` varchar(64) not null\n- `source_filename` varchar(255)\n- `file_id` bigint -> files\n- `status` varchar(32) not null one of: pending, processing, completed, failed, partially_failed\n- `total_rows` integer not null\n- `processed_rows` integer not null\n- `succeeded_rows` integer not null\n- `failed_rows` integer not null\n- `started_by_user_id` bigint -> users\n- `started_at` timestamp\n- `finished_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### import_errors\nImmutable per-row failures captured during an import batch, with the offending raw row preserved as jsonb.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `import_batch_id` bigint not null -> import_batches\n- `source_row_number` integer not null\n- `column_name` varchar(64)\n- `error_code` varchar(64) not null\n- `error_message` varchar(4000) not null\n- `raw_row` json\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### scheduled_tasks\nCron-expression driven recurring tasks; next_run_at is the precomputed next fire time used by the scheduler.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `cron_expression` varchar(2048) not null\n- `timezone` varchar(64) not null\n- `job_id` bigint -> jobs\n- `is_enabled` boolean not null\n- `last_run_at` timestamp\n- `next_run_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### system_health_checks\nsystem_health_checks — immutable periodic health probe results.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `component` varchar(255) not null\n- `is_healthy` boolean not null\n- `latency_ms` integer\n- `details` json\n- `checked_at` timestamp not null\n- `created_at` timestamp not null\n\n### rate_limits\nRequest quota windows scoped per tenant or per API key; an api_key-null row is the tenant-wide default for a scope.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `api_key_id` bigint -> api_keys\n- `limit_scope` varchar(64) not null\n- `max_requests` integer not null\n- `window_seconds` integer not null\n- `is_enabled` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### secrets_vault\nReferences and metadata for secrets held in an external vault; never stores plaintext, only a provider pointer (external_ref).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `key` varchar(64) not null\n- `provider` varchar(64) not null\n- `external_ref` varchar(255) not null (Opaque pointer into the external secret store (e.g. a Vault path or KMS key id); resolved at runtime, never the secret value itself.)\n- `version` integer not null\n- `last_rotated_at` timestamp\n- `expires_at` timestamp\n- `created_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, key)\n\n## Area: pricing\n\n### price_lists\nNamed price books, each scoped to one currency; a tenant keeps a default list per currency plus contract/regional books.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `code` varchar(64) not null\n- `currency_code` varchar(3) not null -> currencies\n- `is_default` boolean not null\n- `is_active` boolean not null\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### prices\nA variant's price within a price list over a validity window; an exclusion constraint forbids overlapping windows for the same list/variant/min_quantity.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `price_list_id` bigint not null -> price_lists\n- `variant_id` bigint not null -> product_variants\n- `currency_code` varchar(3) not null -> currencies\n- `amount` decimal(14,4) not null\n- `compare_at_amount` decimal(14,4) (Optional strike-through / \"was\" price shown next to the effective amount.)\n- `min_quantity` integer not null\n- `valid_from` timestamp not null\n- `valid_to` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### discounts\nReusable discount definitions; coupons, price_rules and promotions point at these for the actual reduction.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `kind` varchar(32) not null one of: percentage, fixed_amount, free_shipping, bogo\n- `discount_value` decimal(14,4) not null (Percentage (0-100) when kind=percentage, else a fixed monetary amount in currency_code.)\n- `currency_code` varchar(3) -> currencies\n- `max_amount` decimal(14,4)\n- `min_subtotal` decimal(14,4)\n- `applies_to_shipping` boolean not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n\n### coupons\nRedeemable codes granting a discount; code is unique per tenant and usage is capped by usage_limit / per_customer_limit.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `discount_id` bigint not null -> discounts\n- `code` varchar(64) not null\n- `status` varchar(32) not null one of: active, scheduled, expired, disabled\n- `usage_limit` integer\n- `per_customer_limit` integer\n- `times_redeemed` integer not null (Denormalized running count of redemptions, capped by usage_limit.)\n- `starts_at` timestamp\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### coupon_redemptions\nImmutable record of each coupon use; order_id references orders.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `coupon_id` bigint not null -> coupons\n- `user_id` bigint -> users\n- `order_id` bigint -> orders\n- `discount_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `redeemed_at` timestamp not null\n- `created_at` timestamp not null\n\n### tax_categories\ntax_categories — product tax classes (e.g. standard goods, digital, food, exempt). Variants/products are mapped to one of these to find their rate.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `code` varchar(64) not null\n- `description` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, code)\n\n### tax_zones\nGeographic tax regions defined at country, region or postal-zone granularity.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `country_id` bigint -> countries\n- `region_id` bigint -> regions\n- `postal_zone_id` bigint -> postal_zones\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### tax_rates\nEffective tax rate for a (tax_category, tax_zone) pair, with an optional validity window so rate changes stay auditable.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `tax_category_id` bigint not null -> tax_categories\n- `tax_zone_id` bigint not null -> tax_zones\n- `name` varchar(255) not null\n- `rate` decimal(8,5) not null (Fractional rate in [0,1]; e.g. 0.20000 for 20% VAT.)\n- `is_compound` boolean not null\n- `is_inclusive` boolean not null (True when displayed prices already include this tax (tax-inclusive pricing).)\n- `valid_from` timestamp not null\n- `valid_to` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### price_rules\nprice_rules — conditional pricing rule headers (e.g. \"10% off when cart has 3+ items from brand X\"). Conditions live in price_rule_conditions. The reward references a discount definition. priority orders evaluation; lower = first.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `discount_id` bigint -> discounts\n- `name` varchar(255) not null\n- `priority` integer not null\n- `is_active` boolean not null\n- `stackable` boolean not null\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### price_rule_conditions\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `price_rule_id` bigint not null -> price_rules\n- `attribute` varchar(255) not null\n- `operator` varchar(64) not null\n- `condition_value` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### promotions\nCampaign-linked promotions; standalone by design so campaigns can reference promotions, never the reverse.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `discount_id` bigint -> discounts\n- `coupon_id` bigint -> coupons\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `campaign_code` varchar(64) (Free-text correlation handle to a marketing campaign; not an FK since marketing is a higher-numbered module.)\n- `is_active` boolean not null\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### promotion_products\nJunction restricting a promotion to specific products.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `promotion_id` bigint not null -> promotions\n- `product_id` bigint not null -> products\n- `created_at` timestamp not null\n- unique: (promotion_id, product_id)\n\n## Area: sales\n\n### carts\nShopping carts, anonymous (user_id null) or owned. Converts to an order; abandoned carts feed recovery campaigns.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `session_token` varchar(255)\n- `status` varchar(32) not null one of: active, converted, abandoned, merged\n- `currency_code` varchar(3) not null -> currencies\n- `item_count` integer not null\n- `subtotal` decimal(14,4) not null\n- `abandoned_at` timestamp\n- `converted_at` timestamp\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### cart_items\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `cart_id` bigint not null -> carts\n- `product_variant_id` bigint not null -> product_variants\n- `quantity` integer not null\n- `unit_price` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (cart_id, product_variant_id)\n\n### orders\nCentral order fact table. public_id is the opaque id shown to customers; totals are denormalized money on the row.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `cart_id` bigint -> carts\n- `order_number` varchar(64) not null (Human-facing sequential order number, unique per tenant (distinct from the opaque public_id).)\n- `status` varchar(32) not null one of: pending, confirmed, paid, partially_fulfilled, fulfilled, cancelled, refunded\n- `billing_address_id` bigint -> addresses\n- `shipping_address_id` bigint -> addresses\n- `currency_code` varchar(3) not null -> currencies\n- `subtotal` decimal(14,4) not null\n- `discount_total` decimal(14,4) not null\n- `tax_total` decimal(14,4) not null\n- `shipping_total` decimal(14,4) not null\n- `grand_total` decimal(14,4) not null (Final amount charged: subtotal - discount_total + tax_total + shipping_total.)\n- `customer_email` varchar(254)\n- `customer_note` varchar(4000)\n- `placed_at` timestamp\n- `cancelled_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id); (tenant_id, order_number)\n\n### order_items\nLine items of an order; sku and name are snapshotted at purchase time so historical orders survive catalog changes.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `product_variant_id` bigint not null -> product_variants\n- `sku` varchar(64) not null\n- `name` varchar(255) not null\n- `quantity` integer not null\n- `quantity_fulfilled` integer not null (Running count of units already dispatched across fulfillments; never exceeds quantity.)\n- `unit_price` decimal(14,4) not null\n- `discount_total` decimal(14,4) not null\n- `tax_total` decimal(14,4) not null\n- `line_total` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### order_status_history\nAppend-only audit of order status transitions; immutable (no updated_at).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `from_status` varchar(32) one of: pending, confirmed, paid, partially_fulfilled, fulfilled, cancelled, refunded\n- `to_status` varchar(32) not null one of: pending, confirmed, paid, partially_fulfilled, fulfilled, cancelled, refunded\n- `changed_by_user_id` bigint -> users\n- `note` varchar(4000)\n- `created_at` timestamp not null\n\n### order_discounts\nDiscounts and coupon redemptions applied to an order; discount_id/coupon_id reference the pricing module.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `discount_id` bigint -> discounts\n- `coupon_id` bigint -> coupons\n- `code` varchar(64)\n- `description` varchar(4000)\n- `amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### fulfillments\nA batch of order items dispatched together from one warehouse; tracks pick/pack/ship lifecycle.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `warehouse_id` bigint -> warehouses\n- `status` varchar(32) not null one of: pending, picked, packed, shipped, delivered, returned\n- `reference` varchar(255)\n- `packed_at` timestamp\n- `shipped_at` timestamp\n- `delivered_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### fulfillment_items\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `fulfillment_id` bigint not null -> fulfillments\n- `order_item_id` bigint not null -> order_items\n- `quantity` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (fulfillment_id, order_item_id)\n\n### shipments\nCarrier shipment for a fulfillment, with tracking number and delivery timestamps.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `fulfillment_id` bigint not null -> fulfillments\n- `carrier` varchar(64) not null\n- `service_level` varchar(64)\n- `tracking_number` varchar(64)\n- `tracking_url` varchar(2048)\n- `shipping_cost` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `weight_grams` integer\n- `shipped_at` timestamp\n- `delivered_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### shipment_tracking\nshipment_tracking — append-only stream of carrier tracking events. IMMUTABLE: occurred_at only, no updated_at.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `shipment_id` bigint not null -> shipments\n- `status` varchar(64) not null\n- `description` varchar(4000)\n- `location` varchar(255)\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### order_returns\nReturn merchandise authorization (RMA) header; status walks requested -> approved -> received -> refunded.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `rma_number` varchar(64) not null\n- `status` varchar(32) not null one of: requested, approved, received, refunded, rejected\n- `reason` varchar(4000)\n- `refund_total` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `requested_at` timestamp not null\n- `approved_at` timestamp\n- `received_at` timestamp\n- `refunded_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, rma_number)\n\n### return_items\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `return_id` bigint not null -> order_returns\n- `order_item_id` bigint not null -> order_items\n- `quantity` integer not null\n- `reason` varchar(4000)\n- `restock` boolean not null (Whether the returned units should be added back to sellable inventory.)\n- `refund_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (return_id, order_item_id)\n\n### gift_cards\nStored-value gift cards; code is unique per tenant and balance decreases as it is redeemed against orders.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `status` varchar(64) not null\n- `initial_balance` decimal(14,4) not null\n- `balance` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `issued_to_user_id` bigint -> users\n- `issued_order_id` bigint -> orders\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, code)\n\n### gift_card_transactions\nImmutable ledger of gift-card balance changes; positive amount credits, negative debits.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `gift_card_id` bigint not null -> gift_cards\n- `order_id` bigint -> orders\n- `amount` decimal(14,4) not null\n- `balance_after` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `note` varchar(4000)\n- `created_at` timestamp not null\n\n### order_notes\norder_notes — internal staff notes attached to an order (not the customer note that lives on the order row itself).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `author_user_id` bigint -> users\n- `body` varchar(65535) not null\n- `is_pinned` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### draft_orders\nMerchant-created quotes/draft orders, optionally converted into a real order.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `created_by_user_id` bigint -> users\n- `converted_order_id` bigint -> orders\n- `name` varchar(255)\n- `status` varchar(64) not null\n- `currency_code` varchar(3) not null -> currencies\n- `subtotal` decimal(14,4) not null\n- `grand_total` decimal(14,4) not null\n- `payload` json not null\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n## Area: support\n\n### support_agents\nSupport staff who handle tickets; a support-specific extension of an users record carrying availability and load limits.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `display_name` varchar(255)\n- `signature` varchar(255)\n- `is_available` boolean not null\n- `max_open_tickets` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, user_id)\n\n### agent_groups\nNamed groups of agents used for ticket routing and assignment.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### agent_group_members\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `agent_group_id` bigint not null -> agent_groups\n- `support_agent_id` bigint not null -> support_agents\n- `is_lead` boolean not null\n- `created_at` timestamp not null\n- unique: (agent_group_id, support_agent_id)\n\n### sla_policies\nLookup of service-level targets (first-response and resolution minutes), usually keyed by ticket priority.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `priority` varchar(32) one of: low, normal, high, urgent\n- `first_response_target_minutes` integer not null\n- `resolution_target_minutes` integer not null\n- `business_hours_only` boolean not null (When true, SLA timers pause outside the tenant business calendar rather than running 24/7.)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### support_tags\nsupport_tags — tag catalog for categorizing tickets.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `color` varchar(64)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### knowledge_categories\nKnowledge base: hierarchical categories + articles + per-article feedback.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `parent_category_id` bigint -> knowledge_categories\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, slug)\n\n### knowledge_articles\nHelp-center articles published to customers; soft-deletable via deleted_at.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `knowledge_category_id` bigint -> knowledge_categories\n- `author_user_id` bigint -> users\n- `title` varchar(255) not null\n- `slug` varchar(255) not null\n- `body` varchar(65535)\n- `is_published` boolean not null\n- `view_count` bigint not null\n- `helpful_count` integer not null (Denormalized count of positive article_feedback rows, kept for fast sorting.)\n- `not_helpful_count` integer not null\n- `published_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### article_feedback\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `knowledge_article_id` bigint not null -> knowledge_articles\n- `user_id` bigint -> users\n- `was_helpful` boolean not null\n- `comment` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### macros\nCanned reply templates agents insert into ticket messages. Soft-deletable.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `created_by_agent_id` bigint -> support_agents\n- `title` varchar(255) not null\n- `body` varchar(65535) not null\n- `is_shared` boolean not null\n- `usage_count` bigint not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, title)\n\n### tickets\nCore customer support request. requester_user_id is the customer; assignee_agent_id is the owning agent (null = unassigned queue).\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `requester_user_id` bigint not null -> users\n- `assignee_agent_id` bigint -> support_agents\n- `agent_group_id` bigint -> agent_groups\n- `sla_policy_id` bigint -> sla_policies\n- `subject` varchar(255) not null\n- `status` varchar(32) not null one of: new, open, pending, on_hold, solved, closed\n- `priority` varchar(32) not null one of: low, normal, high, urgent\n- `channel` varchar(32) not null one of: email, chat, phone, web, social, api\n- `first_response_at` timestamp (Timestamp of the first public agent reply, used to measure SLA first-response compliance.)\n- `resolved_at` timestamp\n- `closed_at` timestamp\n- `last_message_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id)\n\n### ticket_messages\nReplies on a ticket: is_public=true are customer-visible, is_public=false are internal agent notes.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ticket_id` bigint not null -> tickets\n- `author_user_id` bigint -> users\n- `author_agent_id` bigint -> support_agents\n- `body` varchar(65535) not null\n- `is_public` boolean not null\n- `channel` varchar(32) one of: email, chat, phone, web, social, api\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### ticket_events\nImmutable append-only audit of ticket changes such as status transitions, reassignment, and priority changes.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ticket_id` bigint not null -> tickets\n- `actor_user_id` bigint -> users\n- `actor_agent_id` bigint -> support_agents\n- `event_type` varchar(64) not null\n- `old_value` varchar(4000)\n- `new_value` varchar(4000)\n- `metadata` json not null\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### ticket_tags_map\nticket_tags_map — junction tickets × support_tags.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ticket_id` bigint not null -> tickets\n- `support_tag_id` bigint not null -> support_tags\n- `created_at` timestamp not null\n- unique: (ticket_id, support_tag_id)\n\n### sla_breaches\nRecorded SLA-target breaches on a ticket, with how many minutes the target was missed by.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ticket_id` bigint not null -> tickets\n- `sla_policy_id` bigint -> sla_policies\n- `breach_kind` varchar(64) not null\n- `target_at` timestamp not null\n- `breached_at` timestamp not null\n- `minutes_over` integer\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### csat_responses\nCustomer satisfaction rating (1-5) collected after a ticket is resolved; one response per ticket.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ticket_id` bigint not null -> tickets\n- `respondent_user_id` bigint -> users\n- `rated_agent_id` bigint -> support_agents (The agent the customer was rating, captured at submission in case the ticket is later reassigned.)\n- `score` integer not null\n- `comment` varchar(4000)\n- `submitted_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (ticket_id)\n\n\n# Your role\n\nYou are Henrik Aalto, Chief Information Security Officer at Tidewick Commerce."]},"type":"attachment","uuid":"c552cdc9-c1b4-4f6b-8458-43d212985b50","timestamp":"2026-10-04T00:05:00.844Z","userType":"external","entrypoint":"sdk-cli","cwd":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","version":"2.1.275","gitBranch":"HEAD"}
{"type":"last-prompt","lastPrompt":"You are Henrik Aalto, Chief Information Security Officer at Tidewick Commerce. Joined from a cloud provider's security team. Several enterprise deals depend on passing security reviews.  Your area: Ac…","leafUuid":"c552cdc9-c1b4-4f6b-8458-43d212985b50","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36"}
{"type":"atis-latch","atis":"","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36"}
{"type":"ai-title","aiTitle":"Account security briefing questions","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36"}
{"parentUuid":"c552cdc9-c1b4-4f6b-8458-43d212985b50","isSidechain":false,"message":{"model":"claude-sonnet-5","id":"msg_011CfgDh6DK81EdbPs7Farvq","type":"message","role":"assistant","content":[{"type":"text","text":"Subject: Where I'd like your help first — account security snapshot\n\nHi Noah,\n\nGood to have you working with me this week. I'm still building my mental map of the business, so let's start broad and sharpen later. Here's what's on my mind, roughly in priority order:\n\n1. **How exposed are we on sign-in security today?** I want to understand how merchant staff and shoppers are authenticating — are we seeing risky patterns like repeated failed logins, credential-stuffing-type behavior, or logins from unusual places? I need a feel for the baseline before I can say whether we have a problem.\n\n2. **How strong is our multi-factor authentication posture?** I'd like to know roughly how many of our merchants' staff (and ideally shoppers, if meaningful) actually have MFA set up and active, versus how many are relying on password-only access. This matters because it's one of the first things enterprise security reviewers ask about, and I need to know whether we can answer confidently or whether it's a gap we need to close.\n\n3. **Who holds powerful access, and is it still appropriate?** I'm interested in how permissions and roles are granted and whether there's good hygiene around it — e.g., are there dormant privileged accounts, or grants that nobody seems to be reviewing. This feeds directly into access-control questions in security audits.\n\n4. **How healthy are our API keys and service accounts?** These are often the weakest link — long-lived tokens that nobody rotates. I'd like a sense of how many are active, how old they are, and whether any look abandoned or over-privileged.\n\n5. **Can we actually reconstruct \"who did what, when\" if asked?** I want to understand how complete and reliable our audit trail is in practice — are sensitive actions consistently logged, and would we be able to produce a credible record if a merchant or regulator asked us to investigate an incident?\n\nWhat I need this for: I'm walking into enterprise deals where security questionnaires will ask us point-blank about authentication strength, access governance, credential hygiene, and auditability. I need an honest, evidence-based picture — good or bad — so I can brief the CEO and sales on what we can credibly claim today versus what needs fixing before the next review cycle.\n\nGiven the slot budget, let's pick whichever 3 of these will give us the most defensible starting picture this week, and we can go deeper on the rest next week.\n\nThanks,\nHenrik"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":2,"cache_creation_input_tokens":47692,"cache_read_input_tokens":0,"output_tokens":768,"output_tokens_details":{"thinking_tokens":0},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":47692,"ephemeral_5m_input_tokens":0},"inference_geo":"not_available","iterations":[{"input_tokens":2,"output_tokens":768,"cache_read_input_tokens":0,"cache_creation_input_tokens":47692,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":47692},"type":"message"}],"speed":"standard"},"input_transformations":[],"diagnostics":null,"context_management":null},"apiBlockIndex":0,"requestId":"req_011CfgDh5b7ay9ezEYW5rAuA","type":"assistant","uuid":"b45f35f9-ae73-4246-ab29-12155189256c","timestamp":"2026-10-04T00:05:11.684Z","effort":"high","perTurnEffort":null,"userType":"external","entrypoint":"sdk-cli","cwd":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","version":"2.1.275","gitBranch":"HEAD"}
{"parentUuid":"b45f35f9-ae73-4246-ab29-12155189256c","isSidechain":false,"attachment":{"type":"prompt_snapshot","systemPrompt":["# Tidewick Commerce: company briefing\n\n**Tidewick Commerce** is an all-in-one commerce platform for mid-size merchants. It provides:\n\n- storefront and checkout;\n- payments;\n- inventory and fulfillment;\n- subscriptions;\n- a light B2B CRM;\n- a helpdesk;\n- marketing tools.\n\nTidewick was founded in 2018 in Amsterdam. It has offices in London and Austin and about 400 employees.\n\n## Merchants\n\nAbout 120 merchants run their business on Tidewick, and each merchant is a tenant of the platform. A handful of large merchants generate most of the volume, with a long tail of small ones behind them.\n\nThe merchants work in these verticals:\n\n- apparel and footwear;\n- consumer electronics;\n- beauty;\n- home and living;\n- sports and outdoor;\n- food and coffee subscriptions;\n- pet supplies;\n- a few B2B wholesalers (industrial supplies, office equipment).\n\nMerchants are mostly based in the UK, the Netherlands, Germany, France, the Nordics and the US. Their shoppers come from about 40 countries.\n\n## How Tidewick makes money\n\nMerchants pay a monthly platform plan. Tidewick also keeps a processing fee on every payment it settles for them; payouts show this as gross, fee and net.\n\n## The data\n\n- Production runs on one multi-tenant database: every business table carries the merchant's `tenant_id`.\n- The production database is replicated nightly into the analytics warehouse.\n- Tidewick moved to this database at the start of 2023, so history starts on 2023-01-01.\n- Timestamps are stored in UTC.\n- Amounts are in the merchant's currency. `exchange_rates` has daily rates from EUR.\n- The full table catalog is in `schema.md`.\n\n## Today\n\nToday is **Monday, 2026-01-05**. The warehouse holds complete data through 2025-12-31.\n\nTidewick has just hired several new leaders. Each of them wants to understand their part of the business quickly and relies on the analytics team to do so.\n\nThe analytics team works through the warehouse, as described in `handbook.md`.\n\n\n# Analytics handbook\n\nThis handbook explains how the analytics team at Tidewick turns questions into numbers.\n\n## The people\n\n- **Leaders** (CEO, CFO, department heads) bring broad questions about their area.\n- **Analysts** each work for one leader. An analyst:\n  - turns the leader's questions into concrete report requests;\n  - reads the printed reports and follows up;\n  - writes a short memo for the leader at the end of the week.\n- **Data engineers** turn each report request into one SQL query. They can check the query against the schema as often as they like before submitting it.\n\n## The warehouse is expensive\n\nThe warehouse is shared and metered, and every executed query is billed to the department. We therefore run reports as an overnight batch:\n\n- An analyst has **3 report slots per night** and the week has **4 nights**.\n- The memo is due on Friday morning.\n- A slot is spent whenever a request is submitted, whether it runs, fails or is bounced by review.\n- Unused slots do not carry over.\n- Checking a query against the schema (a \"dry run\") is free. It runs the query against an empty copy of the warehouse schema, so it catches errors and shows the output columns, but it never reads or shows any data.\n\nThink before you request. Make one request answer one question well.\n\n## One request, one report\n\nEach request produces one printed report:\n\n- **Title and question.** Every report has a title and the analyst's question it answers.\n- **One grain.** A report is one table at a single grain: every row is the same kind of thing (a country, a week, a merchant, a carrier and month). The columns are measures or attributes of that thing.\n- **The page has a width.** A report has at most 12 columns, and about 10 is comfortable.\n- **The page has a length.** At most 50 rows are printed. The report notes how many rows the query returned in total.\n- **Readable columns.** Order the rows so the important ones come first, and give columns readable names.\n\nDense reports are good. For example, one row per country and month can carry these columns:\n\n- orders;\n- revenue;\n- refund rate;\n- share of late deliveries;\n- tickets per 100 orders;\n- the same month last year.\n\nData engineering reviews every request before it runs and **bounces bundled ones**, where several unrelated questions are packed into one report. Reports wider than 12 columns are bounced too. A bounced request still uses its slot.\n\nIf you have two questions, request two reports.\n\n## House SQL standards (for data engineers)\n\n- Write **one SELECT statement**. Do not end it with a semicolon.\n- Use **standard SQL** wherever possible.\n- **Dates:** never use `CURRENT_DATE` or `now()`. The reporting date is 2026-01-05 and the data ends on 2025-12-31.\n- **Deterministic ordering:** always finish with an ORDER BY whose tie-breaks make the order unique.\n- **Tenants:** filter or group by `tenant_id` when a question is about particular merchants. Join `tenants` for merchant names.\n- **Money:** amounts are in the currency given next to them (usually a `currency_code` column; a merchant's main currency is `tenants.default_currency_code`).\n  - Convert amounts before adding them up across merchants that use different currencies.\n  - `exchange_rates` holds a rate from EUR (`base_code`) for every day; weekends carry the previous business day's rate.\n- **Rates:** round rates and averages for the page.\n- **Catalog:** the table catalog (`schema.md`) lists every table, column, foreign key and the allowed codes of status columns.\n\n\n# Table catalog\n\n# Tidewick Commerce data warehouse: table catalog\n\nThe production database, replicated nightly. Every business table carries the merchant's\n`tenant_id`. Timestamps are UTC. Money columns are in the row's `currency_code` (merchants sell\nin their own currency); `exchange_rates` has daily EUR reference rates.\nStatus columns reference small lookup tables; their codes are listed with each table.\nAll tables live in one schema: refer to them by name only (`shipments`, not `sales.shipments`).\nThe sections below only group the tables by business area.\n\n## Area: analytics\n\n### web_sessions\nVisitor analytics sessions, distinct from sessions (auth). Anonymous traffic still produces a session with a null user_id.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `anonymous_id` uuid (Client-generated visitor identifier used to stitch sessions before the user authenticates.)\n- `started_at` timestamp not null\n- `ended_at` timestamp\n- `landing_page` varchar(2048)\n- `referrer` varchar(2048)\n- `utm_source` varchar(255)\n- `utm_medium` varchar(255)\n- `utm_campaign` varchar(255)\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `is_bounce` boolean not null\n- `page_view_count` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### events\nThe primary product analytics fact table. Each row is one tracked event with a free-form event_name and schemaless JSON properties.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `web_session_id` bigint -> web_sessions (Nullable link to the analytics web session this event occurred in; server-side events may have none.)\n- `event_name` varchar(64) not null\n- `properties` json not null\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### page_views\nIndividual page hits within a web session, used for path and dwell-time analysis.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `web_session_id` bigint not null -> web_sessions\n- `url` varchar(2048) not null\n- `path` varchar(2048)\n- `title` varchar(255)\n- `referrer` varchar(2048)\n- `duration_seconds` integer\n- `viewed_at` timestamp not null\n- `created_at` timestamp not null\n\n### device_profiles\nBrowser/device fingerprints observed per tenant, optionally linked to a user once authenticated.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `fingerprint` varchar(255) not null\n- `device_type` varchar(64)\n- `os` varchar(64)\n- `os_version` varchar(64)\n- `browser` varchar(64)\n- `browser_version` varchar(64)\n- `screen_width` integer\n- `screen_height` integer\n- `first_seen_at` timestamp not null\n- `last_seen_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, fingerprint)\n\n### feature_usage\nPer-user, per-day product feature adoption counters feeding engagement and stickiness metrics.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `feature_key` varchar(64) not null (Stable identifier of the product feature being measured (e.g. bulk_export, dashboard_share).)\n- `usage_count` integer not null\n- `usage_day` date not null\n- `last_used_at` timestamp\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n- unique: (tenant_id, user_id, feature_key, usage_day)\n\n### experiments\nServer-side product experiments (feature experiments), distinct from ab_tests which are message-level tests.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `key` varchar(64) not null\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `hypothesis` varchar(4000)\n- `status` varchar(32) not null one of: draft, running, paused, completed, archived\n- `primary_metric` varchar(255)\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, key)\n\n### experiment_variants\nVariants (arms) of an experiment; exactly one may be flagged is_control, with allocation_pct summing across arms.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `experiment_id` bigint not null -> experiments\n- `key` varchar(64) not null\n- `name` varchar(255) not null\n- `is_control` boolean not null\n- `allocation_pct` decimal(5,2) not null\n- `config` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (experiment_id, key)\n\n### experiment_assignments\nRecords which variant a user (or anonymous visitor) was bucketed into for an experiment.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `experiment_id` bigint not null -> experiments\n- `variant_id` bigint not null -> experiment_variants\n- `user_id` bigint -> users\n- `anonymous_id` uuid\n- `assigned_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (experiment_id, user_id)\n\n### funnels\nOrdered conversion-funnel definitions evaluated over the events stream.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `is_active` boolean not null\n- `window_days` integer\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### funnel_steps\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `funnel_id` bigint not null -> funnels\n- `step_order` integer not null\n- `name` varchar(255) not null\n- `event_name` varchar(64) not null\n- `filters` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (funnel_id, step_order)\n\n### cohorts\nNamed user cohorts, dynamic (rule-based) or static, used for retention and segmentation analysis.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `definition` json not null\n- `is_dynamic` boolean not null\n- `member_count` integer not null\n- `computed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### cohort_members\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `cohort_id` bigint not null -> cohorts\n- `user_id` bigint not null -> users\n- `added_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (cohort_id, user_id)\n\n### metrics_daily\nPre-aggregated daily metric values per tenant; one row per (tenant, metric_key, day) for fast dashboard reads.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `metric_key` varchar(64) not null\n- `metric_date` date not null\n- `metric_value` decimal(20,6) not null\n- `dimensions` json not null (Optional breakdown labels (e.g. {\"channel\":\"organic\"}) when a metric is sliced; empty object for the headline total.)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, metric_key, metric_date)\n\n### kpi_snapshots\nPeriodic captures of headline KPIs (MRR, active users, conversion rate) with the prior-period value for trend deltas.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `kpi_key` varchar(64) not null\n- `period_grain` varchar(64) not null\n- `period_start` date not null\n- `period_end` date not null\n- `kpi_value` decimal(20,6) not null\n- `previous_value` decimal(20,6)\n- `metadata` json not null\n- `captured_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, kpi_key, period_grain, period_start)\n\n## Area: audit\n\n### audit_log\nCentral append-only audit trail of meaningful mutations and security actions. The audited row is referenced polymorphically via entity_schema/entity_table/entity_id (no FK).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `action` varchar(32) not null one of: create, update, delete, login, logout, export, access_denied\n- `actor_user_id` bigint -> users\n- `actor_type` varchar(64) not null (Principal kind that performed the action: user, api_key, service_account, or system.)\n- `entity_schema` varchar(64)\n- `entity_table` varchar(64)\n- `entity_id` bigint (Primary-key id of the audited row in entity_schema.entity_table; intentionally not a foreign key.)\n- `summary` varchar(4000)\n- `metadata` json not null\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `request_id` varchar(64)\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### change_history\nColumn-level before/after JSON diffs for an audited row, usually linked to an audit_log entry.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `audit_log_id` bigint -> audit_log\n- `actor_user_id` bigint -> users\n- `entity_schema` varchar(64) not null\n- `entity_table` varchar(64) not null\n- `entity_id` bigint not null\n- `changed_columns` varchar(255)[] not null\n- `before_data` json not null\n- `after_data` json not null\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### access_log\nAppend-only record of reads of sensitive/PII data, for compliance and breach forensics.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `actor_user_id` bigint -> users\n- `entity_schema` varchar(64) not null\n- `entity_table` varchar(64) not null\n- `entity_id` bigint\n- `access_kind` varchar(64) not null\n- `field_names` varchar(255)[] not null\n- `purpose` varchar(64)\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### data_exports\ndata_exports — GDPR / data-portability export jobs. Tracks request through completion; the produced artifact lives in object storage (file_url).\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `requested_by_user_id` bigint -> users\n- `subject_user_id` bigint -> users\n- `status` varchar(64) not null\n- `format` varchar(64) not null\n- `file_url` varchar(2048)\n- `row_count` bigint\n- `requested_at` timestamp not null\n- `completed_at` timestamp\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id)\n\n### consent_records\nAppend-only per-user consent grants and withdrawals by purpose; the latest row per (user, purpose) is the effective consent state.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `purpose` varchar(64) not null\n- `status` varchar(32) not null one of: granted, withdrawn, expired\n- `source` varchar(64)\n- `policy_version` varchar(64) (Version of the privacy policy / terms the user consented to at the time.)\n- `ip_address` varchar(45)\n- `granted_at` timestamp\n- `withdrawn_at` timestamp\n- `expires_at` timestamp\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### compliance_events\ncompliance_events — flagged compliance incidents / policy violations needing review (suspicious access, retention overrun, failed DSAR SLA, ...).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `event_type` varchar(64) not null\n- `severity` varchar(64) not null\n- `detected_by_user_id` bigint -> users\n- `entity_schema` varchar(64)\n- `entity_table` varchar(64)\n- `entity_id` bigint\n- `details` json not null\n- `resolved` boolean not null\n- `resolved_at` timestamp\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### data_subject_requests\nGDPR/CCPA data-subject requests (access, erasure, rectification, portability) tracked through their processing lifecycle.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `subject_user_id` bigint -> users\n- `subject_email` varchar(254)\n- `request_type` varchar(64) not null (Kind of DSAR: access, erasure, rectification, portability, or restriction.)\n- `status` varchar(64) not null\n- `assigned_to_user_id` bigint -> users\n- `data_export_id` bigint -> data_exports\n- `notes` varchar(4000)\n- `received_at` timestamp not null\n- `due_at` timestamp\n- `completed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id)\n\n### retention_policies\nPer-entity data-retention rules (retention_days + action_on_expiry) driving automated purges.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `entity_schema` varchar(64) not null\n- `entity_table` varchar(64) not null\n- `retention_days` integer not null\n- `action_on_expiry` varchar(64) not null\n- `is_active` boolean not null\n- `last_run_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, entity_schema, entity_table)\n\n### legal_holds\nActive legal/investigation holds that suspend retention-driven deletion for a subject or entity scope.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `reason` varchar(4000)\n- `retention_policy_id` bigint -> retention_policies\n- `subject_user_id` bigint -> users\n- `entity_schema` varchar(64)\n- `entity_table` varchar(64)\n- `entity_id` bigint\n- `placed_by_user_id` bigint -> users\n- `is_active` boolean not null\n- `placed_at` timestamp not null\n- `released_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### audit_log_archive\nCold archive of aged-out audit_log rows; same shape as audit_log, written once by the archival job. source_audit_log_id preserves the original id.\n- `id` bigint not null\n- `source_audit_log_id` bigint\n- `tenant_id` bigint not null -> tenants\n- `action` varchar(32) not null one of: create, update, delete, login, logout, export, access_denied\n- `actor_user_id` bigint\n- `actor_type` varchar(64) not null\n- `entity_schema` varchar(64)\n- `entity_table` varchar(64)\n- `entity_id` bigint\n- `summary` varchar(4000)\n- `metadata` json not null\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `request_id` varchar(64)\n- `occurred_at` timestamp not null\n- `archived_at` timestamp not null\n- `created_at` timestamp not null\n\n## Area: billing\n\n### billing_profiles\nCustomer billing entity per tenant: legal name, tax id, billing email, net payment terms. One marked is_default per tenant.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `legal_name` varchar(255) not null\n- `billing_email` varchar(254)\n- `tax_id` varchar(64)\n- `net_terms_days` integer not null (Payment terms in days (Net-0/Net-30/Net-60) before an invoice is past due.)\n- `default_currency_code` varchar(3) not null -> currencies\n- `is_default` boolean not null\n- `metadata` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n\n### payment_methods\nStored payment instruments. Only a gateway token plus brand/last4 are kept; raw card numbers are never stored.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `billing_profile_id` bigint -> billing_profiles\n- `kind` varchar(32) not null one of: card, bank_transfer, paypal, wallet, wire, credit\n- `gateway` varchar(64) not null\n- `gateway_token` varchar(255) not null\n- `brand` varchar(255)\n- `last4` varchar(4)\n- `exp_month` smallint\n- `exp_year` smallint\n- `is_default` boolean not null\n- `billing_address_id` bigint -> addresses\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (gateway, gateway_token)\n\n### payments\nMoney-in captures against an order and/or invoice. amount_refunded tracks cumulative refunds for partial-refund accounting.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint -> orders\n- `invoice_id` bigint -> invoices\n- `payment_method_id` bigint -> payment_methods\n- `status` varchar(32) not null one of: pending, authorized, captured, failed, voided, refunded, partially_refunded\n- `amount` decimal(14,4) not null\n- `amount_refunded` decimal(14,4) not null (Running total of amounts refunded against this payment; must never exceed amount.)\n- `currency_code` varchar(3) not null -> currencies\n- `gateway` varchar(64) not null\n- `gateway_charge_id` varchar(255)\n- `authorized_at` timestamp\n- `captured_at` timestamp\n- `failure_code` varchar(64)\n- `metadata` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### payment_attempts\nEach gateway round-trip for a payment; a payment may need several attempts before it captures.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `payment_id` bigint not null -> payments\n- `attempt_number` integer not null\n- `status` varchar(32) not null one of: pending, authorized, captured, failed, voided, refunded, partially_refunded\n- `amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `gateway_response` json not null\n- `error_code` varchar(64)\n- `error_message` varchar(4000)\n- `attempted_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (payment_id, attempt_number)\n\n### payment_refunds\nRefunds reversing all or part of a captured payment, tracked through the refund_status lifecycle.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `payment_id` bigint not null -> payments\n- `status` varchar(32) not null one of: pending, succeeded, failed, cancelled\n- `amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `reason` varchar(4000)\n- `gateway_refund_id` varchar(255)\n- `processed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### invoices\nBilling documents (one-off or subscription). amount_due drives dunning; public_id is the opaque external identifier.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `billing_profile_id` bigint -> billing_profiles\n- `user_id` bigint -> users\n- `order_id` bigint -> orders\n- `subscription_id` bigint -> subscriptions\n- `invoice_number` varchar(64) not null\n- `status` varchar(32) not null one of: draft, open, paid, void, uncollectible, past_due\n- `currency_code` varchar(3) not null -> currencies\n- `subtotal` decimal(14,4) not null\n- `tax_total` decimal(14,4) not null\n- `discount_total` decimal(14,4) not null\n- `total` decimal(14,4) not null\n- `amount_paid` decimal(14,4) not null\n- `amount_due` decimal(14,4) not null (Outstanding balance still owed; reaches 0 when the invoice is fully paid.)\n- `issued_at` timestamp\n- `due_at` timestamp\n- `paid_at` timestamp\n- `voided_at` timestamp\n- `notes` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id); (tenant_id, invoice_number)\n\n### invoice_lines\nLine items on an invoice; optionally tied to a subscription_item, with the service period it covers.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `invoice_id` bigint not null -> invoices\n- `subscription_item_id` bigint -> subscription_items\n- `description` varchar(4000) not null\n- `quantity` decimal(14,4) not null\n- `unit_price` decimal(14,4) not null\n- `line_total` decimal(14,4) not null\n- `tax_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `period_start` timestamp\n- `period_end` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### credit_notes\nCredit memos issued against invoices for returns, goodwill, or corrections.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `invoice_id` bigint -> invoices\n- `billing_profile_id` bigint -> billing_profiles\n- `credit_note_number` varchar(64) not null\n- `currency_code` varchar(3) not null -> currencies\n- `subtotal` decimal(14,4) not null\n- `tax_total` decimal(14,4) not null\n- `total` decimal(14,4) not null\n- `reason` varchar(4000)\n- `issued_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, credit_note_number)\n\n### credit_note_lines\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `credit_note_id` bigint not null -> credit_notes\n- `invoice_line_id` bigint -> invoice_lines\n- `description` varchar(4000) not null\n- `quantity` decimal(14,4) not null\n- `unit_price` decimal(14,4) not null\n- `line_total` decimal(14,4) not null\n- `tax_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### payouts\nSettlements of collected funds out to the tenant; net = gross minus fees.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `currency_code` varchar(3) not null -> currencies\n- `gross_amount` decimal(14,4) not null\n- `fee_amount` decimal(14,4) not null\n- `net_amount` decimal(14,4) not null\n- `status` varchar(64) not null\n- `gateway_payout_id` varchar(255)\n- `arrival_date` date\n- `paid_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### payout_items\nThe individual payments settled within a payout; each payment appears in at most one payout.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `payout_id` bigint not null -> payouts\n- `payment_id` bigint not null -> payments\n- `amount` decimal(14,4) not null\n- `fee_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (payment_id)\n\n### ledger_accounts\nledger_accounts — the tenant's chart of accounts for the double-entry ledger. type is the accounting class (asset/liability/revenue/...).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `account_type` varchar(64) not null\n- `currency_code` varchar(3) not null -> currencies\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, code)\n\n### ledger_entries\nImmutable append-only double-entry ledger lines. Postings sharing an entry_group form one balanced journal entry.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ledger_account_id` bigint not null -> ledger_accounts\n- `entry_group` uuid not null (Groups the debit/credit rows of a single balanced journal entry (their amounts must net to zero).)\n- `direction` varchar(32) not null one of: debit, credit\n- `amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `payment_id` bigint -> payments\n- `invoice_id` bigint -> invoices\n- `description` varchar(4000)\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### plans\nSubscription products. code is the stable per-tenant key; default_interval sets the billing cadence.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `default_interval` varchar(32) not null one of: day, week, month, quarter, year\n- `interval_count` integer not null\n- `trial_period_days` integer not null\n- `is_active` boolean not null\n- `is_public` boolean not null\n- `metadata` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### plan_features\nFeature entitlements/limits per plan; limit_value NULL means unlimited.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `plan_id` bigint not null -> plans\n- `feature_key` varchar(64) not null\n- `limit_value` bigint (Quota for the feature (e.g. seats, api_calls); NULL means unlimited.)\n- `is_enabled` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (plan_id, feature_key)\n\n### plan_prices\nPrice points for a plan, one per currency and billing interval.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `plan_id` bigint not null -> plans\n- `currency_code` varchar(3) not null -> currencies\n- `billing_interval` varchar(32) not null one of: day, week, month, quarter, year\n- `interval_count` integer not null\n- `amount` decimal(14,4) not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (plan_id, currency_code, billing_interval, interval_count)\n\n### subscriptions\nA user's recurring commitment to a plan. current_period_* bound the next invoice; cancel_at_period_end defers cancellation.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `plan_id` bigint not null -> plans\n- `billing_profile_id` bigint -> billing_profiles\n- `payment_method_id` bigint -> payment_methods\n- `status` varchar(32) not null one of: trialing, active, past_due, canceled, paused, incomplete\n- `currency_code` varchar(3) not null -> currencies\n- `quantity` integer not null\n- `trial_ends_at` timestamp\n- `current_period_start` timestamp\n- `current_period_end` timestamp\n- `cancel_at_period_end` boolean not null (When true the subscription cancels at current_period_end rather than immediately.)\n- `canceled_at` timestamp\n- `started_at` timestamp not null\n- `ended_at` timestamp\n- `metadata` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id)\n\n### subscription_items\nLine items / add-ons of a subscription; unit_amount snapshots the price at the time of subscription.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `subscription_id` bigint not null -> subscriptions\n- `plan_price_id` bigint -> plan_prices\n- `description` varchar(4000)\n- `quantity` integer not null\n- `unit_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `is_metered` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### usage_records\nMetered usage reported for a subscription_item, aggregated into consumption-based invoice lines.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `subscription_item_id` bigint not null -> subscription_items\n- `quantity` decimal(14,4) not null\n- `unit` varchar(64)\n- `action` varchar(64) not null\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### dunning_attempts\nRetry sequence chasing payment on a past-due invoice; next_attempt_at schedules the following retry.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `invoice_id` bigint not null -> invoices\n- `subscription_id` bigint -> subscriptions\n- `payment_id` bigint -> payments\n- `attempt_number` integer not null\n- `outcome` varchar(64) not null\n- `next_attempt_at` timestamp\n- `attempted_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (invoice_id, attempt_number)\n\n### tax_registrations\nTenant tax IDs per jurisdiction (VAT/GST/sales tax) governing how tax is charged in a region.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `billing_profile_id` bigint -> billing_profiles\n- `country_code` varchar(2) not null\n- `jurisdiction` varchar(64)\n- `tax_type` varchar(64) not null\n- `registration_number` varchar(64) not null (The official tax/VAT registration number issued by the jurisdiction.)\n- `is_active` boolean not null\n- `valid_from` date\n- `valid_until` date\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, country_code, tax_type)\n\n### wallets\nStored-credit balances per user and currency (prepaid credit, refunds-to-credit, promo balance).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `currency_code` varchar(3) not null -> currencies\n- `balance` decimal(14,4) not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (user_id, currency_code)\n\n## Area: catalog\n\n### brands\nManufacturer/brand records that products are grouped under.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `description` varchar(4000)\n- `logo_url` varchar(2048)\n- `website_url` varchar(2048)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### categories\nHierarchical product taxonomy; parent_id is a self-reference to the parent category.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `parent_id` bigint -> categories\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `description` varchar(4000)\n- `sort_position` integer not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### category_closure\nTransitive closure of the category tree (ancestor, descendant, depth) for fast subtree queries.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ancestor_id` bigint not null -> categories\n- `descendant_id` bigint not null -> categories\n- `depth` integer not null (Number of edges between ancestor and descendant; 0 is the self-row.)\n- `created_at` timestamp not null\n- unique: (ancestor_id, descendant_id)\n\n### products\nProduct master records; public_id is the opaque external identifier exposed via API/URL.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `brand_id` bigint -> brands\n- `primary_category_id` bigint -> categories\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `description` varchar(4000)\n- `status` varchar(32) not null one of: draft, active, archived\n- `is_visible` boolean not null\n- `published_at` timestamp\n- `seo_title` varchar(255)\n- `seo_description` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (public_id); (tenant_id, slug)\n\n### product_variants\nSellable SKUs of a product; sku is unique per tenant. Reference price only — authoritative pricing lives in the pricing schema.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `sku` varchar(64) not null\n- `barcode` varchar(64)\n- `name` varchar(255)\n- `sort_position` integer not null\n- `price_amount` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `weight_grams` decimal(14,4)\n- `is_default` boolean not null (Exactly one variant per product may be the default (enforced by a partial unique index).)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (public_id); (tenant_id, sku)\n\n### product_categories\nproduct_categories — junction products×categories (a product can sit in many categories beyond its primary one).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `category_id` bigint not null -> categories\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- unique: (product_id, category_id)\n\n### attributes\nDefinitions of product/variant attributes (e.g. Color, Size); kind drives value interpretation.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `kind` varchar(32) not null one of: text, number, boolean, enum, color, dimension\n- `is_variant_defining` boolean not null (True when this attribute differentiates variants (Color, Size) rather than being descriptive.)\n- `is_filterable` boolean not null\n- `unit` varchar(64)\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, code)\n\n### attribute_values\nAllowed values for enum-kind attributes.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `attribute_id` bigint not null -> attributes\n- `value_code` varchar(255) not null\n- `label` varchar(255)\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (attribute_id, value_code)\n\n### variant_attribute_values\nJunction binding a variant to its attribute/value pairs (the combination that makes it unique).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `attribute_id` bigint not null -> attributes\n- `attribute_value_id` bigint -> attribute_values\n- `value_text` varchar(4000)\n- `created_at` timestamp not null\n- unique: (variant_id, attribute_id)\n\n### product_media\nImages, video, and documents attached to a product and optionally a specific variant.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `variant_id` bigint -> product_variants\n- `kind` varchar(32) not null one of: image, video, document, model_3d\n- `url` varchar(2048) not null\n- `alt_text` varchar(4000)\n- `sort_position` integer not null\n- `is_primary` boolean not null\n- `width_px` integer\n- `height_px` integer\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### collections\nMerchandising groupings of products; may be manual or rule-driven (is_automated).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `description` varchar(4000)\n- `is_automated` boolean not null\n- `rules` json\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### collection_products\ncollection_products — junction collections×products.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `collection_id` bigint not null -> collections\n- `product_id` bigint not null -> products\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- unique: (collection_id, product_id)\n\n### tags\ntags — free-form labels applied to products.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, slug)\n\n### product_tags\nproduct_tags — junction products×tags.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `tag_id` bigint not null -> tags\n- `created_at` timestamp not null\n- unique: (product_id, tag_id)\n\n### product_reviews\nCustomer reviews of products, rated 1-5 stars; one review per user per product.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `user_id` bigint -> users\n- `rating` integer not null\n- `title` varchar(255)\n- `body` varchar(65535)\n- `is_verified_purchase` boolean not null (True when the reviewer is known to have purchased the product.)\n- `is_approved` boolean not null\n- `helpful_count` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (product_id, user_id)\n\n### review_votes\nreview_votes — helpfulness votes cast by users on a review.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `review_id` bigint not null -> product_reviews\n- `user_id` bigint not null -> users\n- `is_helpful` boolean not null\n- `created_at` timestamp not null\n- unique: (review_id, user_id)\n\n### product_relations\nDirectional cross-sell/upsell/related links between two products.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint not null -> products\n- `related_product_id` bigint not null -> products\n- `relation_kind` varchar(64) not null\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (product_id, related_product_id, relation_kind)\n\n### product_bundles\nHeader for a bundle/kit sold as a single offering, composed of variant components.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `product_id` bigint -> products\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `description` varchar(4000)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, slug)\n\n### bundle_items\nComponent variants and quantities that make up a product bundle.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `bundle_id` bigint not null -> product_bundles\n- `variant_id` bigint not null -> product_variants\n- `quantity` integer not null\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (bundle_id, variant_id)\n\n## Area: comms\n\n### notification_templates\nReusable, per-tenant notification bodies keyed by code and channel; rendered into notifications and email/sms logs.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `channel` varchar(64) not null\n- `subject` varchar(255)\n- `body` varchar(65535) not null\n- `locale` varchar(5)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, code)\n\n### notifications\nPer-user notifications with a queued→sent→delivered→read lifecycle. template_id is null when composed ad hoc.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `template_id` bigint -> notification_templates\n- `channel` varchar(64) not null\n- `status` varchar(32) not null one of: queued, sent, delivered, read, failed\n- `title` varchar(255) not null\n- `body` varchar(65535)\n- `data` json not null\n- `sent_at` timestamp\n- `delivered_at` timestamp\n- `read_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### notification_preferences\nPer-user opt-in matrix across channels and categories; an absent row means the tenant default applies.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `channel` varchar(64) not null\n- `category` varchar(255) not null\n- `is_enabled` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (user_id, channel, category)\n\n### message_threads\nIn-app conversation threads between a tenant and a user. last_message_at is denormalized for inbox sorting.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `subject` varchar(255)\n- `channel` varchar(64) not null\n- `is_closed` boolean not null\n- `last_message_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### thread_messages\nthread_messages — individual messages within a thread. direction marks whether the message is inbound (from the user) or outbound (from the tenant).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `thread_id` bigint not null -> message_threads\n- `sender_user_id` bigint -> users\n- `direction` varchar(32) not null one of: inbound, outbound (inbound = sent by the user toward the tenant; outbound = sent by the tenant toward the user.)\n- `body` varchar(65535) not null\n- `attachments` json not null\n- `read_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### webhooks\nSubscriber HTTP endpoints that receive signed event callbacks. Only the hash of the signing secret is stored.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `created_by_user_id` bigint -> users\n- `name` varchar(255) not null\n- `target_url` varchar(2048) not null\n- `secret_hash` varchar(255) not null (Hash of the HMAC signing secret used to sign delivery payloads; the plaintext secret is shown once at creation.)\n- `event_types` varchar(64)[] not null\n- `is_active` boolean not null\n- `last_delivered_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### webhook_deliveries\nAppend-only log of each webhook delivery attempt, including HTTP response_code and retry attempt number.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `webhook_id` bigint not null -> webhooks\n- `event_type` varchar(64) not null\n- `status` varchar(32) not null one of: pending, success, failed, retrying\n- `attempt` integer not null (1-based retry counter; multiple rows per logical event when a delivery is retried.)\n- `response_code` integer\n- `request_body` json\n- `response_body` varchar(65535)\n- `duration_ms` integer\n- `delivered_at` timestamp\n- `created_at` timestamp not null\n\n### email_log\nImmutable record of every outbound email; recipient_email is denormalized since the user or address may be off-platform.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `template_id` bigint -> notification_templates\n- `recipient_email` varchar(254) not null\n- `from_email` varchar(254)\n- `subject` varchar(255)\n- `provider` varchar(64)\n- `provider_message_id` varchar(255)\n- `status` varchar(32) not null one of: pending, success, failed, retrying\n- `error_message` varchar(4000)\n- `sent_at` timestamp\n- `created_at` timestamp not null\n\n### sms_log\nsms_log — IMMUTABLE record of each outbound SMS. (Intentionally uncommented at table level — see CONVENTIONS.md on deliberate comment gaps.)\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `template_id` bigint -> notification_templates\n- `recipient_phone` varchar(32) not null\n- `from_number` varchar(32)\n- `body` varchar(65535) not null\n- `provider` varchar(64)\n- `provider_message_id` varchar(255)\n- `status` varchar(32) not null one of: pending, success, failed, retrying\n- `error_message` varchar(4000)\n- `segments` integer not null\n- `sent_at` timestamp\n- `created_at` timestamp not null\n\n### push_tokens\nDevice push-notification tokens per user; deactivated rather than deleted when a device unregisters.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `platform` varchar(64) not null\n- `token` varchar(255) not null\n- `device_name` varchar(255)\n- `is_active` boolean not null\n- `last_used_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, token)\n\n### unsubscribes\nSuppression list of addresses/channels that must be excluded from further sends; user_id may be null for off-platform addresses.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `channel` varchar(64) not null\n- `address` varchar(255) not null\n- `category` varchar(255) not null\n- `reason` varchar(4000)\n- `unsubscribed_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, channel, address, category)\n\n### contact_channels\nVerified contact endpoints (email/phone) per user with a verification lifecycle; one primary per kind via a partial unique index.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `kind` varchar(64) not null\n- `channel_address` varchar(255) not null\n- `is_primary` boolean not null\n- `is_verified` boolean not null\n- `verified_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (user_id, kind, channel_address)\n\n## Area: crm\n\n### accounts\nThe tenant's own B2B customer companies. Tenant-scoped; distinct from tenants (the platform's customers).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `legal_name` varchar(255)\n- `domain` varchar(255)\n- `industry` varchar(255)\n- `employee_count` integer\n- `annual_revenue` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `owner_user_id` bigint -> users (Internal sales rep (users) who owns the account relationship.)\n- `phone` varchar(32)\n- `website` varchar(2048)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### contacts\nPeople who work at a CRM account. primary_email is denormalized; the full set lives in contact_emails.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `account_id` bigint -> accounts\n- `owner_user_id` bigint -> users\n- `first_name` varchar(255)\n- `last_name` varchar(255)\n- `primary_email` varchar(254)\n- `phone` varchar(32)\n- `job_title` varchar(255)\n- `department` varchar(255)\n- `is_primary` boolean not null\n- `do_not_contact` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n\n### contact_emails\nAll email addresses for a contact; at most one flagged is_primary per contact.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `contact_id` bigint not null -> contacts\n- `email` varchar(254) not null\n- `label` varchar(255) not null\n- `is_primary` boolean not null\n- `verified_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (contact_id, email)\n\n### leads\nUnqualified inbound interest before it becomes an account/contact; converts forward once qualified.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `owner_user_id` bigint -> users\n- `first_name` varchar(255)\n- `last_name` varchar(255)\n- `email` varchar(254)\n- `phone` varchar(32)\n- `company` varchar(255)\n- `job_title` varchar(255)\n- `status` varchar(32) not null one of: new, contacted, qualified, unqualified, converted\n- `source` varchar(64)\n- `score` integer not null (Lead score (0+); higher means more sales-ready.)\n- `converted_account_id` bigint -> accounts\n- `converted_contact_id` bigint -> contacts\n- `converted_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### pipelines\nNamed sales pipelines per tenant; at most one is the default. Stages live in opportunity_stages.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `is_default` boolean not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### opportunity_stages\nOrdered lookup of stages within a pipeline. position sets funnel order; win_probability drives weighted forecasts.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `pipeline_id` bigint not null -> pipelines\n- `name` varchar(255) not null\n- `sort_position` integer not null\n- `win_probability` decimal(5,4) not null (Default close probability for opportunities in this stage, 0..1.)\n- `is_won` boolean not null\n- `is_lost` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (pipeline_id, sort_position); (pipeline_id, name)\n\n### opportunities\nDeals in flight: an account being worked through a pipeline stage with an expected amount.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `account_id` bigint not null -> accounts\n- `pipeline_id` bigint not null -> pipelines\n- `stage_id` bigint not null -> opportunity_stages\n- `primary_contact_id` bigint -> contacts\n- `owner_user_id` bigint -> users\n- `name` varchar(255) not null\n- `amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `probability` decimal(5,4)\n- `source` varchar(64)\n- `is_closed` boolean not null\n- `is_won` boolean not null\n- `expected_close_date` date\n- `closed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### deals\nClosed-won contracts realized from an opportunity, holding the booked contract value and term.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `account_id` bigint not null -> accounts\n- `opportunity_id` bigint -> opportunities\n- `owner_user_id` bigint -> users\n- `name` varchar(255) not null\n- `contract_value` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `signed_at` date\n- `starts_at` date\n- `ends_at` date\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### deal_line_items\nProducts sold on a deal, priced per unit in the deal currency.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `deal_id` bigint not null -> deals\n- `product_id` bigint -> products\n- `description` varchar(4000)\n- `quantity` integer not null\n- `unit_price` decimal(14,4) not null\n- `discount_pct` decimal(5,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### activities\nLogged interactions (calls, emails, meetings) against an account, contact, or opportunity.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `kind` varchar(32) not null one of: call, email, meeting, note, task\n- `account_id` bigint -> accounts\n- `contact_id` bigint -> contacts\n- `opportunity_id` bigint -> opportunities\n- `owner_user_id` bigint -> users\n- `subject` varchar(255)\n- `body` varchar(65535)\n- `occurred_at` timestamp not null\n- `duration_minutes` integer\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### crm_notes\ncrm_notes — free-text notes attached to a CRM entity (polymorphic target).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `account_id` bigint -> accounts\n- `contact_id` bigint -> contacts\n- `opportunity_id` bigint -> opportunities\n- `author_user_id` bigint -> users\n- `body` varchar(65535) not null\n- `is_pinned` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### tasks\nFollow-up to-dos assigned to reps, optionally linked to a CRM entity.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `assignee_user_id` bigint -> users\n- `account_id` bigint -> accounts\n- `contact_id` bigint -> contacts\n- `opportunity_id` bigint -> opportunities\n- `title` varchar(255) not null\n- `description` varchar(4000)\n- `priority` varchar(64) not null\n- `is_done` boolean not null\n- `due_at` timestamp\n- `completed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### account_relationships\nParent/subsidiary and other directed links between accounts within a tenant.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `parent_account_id` bigint not null -> accounts\n- `child_account_id` bigint not null -> accounts\n- `relationship_type` varchar(64) not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (parent_account_id, child_account_id, relationship_type)\n\n### contact_lists\ncontact_lists — static, manually-curated lists of contacts.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `owner_user_id` bigint -> users\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### contact_list_members\ncontact_list_members — junction of contacts into static lists.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `contact_list_id` bigint not null -> contact_lists\n- `contact_id` bigint not null -> contacts\n- `added_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- unique: (contact_list_id, contact_id)\n\n## Area: geo\n\n### countries\nISO-3166 country reference. Global, shared by all tenants.\n- `id` bigint not null\n- `iso2` varchar(2) not null\n- `iso3` varchar(3) not null\n- `name` varchar(255) not null\n- `phone_code` varchar(32)\n- `continent` varchar(64)\n- unique: (iso2); (iso3)\n\n### regions\n- `id` bigint not null\n- `country_id` bigint not null -> countries\n- `code` varchar(64)\n- `name` varchar(255) not null\n\n### cities\n- `id` bigint not null\n- `region_id` bigint -> regions\n- `country_id` bigint not null -> countries\n- `name` varchar(255) not null\n- `latitude` decimal(9,6)\n- `longitude` decimal(9,6)\n\n### currencies\nISO-4217 currencies. The code column (e.g. USD) is the FK target used by all monetary tables.\n- `id` bigint not null\n- `code` varchar(3) not null\n- `name` varchar(255) not null\n- `symbol` varchar(64)\n- `minor_unit` smallint not null (Number of decimal places (2 for USD, 0 for JPY).)\n- unique: (code)\n\n### exchange_rates\nDaily FX rates between currency pairs, used to normalize multi-currency revenue.\n- `id` bigint not null\n- `base_code` varchar(3) not null -> currencies\n- `quote_code` varchar(3) not null -> currencies\n- `rate` decimal(12,6) not null\n- `as_of` date not null\n- unique: (base_code, quote_code, as_of)\n\n### calendar_days\nCalendar dimension: one row per day from 2023-01-01 to 2026-12-31 with week, month and quarter attributes. Join to it to build complete time series (days, weeks or months without activity still appear).\n- `calendar_date` date not null (The day.)\n- `year_num` integer not null (Calendar year.)\n- `quarter_num` smallint not null (Calendar quarter, 1-4.)\n- `month_num` smallint not null (Calendar month, 1-12.)\n- `month_name` varchar(9) not null (English month name, e.g. 'November'.)\n- `month_label` varchar(7) not null (Year and month as 'YYYY-MM'.)\n- `day_of_month` smallint not null (Day of the month, 1-31.)\n- `day_of_year` smallint not null (Day of the year, 1-366.)\n- `iso_year` integer not null (ISO 8601 week-numbering year (differs from year_num around New Year).)\n- `iso_week` smallint not null (ISO 8601 week number, 1-53.)\n- `iso_day_of_week` smallint not null (ISO day of the week: 1 = Monday ... 7 = Sunday.)\n- `day_name` varchar(9) not null (English day name, e.g. 'Monday'.)\n- `is_weekend` boolean not null (Saturday or Sunday.)\n- `week_start` date not null (Monday of the ISO week.)\n- `month_start` date not null (First day of the month.)\n- `quarter_start` date not null (First day of the quarter.)\n\n### locales\n- `id` bigint not null\n- `code` varchar(5) not null\n- `name` varchar(255) not null\n- `native_name` varchar(255)\n- unique: (code)\n\n### timezones\n- `id` bigint not null\n- `name` varchar(255) not null\n- `utc_offset_minutes` integer not null\n- unique: (name)\n\n### addresses\nPostal addresses owned by a tenant and optionally a user. At most one default address per user.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `label` varchar(255)\n- `line1` varchar(255) not null\n- `line2` varchar(255)\n- `city` varchar(255)\n- `region_id` bigint -> regions\n- `country_id` bigint not null -> countries\n- `postal_code` varchar(64)\n- `latitude` decimal(9,6)\n- `longitude` decimal(9,6)\n- `is_default` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### address_validations\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `address_id` bigint not null -> addresses\n- `provider` varchar(64) not null\n- `is_valid` boolean not null\n- `response` json\n- `validated_at` timestamp not null\n\n### postal_zones\n- `id` bigint not null\n- `country_id` bigint not null -> countries\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- unique: (country_id, code)\n\n## Area: identity\n\n### tenants\nCustomer organizations on the platform; the root of multi-tenancy. Every tenant-scoped row hangs off a tenant_id.\n- `id` bigint not null\n- `public_id` uuid not null\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `status` varchar(32) not null one of: trial, active, past_due, suspended, churned\n- `default_currency_code` varchar(3) not null -> currencies (ISO-4217 default currency.)\n- `default_locale` varchar(5) not null -> locales\n- `industry` varchar(255)\n- `employee_count` integer\n- `trial_ends_at` timestamp\n- `signup_source` varchar(64)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (public_id); (slug)\n\n### tenant_settings\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `setting_key` varchar(64) not null\n- `setting_value` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, setting_key)\n\n### users\nEnd users and staff belonging to a tenant. Email is unique per tenant, not globally.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `email` varchar(254) not null\n- `full_name` varchar(255)\n- `status` varchar(32) not null one of: active, invited, suspended, deactivated\n- `is_staff` boolean not null (True for internal/admin operators of the tenant, as opposed to customer end-users.)\n- `locale` varchar(5) -> locales\n- `timezone` varchar(64)\n- `password_hash` varchar(255)\n- `last_login_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (public_id); (tenant_id, email)\n\n### user_profiles\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `avatar_url` varchar(2048)\n- `bio` varchar(4000)\n- `phone` varchar(32)\n- `job_title` varchar(255)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (user_id)\n\n### user_preferences\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `theme` varchar(64) not null\n- `email_opt_in` boolean not null\n- `marketing_opt_in` boolean not null\n- `locale_override` varchar(5)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (user_id)\n\n### roles\nPer-tenant RBAC roles. is_system marks built-in roles that cannot be deleted.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `is_system` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### permissions\nGlobal catalog of permission codes (e.g. orders.refund). Shared across all tenants.\n- `id` bigint not null\n- `code` varchar(64) not null\n- `description` varchar(4000)\n- `category` varchar(255)\n- unique: (code)\n\n### role_permissions\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `role_id` bigint not null -> roles\n- `permission_id` bigint not null -> permissions\n- `created_at` timestamp not null\n- unique: (role_id, permission_id)\n\n### user_roles\nAssignment of roles to users. granted_by_user_id records who made the grant.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `role_id` bigint not null -> roles\n- `granted_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- unique: (user_id, role_id)\n\n### teams\nteams — hierarchical sub-org groupings.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `parent_team_id` bigint -> teams\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### team_members\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `team_id` bigint not null -> teams\n- `user_id` bigint not null -> users\n- `team_role` varchar(64) not null\n- `created_at` timestamp not null\n- unique: (team_id, user_id)\n\n### api_keys\nProgrammatic API credentials for a tenant. Only the hash and a short prefix are stored.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `prefix` varchar(64) not null\n- `hashed_key` varchar(64) not null\n- `status` varchar(32) not null one of: active, revoked, expired\n- `created_by_user_id` bigint -> users\n- `last_used_at` timestamp\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id); (prefix)\n\n### personal_access_tokens\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `name` varchar(255) not null\n- `hashed_token` varchar(255) not null\n- `scopes` varchar(64)[] not null\n- `last_used_at` timestamp\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- unique: (hashed_token)\n\n### sessions\nAuthenticated browser/app sessions. revoked_at non-null means force-logged-out.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `token_hash` varchar(255) not null\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `started_at` timestamp not null\n- `last_seen_at` timestamp not null\n- `expires_at` timestamp not null\n- `revoked_at` timestamp\n- unique: (token_hash)\n\n### oauth_accounts\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `provider` varchar(64) not null\n- `provider_account_id` varchar(255) not null\n- `access_token_hash` varchar(255)\n- `created_at` timestamp not null\n- unique: (provider, provider_account_id)\n\n### mfa_devices\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `kind` varchar(64) not null\n- `secret_hash` varchar(255) not null\n- `confirmed_at` timestamp\n- `created_at` timestamp not null\n\n### invitations\nPending invitations to join a tenant; consumed when status becomes accepted.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `email` varchar(254) not null\n- `role_id` bigint -> roles\n- `invited_by_user_id` bigint -> users\n- `status` varchar(32) not null one of: pending, accepted, expired, revoked\n- `token_hash` varchar(255) not null\n- `expires_at` timestamp not null\n- `accepted_at` timestamp\n- `created_at` timestamp not null\n- unique: (token_hash)\n\n### password_resets\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `token_hash` varchar(255) not null\n- `expires_at` timestamp not null\n- `used_at` timestamp\n- `created_at` timestamp not null\n- unique: (token_hash)\n\n### service_accounts\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### login_attempts\nAudit of authentication attempts, successful and failed, for security analytics.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `email` varchar(254)\n- `succeeded` boolean not null\n- `ip_address` varchar(45)\n- `user_agent` varchar(2048)\n- `occurred_at` timestamp not null\n\n## Area: inventory\n\n### warehouses\nPhysical fulfillment sites for a tenant, each anchored to a postal address; one is flagged as the default.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `address_id` bigint -> addresses\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `is_default` boolean not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### stock_locations\nBins, shelves and zones inside a warehouse. Stock is counted at this granularity, not at the warehouse level.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `warehouse_id` bigint not null -> warehouses\n- `code` varchar(64) not null\n- `name` varchar(255)\n- `kind` varchar(64) not null\n- `is_pickable` boolean not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (warehouse_id, code)\n\n### suppliers\nVendors that goods are purchased from on purchase orders.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `name` varchar(255) not null\n- `contact_email` varchar(254)\n- `contact_phone` varchar(32)\n- `lead_time_days` integer\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### supplier_products\nPer-supplier sourcing of a variant: their SKU and unit cost; is_preferred marks the default source.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `supplier_id` bigint not null -> suppliers\n- `variant_id` bigint not null -> product_variants\n- `supplier_sku` varchar(64)\n- `unit_cost` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `min_order_qty` integer\n- `lead_time_days` integer\n- `is_preferred` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (supplier_id, variant_id)\n\n### stock_items\nLive on-hand quantity of a variant in a single location; one row per (variant, location). The movements ledger is its history.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `stock_location_id` bigint not null -> stock_locations\n- `quantity` integer not null\n- `reserved_quantity` integer not null (Portion of quantity earmarked by open reservations and not available to sell.)\n- `reorder_point` integer (Replenishment trigger: at or below this on-hand level the variant is flagged for reorder.)\n- `reorder_quantity` integer\n- `last_counted_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (variant_id, stock_location_id)\n\n### stock_movements\nImmutable append-only ledger of inventory quantity changes; quantity is signed (in positive, out negative) and the running sum reproduces stock_items.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `stock_location_id` bigint not null -> stock_locations\n- `lot_id` bigint -> lots\n- `kind` varchar(32) not null one of: receipt, shipment, adjustment, transfer, return, cycle_count\n- `quantity` integer not null\n- `reference_type` varchar(64)\n- `reference_id` bigint (Loose pointer to the source document (PO line, order, adjustment) identified by reference_type; not a foreign key.)\n- `note` varchar(4000)\n- `created_by_user_id` bigint -> users\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### stock_reservations\nSoft holds that earmark stock for an order so it is not double-sold; order_id references orders by id but has no FK (forward module).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `stock_location_id` bigint not null -> stock_locations\n- `order_id` bigint -> orders\n- `quantity` integer not null\n- `expires_at` timestamp\n- `released_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### purchase_orders\nReplenishment orders sent to a supplier and delivered to a warehouse; public_id is the opaque external identifier.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `supplier_id` bigint not null -> suppliers\n- `warehouse_id` bigint -> warehouses\n- `po_number` varchar(64) not null\n- `status` varchar(32) not null one of: draft, submitted, partially_received, received, cancelled\n- `currency_code` varchar(3) not null -> currencies\n- `expected_total` decimal(14,4) not null (Denormalized order total; the authoritative figure is the sum over purchase_order_lines.)\n- `notes` varchar(4000)\n- `ordered_at` timestamp\n- `expected_at` timestamp\n- `received_at` timestamp\n- `created_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id); (tenant_id, po_number)\n\n### purchase_order_lines\nLine items on a purchase order: variant, quantity and agreed unit cost, with received quantity accruing as goods arrive.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `purchase_order_id` bigint not null -> purchase_orders\n- `variant_id` bigint not null -> product_variants\n- `quantity_ordered` integer not null\n- `quantity_received` integer not null\n- `unit_cost` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (purchase_order_id, variant_id)\n\n### lots\nBatch/lot tracking for a variant, carrying manufacture and expiry dates for FEFO picking and recalls.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `supplier_id` bigint -> suppliers\n- `lot_code` varchar(64) not null\n- `manufactured_at` date\n- `expires_at` date\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, variant_id, lot_code)\n\n### serial_numbers\nserial_numbers — individually tracked units (one row per physical unit), optionally grouped under a lot and located in a stock location.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `lot_id` bigint -> lots\n- `stock_location_id` bigint -> stock_locations\n- `serial` varchar(64) not null\n- `status` varchar(64) not null\n- `received_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, serial)\n\n### inbound_receipts\nGoods-receipt headers recording a physical delivery into a warehouse, usually against a purchase order.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `purchase_order_id` bigint -> purchase_orders\n- `warehouse_id` bigint not null -> warehouses\n- `receipt_number` varchar(64) not null\n- `carrier` varchar(64)\n- `tracking_number` varchar(64)\n- `received_by_user_id` bigint -> users\n- `received_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, receipt_number)\n\n### receipt_lines\nreceipt_lines — the quantities of each variant actually received on a receipt, put away into a specific location (and optionally a lot).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `inbound_receipt_id` bigint not null -> inbound_receipts\n- `purchase_order_line_id` bigint -> purchase_order_lines\n- `variant_id` bigint not null -> product_variants\n- `stock_location_id` bigint -> stock_locations\n- `lot_id` bigint -> lots\n- `quantity_received` integer not null\n- `quantity_rejected` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### stock_adjustments\nManual corrections to on-hand stock (cycle counts, shrinkage, damage); quantity_delta is signed and a reason is required.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `variant_id` bigint not null -> product_variants\n- `stock_location_id` bigint not null -> stock_locations\n- `lot_id` bigint -> lots\n- `quantity_delta` integer not null\n- `reason` varchar(4000) not null\n- `note` varchar(4000)\n- `adjusted_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n## Area: marketing\n\n### email_templates\nReusable email subject/body templates used by campaigns and per-recipient sends.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `subject` varchar(255) not null\n- `body_html` varchar(65535)\n- `body_text` varchar(65535)\n- `from_name` varchar(255)\n- `from_email` varchar(254)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### segments\nAudience definitions; the selection logic lives in the rules jsonb and is materialized into segment_members.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `rules` json not null\n- `is_dynamic` boolean not null (True if membership is recomputed from rules on a schedule; false for static lists.)\n- `member_count` integer not null\n- `last_refreshed_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### segment_members\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `segment_id` bigint not null -> segments\n- `user_id` bigint not null -> users\n- `added_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (segment_id, user_id)\n\n### landing_pages\nlanding_pages — campaign destination pages. Referenced by campaigns and utm_links, so defined ahead of them.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `slug` varchar(255) not null\n- `title` varchar(255) not null\n- `url` varchar(2048)\n- `content_html` varchar(65535)\n- `is_published` boolean not null\n- `published_at` timestamp\n- `view_count` bigint not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### campaigns\nMarketing campaigns; may target a segment, render via a template, drive to a landing page, and link a pricing promotion.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `channel` varchar(64) not null\n- `status` varchar(32) not null one of: draft, scheduled, sending, sent, paused, archived\n- `segment_id` bigint -> segments\n- `email_template_id` bigint -> email_templates\n- `landing_page_id` bigint -> landing_pages\n- `promotion_id` bigint -> promotions\n- `budget` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `scheduled_at` timestamp\n- `started_at` timestamp\n- `ended_at` timestamp\n- `created_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### campaign_messages\nOne row per recipient send; send_status tracks the delivery and engagement lifecycle.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `campaign_id` bigint not null -> campaigns\n- `user_id` bigint not null -> users\n- `email_template_id` bigint -> email_templates\n- `status` varchar(32) not null one of: queued, sent, delivered, opened, clicked, bounced, complained, failed, unsubscribed\n- `recipient_email` varchar(254)\n- `sent_at` timestamp\n- `delivered_at` timestamp\n- `opened_at` timestamp\n- `clicked_at` timestamp\n- `bounced_at` timestamp\n- `unsubscribed_at` timestamp\n- `error_message` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### ab_tests\nA/B test definitions; winner_variant_id records the chosen variant once the experiment completes.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `campaign_id` bigint -> campaigns\n- `name` varchar(255) not null\n- `hypothesis` varchar(4000)\n- `status` varchar(32) not null one of: draft, running, paused, completed, archived\n- `goal_metric` varchar(255)\n- `started_at` timestamp\n- `ended_at` timestamp\n- `winner_variant_id` bigint\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### ab_variants\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ab_test_id` bigint not null -> ab_tests\n- `name` varchar(255) not null\n- `is_control` boolean not null\n- `traffic_weight` decimal(5,4) not null (Fraction of eligible traffic routed to this variant; weights across a test should sum to ~1.)\n- `config` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (ab_test_id, name)\n\n### ab_assignments\nSticky assignment of a user to one variant within an A/B test; converted_at records goal completion.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ab_test_id` bigint not null -> ab_tests\n- `ab_variant_id` bigint not null -> ab_variants\n- `user_id` bigint not null -> users\n- `assigned_at` timestamp not null\n- `converted_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (ab_test_id, user_id)\n\n### attributions\nTouch attributions crediting a campaign with a conversion; order_id is set for purchase conversions.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `campaign_id` bigint -> campaigns\n- `order_id` bigint -> orders\n- `model` varchar(64) not null\n- `touch_point` varchar(64)\n- `weight` decimal(5,4) not null (Fractional credit for this touch under the attribution model (e.g. linear, last_touch).)\n- `attributed_revenue` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### referrals\nReferral codes owned by a referring user, with optional reward and redemption caps.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `referrer_user_id` bigint not null -> users\n- `code` varchar(64) not null\n- `reward_amount` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `max_redemptions` integer\n- `redemption_count` integer not null\n- `is_active` boolean not null\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### referral_redemptions\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `referral_id` bigint not null -> referrals\n- `referred_user_id` bigint not null -> users\n- `order_id` bigint -> orders\n- `reward_amount` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `redeemed_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (referral_id, referred_user_id)\n\n### loyalty_programs\nLoyalty: program defs -> per-user accounts (balance) -> immutable earn/burn transaction ledger.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `points_per_currency` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `is_active` boolean not null\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, name)\n\n### loyalty_accounts\nPer-user point balance within a loyalty program; points_balance is the current redeemable total.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `loyalty_program_id` bigint not null -> loyalty_programs\n- `user_id` bigint not null -> users\n- `points_balance` bigint not null\n- `lifetime_points` bigint not null\n- `tier` varchar(64)\n- `enrolled_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (loyalty_program_id, user_id)\n\n### loyalty_transactions\nImmutable earn/burn ledger of loyalty point movements; append-only, never updated.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `loyalty_account_id` bigint not null -> loyalty_accounts\n- `user_id` bigint not null -> users\n- `direction` varchar(64) not null\n- `points` bigint not null\n- `reason` varchar(4000)\n- `order_id` bigint -> orders\n- `balance_after` bigint (Account points_balance immediately after this entry was applied.)\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### utm_links\nTracked links carrying UTM parameters, optionally tied to a campaign and landing page.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `campaign_id` bigint -> campaigns\n- `landing_page_id` bigint -> landing_pages\n- `slug` varchar(255) not null\n- `target_url` varchar(2048) not null\n- `utm_source` varchar(255)\n- `utm_medium` varchar(255)\n- `utm_campaign` varchar(255)\n- `utm_term` varchar(255)\n- `utm_content` varchar(255)\n- `click_count` bigint not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n## Area: ops\n\n### feature_flags\nFeature toggle and rollout definitions, addressed by a tenant-unique key. kind distinguishes boolean toggles from multivariate and percentage rollouts.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `key` varchar(64) not null\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `kind` varchar(32) not null one of: boolean, multivariate, percentage_rollout\n- `is_enabled` boolean not null\n- `default_value` json not null (Fallback value served when no rule or override matches; jsonb to support multivariate payloads, not just booleans.)\n- `rollout_percentage` integer\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, key)\n\n### feature_flag_rules\nOrdered targeting rules for a flag; the first rule whose jsonb conditions match decides the served value.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `feature_flag_id` bigint not null -> feature_flags\n- `sort_position` integer not null\n- `conditions` json not null\n- `serve_value` json not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (feature_flag_id, sort_position)\n\n### feature_flag_overrides\nExplicit per-user or tenant-wide overrides that bypass rule evaluation for a flag.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `feature_flag_id` bigint not null -> feature_flags\n- `user_id` bigint -> users\n- `override_value` json not null\n- `reason` varchar(4000)\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### settings\nTyped key/value configuration per tenant. value_type tags how the jsonb value should be interpreted by the application.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `setting_key` varchar(64) not null\n- `setting_value` json not null\n- `value_type` varchar(64) not null (Logical type hint for value (string/number/boolean/json/date); the column itself is always jsonb.)\n- `is_secret` boolean not null\n- `description` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, setting_key)\n\n### jobs\nBackground job definitions registered per tenant; job_runs records each execution.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `queue` varchar(64) not null\n- `handler` varchar(64) not null\n- `payload` json not null\n- `max_attempts` integer not null\n- `is_enabled` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### job_runs\nImmutable record of a single background job execution, including status, attempt number and timing.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `job_id` bigint not null -> jobs\n- `status` varchar(32) not null one of: queued, running, succeeded, failed, cancelled, retrying\n- `attempt` integer not null\n- `queued_at` timestamp not null\n- `started_at` timestamp\n- `finished_at` timestamp\n- `duration_ms` integer\n- `error_message` varchar(4000)\n- `result_payload` json\n- `created_at` timestamp not null\n\n### integrations\nThird-party service connections for a tenant (e.g. ERP, shipping, accounting), with current status and config.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `provider` varchar(64) not null\n- `name` varchar(255) not null\n- `status` varchar(32) not null one of: connected, disconnected, error, syncing\n- `config` json not null\n- `connected_by_user_id` bigint -> users\n- `connected_at` timestamp\n- `last_synced_at` timestamp\n- `last_error` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, provider, name)\n\n### integration_syncs\nImmutable record of a single data-sync run against an integration.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `integration_id` bigint not null -> integrations\n- `status` varchar(32) not null one of: queued, running, succeeded, failed, cancelled, retrying\n- `direction` varchar(64) not null\n- `records_processed` integer not null\n- `records_failed` integer not null\n- `started_at` timestamp\n- `finished_at` timestamp\n- `error_message` varchar(4000)\n- `created_at` timestamp not null\n\n### files\nMetadata for files uploaded to object storage; the actual blob lives in storage_bucket/storage_key, not in the database.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `filename` varchar(255) not null\n- `content_type` varchar(64) not null\n- `byte_size` bigint not null\n- `storage_bucket` varchar(255) not null\n- `storage_key` varchar(2048) not null\n- `checksum` varchar(255)\n- `uploaded_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (public_id); (storage_bucket, storage_key)\n\n### file_versions\nVersion chain for a file; is_current flags the active version (one per file, enforced by partial unique index).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `file_id` bigint not null -> files\n- `version` integer not null\n- `byte_size` bigint not null\n- `storage_key` varchar(2048) not null\n- `checksum` varchar(255)\n- `is_current` boolean not null\n- `created_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (file_id, version)\n\n### import_batches\nBulk data-import runs with row-level progress counters and a terminal status.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `entity_type` varchar(64) not null\n- `source_filename` varchar(255)\n- `file_id` bigint -> files\n- `status` varchar(32) not null one of: pending, processing, completed, failed, partially_failed\n- `total_rows` integer not null\n- `processed_rows` integer not null\n- `succeeded_rows` integer not null\n- `failed_rows` integer not null\n- `started_by_user_id` bigint -> users\n- `started_at` timestamp\n- `finished_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### import_errors\nImmutable per-row failures captured during an import batch, with the offending raw row preserved as jsonb.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `import_batch_id` bigint not null -> import_batches\n- `source_row_number` integer not null\n- `column_name` varchar(64)\n- `error_code` varchar(64) not null\n- `error_message` varchar(4000) not null\n- `raw_row` json\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### scheduled_tasks\nCron-expression driven recurring tasks; next_run_at is the precomputed next fire time used by the scheduler.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `cron_expression` varchar(2048) not null\n- `timezone` varchar(64) not null\n- `job_id` bigint -> jobs\n- `is_enabled` boolean not null\n- `last_run_at` timestamp\n- `next_run_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### system_health_checks\nsystem_health_checks — immutable periodic health probe results.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `component` varchar(255) not null\n- `is_healthy` boolean not null\n- `latency_ms` integer\n- `details` json\n- `checked_at` timestamp not null\n- `created_at` timestamp not null\n\n### rate_limits\nRequest quota windows scoped per tenant or per API key; an api_key-null row is the tenant-wide default for a scope.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `api_key_id` bigint -> api_keys\n- `limit_scope` varchar(64) not null\n- `max_requests` integer not null\n- `window_seconds` integer not null\n- `is_enabled` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### secrets_vault\nReferences and metadata for secrets held in an external vault; never stores plaintext, only a provider pointer (external_ref).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `key` varchar(64) not null\n- `provider` varchar(64) not null\n- `external_ref` varchar(255) not null (Opaque pointer into the external secret store (e.g. a Vault path or KMS key id); resolved at runtime, never the secret value itself.)\n- `version` integer not null\n- `last_rotated_at` timestamp\n- `expires_at` timestamp\n- `created_by_user_id` bigint -> users\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, key)\n\n## Area: pricing\n\n### price_lists\nNamed price books, each scoped to one currency; a tenant keeps a default list per currency plus contract/regional books.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `code` varchar(64) not null\n- `currency_code` varchar(3) not null -> currencies\n- `is_default` boolean not null\n- `is_active` boolean not null\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### prices\nA variant's price within a price list over a validity window; an exclusion constraint forbids overlapping windows for the same list/variant/min_quantity.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `price_list_id` bigint not null -> price_lists\n- `variant_id` bigint not null -> product_variants\n- `currency_code` varchar(3) not null -> currencies\n- `amount` decimal(14,4) not null\n- `compare_at_amount` decimal(14,4) (Optional strike-through / \"was\" price shown next to the effective amount.)\n- `min_quantity` integer not null\n- `valid_from` timestamp not null\n- `valid_to` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### discounts\nReusable discount definitions; coupons, price_rules and promotions point at these for the actual reduction.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `kind` varchar(32) not null one of: percentage, fixed_amount, free_shipping, bogo\n- `discount_value` decimal(14,4) not null (Percentage (0-100) when kind=percentage, else a fixed monetary amount in currency_code.)\n- `currency_code` varchar(3) -> currencies\n- `max_amount` decimal(14,4)\n- `min_subtotal` decimal(14,4)\n- `applies_to_shipping` boolean not null\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n\n### coupons\nRedeemable codes granting a discount; code is unique per tenant and usage is capped by usage_limit / per_customer_limit.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `discount_id` bigint not null -> discounts\n- `code` varchar(64) not null\n- `status` varchar(32) not null one of: active, scheduled, expired, disabled\n- `usage_limit` integer\n- `per_customer_limit` integer\n- `times_redeemed` integer not null (Denormalized running count of redemptions, capped by usage_limit.)\n- `starts_at` timestamp\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, code)\n\n### coupon_redemptions\nImmutable record of each coupon use; order_id references orders.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `coupon_id` bigint not null -> coupons\n- `user_id` bigint -> users\n- `order_id` bigint -> orders\n- `discount_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `redeemed_at` timestamp not null\n- `created_at` timestamp not null\n\n### tax_categories\ntax_categories — product tax classes (e.g. standard goods, digital, food, exempt). Variants/products are mapped to one of these to find their rate.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `code` varchar(64) not null\n- `description` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, code)\n\n### tax_zones\nGeographic tax regions defined at country, region or postal-zone granularity.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `country_id` bigint -> countries\n- `region_id` bigint -> regions\n- `postal_zone_id` bigint -> postal_zones\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### tax_rates\nEffective tax rate for a (tax_category, tax_zone) pair, with an optional validity window so rate changes stay auditable.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `tax_category_id` bigint not null -> tax_categories\n- `tax_zone_id` bigint not null -> tax_zones\n- `name` varchar(255) not null\n- `rate` decimal(8,5) not null (Fractional rate in [0,1]; e.g. 0.20000 for 20% VAT.)\n- `is_compound` boolean not null\n- `is_inclusive` boolean not null (True when displayed prices already include this tax (tax-inclusive pricing).)\n- `valid_from` timestamp not null\n- `valid_to` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### price_rules\nprice_rules — conditional pricing rule headers (e.g. \"10% off when cart has 3+ items from brand X\"). Conditions live in price_rule_conditions. The reward references a discount definition. priority orders evaluation; lower = first.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `discount_id` bigint -> discounts\n- `name` varchar(255) not null\n- `priority` integer not null\n- `is_active` boolean not null\n- `stackable` boolean not null\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### price_rule_conditions\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `price_rule_id` bigint not null -> price_rules\n- `attribute` varchar(255) not null\n- `operator` varchar(64) not null\n- `condition_value` json not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### promotions\nCampaign-linked promotions; standalone by design so campaigns can reference promotions, never the reverse.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `discount_id` bigint -> discounts\n- `coupon_id` bigint -> coupons\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `campaign_code` varchar(64) (Free-text correlation handle to a marketing campaign; not an FK since marketing is a higher-numbered module.)\n- `is_active` boolean not null\n- `starts_at` timestamp\n- `ends_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### promotion_products\nJunction restricting a promotion to specific products.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `promotion_id` bigint not null -> promotions\n- `product_id` bigint not null -> products\n- `created_at` timestamp not null\n- unique: (promotion_id, product_id)\n\n## Area: sales\n\n### carts\nShopping carts, anonymous (user_id null) or owned. Converts to an order; abandoned carts feed recovery campaigns.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `session_token` varchar(255)\n- `status` varchar(32) not null one of: active, converted, abandoned, merged\n- `currency_code` varchar(3) not null -> currencies\n- `item_count` integer not null\n- `subtotal` decimal(14,4) not null\n- `abandoned_at` timestamp\n- `converted_at` timestamp\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### cart_items\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `cart_id` bigint not null -> carts\n- `product_variant_id` bigint not null -> product_variants\n- `quantity` integer not null\n- `unit_price` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (cart_id, product_variant_id)\n\n### orders\nCentral order fact table. public_id is the opaque id shown to customers; totals are denormalized money on the row.\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `cart_id` bigint -> carts\n- `order_number` varchar(64) not null (Human-facing sequential order number, unique per tenant (distinct from the opaque public_id).)\n- `status` varchar(32) not null one of: pending, confirmed, paid, partially_fulfilled, fulfilled, cancelled, refunded\n- `billing_address_id` bigint -> addresses\n- `shipping_address_id` bigint -> addresses\n- `currency_code` varchar(3) not null -> currencies\n- `subtotal` decimal(14,4) not null\n- `discount_total` decimal(14,4) not null\n- `tax_total` decimal(14,4) not null\n- `shipping_total` decimal(14,4) not null\n- `grand_total` decimal(14,4) not null (Final amount charged: subtotal - discount_total + tax_total + shipping_total.)\n- `customer_email` varchar(254)\n- `customer_note` varchar(4000)\n- `placed_at` timestamp\n- `cancelled_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id); (tenant_id, order_number)\n\n### order_items\nLine items of an order; sku and name are snapshotted at purchase time so historical orders survive catalog changes.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `product_variant_id` bigint not null -> product_variants\n- `sku` varchar(64) not null\n- `name` varchar(255) not null\n- `quantity` integer not null\n- `quantity_fulfilled` integer not null (Running count of units already dispatched across fulfillments; never exceeds quantity.)\n- `unit_price` decimal(14,4) not null\n- `discount_total` decimal(14,4) not null\n- `tax_total` decimal(14,4) not null\n- `line_total` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### order_status_history\nAppend-only audit of order status transitions; immutable (no updated_at).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `from_status` varchar(32) one of: pending, confirmed, paid, partially_fulfilled, fulfilled, cancelled, refunded\n- `to_status` varchar(32) not null one of: pending, confirmed, paid, partially_fulfilled, fulfilled, cancelled, refunded\n- `changed_by_user_id` bigint -> users\n- `note` varchar(4000)\n- `created_at` timestamp not null\n\n### order_discounts\nDiscounts and coupon redemptions applied to an order; discount_id/coupon_id reference the pricing module.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `discount_id` bigint -> discounts\n- `coupon_id` bigint -> coupons\n- `code` varchar(64)\n- `description` varchar(4000)\n- `amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### fulfillments\nA batch of order items dispatched together from one warehouse; tracks pick/pack/ship lifecycle.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `warehouse_id` bigint -> warehouses\n- `status` varchar(32) not null one of: pending, picked, packed, shipped, delivered, returned\n- `reference` varchar(255)\n- `packed_at` timestamp\n- `shipped_at` timestamp\n- `delivered_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### fulfillment_items\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `fulfillment_id` bigint not null -> fulfillments\n- `order_item_id` bigint not null -> order_items\n- `quantity` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (fulfillment_id, order_item_id)\n\n### shipments\nCarrier shipment for a fulfillment, with tracking number and delivery timestamps.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `fulfillment_id` bigint not null -> fulfillments\n- `carrier` varchar(64) not null\n- `service_level` varchar(64)\n- `tracking_number` varchar(64)\n- `tracking_url` varchar(2048)\n- `shipping_cost` decimal(14,4)\n- `currency_code` varchar(3) -> currencies\n- `weight_grams` integer\n- `shipped_at` timestamp\n- `delivered_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### shipment_tracking\nshipment_tracking — append-only stream of carrier tracking events. IMMUTABLE: occurred_at only, no updated_at.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `shipment_id` bigint not null -> shipments\n- `status` varchar(64) not null\n- `description` varchar(4000)\n- `location` varchar(255)\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### order_returns\nReturn merchandise authorization (RMA) header; status walks requested -> approved -> received -> refunded.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `rma_number` varchar(64) not null\n- `status` varchar(32) not null one of: requested, approved, received, refunded, rejected\n- `reason` varchar(4000)\n- `refund_total` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `requested_at` timestamp not null\n- `approved_at` timestamp\n- `received_at` timestamp\n- `refunded_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, rma_number)\n\n### return_items\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `return_id` bigint not null -> order_returns\n- `order_item_id` bigint not null -> order_items\n- `quantity` integer not null\n- `reason` varchar(4000)\n- `restock` boolean not null (Whether the returned units should be added back to sellable inventory.)\n- `refund_amount` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (return_id, order_item_id)\n\n### gift_cards\nStored-value gift cards; code is unique per tenant and balance decreases as it is redeemed against orders.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `code` varchar(64) not null\n- `status` varchar(64) not null\n- `initial_balance` decimal(14,4) not null\n- `balance` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `issued_to_user_id` bigint -> users\n- `issued_order_id` bigint -> orders\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, code)\n\n### gift_card_transactions\nImmutable ledger of gift-card balance changes; positive amount credits, negative debits.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `gift_card_id` bigint not null -> gift_cards\n- `order_id` bigint -> orders\n- `amount` decimal(14,4) not null\n- `balance_after` decimal(14,4) not null\n- `currency_code` varchar(3) not null -> currencies\n- `note` varchar(4000)\n- `created_at` timestamp not null\n\n### order_notes\norder_notes — internal staff notes attached to an order (not the customer note that lives on the order row itself).\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `order_id` bigint not null -> orders\n- `author_user_id` bigint -> users\n- `body` varchar(65535) not null\n- `is_pinned` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### draft_orders\nMerchant-created quotes/draft orders, optionally converted into a real order.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint -> users\n- `created_by_user_id` bigint -> users\n- `converted_order_id` bigint -> orders\n- `name` varchar(255)\n- `status` varchar(64) not null\n- `currency_code` varchar(3) not null -> currencies\n- `subtotal` decimal(14,4) not null\n- `grand_total` decimal(14,4) not null\n- `payload` json not null\n- `expires_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n## Area: support\n\n### support_agents\nSupport staff who handle tickets; a support-specific extension of an users record carrying availability and load limits.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `user_id` bigint not null -> users\n- `display_name` varchar(255)\n- `signature` varchar(255)\n- `is_available` boolean not null\n- `max_open_tickets` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, user_id)\n\n### agent_groups\nNamed groups of agents used for ticket routing and assignment.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `description` varchar(4000)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### agent_group_members\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `agent_group_id` bigint not null -> agent_groups\n- `support_agent_id` bigint not null -> support_agents\n- `is_lead` boolean not null\n- `created_at` timestamp not null\n- unique: (agent_group_id, support_agent_id)\n\n### sla_policies\nLookup of service-level targets (first-response and resolution minutes), usually keyed by ticket priority.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `priority` varchar(32) one of: low, normal, high, urgent\n- `first_response_target_minutes` integer not null\n- `resolution_target_minutes` integer not null\n- `business_hours_only` boolean not null (When true, SLA timers pause outside the tenant business calendar rather than running 24/7.)\n- `is_active` boolean not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### support_tags\nsupport_tags — tag catalog for categorizing tickets.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `color` varchar(64)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, name)\n\n### knowledge_categories\nKnowledge base: hierarchical categories + articles + per-article feedback.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `name` varchar(255) not null\n- `slug` varchar(255) not null\n- `parent_category_id` bigint -> knowledge_categories\n- `sort_position` integer not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (tenant_id, slug)\n\n### knowledge_articles\nHelp-center articles published to customers; soft-deletable via deleted_at.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `knowledge_category_id` bigint -> knowledge_categories\n- `author_user_id` bigint -> users\n- `title` varchar(255) not null\n- `slug` varchar(255) not null\n- `body` varchar(65535)\n- `is_published` boolean not null\n- `view_count` bigint not null\n- `helpful_count` integer not null (Denormalized count of positive article_feedback rows, kept for fast sorting.)\n- `not_helpful_count` integer not null\n- `published_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, slug)\n\n### article_feedback\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `knowledge_article_id` bigint not null -> knowledge_articles\n- `user_id` bigint -> users\n- `was_helpful` boolean not null\n- `comment` varchar(4000)\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### macros\nCanned reply templates agents insert into ticket messages. Soft-deletable.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `created_by_agent_id` bigint -> support_agents\n- `title` varchar(255) not null\n- `body` varchar(65535) not null\n- `is_shared` boolean not null\n- `usage_count` bigint not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- `deleted_at` timestamp\n- unique: (tenant_id, title)\n\n### tickets\nCore customer support request. requester_user_id is the customer; assignee_agent_id is the owning agent (null = unassigned queue).\n- `id` bigint not null\n- `public_id` uuid not null\n- `tenant_id` bigint not null -> tenants\n- `requester_user_id` bigint not null -> users\n- `assignee_agent_id` bigint -> support_agents\n- `agent_group_id` bigint -> agent_groups\n- `sla_policy_id` bigint -> sla_policies\n- `subject` varchar(255) not null\n- `status` varchar(32) not null one of: new, open, pending, on_hold, solved, closed\n- `priority` varchar(32) not null one of: low, normal, high, urgent\n- `channel` varchar(32) not null one of: email, chat, phone, web, social, api\n- `first_response_at` timestamp (Timestamp of the first public agent reply, used to measure SLA first-response compliance.)\n- `resolved_at` timestamp\n- `closed_at` timestamp\n- `last_message_at` timestamp\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (public_id)\n\n### ticket_messages\nReplies on a ticket: is_public=true are customer-visible, is_public=false are internal agent notes.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ticket_id` bigint not null -> tickets\n- `author_user_id` bigint -> users\n- `author_agent_id` bigint -> support_agents\n- `body` varchar(65535) not null\n- `is_public` boolean not null\n- `channel` varchar(32) one of: email, chat, phone, web, social, api\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### ticket_events\nImmutable append-only audit of ticket changes such as status transitions, reassignment, and priority changes.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ticket_id` bigint not null -> tickets\n- `actor_user_id` bigint -> users\n- `actor_agent_id` bigint -> support_agents\n- `event_type` varchar(64) not null\n- `old_value` varchar(4000)\n- `new_value` varchar(4000)\n- `metadata` json not null\n- `occurred_at` timestamp not null\n- `created_at` timestamp not null\n\n### ticket_tags_map\nticket_tags_map — junction tickets × support_tags.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ticket_id` bigint not null -> tickets\n- `support_tag_id` bigint not null -> support_tags\n- `created_at` timestamp not null\n- unique: (ticket_id, support_tag_id)\n\n### sla_breaches\nRecorded SLA-target breaches on a ticket, with how many minutes the target was missed by.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ticket_id` bigint not null -> tickets\n- `sla_policy_id` bigint -> sla_policies\n- `breach_kind` varchar(64) not null\n- `target_at` timestamp not null\n- `breached_at` timestamp not null\n- `minutes_over` integer\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n\n### csat_responses\nCustomer satisfaction rating (1-5) collected after a ticket is resolved; one response per ticket.\n- `id` bigint not null\n- `tenant_id` bigint not null -> tenants\n- `ticket_id` bigint not null -> tickets\n- `respondent_user_id` bigint -> users\n- `rated_agent_id` bigint -> support_agents (The agent the customer was rating, captured at submission in case the ticket is later reassigned.)\n- `score` integer not null\n- `comment` varchar(4000)\n- `submitted_at` timestamp not null\n- `created_at` timestamp not null\n- `updated_at` timestamp not null\n- unique: (ticket_id)\n\n\n# Your role\n\nYou are Henrik Aalto, Chief Information Security Officer at Tidewick Commerce."],"tools":[],"cliPrefix":"You are a Claude agent, built on Anthropic's Claude Agent SDK."},"type":"attachment","uuid":"6a37a031-5268-49f5-8c81-3529184a42cc","timestamp":"2026-10-04T00:05:11.734Z","userType":"external","entrypoint":"sdk-cli","cwd":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","version":"2.1.275","gitBranch":"HEAD"}
{"type":"last-prompt","lastPrompt":"You are Henrik Aalto, Chief Information Security Officer at Tidewick Commerce. Joined from a cloud provider's security team. Several enterprise deals depend on passing security reviews.  Your area: Ac…","leafUuid":"6a37a031-5268-49f5-8c81-3529184a42cc","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36"}
{"type":"ai-title","aiTitle":"Account security briefing questions","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36"}
{"type":"atis-latch","atis":"","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36"}
{"type":"queue-operation","operation":"enqueue","timestamp":"2026-10-04T03:14:09.509Z","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","content":"Here is the memo from Noah Fischer:\n\n---\n**To:** Henrik Aalto, CISO\n**From:** Noah Fischer, Analytics\n**Re:** Account security snapshot — week 1 findings\n\nHenrik, here's the evidence-based picture across your five questions, in priority order.\n\n**1. Sign-in security: no attack signal, stable baseline.**\nWeekly login attempts have run 1,300–2,400 with failure rates steady at 5–8% and ~1.5 attempts per distinct IP for 26 straight weeks (*Weekly sign-in attempt volume*). The one spike — +41% in the week of Nov 24 — I drilled into by tenant (*Login attempts by tenant, Nov 24 week*): it was spread across 110 merchants, top tenant's share (17.6%) matched its normal size, failure rates stayed in-band, and distinct IPs tracked distinct emails roughly 1:1. This reads as Black Friday/Cyber Monday shopping traffic, not credential stuffing. **Confident** in this conclusion.\n\n**2. MFA: weak and flat, especially for shoppers.**\nStaff MFA has plateaued at 54–57% all of 2025 (547 staff, 300 with confirmed MFA in Dec = 54.8%) — not improving. Shopper MFA is literally **0.0%** every month, across 11,207 shopper accounts (*Platform-wide MFA adoption trend*). **Confident** in the numbers; **open question** whether shopper MFA is an unused feature or simply doesn't exist for end customers — worth confirming with product before this goes into a questionnaire answer.\n\n**3. Access governance: good news with one concrete gap.**\nAll 120 tenants carry two high-power system roles, Owner (26 permissions) and Administrator (25) (*Privileged role landscape*). I checked every account holding either role for dormancy: 16 qualify, and **all 16 sit on churned tenants** (9 merchants), still flagged `status = active`, up to 599 days since last login (*Dormant users holding Owner/Administrator access*). No dormant privileged access found on any live, paying tenant. The real issue isn't negligent review of active accounts — it's that **we have no process to deactivate accounts when a tenant churns.** Easy to name, easy to fix.\n\n**4. API keys: well-used, but poorly rotated.**\nNo tenant shows an active key that's unused or abandoned (*API key hygiene*). But age tells a different story: 67.3% of active keys (76 of ~113) are 4+ years old, including one 2,743 days old (*Active API key age distribution*). There's no rotation field or process for api_keys in the schema — usage looks healthy, rotation hygiene does not.\n\n**5. Audit trail: real, specific gap on deletions.**\nLogin/logout/create/export are logged for 100% of tenants, update for 94.2% (*Audit log coverage by action type*). But 'delete' events are thin — only 27 ever, 16/120 tenants. I tested this directly against real soft-deletions (*Audit-trail coverage of soft-deletions*): **615 products were soft-deleted across 91 tenants, and 0 of those show up as a 'delete' audit_log row.** Users fared better (27 of 39 logged, 69.2%). We could not credibly reconstruct \"who deleted this product and when\" today. Compliance monitoring is also thin: only 9 compliance_events total in 3 years (all resolved) — too sparse a sample to call it a proven control (*Compliance event resolution health*).\n\n**Caveats on the data:**\n- The users table's earliest soft-delete (2020-04-28) and the oldest API key (~2018) predate our 2023 warehouse history — likely carried-over legacy timestamps; worth a data-quality check before quoting ages externally.\n- Campaigns/coupons/discounts/promotions show zero soft-deletions ever — I can't tell if that's a true audit gap or those features are simply never deleted in practice.\n\n**Next steps I'd pursue:** (1) confirm whether shopper MFA is a real product gap or out of scope; (2) quantify the churn-deactivation gap across all entity types, not just role holders; (3) check whether non-product entities (orders, users, discounts) have the same delete-logging blind spot as products, since one table showing 0% is the most audit-questionnaire-relevant number we have.\n---\n\nYou have another week with Noah Fischer. Reply with a short note:\n\n- what you now want to understand better, or what surprised you and needs explaining;\n- 2 to 4 questions in your own words.\n\nDo not name tables or columns.\n"}
{"type":"queue-operation","operation":"dequeue","timestamp":"2026-10-04T03:14:09.509Z","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36"}
{"parentUuid":"6a37a031-5268-49f5-8c81-3529184a42cc","isSidechain":false,"promptId":"272dfa9b-c504-4080-9240-4d3ca79f307c","type":"user","message":{"role":"user","content":"Here is the memo from Noah Fischer:\n\n---\n**To:** Henrik Aalto, CISO\n**From:** Noah Fischer, Analytics\n**Re:** Account security snapshot — week 1 findings\n\nHenrik, here's the evidence-based picture across your five questions, in priority order.\n\n**1. Sign-in security: no attack signal, stable baseline.**\nWeekly login attempts have run 1,300–2,400 with failure rates steady at 5–8% and ~1.5 attempts per distinct IP for 26 straight weeks (*Weekly sign-in attempt volume*). The one spike — +41% in the week of Nov 24 — I drilled into by tenant (*Login attempts by tenant, Nov 24 week*): it was spread across 110 merchants, top tenant's share (17.6%) matched its normal size, failure rates stayed in-band, and distinct IPs tracked distinct emails roughly 1:1. This reads as Black Friday/Cyber Monday shopping traffic, not credential stuffing. **Confident** in this conclusion.\n\n**2. MFA: weak and flat, especially for shoppers.**\nStaff MFA has plateaued at 54–57% all of 2025 (547 staff, 300 with confirmed MFA in Dec = 54.8%) — not improving. Shopper MFA is literally **0.0%** every month, across 11,207 shopper accounts (*Platform-wide MFA adoption trend*). **Confident** in the numbers; **open question** whether shopper MFA is an unused feature or simply doesn't exist for end customers — worth confirming with product before this goes into a questionnaire answer.\n\n**3. Access governance: good news with one concrete gap.**\nAll 120 tenants carry two high-power system roles, Owner (26 permissions) and Administrator (25) (*Privileged role landscape*). I checked every account holding either role for dormancy: 16 qualify, and **all 16 sit on churned tenants** (9 merchants), still flagged `status = active`, up to 599 days since last login (*Dormant users holding Owner/Administrator access*). No dormant privileged access found on any live, paying tenant. The real issue isn't negligent review of active accounts — it's that **we have no process to deactivate accounts when a tenant churns.** Easy to name, easy to fix.\n\n**4. API keys: well-used, but poorly rotated.**\nNo tenant shows an active key that's unused or abandoned (*API key hygiene*). But age tells a different story: 67.3% of active keys (76 of ~113) are 4+ years old, including one 2,743 days old (*Active API key age distribution*). There's no rotation field or process for api_keys in the schema — usage looks healthy, rotation hygiene does not.\n\n**5. Audit trail: real, specific gap on deletions.**\nLogin/logout/create/export are logged for 100% of tenants, update for 94.2% (*Audit log coverage by action type*). But 'delete' events are thin — only 27 ever, 16/120 tenants. I tested this directly against real soft-deletions (*Audit-trail coverage of soft-deletions*): **615 products were soft-deleted across 91 tenants, and 0 of those show up as a 'delete' audit_log row.** Users fared better (27 of 39 logged, 69.2%). We could not credibly reconstruct \"who deleted this product and when\" today. Compliance monitoring is also thin: only 9 compliance_events total in 3 years (all resolved) — too sparse a sample to call it a proven control (*Compliance event resolution health*).\n\n**Caveats on the data:**\n- The users table's earliest soft-delete (2020-04-28) and the oldest API key (~2018) predate our 2023 warehouse history — likely carried-over legacy timestamps; worth a data-quality check before quoting ages externally.\n- Campaigns/coupons/discounts/promotions show zero soft-deletions ever — I can't tell if that's a true audit gap or those features are simply never deleted in practice.\n\n**Next steps I'd pursue:** (1) confirm whether shopper MFA is a real product gap or out of scope; (2) quantify the churn-deactivation gap across all entity types, not just role holders; (3) check whether non-product entities (orders, users, discounts) have the same delete-logging blind spot as products, since one table showing 0% is the most audit-questionnaire-relevant number we have.\n---\n\nYou have another week with Noah Fischer. Reply with a short note:\n\n- what you now want to understand better, or what surprised you and needs explaining;\n- 2 to 4 questions in your own words.\n\nDo not name tables or columns.\n"},"uuid":"77f6656d-53f4-4797-bff9-02d585d4762f","timestamp":"2026-10-04T03:14:09.514Z","permissionMode":"default","promptSource":"sdk","userType":"external","entrypoint":"sdk-cli","cwd":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","version":"2.1.275","gitBranch":"HEAD"}
{"parentUuid":"77f6656d-53f4-4797-bff9-02d585d4762f","isSidechain":false,"message":{"model":"claude-sonnet-5","id":"msg_011CfgU7eYJhsJ4zkDsPKwce","type":"message","role":"assistant","content":[{"type":"thinking","thinking":"","signature":"EpoPCrwBCBIYAipA2IdO5mvVYHQcJfpbEAYswW6NSDeT9Ifn8cJbv4xLSP/gut/3HJDmL7Mpr5jRptkY4lbYcduNX89whQG8v1cUTDIPY2xhdWRlLXNvbm5ldC01OABCCHRoaW5raW5nWiQ3OGQ5ZDdmNy02ZTRlLTQ2NTItOWZmZi02MGUwOGQ3Y2JkMmRyEOgeZ+ZPTOeF4/ly19pwhKaIAQGaAREKD2NsYXVkZS1zb25uZXQtNagBiImH1gawAQISDOkYLZZtOESDXIsNMBoMVqn57WwEaEJFE1bCIjBtM98mbkE3Et+uMuhcsSgP7Q0YogDTiy0o9Y2+UzCAbzMHXuR8BblnzF5Hawzwehoqig2EtFuawmoyiQI7LURs3OoG9l1GzMvtxO6V1BwyAYow92Larei/JEniKIF/ZOOh0W4RzQwgmX9F/LXxMr/Nq7dfRspAACsj5iEb+/D51Q1Va9nv0VL5gWHk0l3+30XWlx/OFqJlzvEnI4OenCpGSEsdZqBO/0zh8qKlWuLv70//vbuZxYkVMu1fIznKWnH2LS/p+HRWNbI6tQLee7bw5VZSPqUdTBwdA9iGjT4E4fsTfZcgz4+cwCiSBrhebV13v8a7LtRlyDUe0ksghHgsNQIrrg1ibzvaZnFYWQaPQUTfTysjv6bPWsYHApPuVfdMTWDy0vtJkK/NykOZrcFxCVuhlfF6ChKeMMR3BjM/PEdwn78wwOIbdrM7SxHqtyv0er6NzdpbWp3zDGUjzwGfBQ2zMcOCwVI7Y6JRyRbDTfasXVy+vd0/ebAaU0EqmvlgVPHB8RcLaNB5/Ww02Zf/xPtLtm4FkdUiEbDZ+yxfFjfCOfdIZd2Xu28bh/AeZ+KarMXDRI65nS0m5RDAOzB0UrgPsN5PTODg7ttP1d+bXATy/ZQ+LnyWlFbpH4BAljgR4mScYL7O36Xek61Nczr9bsZ/vBbE26vtVN9vurLUGNFCryRlXJL76uwAcBQMXN8ichq9PjzbanTakoOR7wJPSC29nExbBzU1NMhH1+BnI+BfBLEzEhQV5IGaTArRmd/R1pM1R99IzofFcNHOGmoL333RGq4mj+YeTnZ74bXiKv+DtVRvytEKYP1xwCnwP4l0h05J6NvnIynJCUsOzgPPlYAJm4kYkJbQ0oAA5gzNPaSc4oyzpHasJgoJ5IaXb/FZUjwlUC0C9QuN5pKcmrvsKHNQjYAek7PKRj2htJ4U5TsG7AywPZ21gpYNMbIcUwLGYojlsAFpKI9/Dtuq3XNj31mibVqZQTld62WUNt+AZQHZP6ExLPNCufTktGqoke/lSizPcODEmEHHdN2MFINU3V7sRgB8yIgmq5uLXUpXwvS1BD5H+E6OqYS/+QjCOCH1ytv7EOFernJhd4ftR31SfVYCVeJSSuSTpJiMrIRwwEUrbtAWGw9RfD3A/XXoC5JvmY/H4Sln9ukGPrS52rmmuBFKab0Ht252yLt8ukE5v0yXI163ACqyBQtsKsA9g0nV0MkwUcXVcvhVTzMY35ouzERrBtt1kodhy5ncYOcS8FDaZax586VQgX6da/mh19x7U7rnElyk4CPxZ/HW+ofPIGe4i7FpZz5qWN/ziiJ512V4R1MHQXfkRj4O2JcW8TAVeIk+Gtb3WBoHZMaPa0OryWm7K6e9ptewvmwppRUT4d3kumQJ+LkpO7DQTdOFmDuj+ZJjgY6UPLcoLm5uNpVwhAy1mNRLoMRAilpNp3LtjoKl7EfiBBJAi/Tl+hcJkrjX/sMlzaQn8uJ3ZWiM9ZcepOi5GJyMRb4aJkfE88Zrlil77rPJO05W04C6I6mOofl+X2s/ea/0wLl5wq6/3Y/MB20PHCs6cJ/1SlWu71wVqYCGX49n9VgzjGZo1R8bDZMdSxKFsOpgtGQMSmFuNxiHMBi79Ue/fU32OCMr+N52CHtoZ9P4dR7qyNqg50YO051gH9ags0eAYNZsRidoo4Q4x+9CZpqWY2PilsmNoNFsBu05GoduqCgXqBZ3fDCS7x7wMFiuGI6yPpFNneA7GOs7kvu6Kk8FQ6DYIALJXNYNTqtVnncChhl/GTnifgo74vz1P0It3OZK9lRHfl3XbFrkeTuDq/P0LqsZ/by7eEnBrPkSD5Y9zetL6Irt89MQQjLkPmDv3S1dwqQuVpRkCYRBB8sDKrf4rzJgLxIfIvwAIYpv3+1LKwuUeW/2ecsTJzix7HBJeExsqIcgF0K+KeHBeWyLqF8YGuzNEnVCyQrQU2CSJNdPXnXt/0XDLRcY1Xwn0NAnUuD66NyyypVKyUNJLiYKtpIWLl3tgopWRxaY9qK3z5ZqjOSNbgkEFH/NljiUEjEwDNoNgWS8EYt94WVftAbhhAxSfPRKeUBNOCm84EjG2Y5Ot+50pXqvrm8fLLLaBJnPuame15es4puPqgqighGkHvXVfLjiAV12CtWkJLSp/OLcne/HU7sKa3zOg4sRq0Dvu9H1mGZrhUmf/IS8dvhvhjAJEL44YgE9z/xykKE8TH1RyEL8YzTpp1VfiT7NDzbCXA4zMrA2IjYeBhNOhbeGcDbWxcwl8/N17AydLyFzaBdZrUImIRwg/rcYAQ=="}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":50003,"cache_read_input_tokens":0,"output_tokens":1327,"output_tokens_details":{"thinking_tokens":555},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":50003,"ephemeral_5m_input_tokens":0},"inference_geo":"not_available","iterations":[{"input_tokens":1,"output_tokens":1327,"cache_read_input_tokens":0,"cache_creation_input_tokens":50003,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":50003},"type":"message"}],"speed":"standard"},"input_transformations":[],"diagnostics":{"cache_miss_reason":{"type":"previous_message_not_found"}},"context_management":null},"apiBlockIndex":0,"requestId":"req_011CfgU7e2nZs5EfGxyeEnhL","type":"assistant","uuid":"f767e5ec-3e15-4c5e-b40c-e4c64e4779ac","timestamp":"2026-10-04T03:14:17.060Z","effort":"high","perTurnEffort":null,"userType":"external","entrypoint":"sdk-cli","cwd":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","version":"2.1.275","gitBranch":"HEAD"}
{"parentUuid":"f767e5ec-3e15-4c5e-b40c-e4c64e4779ac","isSidechain":false,"message":{"model":"claude-sonnet-5","id":"msg_011CfgU7eYJhsJ4zkDsPKwce","type":"message","role":"assistant","content":[{"type":"text","text":"Subject: Re: Account security snapshot — good start, three gaps worry me more now\n\nNoah,\n\nThis is a strong first week — thank you. The sign-in and access-governance findings give me confidence I can defend in a questionnaire. Three things genuinely surprised me and I want to understand them better before Friday's memo, plus one follow-up.\n\n1. **The audit trail gap is bigger than I initially feared.** If we can't show who deleted something and when for one of our most common objects, I have to assume the same is true for the things a security reviewer or a merchant's own auditor would actually ask about — customer records, orders, discounts, financial documents. Can you check whether this is a product-wide blind spot or specific to a few areas? I need to know how wide the hole is before I can tell leadership whether this is \"fix one thing\" or \"fix how deletion logging works everywhere.\"\n\n2. **Shopper MFA at zero is either a non-issue or a real liability — I need to know which.** Before this goes anywhere near a customer-facing security questionnaire, I want to understand what actually protects a shopper's account today if MFA genuinely isn't offered to them — is there anything else standing in for it (unusual-device checks, step-up verification at checkout, that sort of thing)? I don't want to either overstate a gap that doesn't matter or understate one that does.\n\n3. **The churn-deactivation problem — how far does it go?** You found it through privileged roles, but I suspect it's broader: if accounts aren't deprovisioned when a tenant churns, that likely applies to their sessions, their API access, everything tied to that tenant, not just role holders. I'd like a sense of the overall scale of \"access that should have been turned off but wasn't\" so I can size the fix properly.\n\n4. **On the old API keys — are any of them doing something sensitive?** Age alone isn't the risk; age combined with broad access is. Before I recommend a rotation mandate, I'd like to know whether those oldest keys are concentrated among a few tenants or integrations, and whether any of them look like they carry elevated access rather than routine, narrow use.\n\nIf we can only chase a couple of these deeply this week, I'd prioritize the deletion-audit scope (#1) and the churn-deactivation scope (#3) — those are the two a due-diligence team is most likely to ask us to prove, one way or the other.\n\nThanks,\nHenrik"}],"container":null,"stop_reason":"end_turn","stop_sequence":null,"stop_details":null,"usage":{"input_tokens":1,"cache_creation_input_tokens":50003,"cache_read_input_tokens":0,"output_tokens":1327,"output_tokens_details":{"thinking_tokens":555},"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":50003,"ephemeral_5m_input_tokens":0},"inference_geo":"not_available","iterations":[{"input_tokens":1,"output_tokens":1327,"cache_read_input_tokens":0,"cache_creation_input_tokens":50003,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":50003},"type":"message"}],"speed":"standard"},"input_transformations":[],"diagnostics":{"cache_miss_reason":{"type":"previous_message_not_found"}},"context_management":null},"apiBlockIndex":1,"requestId":"req_011CfgU7e2nZs5EfGxyeEnhL","type":"assistant","uuid":"699b58fd-8e72-4850-b68f-124b1e503057","timestamp":"2026-10-04T03:14:26.119Z","effort":"high","perTurnEffort":null,"userType":"external","entrypoint":"sdk-cli","cwd":"/private/var/folders/[redacted: per-user temporary folder]/T/analytics-k38ajyh1","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36","version":"2.1.275","gitBranch":"HEAD"}
{"type":"last-prompt","lastPrompt":"Here is the memo from Noah Fischer:  --- **To:** Henrik Aalto, CISO **From:** Noah Fischer, Analytics **Re:** Account security snapshot — week 1 findings  Henrik, here's the evidence-based picture acr…","leafUuid":"699b58fd-8e72-4850-b68f-124b1e503057","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36"}
{"type":"mode","mode":"normal","sessionId":"eb19b214-98f3-4abf-b16b-2564f9c81c36"}
