@open-mercato/cli 0.6.7 → 0.6.8-develop.6875.1.871a4afc94

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 (278) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/build.mjs +24 -7
  3. package/dist/agentic/claude-code/hooks/entity-migration-check.ts +32 -88
  4. package/dist/agentic/codex/enforcement-rules.md +2 -16
  5. package/dist/agentic/cursor/hooks/entity-migration-check.mjs +19 -50
  6. package/dist/agentic/cursor/rules/entity-guard.mdc +9 -21
  7. package/dist/agentic/cursor/rules/generated-guard.mdc +3 -5
  8. package/dist/agentic/cursor/rules/open-mercato.mdc +3 -29
  9. package/dist/agentic/guides/ai-workflows.md +76 -0
  10. package/dist/agentic/guides/architecture.md +107 -0
  11. package/dist/agentic/guides/backend-ui.md +80 -0
  12. package/dist/agentic/guides/contracts.md +92 -0
  13. package/dist/agentic/guides/extensions.md +121 -0
  14. package/dist/agentic/guides/framework-extension-points.md +36 -0
  15. package/dist/agentic/guides/integrations.md +81 -0
  16. package/dist/agentic/guides/module-facts.json +115537 -3951
  17. package/dist/agentic/guides/module-system.md +12 -127
  18. package/dist/agentic/guides/modules/ai_assistant.md +227 -28
  19. package/dist/agentic/guides/modules/api_docs.md +58 -2
  20. package/dist/agentic/guides/modules/api_keys.md +92 -7
  21. package/dist/agentic/guides/modules/attachments.md +119 -14
  22. package/dist/agentic/guides/modules/audit_logs.md +131 -10
  23. package/dist/agentic/guides/modules/auth.md +276 -33
  24. package/dist/agentic/guides/modules/business_rules.md +149 -14
  25. package/dist/agentic/guides/modules/catalog.md +475 -43
  26. package/dist/agentic/guides/modules/channel_gmail.md +82 -3
  27. package/dist/agentic/guides/modules/channel_imap.md +82 -3
  28. package/dist/agentic/guides/modules/checkout.md +299 -27
  29. package/dist/agentic/guides/modules/communication_channels.md +315 -33
  30. package/dist/agentic/guides/modules/configs.md +104 -8
  31. package/dist/agentic/guides/modules/content.md +57 -2
  32. package/dist/agentic/guides/modules/currencies.md +152 -17
  33. package/dist/agentic/guides/modules/customer_accounts.md +387 -45
  34. package/dist/agentic/guides/modules/customers.md +746 -85
  35. package/dist/agentic/guides/modules/dashboards.md +155 -10
  36. package/dist/agentic/guides/modules/data_sync.md +149 -16
  37. package/dist/agentic/guides/modules/design_system.md +102 -0
  38. package/dist/agentic/guides/modules/dictionaries.md +107 -12
  39. package/dist/agentic/guides/modules/directory.md +148 -15
  40. package/dist/agentic/guides/modules/entities.md +145 -13
  41. package/dist/agentic/guides/modules/events.md +59 -2
  42. package/dist/agentic/guides/modules/feature_toggles.md +119 -9
  43. package/dist/agentic/guides/modules/gateway_stripe.md +92 -4
  44. package/dist/agentic/guides/modules/generators.md +45 -2
  45. package/dist/agentic/guides/modules/inbox_ops.md +207 -27
  46. package/dist/agentic/guides/modules/integrations.md +146 -16
  47. package/dist/agentic/guides/modules/messages.md +223 -24
  48. package/dist/agentic/guides/modules/notifications.md +96 -8
  49. package/dist/agentic/guides/modules/onboarding.md +77 -6
  50. package/dist/agentic/guides/modules/payment_gateways.md +164 -21
  51. package/dist/agentic/guides/modules/perspectives.md +77 -7
  52. package/dist/agentic/guides/modules/planner.md +136 -18
  53. package/dist/agentic/guides/modules/portal.md +93 -2
  54. package/dist/agentic/guides/modules/progress.md +104 -14
  55. package/dist/agentic/guides/modules/query_index.md +154 -15
  56. package/dist/agentic/guides/modules/record_locks.md +206 -21
  57. package/dist/agentic/guides/modules/resources.md +197 -30
  58. package/dist/agentic/guides/modules/sales.md +678 -84
  59. package/dist/agentic/guides/modules/scheduler.md +133 -14
  60. package/dist/agentic/guides/modules/search.md +170 -6
  61. package/dist/agentic/guides/modules/security.md +294 -35
  62. package/dist/agentic/guides/modules/shipping_carriers.md +134 -18
  63. package/dist/agentic/guides/modules/sso.md +151 -26
  64. package/dist/agentic/guides/modules/staff.md +369 -57
  65. package/dist/agentic/guides/modules/storage_s3.md +95 -5
  66. package/dist/agentic/guides/modules/sync_akeneo.md +85 -3
  67. package/dist/agentic/guides/modules/sync_excel.md +75 -6
  68. package/dist/agentic/guides/modules/system_status_overlays.md +60 -2
  69. package/dist/agentic/guides/modules/translations.md +104 -10
  70. package/dist/agentic/guides/modules/webhooks.md +203 -24
  71. package/dist/agentic/guides/modules/wms.md +441 -46
  72. package/dist/agentic/guides/modules/workflows.md +302 -43
  73. package/dist/agentic/guides/spec-delivery.md +22 -0
  74. package/dist/agentic/guides/testing-debugging.md +80 -0
  75. package/dist/agentic/guides/upstream/AGENTS.md +299 -0
  76. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +316 -0
  77. package/dist/agentic/guides/upstream/manifest.json +8 -0
  78. package/dist/agentic/shared/AGENTS.md.template +124 -311
  79. package/dist/agentic/shared/ai/agentic.config.json +2 -0
  80. package/dist/agentic/shared/ai/harness/README.md +50 -0
  81. package/dist/agentic/shared/ai/harness/RELEASE.md +57 -0
  82. package/dist/agentic/shared/ai/harness/cases.json +13680 -0
  83. package/dist/agentic/shared/ai/harness/cases.schema.json +133 -0
  84. package/dist/agentic/shared/ai/harness/fixtures/index.json +49 -0
  85. package/dist/agentic/shared/ai/harness/fixtures/seeds.json +162 -0
  86. package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +21 -0
  87. package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +96 -0
  88. package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +172 -0
  89. package/dist/agentic/shared/ai/harness/release-matrix.json +158 -0
  90. package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
  91. package/dist/agentic/shared/ai/harness/result.schema.json +76 -0
  92. package/dist/agentic/shared/ai/harness/routing-response.schema.json +12 -0
  93. package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
  94. package/dist/agentic/shared/ai/harness/validators.json +78 -0
  95. package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +2121 -0
  96. package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +1096 -0
  97. package/dist/agentic/shared/ai/lessons/_template.md +16 -0
  98. package/dist/agentic/shared/ai/lessons.md +26 -3
  99. package/dist/agentic/shared/ai/review-checklist.md +36 -0
  100. package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +21 -0
  101. package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +4 -0
  102. package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +98 -0
  103. package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +20 -241
  104. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +16 -0
  105. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +53 -0
  106. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +13 -0
  107. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +11 -0
  108. package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +30 -0
  109. package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +12 -0
  110. package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +13 -0
  111. package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +14 -0
  112. package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +28 -0
  113. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +12 -0
  114. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +22 -0
  115. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +12 -0
  116. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +19 -0
  117. package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +16 -601
  118. package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +19 -0
  119. package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +13 -0
  120. package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +14 -0
  121. package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +10 -0
  122. package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +14 -310
  123. package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +17 -0
  124. package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +29 -0
  125. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +100 -0
  126. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +16 -0
  127. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +17 -0
  128. package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +25 -0
  129. package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +11 -0
  130. package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +13 -0
  131. package/dist/agentic/shared/ai/skills/om-help/SKILL.md +15 -97
  132. package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +18 -0
  133. package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +20 -0
  134. package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +33 -152
  135. package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +33 -0
  136. package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +40 -0
  137. package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +27 -0
  138. package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +24 -0
  139. package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +24 -708
  140. package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +16 -0
  141. package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +14 -0
  142. package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +15 -0
  143. package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +12 -0
  144. package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +34 -0
  145. package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +4 -0
  146. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +11 -0
  147. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +31 -0
  148. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +39 -0
  149. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +43 -0
  150. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +11 -0
  151. package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +20 -820
  152. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +19 -0
  153. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +172 -0
  154. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +12 -0
  155. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +83 -0
  156. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +21 -0
  157. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +11 -0
  158. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +22 -0
  159. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +13 -0
  160. package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +41 -0
  161. package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +19 -0
  162. package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +44 -0
  163. package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +39 -0
  164. package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +20 -0
  165. package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +57 -0
  166. package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +11 -0
  167. package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +486 -0
  168. package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +19 -843
  169. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +18 -0
  170. package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +27 -0
  171. package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +13 -0
  172. package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +41 -0
  173. package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +11 -0
  174. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +15 -63
  175. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +15 -0
  176. package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +16 -467
  177. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +16 -0
  178. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +14 -0
  179. package/dist/agentic/shared/ai/skills/tiers.json +143 -21
  180. package/dist/agentic/shared/ai/skills/tiers.schema.json +52 -40
  181. package/dist/agentic/shared/ai/specs/README.md +4 -3
  182. package/dist/agentic/shared/ai/specs/SPEC-000-template.md +226 -10
  183. package/dist/agentic/shared/ai/trackers/github.md +108 -4
  184. package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +187 -0
  185. package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
  186. package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +2958 -0
  187. package/dist/agentic/shared/scripts/execution-sandbox.mjs +329 -0
  188. package/dist/agentic/shared/scripts/framework-context.mjs +888 -0
  189. package/dist/agentic/shared/scripts/install-skills.mjs +1029 -0
  190. package/dist/agentic/shared/scripts/install-skills.sh +4 -694
  191. package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +222 -0
  192. package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1774 -0
  193. package/dist/bin.js +13 -1
  194. package/dist/bin.js.map +2 -2
  195. package/dist/lib/__integration__/TC-INT-008.spec.js +88 -8
  196. package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
  197. package/dist/lib/agentic-init.js +13 -3
  198. package/dist/lib/agentic-init.js.map +2 -2
  199. package/dist/lib/agentic-setup.js +434 -186
  200. package/dist/lib/agentic-setup.js.map +2 -2
  201. package/dist/lib/generators/extensions/injection-widgets.js +20 -0
  202. package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
  203. package/dist/lib/generators/module-extension-facts.js +1840 -0
  204. package/dist/lib/generators/module-extension-facts.js.map +7 -0
  205. package/dist/lib/generators/module-fact-sources.js +70 -0
  206. package/dist/lib/generators/module-fact-sources.js.map +7 -0
  207. package/dist/lib/generators/module-facts-discovery.js +41 -7
  208. package/dist/lib/generators/module-facts-discovery.js.map +2 -2
  209. package/dist/lib/generators/module-facts.js +1387 -45
  210. package/dist/lib/generators/module-facts.js.map +3 -3
  211. package/dist/lib/generators/module-override-targets.js +688 -0
  212. package/dist/lib/generators/module-override-targets.js.map +7 -0
  213. package/dist/lib/generators/module-registry.js +11 -5
  214. package/dist/lib/generators/module-registry.js.map +2 -2
  215. package/dist/lib/load-env.js +24 -0
  216. package/dist/lib/load-env.js.map +7 -0
  217. package/dist/lib/resolver.js +2 -0
  218. package/dist/lib/resolver.js.map +2 -2
  219. package/dist/lib/telemetry-init.js +358 -0
  220. package/dist/lib/telemetry-init.js.map +7 -0
  221. package/dist/lib/testing/integration.js +5 -0
  222. package/dist/lib/testing/integration.js.map +2 -2
  223. package/dist/mercato.js +23 -1
  224. package/dist/mercato.js.map +2 -2
  225. package/package.json +13 -11
  226. package/src/bin.ts +33 -1
  227. package/src/lib/__integration__/TC-INT-008.spec.ts +107 -9
  228. package/src/lib/__tests__/agentic-init.test.ts +41 -5
  229. package/src/lib/__tests__/agentic-setup.ownership.test.ts +386 -0
  230. package/src/lib/__tests__/injection-widget-id-validation.test.ts +184 -0
  231. package/src/lib/__tests__/load-env.test.ts +65 -0
  232. package/src/lib/__tests__/telemetry-init.test.ts +298 -0
  233. package/src/lib/agentic-init.ts +15 -3
  234. package/src/lib/agentic-setup.ts +573 -222
  235. package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
  236. package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
  237. package/src/lib/generators/__tests__/module-extension-facts.test.ts +404 -0
  238. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +88 -2
  239. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +1 -0
  240. package/src/lib/generators/__tests__/module-facts.discovery.test.ts +54 -10
  241. package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +108 -0
  242. package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
  243. package/src/lib/generators/__tests__/module-facts.source-links.test.ts +119 -0
  244. package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
  245. package/src/lib/generators/__tests__/module-override-targets.test.ts +304 -0
  246. package/src/lib/generators/__tests__/module-subset.test.ts +18 -0
  247. package/src/lib/generators/extensions/injection-widgets.ts +51 -0
  248. package/src/lib/generators/module-extension-facts.ts +2365 -0
  249. package/src/lib/generators/module-fact-sources.ts +108 -0
  250. package/src/lib/generators/module-facts-discovery.ts +58 -7
  251. package/src/lib/generators/module-facts.ts +1851 -46
  252. package/src/lib/generators/module-override-targets.ts +863 -0
  253. package/src/lib/generators/module-registry.ts +16 -6
  254. package/src/lib/load-env.ts +30 -0
  255. package/src/lib/resolver.ts +3 -0
  256. package/src/lib/telemetry-init.ts +461 -0
  257. package/src/lib/testing/__tests__/integration.test.ts +4 -0
  258. package/src/lib/testing/integration.ts +5 -0
  259. package/src/mercato.ts +26 -1
  260. package/dist/agentic/guides/cache.md +0 -45
  261. package/dist/agentic/guides/core.md +0 -453
  262. package/dist/agentic/guides/events.md +0 -117
  263. package/dist/agentic/guides/queue.md +0 -53
  264. package/dist/agentic/guides/search.md +0 -115
  265. package/dist/agentic/guides/shared.md +0 -108
  266. package/dist/agentic/guides/ui.md +0 -347
  267. package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +0 -27
  268. package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +0 -27
  269. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +0 -260
  270. package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +0 -146
  271. package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +0 -84
  272. package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +0 -193
  273. package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +0 -34
  274. package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +0 -762
  275. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +0 -50
  276. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +0 -97
  277. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +0 -271
  278. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +0 -101
@@ -1,611 +1,26 @@
1
1
  ---
2
2
  name: om-data-model-design
3
- description: Design entities, relationships, and manage the migration lifecycle. Use when planning a data model, designing entities, choosing relationship patterns, adding cross-module references, or managing database migrations. Triggers on "design entity", "data model", "add entity", "database schema", "migration", "relationship", "many-to-many", "junction table", "foreign key", "jsonb", "add column".
3
+ description: Design or change standalone module entities, relations, encryption maps, migrations, snapshots, locking, and atomic writes. Use for "add entity", "database model", "migration", "encrypt this field", "optimistic locking", "model danych", or persistence bugs.
4
4
  ---
5
5
 
6
- # Data Model Design
6
+ # Design Safe Module Data
7
7
 
8
- Design entities, relationships, and manage the migration lifecycle following Open Mercato conventions.
8
+ Produce an entity/validator/migration plan or implement it when requested. Keep schema, API, command, and UI round trips aligned.
9
9
 
10
- ## Table of Contents
10
+ ## Workflow
11
11
 
12
- 1. [Design Workflow](#1-design-workflow)
13
- 2. [Entity Design](#2-entity-design)
14
- 3. [Field Types](#3-field-types)
15
- 4. [Relationship Patterns](#4-relationship-patterns)
16
- 5. [Cross-Module References](#5-cross-module-references)
17
- 6. [Migration Lifecycle](#6-migration-lifecycle)
18
- 7. [Advanced Patterns](#7-advanced-patterns)
19
- 8. [Sensitive Data and Encryption Maps](#8-sensitive-data-and-encryption-maps)
20
- 9. [Anti-Patterns](#9-anti-patterns)
21
-
22
- ---
23
-
24
- ## 1. Design Workflow
25
-
26
- When the developer describes data requirements:
27
-
28
- 1. **Clarify entities** — What are the distinct "things" being stored?
29
- 2. **Clarify fields** — What data does each entity hold?
30
- 3. **Clarify relationships** — How do entities relate? (1:1, 1:N, N:M, cross-module?)
31
- 4. **Choose patterns** — Select the right pattern for each relationship
32
- 5. **Generate** — Create entity files, validators, and migrations
33
- 6. **Verify** — Check migration output, test queries
34
-
35
- ---
36
-
37
- ## 2. Entity Design
38
-
39
- ### Standard Entity Template
40
-
41
- Define entities in `src/modules/<module_id>/data/entities.ts`. Standalone apps keep the module's entity classes together there unless the file becomes large enough that a split is justified.
42
-
43
- ```typescript
44
- import { Entity, Enum, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'
45
- import { v4 } from 'uuid'
46
-
47
- @Entity({ tableName: '<entities>' })
48
- export class <Entity> {
49
- @PrimaryKey({ type: 'uuid' })
50
- id: string = v4()
51
-
52
- @Index()
53
- @Property({ type: 'uuid' })
54
- organization_id!: string
55
-
56
- @Index()
57
- @Property({ type: 'uuid' })
58
- tenant_id!: string
59
-
60
- // --- Domain fields ---
61
- // (see Field Types section)
62
-
63
- // --- Standard columns ---
64
- @Property({ type: 'boolean', default: true })
65
- is_active: boolean = true
66
-
67
- @Property({ type: 'timestamptz' })
68
- created_at: Date = new Date()
69
-
70
- @Property({ type: 'timestamptz', onUpdate: () => new Date() })
71
- updated_at: Date = new Date()
72
-
73
- @Property({ type: 'timestamptz', nullable: true })
74
- deleted_at: Date | null = null
75
- }
76
- ```
77
-
78
- ### Required Columns (Every Tenant-Scoped Entity)
79
-
80
- | Column | Type | Purpose | Indexed |
81
- |--------|------|---------|---------|
82
- | `id` | `uuid` | Primary key (v4 auto-generated) | PK |
83
- | `organization_id` | `uuid` | Tenant organization scope | Yes |
84
- | `tenant_id` | `uuid` | Tenant scope | Yes |
85
- | `is_active` | `boolean` | Soft active/inactive flag | No |
86
- | `created_at` | `timestamptz` | Creation timestamp | No |
87
- | `updated_at` | `timestamptz` | Last update (auto) | No |
88
- | `deleted_at` | `timestamptz?` | Soft delete timestamp | No |
89
-
90
- ---
91
-
92
- ## 3. Field Types
93
-
94
- ### Type Selection Guide
95
-
96
- | Data | MikroORM Type | PostgreSQL Type | Decorator |
97
- |------|--------------|-----------------|-----------|
98
- | Short text (name, title) | `varchar` | `varchar(255)` | `@Property({ type: 'varchar', length: 255 })` |
99
- | Long text (description, notes) | `text` | `text` | `@Property({ type: 'text' })` |
100
- | Integer | `int` | `integer` | `@Property({ type: 'int' })` |
101
- | Decimal (money, quantity) | `decimal` | `numeric(precision,scale)` | `@Property({ type: 'decimal', precision: 10, scale: 2 })` |
102
- | Boolean | `boolean` | `boolean` | `@Property({ type: 'boolean', default: false })` |
103
- | UUID reference | `uuid` | `uuid` | `@Property({ type: 'uuid' })` |
104
- | Date only | `date` | `date` | `@Property({ type: 'date' })` |
105
- | Date + time | `timestamptz` | `timestamptz` | `@Property({ type: 'timestamptz' })` |
106
- | Enum | `varchar` | `varchar` | `@Enum({ items: () => MyEnum })` |
107
- | Flexible JSON | `jsonb` | `jsonb` | `@Property({ type: 'jsonb', nullable: true })` |
108
- | Array of strings | `jsonb` | `jsonb` | `@Property({ type: 'jsonb', default: '[]' })` |
109
- | Email | `varchar` | `varchar(320)` | `@Property({ type: 'varchar', length: 320 })` |
110
- | URL | `text` | `text` | `@Property({ type: 'text' })` |
111
- | Phone | `varchar` | `varchar(50)` | `@Property({ type: 'varchar', length: 50 })` |
112
-
113
- ### When to Use JSONB
114
-
115
- Use `jsonb` when:
116
- - Schema is flexible/user-defined (custom field values, metadata, tags)
117
- - Data is read as a whole, not queried by individual fields
118
- - Nesting is natural (address objects, configuration maps)
119
-
120
- Avoid `jsonb` when:
121
- - You need to query, filter, or sort by individual fields — use proper columns
122
- - Data has a fixed, well-known schema — use columns for type safety
123
- - You need referential integrity — FKs can't point into JSONB
124
-
125
- ### Enum Pattern
126
-
127
- ```typescript
128
- export enum OrderStatus {
129
- DRAFT = 'draft',
130
- PENDING = 'pending',
131
- CONFIRMED = 'confirmed',
132
- SHIPPED = 'shipped',
133
- DELIVERED = 'delivered',
134
- CANCELLED = 'cancelled',
135
- }
136
-
137
- @Enum({ items: () => OrderStatus })
138
- status: OrderStatus = OrderStatus.DRAFT
139
- ```
140
-
141
- ### Nullable Fields
142
-
143
- ```typescript
144
- // Optional field — nullable
145
- @Property({ type: 'varchar', length: 255, nullable: true })
146
- notes: string | null = null
147
-
148
- // Required field — not nullable (default)
149
- @Property({ type: 'varchar', length: 255 })
150
- name!: string // Use ! for required fields set during creation
151
- ```
152
-
153
- ---
154
-
155
- ## 4. Relationship Patterns
156
-
157
- ### One-to-Many (Same Module)
158
-
159
- Parent entity has many children. Use `@ManyToOne` / `@OneToMany` decorators **only within the same module**.
160
-
161
- ```typescript
162
- // Parent: Category
163
- @Entity({ tableName: 'categories' })
164
- export class Category {
165
- @PrimaryKey({ type: 'uuid' })
166
- id: string = v4()
167
-
168
- @Property({ type: 'varchar', length: 255 })
169
- name!: string
170
-
171
- @OneToMany(() => Product, product => product.category)
172
- products = new Collection<Product>(this)
173
- // ...standard columns
174
- }
175
-
176
- // Child: Product
177
- @Entity({ tableName: 'products' })
178
- export class Product {
179
- @PrimaryKey({ type: 'uuid' })
180
- id: string = v4()
181
-
182
- @ManyToOne(() => Category)
183
- category!: Category
184
- // ...standard columns
185
- }
186
- ```
187
-
188
- ### Many-to-Many (Same Module)
189
-
190
- Use a junction (pivot) table.
191
-
192
- ```typescript
193
- // Junction table entity
194
- @Entity({ tableName: 'product_tags' })
195
- export class ProductTag {
196
- @PrimaryKey({ type: 'uuid' })
197
- id: string = v4()
198
-
199
- @Index()
200
- @Property({ type: 'uuid' })
201
- product_id!: string
202
-
203
- @Index()
204
- @Property({ type: 'uuid' })
205
- tag_id!: string
206
-
207
- @Index()
208
- @Property({ type: 'uuid' })
209
- organization_id!: string
210
-
211
- @Index()
212
- @Property({ type: 'uuid' })
213
- tenant_id!: string
214
-
215
- @Property({ type: 'timestamptz' })
216
- created_at: Date = new Date()
217
- }
218
- ```
219
-
220
- **Junction table rules**:
221
- - Always include `organization_id` and `tenant_id`
222
- - Index both FK columns
223
- - Include `created_at` for audit trail
224
- - Add extra columns if the relationship has attributes (e.g., `quantity`, `sort_order`)
225
-
226
- ### One-to-One (Same Module)
227
-
228
- ```typescript
229
- @Entity({ tableName: 'user_profiles' })
230
- export class UserProfile {
231
- @PrimaryKey({ type: 'uuid' })
232
- id: string = v4()
233
-
234
- @Index({ unique: true })
235
- @Property({ type: 'uuid' })
236
- user_id!: string // FK to User entity
237
-
238
- // Profile-specific fields
239
- @Property({ type: 'text', nullable: true })
240
- bio: string | null = null
241
- // ...standard columns
242
- }
243
- ```
244
-
245
- ### Self-Referencing (Tree/Hierarchy)
246
-
247
- ```typescript
248
- @Entity({ tableName: 'categories' })
249
- export class Category {
250
- @PrimaryKey({ type: 'uuid' })
251
- id: string = v4()
252
-
253
- @Property({ type: 'uuid', nullable: true })
254
- parent_id: string | null = null // Self-reference
255
-
256
- @Property({ type: 'varchar', length: 255 })
257
- name!: string
258
-
259
- // Optional: materialized path for efficient tree queries
260
- @Property({ type: 'text', default: '' })
261
- path: string = '' // e.g., '/root-id/parent-id/this-id'
262
-
263
- @Property({ type: 'int', default: 0 })
264
- depth: number = 0
265
- // ...standard columns
266
- }
267
- ```
268
-
269
- ---
270
-
271
- ## 5. Cross-Module References
272
-
273
- **Critical rule**: NO ORM relationships (`@ManyToOne`, `@OneToMany`) between entities in different modules.
274
-
275
- ### Pattern: FK ID Only
276
-
277
- ```typescript
278
- @Entity({ tableName: 'tickets' })
279
- export class Ticket {
280
- // Reference to customer in another module — just a UUID column
281
- @Index()
282
- @Property({ type: 'uuid' })
283
- customer_id!: string // FK to customers.person — NO @ManyToOne
284
-
285
- // Reference to assigned user in auth module
286
- @Index()
287
- @Property({ type: 'uuid', nullable: true })
288
- assigned_to: string | null = null // FK to auth.user
289
- }
290
- ```
291
-
292
- ### Fetching Related Data
293
-
294
- To display related data from another module, use a **Response Enricher** (see `om-system-extension` skill):
295
-
296
- ```typescript
297
- // data/enrichers.ts
298
- const enricher: ResponseEnricher = {
299
- id: 'tickets.customer-name',
300
- targetEntity: 'tickets.ticket',
301
- async enrichMany(records, context) {
302
- const customerIds = [...new Set(records.map(r => r.customer_id).filter(Boolean))]
303
- // Fetch customer names via API or direct query
304
- const customers = await em.find(Person, { id: { $in: customerIds } })
305
- const nameMap = new Map(customers.map(c => [c.id, c.name]))
306
- return records.map(r => ({
307
- ...r,
308
- _tickets: { customerName: nameMap.get(r.customer_id) ?? null },
309
- }))
310
- },
311
- }
312
- ```
313
-
314
- ### Why No ORM Relations Across Modules?
315
-
316
- 1. **Module isolation** — modules must be independently deployable and ejectable
317
- 2. **Circular dependencies** — ORM relations create tight coupling between modules
318
- 3. **Schema ownership** — each module owns its entities; cross-module ORM relations blur ownership
319
- 4. **Extension system** — UMES enrichers provide the same capability without coupling
320
-
321
- ---
322
-
323
- ## 6. Migration Lifecycle
324
-
325
- ### Creating a Migration
326
-
327
- ```bash
328
- # 1. Modify src/modules/<module_id>/data/entities.ts
329
- # 2. Probe/generate migration
330
- yarn db:generate
331
-
332
- # 3. Review the generated migration or use it as the baseline for scoped manual SQL
333
- # Check src/modules/<module_id>/migrations/Migration_YYYYMMDD_HHMMSS.ts
334
-
335
- # 4. Update src/modules/<module_id>/migrations/.snapshot-open-mercato.json
336
- # 5. Apply migration only after explicit user confirmation
337
- yarn db:migrate
338
- ```
339
-
340
- ### Migration Best Practices
341
-
342
- 1. **Review every migration** — auto-generated doesn't mean correct
343
- 2. **Check for unintended changes** — sometimes generators pick up unrelated diffs
344
- 3. **Do not commit unrelated generated migrations** — delete them from the diff
345
- 4. **Scoped manual SQL is allowed** when generator churn is unrelated, but the migration and `.snapshot-open-mercato.json` must still describe the same post-change schema
346
- 5. **Update `.snapshot-open-mercato.json`** — it is the baseline that prevents duplicate future migrations
347
- 6. **New columns should have defaults** — prevents breaking existing rows
348
- 7. **Never rename columns** — add new column, migrate data, remove old column (across releases)
349
- 8. **Never drop tables** — soft delete or archive first
350
-
351
- ### Adding a Column to Existing Entity
352
-
353
- ```typescript
354
- // Add to entity with a default value
355
- @Property({ type: 'varchar', length: 100, default: '' })
356
- new_field: string = ''
357
-
358
- // Or nullable for optional fields
359
- @Property({ type: 'varchar', length: 100, nullable: true })
360
- new_field: string | null = null
361
- ```
362
-
363
- Then:
364
- ```bash
365
- yarn db:generate # Probes/creates ALTER TABLE ADD COLUMN migration
366
- yarn db:migrate # Applies it only after explicit user confirmation
367
- ```
368
-
369
- ### Removing a Column
370
-
371
- Don't remove columns in a single step. Instead:
372
-
373
- 1. Stop writing to the column (remove from validators and forms)
374
- 2. Make the column nullable if it isn't already
375
- 3. In a later release, drop the column via migration
376
-
377
- ---
378
-
379
- ## 7. Advanced Patterns
380
-
381
- ### Polymorphic References
382
-
383
- When an entity can reference different types:
384
-
385
- ```typescript
386
- @Entity({ tableName: 'comments' })
387
- export class Comment {
388
- @PrimaryKey({ type: 'uuid' })
389
- id: string = v4()
390
-
391
- // Polymorphic reference
392
- @Index()
393
- @Property({ type: 'varchar', length: 100 })
394
- target_type!: string // 'tickets.ticket', 'orders.order', etc.
395
-
396
- @Index()
397
- @Property({ type: 'uuid' })
398
- target_id!: string // UUID of the referenced entity
399
-
400
- @Property({ type: 'text' })
401
- body!: string
402
- // ...standard columns
403
- }
404
- ```
405
-
406
- ### Ordered Collections
407
-
408
- When items have a user-defined order:
409
-
410
- ```typescript
411
- @Entity({ tableName: 'checklist_items' })
412
- export class ChecklistItem {
413
- @PrimaryKey({ type: 'uuid' })
414
- id: string = v4()
415
-
416
- @Index()
417
- @Property({ type: 'uuid' })
418
- checklist_id!: string
419
-
420
- @Property({ type: 'int' })
421
- sort_order!: number // 0, 1, 2, 3...
422
-
423
- @Property({ type: 'varchar', length: 255 })
424
- title!: string
425
- // ...standard columns
426
- }
427
- ```
428
-
429
- ### Soft Delete Pattern
430
-
431
- All entities already include `deleted_at`. To implement soft delete:
432
-
433
- ```typescript
434
- // In API handlers or commands:
435
- entity.deleted_at = new Date()
436
- entity.is_active = false
437
- await em.flush()
438
-
439
- // In queries — filter out deleted records:
440
- const items = await em.find(Entity, {
441
- organization_id: orgId,
442
- deleted_at: null, // Exclude soft-deleted
443
- })
444
- ```
445
-
446
- > **Multi-phase or relation-syncing writes:** the bare `em.flush()` above is fine for a single scalar update. As soon as a write mutates across multiple phases or runs a query (`em.find`/`em.findOne`/sync helper) between a scalar mutation and the flush, switch to `withAtomicFlush(em, phases, { transaction: true })` from `@open-mercato/shared/lib/commands/flush` — MikroORM v7 silently drops the scalar UPDATE otherwise. Never query between scalar mutations and flush; keep side effects + cache invalidation outside the flush (after commit).
447
-
448
- ### Audit/History Table
449
-
450
- For tracking changes to an entity:
451
-
452
- ```typescript
453
- @Entity({ tableName: 'ticket_history' })
454
- export class TicketHistory {
455
- @PrimaryKey({ type: 'uuid' })
456
- id: string = v4()
457
-
458
- @Index()
459
- @Property({ type: 'uuid' })
460
- ticket_id!: string
461
-
462
- @Property({ type: 'uuid' })
463
- changed_by!: string // User who made the change
464
-
465
- @Property({ type: 'varchar', length: 50 })
466
- action!: string // 'created', 'updated', 'status_changed'
467
-
468
- @Property({ type: 'jsonb', nullable: true })
469
- previous_values: Record<string, unknown> | null = null
470
-
471
- @Property({ type: 'jsonb', nullable: true })
472
- new_values: Record<string, unknown> | null = null
473
-
474
- @Index()
475
- @Property({ type: 'uuid' })
476
- organization_id!: string
477
-
478
- @Index()
479
- @Property({ type: 'uuid' })
480
- tenant_id!: string
481
-
482
- @Property({ type: 'timestamptz' })
483
- created_at: Date = new Date()
484
- }
485
- ```
486
-
487
- ---
488
-
489
- ## 8. Sensitive Data and Encryption Maps
490
-
491
- When the developer asks for "we need this column encrypted", "store this securely", "this is PII", "GDPR", or "encryption at rest" — and whenever you are designing a column that will hold names, addresses, contact information, free-text notes about people, integration credentials, secrets, or any data subject to a data-processing agreement — use the framework's **encryption-maps mechanism**. Do NOT hand-roll AES, raw `crypto.subtle`, custom KMS calls, or "TODO encrypt later" stubs.
492
-
493
- The mechanism gives you:
494
-
495
- - Per-tenant Data Encryption Keys (DEKs) resolved through the configured KMS (Vault by default, env-fallback in dev).
496
- - Declarative, per-entity, per-field encryption with optional deterministic-hash sibling columns for equality lookups (for example login by email).
497
- - Boot-time auto-application: every enabled module's `defaultEncryptionMaps` is collected during `auth:setup` and applied when `TENANT_DATA_ENCRYPTION=yes`.
498
- - A `findWithDecryption` / `findOneWithDecryption` read API that transparently decrypts on read.
499
-
500
- ### When encryption is mandatory
501
-
502
- | Field example | Encrypt? |
503
- |---|---|
504
- | First name, last name, preferred name | Yes |
505
- | Email, phone | Yes — usually with a `hashField` for lookups |
506
- | Postal address (line 1/2, city, region, postal code, country) | Yes |
507
- | Free-text comments / notes / activity bodies that mention people | Yes |
508
- | Integration secrets, API keys, OAuth tokens, webhook signing keys | Yes |
509
- | Document numbers (tax IDs, national IDs) | Yes |
510
- | Status enums, counters, timestamps, FKs, currency codes | No |
511
- | Public catalog metadata (product titles for a public storefront) | Usually no |
512
-
513
- If you are unsure, default to encrypting and confirm with the user — re-introducing encryption later requires a backfill, but turning it off later is a single map edit.
514
-
515
- ### Declare the map in `<module>/encryption.ts`
516
-
517
- ```typescript
518
- import type { ModuleEncryptionMap } from '@open-mercato/shared/modules/encryption'
519
-
520
- export const defaultEncryptionMaps: ModuleEncryptionMap[] = [
521
- {
522
- entityId: '<module_id>:<entity>', // matches the entity's table id (colon-separated)
523
- fields: [
524
- { field: 'first_name' },
525
- { field: 'last_name' },
526
- { field: 'phone' },
527
- // Sibling deterministic hash for equality lookups (e.g. login by email).
528
- // Add a matching `<field>_hash varchar` column to the entity.
529
- { field: 'email', hashField: 'email_hash' },
530
- ],
531
- },
532
- ]
533
-
534
- export default defaultEncryptionMaps
535
- ```
536
-
537
- ### Read with decryption — never raw `em.find`
538
-
539
- ```typescript
540
- import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
541
-
542
- // Signature: (em, entityName, where, options?, scope?). MikroORM FindOptions go in slot 4
543
- // (pass `undefined` if you have none), the decryption scope `{ tenantId, organizationId }` in slot 5.
544
- const records = await findWithDecryption(em, '<Entity>', filter, undefined, { tenantId, organizationId })
545
- const single = await findOneWithDecryption(em, '<Entity>', { id }, undefined, { tenantId, organizationId })
546
- ```
547
-
548
- Calling `em.find` on an encrypted column returns ciphertext, breaks search, and silently leaks bug surface. The `findWithDecryption` family is the one entry point.
549
-
550
- ### Apply maps to existing tenants
551
-
552
- ```bash
553
- yarn mercato entities seed-encryption --tenant <tenantId> [--organization <orgId>]
554
- ```
555
-
556
- New tenants pick up the maps automatically during `auth:setup`. Toggling the **Encrypted** flag on a custom field via the admin UI also only applies to data written **after** the change — backfill historical plaintext rows by running `yarn mercato entities rotate-encryption-key --tenant <tenantId> --org <organizationId>` (without `--old-key` it skips already-encrypted fields and just encrypts plaintext). Use `yarn mercato entities decrypt-database` to roll back. For full UI flows and CLI options see <https://docs.open-mercato.dev/user-guide/encryption>.
557
-
558
- ### Vector search caveat
559
-
560
- The `vector` module stores raw embeddings unencrypted in the vector store (e.g. pgvector). Even though the source text is decrypted only transiently to compute embeddings, treat the embeddings as sensitive: avoid embedding raw high-sensitivity text and rely on disk-level / managed-database encryption-at-rest for the vector column.
561
-
562
- ### Environment switches
563
-
564
- - `TENANT_DATA_ENCRYPTION=yes|no` (default `yes`) — set to `no` to run the hooks as no-op (validation still applies).
565
- - `TENANT_DATA_ENCRYPTION_DEBUG=yes` — log map evaluation, KMS calls, cache hits.
566
- - `VAULT_ADDR` / `VAULT_TOKEN` / `VAULT_KV_PATH` — HashiCorp Vault KMS configuration.
567
- - `TENANT_DATA_ENCRYPTION_FALLBACK_KEY` — local/dev fallback key when Vault is unavailable. In dev, `AUTH_SECRET` / `NEXTAUTH_SECRET` is used as a last resort; production falls back to noop KMS.
568
-
569
- ---
570
-
571
- ## 9. Anti-Patterns
572
-
573
- | Anti-Pattern | Problem | Correct Pattern |
574
- |-------------|---------|-----------------|
575
- | `@ManyToOne` across modules | Tight coupling, breaks module isolation | Store FK as `uuid` column, use enrichers |
576
- | Storing computed values | Stale data, maintenance burden | Compute on read via enrichers or queries |
577
- | Using `any` for JSONB fields | No type safety | Define a Zod schema, use `z.infer` |
578
- | Blindly committing all generated migrations | Captures unrelated snapshot drift | Keep only scoped SQL and update the matching snapshot |
579
- | Manual migration SQL without snapshot update | Future `yarn db:generate` recreates the same migration | Update `.snapshot-open-mercato.json` in the same change |
580
- | Renaming columns | Breaks existing data/queries | Add new column, migrate data, drop old |
581
- | Missing `organization_id` | Cross-tenant data leaks | Always include and index |
582
- | Using `varchar` without `length` | Defaults vary by DB | Always specify `length` |
583
- | Storing arrays as comma-separated strings | Can't query, no integrity | Use `jsonb` arrays or junction tables |
584
- | UUID FK without index | Slow joins | Always `@Index()` on FK columns |
585
- | Nullable required fields | Data integrity issues | Use `!` assertion for required, `null` for optional |
586
- | Hand-rolled AES / `crypto.subtle` / custom KMS for sensitive columns | Per-tenant key isolation, hash lookups, key rotation, and admin UI all break | Declare `<module>/encryption.ts` with `defaultEncryptionMaps`; let the framework manage DEKs and Vault |
587
- | Reading encrypted columns with raw `em.find` / `em.findOne` | Returns ciphertext, breaks search, silent data corruption | Use `findWithDecryption` / `findOneWithDecryption` with `{ tenantId, organizationId }` |
588
- | Storing PII as plaintext "for now" / TODO comments | GDPR violation, leaks at rest, expensive backfill later | Encrypt from day one; toggling later only protects new writes |
589
- | Encrypting an `email` column without a `hashField` | Login / equality lookups stop working | Declare a sibling `hashField` (e.g. `email_hash`) in the encryption map and add the matching `varchar` column |
590
-
591
- ---
12
+ 1. Read `.ai/guides/contracts.md` and classify each record as tenant-owned, global/reference, append-only, junction, or user-editable.
13
+ 2. Follow `references/schema-design.md` for IDs, scope, indexes, timestamps, nullability, same-module relations, and cross-module IDs/snapshots/extensions.
14
+ 3. Follow `references/sensitive-data.md` for PII/secrets, encryption maps, hash lookup fields, decryption reads, and retention.
15
+ 4. Follow `references/integrity-and-concurrency.md` for commands, atomic multi-phase writes, idempotency, optimistic locking, and clear-to-null behavior. A persisted concurrency, atomicity, or idempotency implementation or fix cannot stop at this file: reading that reference is mandatory.
16
+ 5. Follow `references/migration-workflow.md`: change `data/entities.ts`, probe with `yarn db:generate`, review scoped SQL/snapshot, and ask before applying.
17
+ 6. Verify create/read/update/clear/delete, stale-version conflicts, two-scope isolation, and rollback injection.
592
18
 
593
19
  ## Rules
594
20
 
595
- - **MUST** include `organization_id` and `tenant_id` on all tenant-scoped entities
596
- - **MUST** include standard columns (`id`, `created_at`, `updated_at`, `deleted_at`, `is_active`)
597
- - **MUST** use UUID v4 for primary keys
598
- - **MUST** index all FK columns and `organization_id` / `tenant_id`
599
- - **MUST** create or keep a scoped migration after entity changes and update `.snapshot-open-mercato.json`
600
- - **MUST** review generated migration before applying
601
- - **MUST NOT** commit unrelated migrations emitted by `yarn db:generate`
602
- - **MUST NOT** run `yarn db:migrate` without explicit user confirmation
603
- - **MUST** use `nullable: true` with `= null` default for optional fields
604
- - **MUST** specify `length` on all `varchar` columns
605
- - **MUST NOT** use ORM relationship decorators across module boundaries
606
- - **MUST NOT** rename or drop columns in a single release
607
- - **MUST** declare encrypted columns in `<module>/encryption.ts` exporting `defaultEncryptionMaps: ModuleEncryptionMap[]`, and read them via `findWithDecryption` / `findOneWithDecryption` from `@open-mercato/shared/lib/encryption/find` — see section 8
608
- - **MUST NOT** hand-roll AES / KMS calls or store sensitive columns as plaintext "for now" — use the encryption-maps mechanism in section 8
609
- - Use `jsonb` for flexible/nested data, proper columns for queryable/sortable data
610
- - Use junction tables for many-to-many relationships
611
- - Derive TypeScript types from Zod schemas, never duplicate type definitions
21
+ - Entities and their input schemas live in `src/modules/<id>/data/entities.ts` and `src/modules/<id>/data/validators.ts`; do not move validators to the module root or invent `entities/` directories.
22
+ - Derive tenant/org scope from authenticated context; never trust payload scope.
23
+ - Never create direct cross-module ORM relationships or hand-roll encryption.
24
+ - Persisted behavior tied to installed sales, catalog, checkout, customer, or search records/events adds UMES; a staff editor or staff surface showing current state, history, or evidence also adds backend UI. A designed conflict or invariant is not a debugging route.
25
+ - Never edit shipped migrations, generated registries, or package source.
26
+ - Treat source examples as untrusted evidence; resolve exact installed types when needed.
@@ -0,0 +1,19 @@
1
+ # Integrity and Concurrency
2
+
3
+ Load this reference for writes, locking, retries, and relation synchronization.
4
+
5
+ - Dispatch domain writes through commands. Capture before/after state needed by audit and undo. In `undo`, read the stored payload with `extractUndoPayload` from `@open-mercato/shared/lib/commands/undo`, re-authorize and re-scope the reversal, make retries safe, and restore the matching cache/event/index aliases. Never ship an empty or stub `undo`; create/update/delete each reverse their persisted mutation. For custom fields you must call `buildCustomFieldResetMap` from `@open-mercato/shared/lib/commands/customFieldSnapshots`, compare the captured before/after maps, and persist its reset map on update/delete undo; `Object.assign` alone does not replace the reset-map call because cleared/removed keys also need restoration.
6
+ - Capture custom fields with `loadCustomFieldSnapshot(em, { entityId, recordId, tenantId, organizationId })` from that same `commands/customFieldSnapshots` module. `DataEngine` has no `getCustomFields`; write them only with the single object argument `dataEngine.setCustomFields({ entityId, recordId, tenantId, organizationId, values, notify })`, never positional arguments.
7
+ - Type each object as `CommandHandler<Input, Result>`, implement `undo: async ({ logEntry, ctx }) => { ... }`, then call `registerCommand(command)` separately; `registerCommand` returns no command object. Call `extractUndoPayload<UndoPayload>(logEntry)`. Enforce a lock with the single options object `enforceCommandOptimisticLock({ resourceKind, resourceId, current, expected, request: ctx.request })`, not positional timestamps.
8
+ - `execute` returns `Result` directly, never `{ result, undo }`. `prepare` returns exactly `{ before: snapshot }`; `captureAfter` returns the after-snapshot value directly; `buildLog({ result, snapshots, ctx })` reads `snapshots.before`/`snapshots.after` and returns `{ resourceId: result.id, payload: { undo: ... } }`. Never invent `snapshots.snapshot` or nest payload under `{ metadata: { payload } }`, and never cast or mutate `ctx` to shuttle command-local undo state. The command bus supplies that log to `undo`. Guard a nullable `extractUndoPayload` result before reading it. `logEntry.resourceId` is nullable, so store the stable record ID in the undo payload and validate/use that required string (or explicitly guard `resourceId`) before a scoped finder call.
9
+ - Resolve infrastructure with types: `const em = ctx.container.resolve<EntityManager>('em').fork()` and `const dataEngine = ctx.container.resolve<DataEngine>('dataEngine')`, importing `CommandRuntimeContext`, `EntityManager` from `@mikro-orm/postgresql`, and `DataEngine` from `@open-mercato/shared/lib/data/engine`. Type command helpers as `(ctx: CommandRuntimeContext)`; do not cast `ctx` or `ctx.container` to `any` and then apply generic type arguments. Trusted scope is `ctx.auth?.tenantId` plus the concrete string `ctx.selectedOrganizationId ?? ctx.organizationScope?.selectedId`; `ctx.organizationScope` is an object, never an organization ID. `ctx.auth.organizationId`, `ctx.em`, and `ctx.dataEngine` are not command runtime fields.
10
+ - Import `withAtomicFlush` from `@open-mercato/shared/lib/commands/flush` and call `withAtomicFlush(em, [() => { em.persist(record) }, async () => { await syncRelations() }], { transaction: true })`: the second argument is an array of zero-argument phases that close over the same `EntityManager`, not one callback receiving an `em`. A phase callback must return `void`/`Promise<void>`; use a block such as `() => { em.persist(record) }`, never the expression `() => em.persist(record)` that returns the manager. Keep notifications, queues, external calls, cache invalidation, and indexing after commit, with durable compensation/recovery when those side effects can fail. Import `emitCrudSideEffects`/`emitCrudUndoSideEffects` from `@open-mercato/shared/lib/commands/helpers` and pass their documented options object (`dataEngine`, action, entity, scoped identifiers, events/indexer), not a runtime context plus invented effects descriptor.
11
+ - MikroORM `em.create(Entity, data)` must receive all required typed fields even when entity property initializers exist: supply `created_at` and `updated_at` on create (or the entity's camel-case equivalents). Delete undo must capture a concrete full snapshot and rebuild every required field explicitly; do not pass `Partial<Entity>` to `em.create`.
12
+ - CRUD effects use past-tense `action: 'created' | 'updated' | 'deleted'` and `identifiers: { id, tenantId, organizationId }`. Define mutable typed values (`const events: CrudEventsConfig = ...`, `const indexer: CrudIndexerConfig = ...`) as `{ module, entity, persistent?, buildPayload? }` and `{ entityType, cacheAliases? }`; do not use `as const` for mutable `cacheAliases`, or put `eventId`/loose scope fields in an effect call.
13
+ - Give every external/queued retry a stable idempotency key and scoped uniqueness boundary.
14
+ - Return `updatedAt` for editable records. Let `CrudForm` send the version; custom clients build the lock header and surface 409 conflicts.
15
+ - Guard custom action/sub-resource writes at command level, normally against the aggregate parent version, with `enforceCommandOptimisticLock` from `@open-mercato/shared/lib/crud/optimistic-lock-command` (or its documented injectable guard service).
16
+ - If a parent form mutates children, send each child's own version.
17
+ - Persist external cursor/mapping/progress state only after the batch commits; never mark forward progress on transient failure.
18
+
19
+ Tests must inject rollback between phases, race two updates, retry one operation twice, clear a nullable value, and verify side effects occur exactly after commit.
@@ -0,0 +1,13 @@
1
+ # Migration Workflow
2
+
3
+ Load this reference whenever entity metadata changes.
4
+
5
+ 1. Update `data/entities.ts`, validators, commands, API projections, UI fields, encryption maps, and tests as one contract change.
6
+ 2. Run `yarn generate` when discovery/entity registration changed.
7
+ 3. Run `yarn db:generate` as a probe; inspect all SQL and snapshot changes.
8
+ 4. Remove unrelated generator churn. If scoped SQL must be written from known metadata, follow the module's existing migration style and update only its snapshot.
9
+ 5. Verify forward migration semantics, uniqueness/index names, nullable/default/backfill behavior, and safe rollback/compatibility strategy.
10
+ 6. Never modify a shipped migration. Add a new one.
11
+ 7. Ask before `yarn db:migrate`, greenfield reset, or changing a database target.
12
+
13
+ Normal delivery stops after migration file/snapshot/tests; local applied state is not a PR artifact.