@synapsor/runner 1.6.3 → 1.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/AGENTS.md +2 -2
  2. package/CHANGELOG.md +72 -8
  3. package/CONTRIBUTING.md +10 -3
  4. package/README.md +61 -93
  5. package/SECURITY.md +7 -3
  6. package/THREAT_MODEL.md +37 -0
  7. package/dist/authoring.mjs +98 -13
  8. package/dist/cli.d.ts +12 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/local-ui.d.ts +60 -0
  11. package/dist/local-ui.d.ts.map +1 -1
  12. package/dist/runner.mjs +7321 -2000
  13. package/dist/runtime.mjs +528 -51
  14. package/docs/README.md +14 -0
  15. package/docs/agent-guided-setup.md +7 -7
  16. package/docs/aggregate-reads.md +14 -5
  17. package/docs/app-owned-executors.md +5 -6
  18. package/docs/auto-boundary-and-scoped-explore.md +58 -6
  19. package/docs/capability-authoring.md +2 -2
  20. package/docs/client-recipes.md +1 -1
  21. package/docs/cloud-mode.md +7 -7
  22. package/docs/conformance.md +6 -3
  23. package/docs/current-scope.md +13 -2
  24. package/docs/cursor-plugin.md +3 -3
  25. package/docs/database-enforced-scope.md +6 -0
  26. package/docs/dsl-reference.md +12 -1
  27. package/docs/fresh-developer-usability.md +6 -6
  28. package/docs/getting-started-own-database.md +41 -41
  29. package/docs/guided-onboarding.md +98 -30
  30. package/docs/limitations.md +34 -14
  31. package/docs/local-mode.md +20 -20
  32. package/docs/mcp-audit.md +14 -14
  33. package/docs/mcp-client-setup.md +11 -14
  34. package/docs/mcp-clients.md +1 -1
  35. package/docs/migrating-to-synapsor-spec.md +5 -1
  36. package/docs/openai-agents-sdk.md +2 -2
  37. package/docs/proposal-evidence-freshness.md +7 -7
  38. package/docs/recipes.md +6 -6
  39. package/docs/release-notes.md +86 -10
  40. package/docs/release-policy.md +20 -7
  41. package/docs/reversible-change-sets.md +1 -1
  42. package/docs/reviewed-database-views.md +132 -0
  43. package/docs/reviewed-relationships.md +245 -0
  44. package/docs/rfcs/006-reviewed-reversible-change-sets.md +1 -1
  45. package/docs/runner-bundles.md +7 -7
  46. package/docs/schema-api-candidates.md +1 -1
  47. package/docs/security-boundary.md +30 -0
  48. package/docs/troubleshooting-first-run.md +52 -12
  49. package/docs/use-your-own-database.md +6 -4
  50. package/docs/workbench-ask.md +279 -0
  51. package/docs/writeback-executors.md +7 -7
  52. package/examples/auto-boundary-churn/README.md +1 -1
  53. package/examples/community-solar-clean-room/README.md +7 -0
  54. package/examples/community-solar-clean-room/docker-compose.yml +16 -0
  55. package/examples/community-solar-clean-room/package.json +15 -0
  56. package/examples/community-solar-clean-room/prisma/schema.prisma +171 -0
  57. package/examples/community-solar-clean-room/seed/postgres.sql +571 -0
  58. package/examples/fitflow-guided-onboarding/README.md +2 -2
  59. package/examples/openai-agents-http/Makefile +1 -1
  60. package/examples/openai-agents-http/README.md +1 -1
  61. package/examples/openai-agents-stdio/Makefile +1 -1
  62. package/examples/openai-agents-stdio/README.md +2 -2
  63. package/examples/openai-agents-stdio/agent.py +1 -4
  64. package/examples/reference-support-billing-app/README.md +16 -16
  65. package/examples/retail-clean-room/README.md +7 -0
  66. package/examples/retail-clean-room/docker-compose.yml +16 -0
  67. package/examples/retail-clean-room/package.json +9 -0
  68. package/examples/retail-clean-room/prisma/schema.prisma +239 -0
  69. package/examples/retail-clean-room/seed/postgres.sql +781 -0
  70. package/examples/retail-clean-room/view-recipe/average-retained-revenue.synapsor.sql +21 -0
  71. package/examples/retail-clean-room/view-recipe/synapsor.runner.json +25 -0
  72. package/examples/support-plan-credit/mcp-client-examples/claude-code.sh +1 -4
  73. package/examples/support-plan-credit/mcp-client-examples/claude-desktop.json +1 -4
  74. package/examples/support-plan-credit/mcp-client-examples/codex.config.toml +1 -4
  75. package/examples/support-plan-credit/mcp-client-examples/cursor-global.mcp.json +1 -4
  76. package/examples/support-plan-credit/mcp-client-examples/cursor-project.mcp.json +1 -4
  77. package/examples/support-plan-credit/mcp-client-examples/generic-stdio.json +1 -4
  78. package/examples/support-plan-credit/mcp-client-examples/generic-stdio.mjs +1 -1
  79. package/examples/support-plan-credit/mcp-client-examples/google-adk.py +1 -1
  80. package/examples/support-plan-credit/mcp-client-examples/langchain.mjs +1 -1
  81. package/examples/support-plan-credit/mcp-client-examples/llamaindex.py +1 -1
  82. package/examples/support-plan-credit/mcp-client-examples/openai-agents-stdio.ts +1 -1
  83. package/examples/support-plan-credit/mcp-client-examples/vscode.mcp.json +1 -4
  84. package/fixtures/compatibility/published-1.6.3/manifest.json +76 -0
  85. package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/aggregate-read.synapsor.sql +21 -0
  86. package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/billing-late-fee.synapsor.sql +56 -0
  87. package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/bounded-set-multi-term.synapsor.sql +30 -0
  88. package/fixtures/compatibility/published-1.6.3/sources/packages/dsl/examples/principal-row-scope.synapsor.sql +23 -0
  89. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/aggregate-read/contract.json +119 -0
  90. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/approval-quorum/contract.json +44 -0
  91. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/bounded-set-threats/contract.json +115 -0
  92. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/principal-row-scope/contract.json +78 -0
  93. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/proposal-capability/contract.json +101 -0
  94. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/conformance/reversible-change-sets/contract.json +98 -0
  95. package/fixtures/compatibility/published-1.6.3/sources/packages/spec/fixtures/valid/basic-read.contract.json +60 -0
  96. package/llms.txt +10 -5
  97. package/package.json +8 -2
@@ -0,0 +1,781 @@
1
+ DO $$
2
+ BEGIN
3
+ CREATE ROLE retail_manager_reader LOGIN PASSWORD 'retail_manager_reader_password';
4
+ EXCEPTION WHEN duplicate_object THEN NULL;
5
+ END
6
+ $$;
7
+
8
+ DO $$
9
+ BEGIN
10
+ CREATE ROLE retail_writer LOGIN PASSWORD 'retail_writer_password';
11
+ EXCEPTION WHEN duplicate_object THEN NULL;
12
+ END
13
+ $$;
14
+
15
+ DO $$
16
+ BEGIN
17
+ CREATE ROLE retail_setup LOGIN PASSWORD 'retail_setup_password';
18
+ EXCEPTION WHEN duplicate_object THEN NULL;
19
+ END
20
+ $$;
21
+
22
+ ALTER ROLE retail_manager_reader SET default_transaction_read_only = on;
23
+
24
+ CREATE TABLE public.merchants (
25
+ id text PRIMARY KEY,
26
+ name text NOT NULL
27
+ );
28
+
29
+ CREATE TABLE public.regions (
30
+ id text PRIMARY KEY,
31
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
32
+ name text NOT NULL,
33
+ shipping_region text NOT NULL,
34
+ UNIQUE (merchant_id, name)
35
+ );
36
+
37
+ CREATE TABLE public.stores (
38
+ id text PRIMARY KEY,
39
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
40
+ region_id text NOT NULL REFERENCES public.regions(id) ON DELETE RESTRICT,
41
+ name text NOT NULL,
42
+ channel text NOT NULL CHECK (channel IN ('retail', 'outlet', 'online')),
43
+ accounting_period text NOT NULL,
44
+ UNIQUE (merchant_id, name)
45
+ );
46
+
47
+ CREATE TABLE public.staff_roles (
48
+ id text PRIMARY KEY,
49
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
50
+ role_code text NOT NULL,
51
+ display_name text NOT NULL,
52
+ UNIQUE (merchant_id, role_code)
53
+ );
54
+
55
+ CREATE TABLE public.staff (
56
+ id text PRIMARY KEY,
57
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
58
+ role_id text NOT NULL REFERENCES public.staff_roles(id) ON DELETE RESTRICT,
59
+ display_name text NOT NULL,
60
+ role_code text NOT NULL,
61
+ password_hash text NOT NULL,
62
+ session_token text NOT NULL,
63
+ api_access_token text NOT NULL,
64
+ risk_review_status text NOT NULL
65
+ );
66
+
67
+ CREATE TABLE public.store_assignments (
68
+ id text PRIMARY KEY,
69
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
70
+ store_id text NOT NULL REFERENCES public.stores(id) ON DELETE CASCADE,
71
+ staff_id text NOT NULL REFERENCES public.staff(id) ON DELETE CASCADE,
72
+ assignment_role text NOT NULL,
73
+ assigned_at timestamptz NOT NULL,
74
+ UNIQUE (store_id, staff_id, assignment_role)
75
+ );
76
+
77
+ CREATE TABLE public.customers (
78
+ id text PRIMARY KEY,
79
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
80
+ email text NOT NULL,
81
+ phone text NOT NULL,
82
+ tax_id text NOT NULL,
83
+ internal_risk_score integer NOT NULL CHECK (internal_risk_score BETWEEN 0 AND 1000),
84
+ status text NOT NULL CHECK (status IN ('active', 'paused', 'closed')),
85
+ created_at timestamptz NOT NULL
86
+ );
87
+
88
+ CREATE TABLE public.customer_addresses (
89
+ id text PRIMARY KEY,
90
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
91
+ customer_id text NOT NULL REFERENCES public.customers(id) ON DELETE CASCADE,
92
+ address_line_1 text NOT NULL,
93
+ address_line_2 text,
94
+ city text NOT NULL,
95
+ postal_code text NOT NULL,
96
+ shipping_region text NOT NULL,
97
+ home_address text NOT NULL
98
+ );
99
+
100
+ CREATE TABLE public.customer_sessions (
101
+ id text PRIMARY KEY,
102
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
103
+ customer_id text NOT NULL REFERENCES public.customers(id) ON DELETE CASCADE,
104
+ session_token text NOT NULL,
105
+ refresh_token text NOT NULL,
106
+ expires_at timestamptz NOT NULL
107
+ );
108
+
109
+ CREATE TABLE public.api_keys (
110
+ id text PRIMARY KEY,
111
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
112
+ owner_id text NOT NULL,
113
+ key_name text NOT NULL,
114
+ api_key_secret text NOT NULL,
115
+ created_at timestamptz NOT NULL
116
+ );
117
+
118
+ CREATE TABLE public.suppliers (
119
+ id text PRIMARY KEY,
120
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
121
+ name text NOT NULL,
122
+ status text NOT NULL,
123
+ contact_email text NOT NULL
124
+ );
125
+
126
+ CREATE TABLE public.supplier_accounts (
127
+ id text PRIMARY KEY,
128
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
129
+ supplier_id text NOT NULL REFERENCES public.suppliers(id) ON DELETE CASCADE,
130
+ bank_account_number text NOT NULL,
131
+ routing_number text NOT NULL,
132
+ payout_token text NOT NULL,
133
+ payment_status text NOT NULL
134
+ );
135
+
136
+ CREATE TABLE public.product_categories (
137
+ id text PRIMARY KEY,
138
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
139
+ name text NOT NULL,
140
+ category_group text NOT NULL,
141
+ UNIQUE (merchant_id, name)
142
+ );
143
+
144
+ CREATE TABLE public.products (
145
+ id text PRIMARY KEY,
146
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
147
+ primary_category_id text NOT NULL REFERENCES public.product_categories(id) ON DELETE RESTRICT,
148
+ supplier_id text NOT NULL REFERENCES public.suppliers(id) ON DELETE RESTRICT,
149
+ owner_id text NOT NULL,
150
+ name text NOT NULL,
151
+ status text NOT NULL CHECK (status IN ('draft', 'active', 'retired')),
152
+ version integer NOT NULL DEFAULT 1
153
+ );
154
+
155
+ CREATE TABLE public.product_variants (
156
+ id text PRIMARY KEY,
157
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
158
+ product_id text NOT NULL REFERENCES public.products(id) ON DELETE CASCADE,
159
+ sku text NOT NULL,
160
+ card_brand_display text NOT NULL,
161
+ pan_size_cm integer NOT NULL,
162
+ list_price_cents integer NOT NULL CHECK (list_price_cents >= 0),
163
+ UNIQUE (merchant_id, sku)
164
+ );
165
+
166
+ CREATE TABLE public.product_category_links (
167
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
168
+ product_id text NOT NULL REFERENCES public.products(id) ON DELETE CASCADE,
169
+ category_id text NOT NULL REFERENCES public.product_categories(id) ON DELETE CASCADE,
170
+ PRIMARY KEY (product_id, category_id)
171
+ );
172
+
173
+ CREATE TABLE public.price_history (
174
+ id text PRIMARY KEY,
175
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
176
+ product_variant_id text NOT NULL REFERENCES public.product_variants(id) ON DELETE CASCADE,
177
+ price_cents integer NOT NULL CHECK (price_cents >= 0),
178
+ effective_at timestamptz NOT NULL
179
+ );
180
+
181
+ CREATE TABLE public.warehouses (
182
+ id text PRIMARY KEY,
183
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
184
+ region_id text NOT NULL REFERENCES public.regions(id) ON DELETE RESTRICT,
185
+ name text NOT NULL,
186
+ status text NOT NULL
187
+ );
188
+
189
+ CREATE TABLE public.inventory_levels (
190
+ id text PRIMARY KEY,
191
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
192
+ warehouse_id text NOT NULL REFERENCES public.warehouses(id) ON DELETE RESTRICT,
193
+ product_variant_id text NOT NULL REFERENCES public.product_variants(id) ON DELETE RESTRICT,
194
+ assigned_manager_id text NOT NULL REFERENCES public.staff(id) ON DELETE RESTRICT,
195
+ available_quantity integer NOT NULL CHECK (available_quantity >= 0),
196
+ reserved_quantity integer NOT NULL CHECK (reserved_quantity >= 0),
197
+ stockout_hours integer NOT NULL CHECK (stockout_hours >= 0),
198
+ version integer NOT NULL DEFAULT 1,
199
+ UNIQUE (warehouse_id, product_variant_id)
200
+ );
201
+
202
+ CREATE TABLE public.stock_movements (
203
+ id text PRIMARY KEY,
204
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
205
+ inventory_level_id text NOT NULL REFERENCES public.inventory_levels(id) ON DELETE RESTRICT,
206
+ movement_type text NOT NULL,
207
+ quantity integer NOT NULL,
208
+ occurred_at timestamptz NOT NULL
209
+ );
210
+
211
+ CREATE TABLE public.reorder_rules (
212
+ id text PRIMARY KEY,
213
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
214
+ inventory_level_id text NOT NULL REFERENCES public.inventory_levels(id) ON DELETE CASCADE,
215
+ reorder_point integer NOT NULL CHECK (reorder_point >= 0),
216
+ reorder_quantity integer NOT NULL CHECK (reorder_quantity > 0)
217
+ );
218
+
219
+ CREATE TABLE public.carts (
220
+ id text PRIMARY KEY,
221
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
222
+ store_id text NOT NULL REFERENCES public.stores(id) ON DELETE RESTRICT,
223
+ customer_id text NOT NULL REFERENCES public.customers(id) ON DELETE RESTRICT,
224
+ status text NOT NULL,
225
+ created_at timestamptz NOT NULL
226
+ );
227
+
228
+ CREATE TABLE public.cart_items (
229
+ id text PRIMARY KEY,
230
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
231
+ cart_id text NOT NULL REFERENCES public.carts(id) ON DELETE CASCADE,
232
+ product_variant_id text NOT NULL REFERENCES public.product_variants(id) ON DELETE RESTRICT,
233
+ quantity integer NOT NULL CHECK (quantity > 0)
234
+ );
235
+
236
+ CREATE TABLE public.checkout_sessions (
237
+ id text PRIMARY KEY,
238
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
239
+ cart_id text NOT NULL REFERENCES public.carts(id) ON DELETE RESTRICT,
240
+ checkout_token text NOT NULL,
241
+ status text NOT NULL,
242
+ expires_at timestamptz NOT NULL
243
+ );
244
+
245
+ CREATE TABLE public.orders (
246
+ id text PRIMARY KEY,
247
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
248
+ store_id text NOT NULL REFERENCES public.stores(id) ON DELETE RESTRICT,
249
+ region_id text NOT NULL REFERENCES public.regions(id) ON DELETE RESTRICT,
250
+ customer_id text NOT NULL REFERENCES public.customers(id) ON DELETE RESTRICT,
251
+ assigned_manager_id text NOT NULL REFERENCES public.staff(id) ON DELETE RESTRICT,
252
+ channel text NOT NULL CHECK (channel IN ('retail', 'online', 'marketplace')),
253
+ status text NOT NULL CHECK (status IN ('placed', 'processing', 'fulfilled', 'cancelled')),
254
+ gross_revenue_cents integer NOT NULL CHECK (gross_revenue_cents >= 0),
255
+ net_revenue_cents integer NOT NULL CHECK (net_revenue_cents >= 0),
256
+ placed_at timestamptz NOT NULL,
257
+ version integer NOT NULL DEFAULT 1,
258
+ private_customer_note text NOT NULL
259
+ );
260
+
261
+ CREATE INDEX orders_trusted_scope_idx
262
+ ON public.orders(merchant_id, assigned_manager_id, placed_at);
263
+
264
+ CREATE TABLE public.order_items (
265
+ id text PRIMARY KEY,
266
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
267
+ order_id text NOT NULL REFERENCES public.orders(id) ON DELETE CASCADE,
268
+ product_id text NOT NULL REFERENCES public.products(id) ON DELETE RESTRICT,
269
+ product_variant_id text NOT NULL REFERENCES public.product_variants(id) ON DELETE RESTRICT,
270
+ quantity integer NOT NULL CHECK (quantity > 0),
271
+ gross_line_revenue_cents integer NOT NULL CHECK (gross_line_revenue_cents >= 0),
272
+ net_line_revenue_cents integer NOT NULL CHECK (net_line_revenue_cents >= 0)
273
+ );
274
+
275
+ CREATE TABLE public.payment_methods (
276
+ id text PRIMARY KEY,
277
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
278
+ customer_id text NOT NULL REFERENCES public.customers(id) ON DELETE CASCADE,
279
+ card_on_file text NOT NULL,
280
+ full_pan text NOT NULL,
281
+ card_brand_display text NOT NULL,
282
+ bank_account_number text NOT NULL,
283
+ routing_number text NOT NULL
284
+ );
285
+
286
+ CREATE TABLE public.payments (
287
+ id text PRIMARY KEY,
288
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
289
+ order_id text NOT NULL REFERENCES public.orders(id) ON DELETE RESTRICT,
290
+ payment_method_id text NOT NULL REFERENCES public.payment_methods(id) ON DELETE RESTRICT,
291
+ amount_cents integer NOT NULL CHECK (amount_cents >= 0),
292
+ payment_status text NOT NULL,
293
+ payment_token text NOT NULL,
294
+ pan_last_four text NOT NULL,
295
+ cvv_value text NOT NULL,
296
+ created_at timestamptz NOT NULL
297
+ );
298
+
299
+ CREATE TABLE public.payment_attempts (
300
+ id text PRIMARY KEY,
301
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
302
+ payment_id text NOT NULL REFERENCES public.payments(id) ON DELETE CASCADE,
303
+ attempt_number integer NOT NULL,
304
+ status text NOT NULL,
305
+ processor_token text NOT NULL,
306
+ attempted_at timestamptz NOT NULL
307
+ );
308
+
309
+ CREATE TABLE public.refunds (
310
+ id text PRIMARY KEY,
311
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
312
+ order_id text NOT NULL REFERENCES public.orders(id) ON DELETE RESTRICT,
313
+ payment_id text NOT NULL REFERENCES public.payments(id) ON DELETE RESTRICT,
314
+ amount_cents integer NOT NULL CHECK (amount_cents >= 0),
315
+ reason_code text NOT NULL,
316
+ status text NOT NULL,
317
+ created_at timestamptz NOT NULL
318
+ );
319
+
320
+ CREATE TABLE public.shipments (
321
+ id text PRIMARY KEY,
322
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
323
+ order_id text NOT NULL REFERENCES public.orders(id) ON DELETE RESTRICT,
324
+ warehouse_id text NOT NULL REFERENCES public.warehouses(id) ON DELETE RESTRICT,
325
+ carrier text NOT NULL,
326
+ tracking_token text NOT NULL,
327
+ status text NOT NULL,
328
+ shipped_at timestamptz
329
+ );
330
+
331
+ CREATE TABLE public.fulfillment_events (
332
+ id text PRIMARY KEY,
333
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
334
+ shipment_id text NOT NULL REFERENCES public.shipments(id) ON DELETE CASCADE,
335
+ event_type text NOT NULL,
336
+ delay_minutes integer NOT NULL CHECK (delay_minutes >= 0),
337
+ occurred_at timestamptz NOT NULL
338
+ );
339
+
340
+ CREATE TABLE public.return_reasons (
341
+ id text PRIMARY KEY,
342
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
343
+ reason_category text NOT NULL,
344
+ display_name text NOT NULL
345
+ );
346
+
347
+ CREATE TABLE public.returns (
348
+ id text PRIMARY KEY,
349
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
350
+ order_id text NOT NULL REFERENCES public.orders(id) ON DELETE RESTRICT,
351
+ reason_id text NOT NULL REFERENCES public.return_reasons(id) ON DELETE RESTRICT,
352
+ status text NOT NULL,
353
+ requested_at timestamptz NOT NULL
354
+ );
355
+
356
+ CREATE TABLE public.return_items (
357
+ id text PRIMARY KEY,
358
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
359
+ return_id text NOT NULL REFERENCES public.returns(id) ON DELETE CASCADE,
360
+ order_item_id text NOT NULL REFERENCES public.order_items(id) ON DELETE RESTRICT,
361
+ quantity integer NOT NULL CHECK (quantity > 0)
362
+ );
363
+
364
+ CREATE TABLE public.promotions (
365
+ id text PRIMARY KEY,
366
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
367
+ name text NOT NULL,
368
+ promotion_category text NOT NULL,
369
+ starts_at timestamptz NOT NULL,
370
+ ends_at timestamptz NOT NULL
371
+ );
372
+
373
+ CREATE TABLE public.promotion_categories (
374
+ id text PRIMARY KEY,
375
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
376
+ name text NOT NULL
377
+ );
378
+
379
+ CREATE TABLE public.promotion_products (
380
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
381
+ promotion_id text NOT NULL REFERENCES public.promotions(id) ON DELETE CASCADE,
382
+ product_id text NOT NULL REFERENCES public.products(id) ON DELETE CASCADE,
383
+ PRIMARY KEY (promotion_id, product_id)
384
+ );
385
+
386
+ CREATE TABLE public.discounts (
387
+ id text PRIMARY KEY,
388
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
389
+ order_id text NOT NULL REFERENCES public.orders(id) ON DELETE CASCADE,
390
+ promotion_id text REFERENCES public.promotions(id) ON DELETE SET NULL,
391
+ amount_cents integer NOT NULL CHECK (amount_cents >= 0),
392
+ discount_type text NOT NULL
393
+ );
394
+
395
+ CREATE TABLE public.support_cases (
396
+ id text PRIMARY KEY,
397
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
398
+ customer_id text NOT NULL REFERENCES public.customers(id) ON DELETE RESTRICT,
399
+ order_id text REFERENCES public.orders(id) ON DELETE SET NULL,
400
+ assigned_agent_id text NOT NULL REFERENCES public.staff(id) ON DELETE RESTRICT,
401
+ status text NOT NULL CHECK (status IN ('open', 'waiting', 'resolved')),
402
+ subject text NOT NULL,
403
+ private_support_notes text NOT NULL,
404
+ version integer NOT NULL DEFAULT 1
405
+ );
406
+
407
+ CREATE INDEX support_cases_trusted_scope_idx
408
+ ON public.support_cases(merchant_id, assigned_agent_id);
409
+
410
+ CREATE TABLE public.support_case_notes (
411
+ id text PRIMARY KEY,
412
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
413
+ support_case_id text NOT NULL REFERENCES public.support_cases(id) ON DELETE CASCADE,
414
+ author_id text NOT NULL REFERENCES public.staff(id) ON DELETE RESTRICT,
415
+ public_note text NOT NULL,
416
+ private_support_notes text NOT NULL,
417
+ created_at timestamptz NOT NULL
418
+ );
419
+
420
+ CREATE TABLE public.sales_line_facts (
421
+ id text PRIMARY KEY,
422
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
423
+ store_id text NOT NULL REFERENCES public.stores(id) ON DELETE RESTRICT,
424
+ region_id text NOT NULL REFERENCES public.regions(id) ON DELETE RESTRICT,
425
+ product_id text NOT NULL REFERENCES public.products(id) ON DELETE RESTRICT,
426
+ product_category_id text NOT NULL REFERENCES public.product_categories(id) ON DELETE RESTRICT,
427
+ assigned_manager_id text NOT NULL REFERENCES public.staff(id) ON DELETE RESTRICT,
428
+ order_id text NOT NULL REFERENCES public.orders(id) ON DELETE RESTRICT,
429
+ channel text NOT NULL,
430
+ quantity integer NOT NULL CHECK (quantity > 0),
431
+ net_revenue_cents integer NOT NULL CHECK (net_revenue_cents >= 0),
432
+ sold_at timestamptz NOT NULL
433
+ );
434
+
435
+ CREATE INDEX sales_line_facts_scope_idx
436
+ ON public.sales_line_facts(merchant_id, assigned_manager_id, sold_at);
437
+
438
+ CREATE TABLE public.domain_events (
439
+ id text PRIMARY KEY,
440
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
441
+ aggregate_type text NOT NULL,
442
+ aggregate_id text NOT NULL,
443
+ event_type text NOT NULL,
444
+ payload_token text NOT NULL,
445
+ occurred_at timestamptz NOT NULL
446
+ );
447
+
448
+ CREATE TABLE public.audit_events (
449
+ id text PRIMARY KEY,
450
+ merchant_id text NOT NULL REFERENCES public.merchants(id) ON DELETE RESTRICT,
451
+ actor_id text NOT NULL,
452
+ action text NOT NULL,
453
+ object_type text NOT NULL,
454
+ object_id text NOT NULL,
455
+ private_audit_payload text NOT NULL,
456
+ occurred_at timestamptz NOT NULL
457
+ );
458
+
459
+ INSERT INTO public.merchants VALUES
460
+ ('merchant-northstar', 'Northstar Outfitters'),
461
+ ('merchant-rival', 'Rival Retail Group');
462
+
463
+ INSERT INTO public.regions VALUES
464
+ ('region-pacific', 'merchant-northstar', 'Pacific', 'west'),
465
+ ('region-mountain', 'merchant-northstar', 'Mountain', 'central'),
466
+ ('region-rival', 'merchant-rival', 'Rival Region', 'east');
467
+
468
+ INSERT INTO public.stores VALUES
469
+ ('store-seattle', 'merchant-northstar', 'region-pacific', 'Seattle Flagship', 'retail', '2026-Q2'),
470
+ ('store-portland', 'merchant-northstar', 'region-pacific', 'Portland Market', 'retail', '2026-Q2'),
471
+ ('store-denver', 'merchant-northstar', 'region-mountain', 'Denver Outlet', 'outlet', '2026-Q2'),
472
+ ('store-rival', 'merchant-rival', 'region-rival', 'Rival Store', 'retail', '2026-Q2');
473
+
474
+ INSERT INTO public.staff_roles VALUES
475
+ ('role-manager', 'merchant-northstar', 'store_manager', 'Store manager'),
476
+ ('role-support', 'merchant-northstar', 'support_agent', 'Support agent'),
477
+ ('role-rival', 'merchant-rival', 'store_manager', 'Store manager');
478
+
479
+ INSERT INTO public.staff VALUES
480
+ ('staff-manager-alex', 'merchant-northstar', 'role-manager', 'Alex Rivera', 'store_manager', 'synthetic-password-hash-alex', 'synthetic-session-token-alex', 'synthetic-api-token-alex', 'clear'),
481
+ ('staff-manager-jordan', 'merchant-northstar', 'role-manager', 'Jordan Lee', 'store_manager', 'synthetic-password-hash-jordan', 'synthetic-session-token-jordan', 'synthetic-api-token-jordan', 'reviewed'),
482
+ ('staff-support-maya', 'merchant-northstar', 'role-support', 'Maya Chen', 'support_agent', 'synthetic-password-hash-maya', 'synthetic-session-token-maya', 'synthetic-api-token-maya', 'clear'),
483
+ ('staff-support-noah', 'merchant-northstar', 'role-support', 'Noah Smith', 'support_agent', 'synthetic-password-hash-noah', 'synthetic-session-token-noah', 'synthetic-api-token-noah', 'clear'),
484
+ ('staff-rival', 'merchant-rival', 'role-rival', 'Rival Operator', 'store_manager', 'rival-password-hash', 'rival-session-token', 'rival-api-token', 'clear');
485
+
486
+ INSERT INTO public.store_assignments VALUES
487
+ ('assign-1', 'merchant-northstar', 'store-seattle', 'staff-manager-alex', 'manager', '2026-01-01T00:00:00Z'),
488
+ ('assign-2', 'merchant-northstar', 'store-portland', 'staff-manager-alex', 'manager', '2026-01-01T00:00:00Z'),
489
+ ('assign-3', 'merchant-northstar', 'store-denver', 'staff-manager-jordan', 'manager', '2026-01-01T00:00:00Z'),
490
+ ('assign-rival', 'merchant-rival', 'store-rival', 'staff-rival', 'manager', '2026-01-01T00:00:00Z');
491
+
492
+ INSERT INTO public.customers
493
+ SELECT
494
+ 'customer-' || n,
495
+ 'merchant-northstar',
496
+ 'customer' || n || '@example.test',
497
+ '+1-555-01' || lpad(n::text, 2, '0'),
498
+ 'synthetic-tax-id-' || n,
499
+ 100 + n,
500
+ 'active',
501
+ '2026-01-01T00:00:00Z'::timestamptz + (n || ' days')::interval
502
+ FROM generate_series(1, 12) AS n;
503
+
504
+ INSERT INTO public.customers VALUES
505
+ ('customer-rival', 'merchant-rival', 'rival@example.test', '+1-555-9999', 'rival-tax-id', 900, 'active', '2026-01-01T00:00:00Z');
506
+
507
+ INSERT INTO public.customer_addresses VALUES
508
+ ('address-1', 'merchant-northstar', 'customer-1', '101 Private Way', NULL, 'Seattle', '98101', 'west', '101 Private Way, Seattle'),
509
+ ('address-rival', 'merchant-rival', 'customer-rival', '999 Rival Road', NULL, 'Boston', '02101', 'east', '999 Rival Road, Boston');
510
+
511
+ INSERT INTO public.customer_sessions VALUES
512
+ ('customer-session-1', 'merchant-northstar', 'customer-1', 'synthetic-customer-session-token', 'synthetic-refresh-token', '2027-01-01T00:00:00Z');
513
+
514
+ INSERT INTO public.api_keys VALUES
515
+ ('api-key-1', 'merchant-northstar', 'staff-manager-alex', 'warehouse sync', 'synthetic-secret-api-key', '2026-01-01T00:00:00Z');
516
+
517
+ INSERT INTO public.suppliers VALUES
518
+ ('supplier-outdoor', 'merchant-northstar', 'Outdoor Supply Co', 'active', 'supplier@example.test'),
519
+ ('supplier-rival', 'merchant-rival', 'Rival Supplier', 'active', 'rival-supplier@example.test');
520
+
521
+ INSERT INTO public.supplier_accounts VALUES
522
+ ('supplier-account-1', 'merchant-northstar', 'supplier-outdoor', '000111222333', '110000000', 'synthetic-payout-token', 'verified');
523
+
524
+ INSERT INTO public.product_categories VALUES
525
+ ('category-camping', 'merchant-northstar', 'Camping', 'outdoor'),
526
+ ('category-running', 'merchant-northstar', 'Running', 'fitness'),
527
+ ('category-rival', 'merchant-rival', 'Rival Category', 'other');
528
+
529
+ INSERT INTO public.products VALUES
530
+ ('product-tent', 'merchant-northstar', 'category-camping', 'supplier-outdoor', 'catalog-owner-1', 'Trail Tent', 'active', 1),
531
+ ('product-shoe', 'merchant-northstar', 'category-running', 'supplier-outdoor', 'catalog-owner-2', 'Ridge Runner Shoe', 'active', 1),
532
+ ('product-rival', 'merchant-rival', 'category-rival', 'supplier-rival', 'rival-owner', 'Rival Product', 'active', 1);
533
+
534
+ INSERT INTO public.product_variants VALUES
535
+ ('variant-tent', 'merchant-northstar', 'product-tent', 'TENT-001', 'not_applicable', 42, 19900),
536
+ ('variant-shoe', 'merchant-northstar', 'product-shoe', 'SHOE-001', 'not_applicable', 31, 12900),
537
+ ('variant-rival', 'merchant-rival', 'product-rival', 'RIVAL-001', 'not_applicable', 28, 9900);
538
+
539
+ INSERT INTO public.product_category_links VALUES
540
+ ('merchant-northstar', 'product-tent', 'category-camping'),
541
+ ('merchant-northstar', 'product-tent', 'category-running'),
542
+ ('merchant-northstar', 'product-shoe', 'category-running');
543
+
544
+ INSERT INTO public.price_history VALUES
545
+ ('price-1', 'merchant-northstar', 'variant-tent', 19900, '2026-01-01T00:00:00Z'),
546
+ ('price-2', 'merchant-northstar', 'variant-shoe', 12900, '2026-01-01T00:00:00Z');
547
+
548
+ INSERT INTO public.warehouses VALUES
549
+ ('warehouse-pacific', 'merchant-northstar', 'region-pacific', 'Pacific Warehouse', 'active'),
550
+ ('warehouse-mountain', 'merchant-northstar', 'region-mountain', 'Mountain Warehouse', 'active');
551
+
552
+ INSERT INTO public.inventory_levels VALUES
553
+ ('inventory-tent', 'merchant-northstar', 'warehouse-pacific', 'variant-tent', 'staff-manager-alex', 42, 8, 3, 1),
554
+ ('inventory-shoe', 'merchant-northstar', 'warehouse-pacific', 'variant-shoe', 'staff-manager-alex', 64, 12, 1, 1);
555
+
556
+ INSERT INTO public.stock_movements VALUES
557
+ ('movement-1', 'merchant-northstar', 'inventory-tent', 'receipt', 20, '2026-06-01T00:00:00Z');
558
+
559
+ INSERT INTO public.reorder_rules VALUES
560
+ ('reorder-1', 'merchant-northstar', 'inventory-tent', 10, 50);
561
+
562
+ INSERT INTO public.carts VALUES
563
+ ('cart-1', 'merchant-northstar', 'store-seattle', 'customer-1', 'active', '2026-06-01T00:00:00Z');
564
+
565
+ INSERT INTO public.cart_items VALUES
566
+ ('cart-item-1', 'merchant-northstar', 'cart-1', 'variant-tent', 1);
567
+
568
+ INSERT INTO public.checkout_sessions VALUES
569
+ ('checkout-1', 'merchant-northstar', 'cart-1', 'synthetic-checkout-token', 'complete', '2026-07-01T00:00:00Z');
570
+
571
+ INSERT INTO public.orders
572
+ SELECT
573
+ 'order-' || lpad(n::text, 3, '0'),
574
+ 'merchant-northstar',
575
+ CASE WHEN n % 3 = 0 THEN 'store-portland' ELSE 'store-seattle' END,
576
+ 'region-pacific',
577
+ 'customer-' || (((n - 1) % 12) + 1),
578
+ 'staff-manager-alex',
579
+ CASE WHEN n % 2 = 0 THEN 'online' ELSE 'retail' END,
580
+ CASE WHEN n % 5 = 0 THEN 'processing' ELSE 'fulfilled' END,
581
+ 10000 + n * 100,
582
+ 9000 + n * 90,
583
+ '2026-04-01T12:00:00Z'::timestamptz + (n * interval '3 days'),
584
+ 1,
585
+ 'synthetic private customer note ' || n
586
+ FROM generate_series(1, 30) AS n;
587
+
588
+ INSERT INTO public.orders VALUES
589
+ ('order-manager-other', 'merchant-northstar', 'store-denver', 'region-mountain', 'customer-1', 'staff-manager-jordan', 'retail', 'processing', 18000, 16000, '2026-06-20T12:00:00Z', 1, 'other manager private note'),
590
+ ('order-rival', 'merchant-rival', 'store-rival', 'region-rival', 'customer-rival', 'staff-rival', 'retail', 'fulfilled', 9900, 9000, '2026-06-20T12:00:00Z', 1, 'rival private note');
591
+
592
+ INSERT INTO public.order_items
593
+ SELECT
594
+ 'item-' || lpad(n::text, 3, '0'),
595
+ 'merchant-northstar',
596
+ 'order-' || lpad(n::text, 3, '0'),
597
+ CASE WHEN n % 2 = 0 THEN 'product-shoe' ELSE 'product-tent' END,
598
+ CASE WHEN n % 2 = 0 THEN 'variant-shoe' ELSE 'variant-tent' END,
599
+ 1,
600
+ 10000 + n * 100,
601
+ 9000 + n * 90
602
+ FROM generate_series(1, 30) AS n;
603
+
604
+ INSERT INTO public.payment_methods VALUES
605
+ ('payment-method-1', 'merchant-northstar', 'customer-1', 'synthetic-card-on-file-token', '4111111111111111', 'Visa ending 1111', '000111222333', '110000000');
606
+
607
+ INSERT INTO public.payments
608
+ SELECT
609
+ 'payment-' || lpad(n::text, 3, '0'),
610
+ 'merchant-northstar',
611
+ 'order-' || lpad(n::text, 3, '0'),
612
+ 'payment-method-1',
613
+ 9000 + n * 90,
614
+ 'settled',
615
+ 'synthetic-payment-token-' || n,
616
+ '1111',
617
+ '123',
618
+ '2026-04-01T12:05:00Z'::timestamptz + (n * interval '3 days')
619
+ FROM generate_series(1, 30) AS n;
620
+
621
+ INSERT INTO public.payment_attempts VALUES
622
+ ('attempt-1', 'merchant-northstar', 'payment-001', 1, 'succeeded', 'synthetic-processor-token', '2026-04-04T12:05:00Z');
623
+
624
+ INSERT INTO public.refunds VALUES
625
+ ('refund-1', 'merchant-northstar', 'order-005', 'payment-005', 1200, 'size', 'settled', '2026-05-01T00:00:00Z'),
626
+ ('refund-2', 'merchant-northstar', 'order-010', 'payment-010', 800, 'damaged', 'settled', '2026-06-01T00:00:00Z');
627
+
628
+ INSERT INTO public.shipments VALUES
629
+ ('shipment-1', 'merchant-northstar', 'order-001', 'warehouse-pacific', 'Synthetic Carrier', 'synthetic-tracking-token', 'delivered', '2026-04-05T00:00:00Z');
630
+
631
+ INSERT INTO public.fulfillment_events VALUES
632
+ ('fulfillment-1', 'merchant-northstar', 'shipment-1', 'delivered', 15, '2026-04-07T00:00:00Z');
633
+
634
+ INSERT INTO public.return_reasons VALUES
635
+ ('reason-size', 'merchant-northstar', 'size', 'Wrong size'),
636
+ ('reason-damaged', 'merchant-northstar', 'damaged', 'Damaged item');
637
+
638
+ INSERT INTO public.returns VALUES
639
+ ('return-1', 'merchant-northstar', 'order-005', 'reason-size', 'received', '2026-05-02T00:00:00Z'),
640
+ ('return-2', 'merchant-northstar', 'order-010', 'reason-damaged', 'received', '2026-06-02T00:00:00Z');
641
+
642
+ INSERT INTO public.return_items VALUES
643
+ ('return-item-1', 'merchant-northstar', 'return-1', 'item-005', 1);
644
+
645
+ INSERT INTO public.promotions VALUES
646
+ ('promotion-spring', 'merchant-northstar', 'Spring Launch', 'seasonal', '2026-04-01T00:00:00Z', '2026-05-31T23:59:59Z');
647
+
648
+ INSERT INTO public.promotion_categories VALUES
649
+ ('promotion-category-seasonal', 'merchant-northstar', 'Seasonal');
650
+
651
+ INSERT INTO public.promotion_products VALUES
652
+ ('merchant-northstar', 'promotion-spring', 'product-tent'),
653
+ ('merchant-northstar', 'promotion-spring', 'product-shoe');
654
+
655
+ INSERT INTO public.discounts VALUES
656
+ ('discount-1', 'merchant-northstar', 'order-001', 'promotion-spring', 500, 'promotion');
657
+
658
+ INSERT INTO public.support_cases VALUES
659
+ ('case-assigned', 'merchant-northstar', 'customer-1', 'order-001', 'staff-support-maya', 'open', 'Delivery question', 'synthetic private support note assigned', 1),
660
+ ('case-unassigned', 'merchant-northstar', 'customer-2', 'order-002', 'staff-support-noah', 'open', 'Return question', 'synthetic private support note unassigned', 1),
661
+ ('case-rival', 'merchant-rival', 'customer-rival', 'order-rival', 'staff-rival', 'open', 'Rival case', 'rival private support note', 1);
662
+
663
+ INSERT INTO public.support_case_notes VALUES
664
+ ('case-note-1', 'merchant-northstar', 'case-assigned', 'staff-support-maya', 'We are checking delivery.', 'private escalation note', '2026-06-01T00:00:00Z');
665
+
666
+ INSERT INTO public.sales_line_facts
667
+ SELECT
668
+ 'sales-fact-' || lpad(n::text, 3, '0'),
669
+ 'merchant-northstar',
670
+ CASE WHEN n % 3 = 0 THEN 'store-portland' ELSE 'store-seattle' END,
671
+ 'region-pacific',
672
+ CASE WHEN n % 2 = 0 THEN 'product-shoe' ELSE 'product-tent' END,
673
+ CASE WHEN n % 2 = 0 THEN 'category-running' ELSE 'category-camping' END,
674
+ 'staff-manager-alex',
675
+ 'order-' || lpad((((n - 1) % 30) + 1)::text, 3, '0'),
676
+ CASE WHEN n % 2 = 0 THEN 'online' ELSE 'retail' END,
677
+ 1,
678
+ 5000 + n * 75,
679
+ '2026-04-01T12:00:00Z'::timestamptz + (n * interval '3 hours')
680
+ FROM generate_series(1, 192) AS n;
681
+
682
+ INSERT INTO public.sales_line_facts VALUES
683
+ ('sales-fact-other-manager', 'merchant-northstar', 'store-denver', 'region-mountain', 'product-tent', 'category-camping', 'staff-manager-jordan', 'order-manager-other', 'retail', 1, 16000, '2026-06-20T12:00:00Z'),
684
+ ('sales-fact-rival', 'merchant-rival', 'store-rival', 'region-rival', 'product-rival', 'category-rival', 'staff-rival', 'order-rival', 'retail', 1, 9000, '2026-06-20T12:00:00Z');
685
+
686
+ INSERT INTO public.domain_events VALUES
687
+ ('domain-event-1', 'merchant-northstar', 'order', 'order-001', 'order.fulfilled', 'synthetic-domain-payload-token', '2026-04-08T00:00:00Z');
688
+
689
+ INSERT INTO public.audit_events VALUES
690
+ ('audit-event-1', 'merchant-northstar', 'staff-manager-alex', 'order.viewed', 'order', 'order-001', 'synthetic private audit payload', '2026-04-08T00:00:00Z');
691
+
692
+ ALTER TABLE public.merchants ENABLE ROW LEVEL SECURITY;
693
+ ALTER TABLE public.merchants FORCE ROW LEVEL SECURITY;
694
+ CREATE POLICY merchants_tenant_select
695
+ ON public.merchants FOR SELECT TO retail_manager_reader, retail_writer
696
+ USING (id = current_setting('app.tenant_id', true));
697
+
698
+ DO $$
699
+ DECLARE
700
+ table_name text;
701
+ BEGIN
702
+ FOR table_name IN
703
+ SELECT c.table_name
704
+ FROM information_schema.columns c
705
+ WHERE c.table_schema = 'public'
706
+ AND c.column_name = 'merchant_id'
707
+ AND c.table_name NOT IN ('orders', 'support_cases', 'sales_line_facts')
708
+ ORDER BY c.table_name
709
+ LOOP
710
+ EXECUTE format('ALTER TABLE public.%I ENABLE ROW LEVEL SECURITY', table_name);
711
+ EXECUTE format('ALTER TABLE public.%I FORCE ROW LEVEL SECURITY', table_name);
712
+ EXECUTE format(
713
+ 'CREATE POLICY %I ON public.%I FOR SELECT TO retail_manager_reader, retail_writer USING (merchant_id = current_setting(''app.tenant_id'', true))',
714
+ table_name || '_tenant_select',
715
+ table_name
716
+ );
717
+ END LOOP;
718
+ END
719
+ $$;
720
+
721
+ ALTER TABLE public.orders ENABLE ROW LEVEL SECURITY;
722
+ ALTER TABLE public.orders FORCE ROW LEVEL SECURITY;
723
+ CREATE POLICY orders_manager_select
724
+ ON public.orders FOR SELECT TO retail_manager_reader, retail_writer
725
+ USING (
726
+ merchant_id = current_setting('app.tenant_id', true)
727
+ AND assigned_manager_id = current_setting('app.principal', true)
728
+ );
729
+ CREATE POLICY orders_manager_update
730
+ ON public.orders FOR UPDATE TO retail_writer
731
+ USING (
732
+ merchant_id = current_setting('app.tenant_id', true)
733
+ AND assigned_manager_id = current_setting('app.principal', true)
734
+ )
735
+ WITH CHECK (
736
+ merchant_id = current_setting('app.tenant_id', true)
737
+ AND assigned_manager_id = current_setting('app.principal', true)
738
+ );
739
+
740
+ ALTER TABLE public.support_cases ENABLE ROW LEVEL SECURITY;
741
+ ALTER TABLE public.support_cases FORCE ROW LEVEL SECURITY;
742
+ CREATE POLICY support_cases_agent_select
743
+ ON public.support_cases FOR SELECT TO retail_manager_reader, retail_writer
744
+ USING (
745
+ merchant_id = current_setting('app.tenant_id', true)
746
+ AND assigned_agent_id = current_setting('app.principal', true)
747
+ );
748
+
749
+ ALTER TABLE public.sales_line_facts ENABLE ROW LEVEL SECURITY;
750
+ ALTER TABLE public.sales_line_facts FORCE ROW LEVEL SECURITY;
751
+ CREATE POLICY sales_line_facts_manager_select
752
+ ON public.sales_line_facts FOR SELECT TO retail_manager_reader, retail_writer
753
+ USING (
754
+ merchant_id = current_setting('app.tenant_id', true)
755
+ AND assigned_manager_id = current_setting('app.principal', true)
756
+ );
757
+
758
+ CREATE VIEW public.reviewed_order_performance
759
+ WITH (security_barrier = true, security_invoker = true) AS
760
+ SELECT
761
+ orders.id AS order_id,
762
+ orders.merchant_id,
763
+ orders.assigned_manager_id,
764
+ orders.region_id,
765
+ ROUND(
766
+ (orders.net_revenue_cents::numeric / NULLIF(orders.gross_revenue_cents, 0))
767
+ * 10000
768
+ )::integer AS net_revenue_retention_basis_points
769
+ FROM public.orders;
770
+
771
+ GRANT CONNECT ON DATABASE northstar_commerce TO retail_manager_reader, retail_writer, retail_setup;
772
+ GRANT USAGE ON SCHEMA public TO retail_manager_reader, retail_writer, retail_setup;
773
+ GRANT SELECT ON ALL TABLES IN SCHEMA public TO retail_manager_reader, retail_writer;
774
+ GRANT UPDATE (status, version) ON public.orders TO retail_writer;
775
+ GRANT UPDATE (available_quantity, reserved_quantity, version) ON public.inventory_levels TO retail_writer;
776
+
777
+ CREATE TABLE public.synapsor_fixture_ready (
778
+ initialized_at timestamptz NOT NULL
779
+ );
780
+
781
+ INSERT INTO public.synapsor_fixture_ready VALUES (clock_timestamp());