@open-mercato/cli 0.8.1-develop.7297.1.67605e74f9 → 0.8.1-develop.7441.1.b42958b98c

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 (217) hide show
  1. package/.turbo/turbo-build.log +8 -2
  2. package/AGENTS.md +2 -0
  3. package/dist/agentic/guides/module-facts.json +7614 -850
  4. package/dist/agentic/guides/module-facts.v2.json +7614 -850
  5. package/dist/agentic/guides/modules/agent_orchestrator/active-extension-bindings.md +1 -1
  6. package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
  7. package/dist/agentic/guides/modules/ai_assistant/encryption.md +13 -0
  8. package/dist/agentic/guides/modules/ai_assistant/exact-override-targets.md +3 -0
  9. package/dist/agentic/guides/modules/ai_assistant/index.md +4 -3
  10. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  11. package/dist/agentic/guides/modules/api_keys/domain-commands.md +12 -0
  12. package/dist/agentic/guides/modules/api_keys/index.md +3 -2
  13. package/dist/agentic/guides/modules/attachments/index.md +1 -1
  14. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  15. package/dist/agentic/guides/modules/auth/index.md +1 -1
  16. package/dist/agentic/guides/modules/availability/acl-features.md +13 -0
  17. package/dist/agentic/guides/modules/availability/backend-pages.md +14 -0
  18. package/dist/agentic/guides/modules/availability/di-registrations-rich.md +11 -0
  19. package/dist/agentic/guides/modules/availability/domain-commands.md +13 -0
  20. package/dist/agentic/guides/modules/availability/entities.md +11 -0
  21. package/dist/agentic/guides/modules/availability/events.md +13 -0
  22. package/dist/agentic/guides/modules/availability/exact-override-targets.md +19 -0
  23. package/dist/agentic/guides/modules/availability/host-extension-points.md +10 -0
  24. package/dist/agentic/guides/modules/availability/index.md +22 -0
  25. package/dist/agentic/guides/modules/availability/owned-contract-module-metadata.md +11 -0
  26. package/dist/agentic/guides/modules/availability/setup.md +11 -0
  27. package/dist/agentic/guides/modules/availability/umes-hosts.md +14 -0
  28. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  29. package/dist/agentic/guides/modules/catalog/active-extension-bindings.md +1 -1
  30. package/dist/agentic/guides/modules/catalog/di-registrations-rich.md +1 -0
  31. package/dist/agentic/guides/modules/catalog/exact-override-targets.md +3 -0
  32. package/dist/agentic/guides/modules/catalog/index.md +1 -1
  33. package/dist/agentic/guides/modules/catalog/workers.md +2 -0
  34. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  35. package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
  36. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  37. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  38. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  39. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  40. package/dist/agentic/guides/modules/channel_resend/index.md +1 -1
  41. package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
  42. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  43. package/dist/agentic/guides/modules/communication_channels/acl-features.md +2 -1
  44. package/dist/agentic/guides/modules/communication_channels/contribution-resolutions.md +1 -0
  45. package/dist/agentic/guides/modules/communication_channels/di-registrations-rich.md +11 -10
  46. package/dist/agentic/guides/modules/communication_channels/domain-commands.md +1 -0
  47. package/dist/agentic/guides/modules/communication_channels/events.md +2 -1
  48. package/dist/agentic/guides/modules/communication_channels/exact-override-targets.md +12 -10
  49. package/dist/agentic/guides/modules/communication_channels/incoming-installed-contributions.md +1 -0
  50. package/dist/agentic/guides/modules/communication_channels/index.md +2 -2
  51. package/dist/agentic/guides/modules/communication_channels/umes-contributions.md +1 -0
  52. package/dist/agentic/guides/modules/communication_channels/umes-hosts.md +1 -0
  53. package/dist/agentic/guides/modules/configs/index.md +1 -1
  54. package/dist/agentic/guides/modules/content/index.md +1 -1
  55. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  56. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  57. package/dist/agentic/guides/modules/customer_groups/acl-features.md +16 -0
  58. package/dist/agentic/guides/modules/customer_groups/active-extension-bindings.md +13 -0
  59. package/dist/agentic/guides/modules/customer_groups/backend-pages.md +14 -0
  60. package/dist/agentic/guides/modules/customer_groups/cli-commands.md +11 -0
  61. package/dist/agentic/guides/modules/customer_groups/contribution-resolutions.md +13 -0
  62. package/dist/agentic/guides/modules/customer_groups/di-registrations-rich.md +13 -0
  63. package/dist/agentic/guides/modules/customer_groups/di-service-tokens.md +11 -0
  64. package/dist/agentic/guides/modules/customer_groups/domain-commands.md +11 -0
  65. package/dist/agentic/guides/modules/customer_groups/entities.md +13 -0
  66. package/dist/agentic/guides/modules/customer_groups/events.md +17 -0
  67. package/dist/agentic/guides/modules/customer_groups/exact-override-targets.md +27 -0
  68. package/dist/agentic/guides/modules/customer_groups/index.md +26 -0
  69. package/dist/agentic/guides/modules/customer_groups/owned-contract-module-metadata.md +11 -0
  70. package/dist/agentic/guides/modules/customer_groups/setup.md +11 -0
  71. package/dist/agentic/guides/modules/customer_groups/umes-contributions.md +13 -0
  72. package/dist/agentic/guides/modules/customer_groups/umes-hosts.md +21 -0
  73. package/dist/agentic/guides/modules/customers/acl-features.md +2 -1
  74. package/dist/agentic/guides/modules/customers/active-extension-bindings.md +8 -7
  75. package/dist/agentic/guides/modules/customers/contribution-resolutions.md +1 -0
  76. package/dist/agentic/guides/modules/customers/domain-commands.md +1 -0
  77. package/dist/agentic/guides/modules/customers/entities.md +1 -0
  78. package/dist/agentic/guides/modules/customers/events.md +2 -1
  79. package/dist/agentic/guides/modules/customers/exact-override-targets.md +2 -0
  80. package/dist/agentic/guides/modules/customers/index.md +3 -3
  81. package/dist/agentic/guides/modules/customers/umes-contributions.md +1 -0
  82. package/dist/agentic/guides/modules/customers/umes-hosts.md +3 -0
  83. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  84. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  85. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  86. package/dist/agentic/guides/modules/devices/index.md +1 -1
  87. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  88. package/dist/agentic/guides/modules/directory/active-extension-bindings.md +1 -1
  89. package/dist/agentic/guides/modules/directory/index.md +1 -1
  90. package/dist/agentic/guides/modules/documents/index.md +1 -1
  91. package/dist/agentic/guides/modules/entities/active-extension-bindings.md +1 -1
  92. package/dist/agentic/guides/modules/entities/index.md +1 -1
  93. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  94. package/dist/agentic/guides/modules/events/index.md +1 -1
  95. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  96. package/dist/agentic/guides/modules/forms/acl-features.md +16 -0
  97. package/dist/agentic/guides/modules/forms/active-extension-bindings.md +14 -0
  98. package/dist/agentic/guides/modules/forms/backend-pages.md +17 -0
  99. package/dist/agentic/guides/modules/forms/contribution-resolutions.md +21 -0
  100. package/dist/agentic/guides/modules/forms/di-registrations-rich.md +27 -0
  101. package/dist/agentic/guides/modules/forms/di-service-tokens.md +19 -0
  102. package/dist/agentic/guides/modules/forms/domain-commands.md +30 -0
  103. package/dist/agentic/guides/modules/forms/encryption.md +11 -0
  104. package/dist/agentic/guides/modules/forms/entities.md +21 -0
  105. package/dist/agentic/guides/modules/forms/events.md +28 -0
  106. package/dist/agentic/guides/modules/forms/exact-override-targets.md +59 -0
  107. package/dist/agentic/guides/modules/forms/frontend-pages.md +16 -0
  108. package/dist/agentic/guides/modules/forms/index.md +28 -0
  109. package/dist/agentic/guides/modules/forms/owned-contract-module-metadata.md +11 -0
  110. package/dist/agentic/guides/modules/forms/setup.md +11 -0
  111. package/dist/agentic/guides/modules/forms/umes-contributions.md +21 -0
  112. package/dist/agentic/guides/modules/forms/umes-hosts.md +43 -0
  113. package/dist/agentic/guides/modules/forms/workers.md +12 -0
  114. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  115. package/dist/agentic/guides/modules/generators/index.md +1 -1
  116. package/dist/agentic/guides/modules/inbox_ops/active-extension-bindings.md +1 -1
  117. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  118. package/dist/agentic/guides/modules/integrations/di-registrations-rich.md +8 -8
  119. package/dist/agentic/guides/modules/integrations/di-service-tokens.md +4 -4
  120. package/dist/agentic/guides/modules/integrations/exact-override-targets.md +8 -8
  121. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  122. package/dist/agentic/guides/modules/messages/index.md +1 -1
  123. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  124. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  125. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  126. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  127. package/dist/agentic/guides/modules/phone_calls/index.md +1 -1
  128. package/dist/agentic/guides/modules/planner/active-extension-bindings.md +1 -1
  129. package/dist/agentic/guides/modules/planner/index.md +1 -1
  130. package/dist/agentic/guides/modules/portal/index.md +1 -1
  131. package/dist/agentic/guides/modules/progress/index.md +1 -1
  132. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  133. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  134. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  135. package/dist/agentic/guides/modules/resources/active-extension-bindings.md +1 -1
  136. package/dist/agentic/guides/modules/resources/index.md +1 -1
  137. package/dist/agentic/guides/modules/sales/active-extension-bindings.md +1 -1
  138. package/dist/agentic/guides/modules/sales/exact-override-targets.md +4 -4
  139. package/dist/agentic/guides/modules/sales/index.md +1 -1
  140. package/dist/agentic/guides/modules/sales/setup.md +1 -1
  141. package/dist/agentic/guides/modules/scheduler/exact-override-targets.md +1 -1
  142. package/dist/agentic/guides/modules/scheduler/index.md +1 -1
  143. package/dist/agentic/guides/modules/scheduler/workers.md +1 -1
  144. package/dist/agentic/guides/modules/search/index.md +1 -1
  145. package/dist/agentic/guides/modules/security/index.md +1 -1
  146. package/dist/agentic/guides/modules/seeds/index.md +1 -1
  147. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  148. package/dist/agentic/guides/modules/sso/index.md +1 -1
  149. package/dist/agentic/guides/modules/staff/active-extension-bindings.md +1 -1
  150. package/dist/agentic/guides/modules/staff/index.md +1 -1
  151. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  152. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  153. package/dist/agentic/guides/modules/sync_excel/active-extension-bindings.md +11 -0
  154. package/dist/agentic/guides/modules/sync_excel/index.md +3 -2
  155. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  156. package/dist/agentic/guides/modules/telemetry/index.md +1 -1
  157. package/dist/agentic/guides/modules/tillio/index.md +1 -1
  158. package/dist/agentic/guides/modules/translations/index.md +1 -1
  159. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  160. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  161. package/dist/agentic/guides/modules/wms/active-extension-bindings.md +1 -1
  162. package/dist/agentic/guides/modules/wms/contribution-resolutions.md +3 -0
  163. package/dist/agentic/guides/modules/wms/di-registrations-rich.md +8 -8
  164. package/dist/agentic/guides/modules/wms/exact-override-targets.md +11 -8
  165. package/dist/agentic/guides/modules/wms/index.md +3 -3
  166. package/dist/agentic/guides/modules/wms/umes-contributions.md +3 -0
  167. package/dist/agentic/guides/modules/workflows/active-extension-bindings.md +3 -3
  168. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  169. package/dist/agentic/guides/reference-module-facts.json +1 -1
  170. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +73 -1
  171. package/dist/agentic/guides/upstream/manifest.json +2 -2
  172. package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +1 -1
  173. package/dist/bin.js +2 -2
  174. package/dist/bin.js.map +2 -2
  175. package/dist/lib/db/commands.js +84 -5
  176. package/dist/lib/db/commands.js.map +2 -2
  177. package/dist/lib/db/encryption-backfill-check.js +112 -0
  178. package/dist/lib/db/encryption-backfill-check.js.map +7 -0
  179. package/dist/lib/db/migration-reindex.js +2 -1
  180. package/dist/lib/db/migration-reindex.js.map +2 -2
  181. package/dist/lib/events-single-delivery.js.map +2 -2
  182. package/dist/lib/flush-before-exit.js +19 -0
  183. package/dist/lib/flush-before-exit.js.map +7 -0
  184. package/dist/lib/generate-watch-structure.js +113 -19
  185. package/dist/lib/generate-watch-structure.js.map +3 -3
  186. package/dist/lib/generators/module-registry.js +4 -2
  187. package/dist/lib/generators/module-registry.js.map +2 -2
  188. package/dist/lib/generators/web-research-adapters.js +32 -11
  189. package/dist/lib/generators/web-research-adapters.js.map +2 -2
  190. package/dist/lib/telemetry-init.js +69 -11
  191. package/dist/lib/telemetry-init.js.map +3 -3
  192. package/dist/lib/testing/integration.js +29 -2
  193. package/dist/lib/testing/integration.js.map +2 -2
  194. package/package.json +7 -6
  195. package/src/bin.ts +4 -4
  196. package/src/lib/__tests__/flush-before-exit.test.ts +32 -0
  197. package/src/lib/__tests__/generate-watch-structure.test.ts +261 -3
  198. package/src/lib/__tests__/telemetry-init.test.ts +89 -0
  199. package/src/lib/db/__tests__/commands.test.ts +43 -0
  200. package/src/lib/db/__tests__/dynamic-import-specifier.test.ts +36 -0
  201. package/src/lib/db/__tests__/encryption-backfill-check.test.ts +146 -0
  202. package/src/lib/db/__tests__/migration-reindex.test.ts +48 -3
  203. package/src/lib/db/commands.ts +113 -10
  204. package/src/lib/db/encryption-backfill-check.ts +163 -0
  205. package/src/lib/db/migration-reindex.ts +2 -1
  206. package/src/lib/events-single-delivery.ts +5 -2
  207. package/src/lib/flush-before-exit.ts +39 -0
  208. package/src/lib/generate-watch-structure.ts +134 -19
  209. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +14 -3
  210. package/src/lib/generators/__tests__/module-package-sources.test.ts +2 -2
  211. package/src/lib/generators/__tests__/registry-variant-parity.test.ts +16 -0
  212. package/src/lib/generators/__tests__/web-research-adapters.test.ts +141 -0
  213. package/src/lib/generators/module-registry.ts +2 -0
  214. package/src/lib/generators/web-research-adapters.ts +41 -20
  215. package/src/lib/telemetry-init.ts +68 -11
  216. package/src/lib/testing/__tests__/integration.test.ts +26 -0
  217. package/src/lib/testing/integration.ts +32 -2
@@ -12,6 +12,9 @@
12
12
  | search:wms:product_inventory_profile | module:wms:product_inventory_profile @wms | capability-only | — | [search.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/search.ts) |
13
13
  | search:wms:warehouse_location | module:wms:warehouse_location @wms | capability-only | — | [search.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/search.ts) |
14
14
  | search:wms:warehouse | module:wms:warehouse @wms | capability-only | — | [search.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/search.ts) |
15
+ | wms:invalidate-availability-cache-policy | wildcard:availability.policy.* | wildcard | — | [subscribers/invalidate-availability-cache-policy.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-availability-cache-policy.ts) |
16
+ | wms:invalidate-availability-cache-profile | wildcard:wms.inventory_profile.* | wildcard | — | [subscribers/invalidate-availability-cache-profile.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-availability-cache-profile.ts) |
17
+ | wms:invalidate-availability-cache | wildcard:wms.inventory_balance.* | wildcard | — | [subscribers/invalidate-availability-cache.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-availability-cache.ts) |
15
18
  | wms:invalidate-enricher-cache-balance | wildcard:wms.inventory_balance.* | wildcard | — | [subscribers/invalidate-enricher-cache-balance.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-enricher-cache-balance.ts) |
16
19
  | wms:invalidate-enricher-cache-inventory | wildcard:wms.inventory.* | wildcard | — | [subscribers/invalidate-enricher-cache-inventory.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-enricher-cache-inventory.ts) |
17
20
  | wms:invalidate-enricher-cache-movement | wildcard:wms.inventory_movement.* | wildcard | — | [subscribers/invalidate-enricher-cache-movement.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-enricher-cache-movement.ts) |
@@ -6,13 +6,13 @@
6
6
 
7
7
  | Token | Kind | Lifetime | Injection | Provider | Source |
8
8
  |---|---|---|---|---|---|
9
- | InventoryBalance | value | — | — | — | [di.ts:21](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L21) |
10
- | InventoryLot | value | — | — | — | [di.ts:20](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L20) |
11
- | InventoryMovement | value | — | — | — | [di.ts:23](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L23) |
12
- | InventoryReservation | value | — | — | — | [di.ts:22](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L22) |
13
- | ProductInventoryProfile | value | — | — | — | [di.ts:19](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L19) |
14
- | Warehouse | value | — | — | — | [di.ts:16](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L16) |
15
- | WarehouseLocation | value | — | — | — | [di.ts:18](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L18) |
16
- | WarehouseZone | value | — | — | — | [di.ts:17](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L17) |
9
+ | InventoryBalance | value | — | — | — | [di.ts:23](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L23) |
10
+ | InventoryLot | value | — | — | — | [di.ts:22](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L22) |
11
+ | InventoryMovement | value | — | — | — | [di.ts:25](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L25) |
12
+ | InventoryReservation | value | — | — | — | [di.ts:24](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L24) |
13
+ | ProductInventoryProfile | value | — | — | — | [di.ts:21](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L21) |
14
+ | Warehouse | value | — | — | — | [di.ts:18](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L18) |
15
+ | WarehouseLocation | value | — | — | — | [di.ts:20](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L20) |
16
+ | WarehouseZone | value | — | — | — | [di.ts:19](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L19) |
17
17
 
18
18
  <!-- end module facts section: wms/di-registrations-rich -->
@@ -17,17 +17,20 @@
17
17
  | acl | `overrides.acl.features["wms.receive_inventory"]` | disable-replace | aclFeatures:wms.receive_inventory | [acl.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/acl.ts) |
18
18
  | acl | `overrides.acl.features["wms.view"]` | disable-replace | aclFeatures:wms.view | [acl.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/acl.ts) |
19
19
  | cli | `overrides.cli["verify-balances"]` | disable-replace | cliCommands:verify-balances | [cli.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/cli.ts) |
20
- | di | `overrides.di["InventoryBalance"]` | disable-replace | ownedContracts.di-registration:InventoryBalance | [di.ts:21](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L21) |
21
- | di | `overrides.di["InventoryLot"]` | disable-replace | ownedContracts.di-registration:InventoryLot | [di.ts:20](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L20) |
22
- | di | `overrides.di["InventoryMovement"]` | disable-replace | ownedContracts.di-registration:InventoryMovement | [di.ts:23](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L23) |
23
- | di | `overrides.di["InventoryReservation"]` | disable-replace | ownedContracts.di-registration:InventoryReservation | [di.ts:22](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L22) |
24
- | di | `overrides.di["ProductInventoryProfile"]` | disable-replace | ownedContracts.di-registration:ProductInventoryProfile | [di.ts:19](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L19) |
25
- | di | `overrides.di["Warehouse"]` | disable-replace | ownedContracts.di-registration:Warehouse | [di.ts:16](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L16) |
26
- | di | `overrides.di["WarehouseLocation"]` | disable-replace | ownedContracts.di-registration:WarehouseLocation | [di.ts:18](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L18) |
27
- | di | `overrides.di["WarehouseZone"]` | disable-replace | ownedContracts.di-registration:WarehouseZone | [di.ts:17](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L17) |
20
+ | di | `overrides.di["InventoryBalance"]` | disable-replace | ownedContracts.di-registration:InventoryBalance | [di.ts:23](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L23) |
21
+ | di | `overrides.di["InventoryLot"]` | disable-replace | ownedContracts.di-registration:InventoryLot | [di.ts:22](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L22) |
22
+ | di | `overrides.di["InventoryMovement"]` | disable-replace | ownedContracts.di-registration:InventoryMovement | [di.ts:25](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L25) |
23
+ | di | `overrides.di["InventoryReservation"]` | disable-replace | ownedContracts.di-registration:InventoryReservation | [di.ts:24](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L24) |
24
+ | di | `overrides.di["ProductInventoryProfile"]` | disable-replace | ownedContracts.di-registration:ProductInventoryProfile | [di.ts:21](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L21) |
25
+ | di | `overrides.di["Warehouse"]` | disable-replace | ownedContracts.di-registration:Warehouse | [di.ts:18](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L18) |
26
+ | di | `overrides.di["WarehouseLocation"]` | disable-replace | ownedContracts.di-registration:WarehouseLocation | [di.ts:20](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L20) |
27
+ | di | `overrides.di["WarehouseZone"]` | disable-replace | ownedContracts.di-registration:WarehouseZone | [di.ts:19](../../../../node_modules/@open-mercato/core/src/modules/wms/di.ts#L19) |
28
28
  | enrichers | `overrides.enrichers["wms.catalog-product-inventory"]` | disable-replace | extensionSurfaces.contributions:wms.catalog-product-inventory | [data/enrichers.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/data/enrichers.ts) |
29
29
  | enrichers | `overrides.enrichers["wms.catalog-variant-inventory"]` | disable-replace | extensionSurfaces.contributions:wms.catalog-variant-inventory | [data/enrichers.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/data/enrichers.ts) |
30
30
  | enrichers | `overrides.enrichers["wms.sales-order-inventory"]` | disable-replace | extensionSurfaces.contributions:wms.sales-order-inventory | [data/enrichers.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/data/enrichers.ts) |
31
+ | events | `overrides.events.subscribers["wms:invalidate-availability-cache"]` | disable-replace | extensionSurfaces.contributions:wms:invalidate-availability-cache | [subscribers/invalidate-availability-cache.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-availability-cache.ts) |
32
+ | events | `overrides.events.subscribers["wms:invalidate-availability-cache-policy"]` | disable-replace | extensionSurfaces.contributions:wms:invalidate-availability-cache-policy | [subscribers/invalidate-availability-cache-policy.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-availability-cache-policy.ts) |
33
+ | events | `overrides.events.subscribers["wms:invalidate-availability-cache-profile"]` | disable-replace | extensionSurfaces.contributions:wms:invalidate-availability-cache-profile | [subscribers/invalidate-availability-cache-profile.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-availability-cache-profile.ts) |
31
34
  | events | `overrides.events.subscribers["wms:invalidate-enricher-cache-balance"]` | disable-replace | extensionSurfaces.contributions:wms:invalidate-enricher-cache-balance | [subscribers/invalidate-enricher-cache-balance.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-enricher-cache-balance.ts) |
32
35
  | events | `overrides.events.subscribers["wms:invalidate-enricher-cache-inventory"]` | disable-replace | extensionSurfaces.contributions:wms:invalidate-enricher-cache-inventory | [subscribers/invalidate-enricher-cache-inventory.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-enricher-cache-inventory.ts) |
33
36
  | events | `overrides.events.subscribers["wms:invalidate-enricher-cache-movement"]` | disable-replace | extensionSurfaces.contributions:wms:invalidate-enricher-cache-movement | [subscribers/invalidate-enricher-cache-movement.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-enricher-cache-movement.ts) |
@@ -1,5 +1,5 @@
1
1
  # wms — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.8.1-develop.7297.1.67605e74f9; core 0.8.1-develop.7297.1.67605e74f9 — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.8.1-develop.7441.1.b42958b98c; core 0.8.1-develop.7441.1.b42958b98c — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/core/src/modules/wms
4
4
 
5
5
  ## Sections
@@ -11,13 +11,13 @@ Source root: node_modules/@open-mercato/core/src/modules/wms
11
11
  - [Search entities](search-entities.md) — ~1 KB — Generated search entities facts for wms.
12
12
  - [Host extension points](host-extension-points.md) — ~1 KB — Generated host extension points facts for wms.
13
13
  - [UMES hosts](umes-hosts.md) — ~32 KB — Generated umes hosts facts for wms.
14
- - [UMES contributions](umes-contributions.md) — ~10 KB — Generated umes contributions facts for wms.
14
+ - [UMES contributions](umes-contributions.md) — ~11 KB — Generated umes contributions facts for wms.
15
15
  - [Active extension bindings](active-extension-bindings.md) — ~1 KB — Generated active extension bindings facts for wms.
16
16
  - [Contribution resolutions](contribution-resolutions.md) — ~8 KB — Generated contribution resolutions facts for wms.
17
17
  - [UMES diagnostics](umes-diagnostics.md) — ~1 KB — Generated umes diagnostics facts for wms.
18
18
  - [Notifications](notifications.md) — ~1 KB — Generated notifications facts for wms.
19
19
  - [CLI commands](cli-commands.md) — ~1 KB — Generated cli commands facts for wms.
20
- - [Exact override targets](exact-override-targets.md) — ~15 KB — Generated exact override targets facts for wms.
20
+ - [Exact override targets](exact-override-targets.md) — ~16 KB — Generated exact override targets facts for wms.
21
21
  - [Owned contract — module metadata](owned-contract-module-metadata.md) — ~1 KB — Generated owned contract — module metadata facts for wms.
22
22
  - [Domain commands](domain-commands.md) — ~4 KB — Generated domain commands facts for wms.
23
23
  - [Setup](setup.md) — ~1 KB — Generated setup facts for wms.
@@ -20,6 +20,9 @@
20
20
  | search:wms:product_inventory_profile | specialized-registry | wms:product_inventory_profile | — | registry=search; registryId=wms:product_inventory_profile; specialistRoute=search | fact-ref | [search.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/search.ts) |
21
21
  | search:wms:warehouse | specialized-registry | wms:warehouse | — | registry=search; registryId=wms:warehouse; specialistRoute=search | fact-ref | [search.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/search.ts) |
22
22
  | search:wms:warehouse_location | specialized-registry | wms:warehouse_location | — | registry=search; registryId=wms:warehouse_location; specialistRoute=search | fact-ref | [search.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/search.ts) |
23
+ | wms:invalidate-availability-cache | subscriber | wms.inventory_balance.* | before-or-after | event=wms.inventory_balance.*; persistent=false; subscriberId=wms:invalidate-availability-cache; sync=true | pattern | [subscribers/invalidate-availability-cache.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-availability-cache.ts) |
24
+ | wms:invalidate-availability-cache-policy | subscriber | availability.policy.* | before-or-after | event=availability.policy.*; persistent=false; subscriberId=wms:invalidate-availability-cache-policy; sync=true | pattern | [subscribers/invalidate-availability-cache-policy.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-availability-cache-policy.ts) |
25
+ | wms:invalidate-availability-cache-profile | subscriber | wms.inventory_profile.* | before-or-after | event=wms.inventory_profile.*; persistent=false; subscriberId=wms:invalidate-availability-cache-profile; sync=true | pattern | [subscribers/invalidate-availability-cache-profile.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-availability-cache-profile.ts) |
23
26
  | wms:invalidate-enricher-cache-balance | subscriber | wms.inventory_balance.* | before-or-after | event=wms.inventory_balance.*; persistent=false; subscriberId=wms:invalidate-enricher-cache-balance; sync=true | pattern | [subscribers/invalidate-enricher-cache-balance.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-enricher-cache-balance.ts) |
24
27
  | wms:invalidate-enricher-cache-inventory | subscriber | wms.inventory.* | before-or-after | event=wms.inventory.*; persistent=false; subscriberId=wms:invalidate-enricher-cache-inventory; sync=true | pattern | [subscribers/invalidate-enricher-cache-inventory.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-enricher-cache-inventory.ts) |
25
28
  | wms:invalidate-enricher-cache-movement | subscriber | wms.inventory_movement.* | before-or-after | event=wms.inventory_movement.*; persistent=false; subscriberId=wms:invalidate-enricher-cache-movement; sync=true | pattern | [subscribers/invalidate-enricher-cache-movement.ts](../../../../node_modules/@open-mercato/core/src/modules/wms/subscribers/invalidate-enricher-cache-movement.ts) |
@@ -6,9 +6,9 @@
6
6
 
7
7
  | Activation | Kind | Host | Contribution kinds | Phases | Bridge | Source |
8
8
  |---|---|---|---|---|---|---|
9
- | entity:workflows.definition_draft:mutation-guard | mutation-guard | entity:workflows.definition_draft @workflows | mutation-guard | — | — | [api/definitions/[id]/draft/route.ts:224](../../../../node_modules/@open-mercato/core/src/modules/workflows/api/definitions/[id]/draft/route.ts#L224) |
10
- | entity:workflows.definition:mutation-guard | mutation-guard | entity:workflows.definition @workflows | mutation-guard | — | — | [api/definitions/[id]/customize/route.ts:65](../../../../node_modules/@open-mercato/core/src/modules/workflows/api/definitions/[id]/customize/route.ts#L65) |
11
- | entity:workflows.instance:mutation-guard | mutation-guard | entity:workflows.instance @workflows | mutation-guard | — | — | [api/instances/[id]/context/route.ts:104](../../../../node_modules/@open-mercato/core/src/modules/workflows/api/instances/[id]/context/route.ts#L104) |
9
+ | entity:workflows.definition_draft:mutation-guard | mutation-guard | entity:workflows.definition_draft @workflows | mutation-guard | — | — | [api/definitions/[id]/draft/route.ts:227](../../../../node_modules/@open-mercato/core/src/modules/workflows/api/definitions/[id]/draft/route.ts#L227) |
10
+ | entity:workflows.definition:mutation-guard | mutation-guard | entity:workflows.definition @workflows | mutation-guard | — | — | [api/definitions/[id]/customize/route.ts:67](../../../../node_modules/@open-mercato/core/src/modules/workflows/api/definitions/[id]/customize/route.ts#L67) |
11
+ | entity:workflows.instance:mutation-guard | mutation-guard | entity:workflows.instance @workflows | mutation-guard | — | — | [api/instances/[id]/context/route.ts:105](../../../../node_modules/@open-mercato/core/src/modules/workflows/api/instances/[id]/context/route.ts#L105) |
12
12
  | widget-spot:data-table:workflows.tasks.list:row-actions:widget-injection-consumer | widget-injection-consumer | widget-spot:data-table:workflows.tasks.list:row-actions @workflows | widget, data-table, crud-form | — | hosts:tasksTable.rowActions | [extension-points.ts](../../../../node_modules/@open-mercato/core/src/modules/workflows/extension-points.ts) |
13
13
 
14
14
  <!-- end module facts section: workflows/active-extension-bindings -->
@@ -1,5 +1,5 @@
1
1
  # workflows — module facts (generated, do not edit)
2
- <!-- generated from @open-mercato/core 0.8.1-develop.7297.1.67605e74f9; core 0.8.1-develop.7297.1.67605e74f9 — R1 staleness stamp -->
2
+ <!-- generated from @open-mercato/core 0.8.1-develop.7441.1.b42958b98c; core 0.8.1-develop.7441.1.b42958b98c — R1 staleness stamp -->
3
3
  Source root: node_modules/@open-mercato/core/src/modules/workflows
4
4
 
5
5
  ## Sections
@@ -1185,7 +1185,7 @@
1185
1185
  "facts": {
1186
1186
  "title": "Example Module",
1187
1187
  "description": "Demonstrates pages, APIs, entities and DI.",
1188
- "coreVersion": "0.8.1-develop.7297.1.67605e74f9",
1188
+ "coreVersion": "0.8.1-develop.7441.1.b42958b98c",
1189
1189
  "sourcePackage": null,
1190
1190
  "sourceVersion": null,
1191
1191
  "sourceRoot": "src/modules/example",
@@ -102,7 +102,7 @@ These exported types are consumed by module developers. Required fields MUST NOT
102
102
  - `WorkerMeta`: `queue` — MUST NOT remove
103
103
  - `TelemetryProvider` (`@open-mercato/telemetry`): `name`, `supports`, `start`, `shutdown`, `runInSpan`, `activeSpan`, `activeTraceContext`, `inject`, `runInRemoteSpan`, `emitLog`, `recordMetric` — MUST NOT remove. `reportError?()` was added 2026-09-08 as an additive OPTIONAL method (see [spec](.ai/specs/2026-09-08-error-reporting-policy.md)); it **MUST stay optional** — third parties implement this interface and a required method would break every existing provider. The facade calls it as `provider.reportError?.(…)` *in addition to* the span/log/metric path, so a provider that omits it loses nothing.
104
104
  - `ReportErrorContext` (`@open-mercato/telemetry`) and the `reportError` context of `TelemetryRuntime` (`@open-mercato/shared/lib/telemetry/runtime`): `module?` and `attributes?` — MUST NOT remove. `code?: string` was added 2026-09-08 as an additive optional field carrying the enumerated `module.reason` fingerprint the backend groups on; it MUST stay optional so existing callers (the API dispatcher, the CRUD factory) keep compiling and keep emitting exactly what they emit today. `om.errors` gains an `error.code` label only when a caller supplies one — the same attribute name the span and the log record use, so one query works against all three.
105
- - `RefreshCredentialsInput` (communication_channels hub): `channelId`, `credentials`, `scope` — MUST NOT remove. `oauthClient?` was added 2026-05-27 as an additive optional field (see [Spec A](.ai/specs/implemented/2026-05-27-email-integration-inbound-reliability-and-threading.md)). The legacy `credentials._client` read path in the Gmail adapter is **deprecated and slated for removal in the next minor release** — pass OAuth client config via `RefreshCredentialsInput.oauthClient` instead.
105
+ - `RefreshCredentialsInput` (communication_channels hub): `channelId`, `credentials`, `scope` — MUST NOT remove. `oauthClient?` was added 2026-05-27 as an additive optional field (see [Spec A](.ai/specs/implemented/2026-05-27-email-integration-inbound-reliability-and-threading.md)). The legacy `credentials._client` read path in the Gmail adapter was **removed** (see #3828 / UPGRADE_NOTES) — OAuth client config MUST be passed via `RefreshCredentialsInput.oauthClient`.
106
106
  - `OAuthClientConfig` (communication_channels hub): added 2026-05-27 with `clientId` required; optional `clientSecret`, `tenantId`, `scopes`. New optional fields may be added; required `clientId` MUST NOT be removed.
107
107
  - `BackendChromePayload`: `groups`, `settingsSections`, `settingsPathPrefixes`, `profileSections`, `profilePathPrefixes`, `grantedFeatures`, `roles` — MUST NOT remove. `currentOrganization?` (`BackendChromeCurrentOrganization | null`) was added 2026-07-30 as an additive optional field (see [spec](.ai/specs/2026-07-30-backend-chrome-current-organization.md)); it is `null` under an all-organizations selection, when no organization is in scope, and when the lookup fails, so consumers MUST treat `null` as "unknown" rather than "no organization". `brand?` is **unchanged** and remains the branding channel — it populates only when the organization has a `logoUrl`, and `currentOrganization` does not supersede it.
108
108
 
@@ -360,6 +360,29 @@ Files in `apps/mercato/.mercato/generated/` are produced by the CLI generators.
360
360
 
361
361
  ---
362
362
 
363
+ ## Canonical API Interceptor Route Identity (2026-10-04)
364
+
365
+ API dispatch and API-interceptor selection now share the same matched-route identity.
366
+ This closes a policy bypass where a mixed-case or percent-encoded spelling dispatched
367
+ to a handler but did not select its exact `before` interceptor.
368
+
369
+ | Surface | Change | Classification |
370
+ |---------|--------|----------------|
371
+ | Function signature (`findApiRouteManifestMatch`) | New optional fourth parameter `request?: Request`; existing three-argument callers keep the same return value and matching behavior | ✓ ADDITIVE (optional parameter appended) |
372
+ | API route URLs and handler context | No route is added, removed, or renamed; handler `params` and request query semantics are unchanged | ✓ No contract change |
373
+ | Interceptor selection | Static segments use the matched manifest spelling, while matched dynamic/catch-all values stay concrete. Alternate spellings that already reached a route now run the same exact/prefix interceptors as its normal spelling; malformed percent encoding reaches no handler | ⚠️ Intentional security narrowing only (previous policy-bypass requests are denied or intercepted) |
374
+ | Request cloning/reconstruction | Dispatcher identity survives `Request.clone()` and `new Request(request.url, request)`. Unknown, forged, or evicted internal continuity metadata fails closed; unbound direct handlers retain legacy URL-derived matching | ✓ Behavior-preserving for valid dispatches; intentional security narrowing for forged metadata |
375
+ | Handler ordering | Authorization and interceptor `before`/`after` positions are unchanged | ✓ Behavior-preserving for normally spelled requests |
376
+
377
+ **Migration path for existing modules**: none. Keep authored `targetRoute` values.
378
+ Callers that dispatch through the framework catch-all receive canonical binding
379
+ automatically; direct three-argument matcher consumers continue to work unchanged.
380
+ Synthetic dispatchers should pass the exact request they invoke as argument four.
381
+ Request wrappers should retain headers when reconstructing the request, or pass an
382
+ authored route constant directly to the interceptor runner.
383
+
384
+ ---
385
+
363
386
  ## CRUD Foreign-Key Violations Answer 409 (2026-09-07)
364
387
 
365
388
  Deleting a user who had customised their sidebar failed on the `user_sidebar_preferences` / `sidebar_variants` foreign keys and surfaced as a generic `500`. The fix clears those rows in `auth.users.delete`, gives both FKs `ON DELETE CASCADE`, and teaches `makeCrudRoute` to recognise a Postgres foreign-key violation (SQLSTATE 23503). **All changes are additive** and pass the contract-surface checks above:
@@ -472,6 +495,24 @@ Spec: [`.ai/specs/2026-09-08-error-reporting-policy.md`](.ai/specs/2026-09-08-er
472
495
 
473
496
  **Volume note for operators.** Reported error *volume* rises where errors were previously only recorded: an integration that writes 115 error rows now also reports 115 errors, grouped by `error.code` at the backend. This is deliberate — see the spec's §S3 — and the controls are the collector's filtering/sampling and the backend's own quotas, not a framework switch.
474
497
 
498
+ ## Data Sync Retry, Resume and Run Again (2026-09-16)
499
+
500
+ [`.ai/specs/2026-09-16-data-sync-retry-resume-actions.md`](.ai/specs/2026-09-16-data-sync-retry-resume-actions.md) splits the Data Sync dashboard's single overloaded "Retry" button into three named operator actions — **Retry** (labelled **Resume** on a cancelled run), **Retry from the beginning**, and **Run again** — and states, on the run detail page, where a retry would resume. **All changes are additive** and pass the contract-surface checks above:
501
+
502
+ | Surface | Change | Classification |
503
+ |---------|--------|----------------|
504
+ | Type definitions (§2) | New exported types `ResumePoint` (a discriminated union of `none` / `noCommittedBatch` / `resumes`) and `ResumePointRun` | ✓ ADDITIVE (new types, nothing renamed — same shape as the `StartControlMap` addition before it) |
505
+ | Import paths (§4) | New module `data_sync/lib/resume-point.ts` exporting `resolveResumePoint`, `isRetryableRunStatus`, `ResumePoint`, `ResumePointRun` | ✓ ADDITIVE (new path; nothing moved or re-exported) |
506
+ | API route URLs (§7) | **No change.** `POST /api/data_sync/runs/[id]/retry` and `POST /api/data_sync/run` are untouched — no new request field, no new response field, no new status code and no new error code. The UI starts sending the already-accepted `fromBeginning: true` for the first time, and reads `cursor` / `initialCursor` / `batchesCompleted`, all of which `GET /api/data_sync/runs` and `GET /api/data_sync/runs/[id]` have returned since 2026-08-12 | ✓ n/a |
507
+ | Database schema (§8) | No change. No entity, column, migration or `.snapshot-open-mercato.json` update; no new run status — the vestigial `paused` status is left exactly as it was | ✓ n/a |
508
+ | Auto-discovery, function signatures, event IDs, widget spot IDs, DI names, ACL features, notification IDs, CLI commands, generated files | No change | ✓ n/a |
509
+
510
+ **Contract commitments**: the retry endpoint deliberately does **not** enforce `supportsStartControl('fullSync', entityType)` before honouring `fromBeginning: true`. [§ Data Sync Start Control Applicability (2026-09-02)](#data-sync-start-control-applicability-2026-09-02) already commits that the declaration "governs what the dashboard **offers**, never what the run API **accepts**", and `fromBeginning: true` on `retry` requests the identical thing as `fullSync: true` on `run` — a `null` start cursor — so the two siblings MUST answer identically. An adapter-declared restriction hides the from-the-beginning action in the UI and nothing more; a scripted or API client keeps the documented escape hatch. Making either endpoint strict is a breaking change under §7 and needs the deprecation protocol, not a UI spec.
511
+
512
+ `ResumePoint` also has **no `fromBeginning` variant, by design**. `api/runs/[id]/retry.ts` resolves a resumable retry as `previous.cursor ?? resolveStartCursor(...)`, and `resolveStartCursor` reads the shared `sync_cursors` row — state the run row does not carry — so a run that committed no batch of its own may still resume at a cursor an earlier run wrote. `noCommittedBatch` therefore means only "this run committed nothing" and MUST NOT be widened by a caller into a positional claim about where a retry starts. A future additive `retryStartCursor` response field is the only way to close that gap; it is out of scope here.
513
+
514
+ **Migration path for existing adapters and clients**: none. The module is new, every endpoint is unchanged, and an adapter or API client that ignores all of this behaves exactly as before.
515
+
475
516
  ## Customers Quick-Create Injection Spot Bridge (2026-09-16)
476
517
 
477
518
  [`.ai/specs/2026-09-16-customers-quick-create-injection-spot-bridge.md`](.ai/specs/2026-09-16-customers-quick-create-injection-spot-bridge.md) binds the sales document form's Person/Company quick-create dialogs to the customers module's declared `crud-form:customers.person` / `…company` hosts. The dialogs previously had no `injectionSpotId`, so `CrudForm` auto-derived `crud-form:customers.customer_entity` (§6, FROZEN) there instead.
@@ -484,3 +525,34 @@ Spec: [`.ai/specs/2026-09-08-error-reporting-policy.md`](.ai/specs/2026-09-08-er
484
525
  | Type definitions (§2) | New optional `CrudForm` prop `legacyInjectionSpotId?: string` | ✓ ADDITIVE |
485
526
 
486
527
  **Deprecation window.** `legacyInjectionSpotId` is scoped to these two call sites and intended for removal after at least one minor version (Deprecation Protocol step 1), tracked in the spec's Changelog and in `UPGRADE_NOTES.md`. No maintainer waiver was needed — nothing is removed by this change.
528
+
529
+ ## Atomic Auth Command Replay (2026-10-04)
530
+
531
+ [`.ai/specs/2026-07-28-protected-roles-and-audit-seam.md`](.ai/specs/2026-07-28-protected-roles-and-audit-seam.md) closes the auth undo/redo transaction, authorization, and membership-phantom gaps without changing existing command handlers by default.
532
+
533
+ | Surface | Change | Classification |
534
+ |---------|--------|----------------|
535
+ | Type definitions (`CommandHandler`, `CommandRuntimeContext`, `CommandExecuteResult`) | New optional `atomicReplay?: boolean` and `stabilizeReplay(...)` handler capabilities, optional `replayTransactionGuard` request guard, optional caller-owned `transactionLifetime`, and optional `replaySourceFinalized?: boolean` result signal. `TransactionLifetime` plus its observation/completion-registration helpers are new exports; lifetime creation/completion remains owned by `withAtomicFlush` | ✓ ADDITIVE (optional fields and exports; omitted handlers and contexts keep the prior flow) |
536
+ | `ActionLogService` method signatures | `log`, `claimForUndo`, `releaseUndoClaim`, and `markUndone` accept an optional trailing transactional `EntityManager`; new `claimForRedo` method | ✓ ADDITIVE (optional parameters appended and a new method; existing calls retain behavior) |
537
+ | `RbacService` | New `loadAclWithEntityManager`, `userHasAllFeaturesWithEntityManager`, and `getGrantedFeaturesWithEntityManager` methods bypass caches and bind every query to the supplied transaction | ✓ ADDITIVE (new methods; existing cache-backed methods are unchanged) |
538
+ | Auth command replay behavior | Auth user/role handlers opt in so source-log state, domain changes, undo trace or new redo log, and redo source finalization commit or roll back together. Before the request guard or feature interceptors run, `stabilizeReplay` discovers and seals one actor/target/destination footprint and locks API keys, users, roles, tenant/organization hierarchy, and authorization children in a total order using the same transaction | ⚠️ Intentional security hardening. A race that previously could deadlock or replay with stale authority is rejected with the existing domain `409`; successful, authorized replay keeps the same response shape |
539
+ | Action-log actor persistence and self replay | `actor_user_id` remains a UUID; new entries also store the canonical user or `api_key:<uuid>` subject in `context.actorSubject`. API-key list ownership, undo/redo ownership, exact-EM RBAC, locks, and latest replay queries use the canonical subject. Canonical history is selected before the legacy bare-UUID fallback, which additionally requires a coherent live `isApiKey`/`keyId`/`sub` tuple | ✓ Database-compatible additive context metadata; same-UUID user, malformed, or ambiguous ownership cannot shadow canonical key history and fails closed |
540
+ | Auth authorization-state writers | First-party `UserRole`, `UserAcl`, and `RoleAcl` writers in commands, setup, CLI, execution principals, SSO, and agent orchestration now share the canonical parent-lock protocol; SSO membership and grant rows commit together | ⚠️ Intentional concurrency hardening; no request, response, schema, import-path, event, feature, or CLI contract changes |
541
+ | Non-auth command replay | Handlers that omit `atomicReplay` retain the existing undo claim/release compensation and route-level redo finalization | ✓ Behavior-preserving |
542
+ | API routes, response schemas, database schema, event IDs, ACL feature IDs, DI names, CLI commands, generated files | No shape or identifier change | ✓ n/a |
543
+
544
+ **Migration path for existing modules**: none. Third-party commands remain on the established replay lifecycle unless they explicitly set `atomicReplay: true`. An opting-in handler must perform all replay database work through `ctx.transactionalEm`; when authorization depends on mutable domain rows it may add `stabilizeReplay` to acquire its complete footprint before guards and interceptors. The command bus supplies and owns the transaction by default. A caller deliberately composing replay inside an already-active `ctx.transactionalEm` must open that outer transaction with `withAtomicFlush(..., { transaction: true })`, obtain its exact `TransactionLifetime` inside the phase, and pass it as `ctx.transactionLifetime`; otherwise replay fails before mutation. Commit-only hooks and redo-finalization reporting follow the true outer completion. The capability does not claim that legacy non-opted-in action-log and domain writes are transactionally atomic.
545
+
546
+ ---
547
+
548
+ ## Organization Update Preserves Omitted Hierarchy Fields (2026-10-01)
549
+
550
+ `directory.organizations.update` treated an omitted `parentId` as `null` and an omitted `childIds` as `[]`, so a partial update — notably `PUT /api/directory/organization-branding`, which sends only logo fields — detached the organization from its parent and orphaned its children. Omitted hierarchy fields are now preserved:
551
+
552
+ | Surface | Change | Classification |
553
+ |---------|--------|----------------|
554
+ | Command behaviour (`directory.organizations.update`) | `parentId` is written and validated only when present in the input; children are reconciled only when `childIds` is present. Explicit `parentId: null` and `childIds: []` still clear. With `childIds` present and `parentId` omitted, listing the current parent answers `400 Child cannot equal parent` (it answered `400 Cannot assign ancestor as child`) | ⚠️ Behaviour change on a previously-corrupting path. A caller that omitted the fields to clear the hierarchy must send the explicit values. Regression-tested in `updateOrganization.partial-hierarchy.test.ts` and `TC-DIR-018` |
555
+ | API routes (`PUT /api/directory/organizations`, `PUT /api/directory/organization-branding`) | Same URLs, request schema (`organizationUpdateSchema` already declares both fields optional) and response shapes | ✓ No shape change |
556
+ | Database schema, event IDs, ACL features, DI names, CLI commands | No change | ✓ n/a |
557
+
558
+ **Migration path for existing modules**: send `parentId: null` / `childIds: []` where clearing is intended. Trees flattened before the upgrade are not repaired (see UPGRADE_NOTES.md).
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": 1,
3
- "generator": "@open-mercato/cli@0.8.1-develop.7297.1.67605e74f9",
3
+ "generator": "@open-mercato/cli@0.8.1-develop.7441.1.b42958b98c",
4
4
  "files": {
5
5
  "AGENTS.md": "9fdaca247f55c64f5966cc103265cfb03233e0532f58f14aff538ae18aa3555d",
6
- "BACKWARD_COMPATIBILITY.md": "7f7d93266be9dbf1a1e154df6d40efd6de4ffeea3a2d7b2822f3081d6395c272"
6
+ "BACKWARD_COMPATIBILITY.md": "853f6b5a0b88f8439d7644d463d5b27c2e917366472a1e1fc3ac4586ef47c859"
7
7
  }
8
8
  }
@@ -6,5 +6,5 @@ Load this reference when records contain PII, credentials, addresses, contact in
6
6
  2. In module `encryption.ts`, import `ModuleEncryptionMap` from `@open-mercato/shared/modules/encryption`, declare `defaultEncryptionMaps: ModuleEncryptionMap[] = [{ entityId: '<module>:<entity>', fields: [{ field: '<database_field>' }] }]`, then `export default defaultEncryptionMaps` for generated registry compatibility. The entries are field-rule objects, not string names. Use a sibling `hashField` only for deterministic equality lookup. Let `TenantDataEncryptionService` populate it during encrypted writes, and query it with `lookupHashCandidates(value)` from `@open-mercato/shared/lib/encryption/aes`; use `hashForLookup(value)` from the same import only when the installed write contract explicitly requires a single keyed value. Never use raw SHA-256 for low-entropy PII such as email or phone values.
7
7
  3. Import `findWithDecryption`, `findOneWithDecryption`, or `findAndCountWithDecryption` from `@open-mercato/shared/lib/encryption/find` and make a concrete call in every implemented direct sensitive-record read path; an unused import or encryption map alone is not a decrypted read. For `makeCrudRoute`, its `entityId` + `fields` factory QueryEngine owns list decryption and `list` has no `findAndCount` override key—do not insert a decryption helper as an unsupported option. Put trusted tenant/organization constraints in each direct helper query `where` **and** pass `{ tenantId, organizationId }` as the fifth-argument decryption scope. The scope selects keys; it does not authorize or scope the ORM query. Use a null/global scope only when the installed contract explicitly permits it. Audit detail/export/search/worker/CLI and other direct ORM paths.
8
8
  4. Keep secrets out of responses, logs, errors, events, snapshots, cache keys, search documents, vector sources, and test artifacts. Exclude encrypted source values from `search.ts` field policies and indexes; index only an explicitly approved hash-only sibling for exact equality. Never sort or fuzzy-filter ciphertext.
9
- 5. Seed/update encryption configuration through the supported command; never hand-roll KMS/AES. For every new or changed map, use an isolated initialized test tenant to reconcile the map, read back its registration, insert a fixture through the real write path, and assert the sensitive database columns are ciphertext before claiming runtime coverage.
9
+ 5. Seed/update encryption configuration through the supported command; never hand-roll KMS/AES. For every new or changed map, use an isolated initialized test tenant to reconcile the map, read back its registration, insert a fixture through the real write path, and assert the sensitive database columns are ciphertext before claiming runtime coverage. A fresh test tenant does not prove existing tenants are covered: maps are seeded only when a tenant is created, and nothing re-seeds tenants that already exist. When you add a map, or a field to an existing map, in a module that may already be deployed, ship a backfill migration in the same module: `this.addSql(buildEncryptionMapBackfillSql({ entityId, fields }))` from `@open-mercato/shared/lib/encryption/migration-backfill`. It inserts the map for every existing (tenant, organization) scope that already has active maps (`NOT EXISTS` guard) and appends missing fields to existing maps. Write the entity id and fields literally in the migration and leave `down()` empty. `yarn db:generate` warns when the local database shows an unbackfilled field. Plaintext rows written before the backfill are encrypted by `yarn mercato entities rotate-encryption-key --tenant <id>` without `--old-key`.
10
10
  6. Test authorized decryption, cross-scope denial, redaction, missing keys, export/search behavior, and cleanup/retention.
package/dist/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { isTelemetryBackendEnabled } from "@open-mercato/shared/lib/telemetry/runtime";
3
3
  import { resolveCliBootstrapMode } from "./lib/cli-bootstrap-mode.js";
4
- import { flushTelemetry } from "./lib/flush-telemetry.js";
4
+ import { flushBeforeExit } from "./lib/flush-before-exit.js";
5
5
  function assertNode24Runtime() {
6
6
  const detectedNodeVersion = process.versions.node;
7
7
  const majorVersion = Number.parseInt(detectedNodeVersion.split(".")[0] ?? "0", 10);
@@ -72,7 +72,7 @@ async function main() {
72
72
  }
73
73
  const { run } = await import("./mercato.js");
74
74
  const code = await run(process.argv);
75
- await flushTelemetry();
75
+ await flushBeforeExit();
76
76
  process.exit(code ?? 0);
77
77
  }
78
78
  main().catch((error) => {
package/dist/bin.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/bin.ts"],
4
- "sourcesContent": ["/**\n * CLI binary entry point for @open-mercato/cli package.\n *\n * Called from within a Next.js app directory as: yarn mercato <command>\n * Uses dynamic app resolution to find generated files at .mercato/generated/\n */\nimport { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { resolveCliBootstrapMode, type CliBootstrapMode } from './lib/cli-bootstrap-mode.js'\nimport { flushTelemetry } from './lib/flush-telemetry.js'\n// `run` is imported dynamically inside `main()` so telemetry can initialize\n// before the mercato entry (and its Postgres driver) loads \u2014 see main().\n\nfunction assertNode24Runtime(): void {\n const detectedNodeVersion = process.versions.node\n const majorVersion = Number.parseInt(detectedNodeVersion.split('.')[0] ?? '0', 10)\n if (majorVersion >= 24) {\n return\n }\n throw new Error(\n [\n 'Unsupported Node.js runtime.',\n `Cause: Detected Node ${detectedNodeVersion}, but Open Mercato requires Node 24.x.`,\n 'What to do: switch your shell to Node 24 (for example `nvm use 24`), run `yarn install`, then retry.',\n ].join(' '),\n )\n}\n\nasync function tryBootstrap(mode: Exclude<CliBootstrapMode, 'none'>): Promise<boolean> {\n try {\n // Use the CLI resolver to find the app directory (handles monorepo detection)\n const { createResolver } = await import('./lib/resolver.js')\n const resolver = createResolver()\n const appDir = resolver.getAppDir()\n\n if (mode === 'dev-supervisor') {\n const {\n canUseLightweightDevSupervisor,\n loadDevSupervisorManifest,\n registerDevSupervisorManifest,\n } = await import(\n './lib/dev-supervisor-manifest.js'\n )\n const manifest = loadDevSupervisorManifest(appDir)\n if (canUseLightweightDevSupervisor(manifest)) {\n registerDevSupervisorManifest(manifest)\n return true\n }\n }\n\n const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')\n const { registerCliModules } = await import('./mercato.js')\n const data = await bootstrapFromAppRoot(appDir)\n // Register CLI modules directly to avoid module resolution issues\n registerCliModules(data.modules)\n return true\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n // Check if the error is about missing generated files\n if (\n message.includes('dev-supervisor.generated.json') ||\n (\n message.includes('Cannot find module') &&\n (message.includes('/generated/') || message.includes('.generated') || message.includes('.mercato'))\n )\n ) {\n return false\n }\n // Re-throw other errors\n throw err\n }\n}\n\nasync function main(): Promise<void> {\n assertNode24Runtime()\n const bootstrapMode = resolveCliBootstrapMode(process.argv)\n\n if (bootstrapMode !== 'none') {\n // Load the app's `.env` BEFORE initTelemetry(): `run()` only dotenv-loads it\n // later (ensureEnvLoaded), which is too late \u2014 TELEMETRY_BACKEND set only in\n // `.env` would silently resolve to `noop` for worker/scheduler processes.\n // The loader touches only the resolver + dotenv (no `pg`), preserving the\n // instrumentation load-order guarantee below.\n const { loadAppEnv } = await import('./lib/load-env.js')\n await loadAppEnv()\n\n // Initialize telemetry BEFORE bootstrapping the app graph. Bootstrap and the\n // per-command handlers load MikroORM's Postgres driver \u2192 `pg`, and the\n // OpenTelemetry pg/undici auto-instrumentation only records spans for a\n // driver required AFTER the SDK has started. Registering here \u2014 ahead of any\n // app module \u2014 is what lets long-running worker/scheduler processes emit DB\n // spans (not just the bullmq-otel add/process envelope). The package itself\n // is not imported unless an explicit supported backend is selected.\n if (isTelemetryBackendEnabled()) {\n const { initTelemetry } = await import('@open-mercato/telemetry')\n await initTelemetry()\n }\n\n const bootstrapSucceeded = await tryBootstrap(bootstrapMode)\n if (!bootstrapSucceeded) {\n console.error('\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557')\n console.error('\u2551 Generated files not found! \u2551')\n console.error('\u2551 \u2551')\n console.error('\u2551 The CLI requires generated files to discover modules. \u2551')\n console.error('\u2551 Please run the following command first: \u2551')\n console.error('\u2551 \u2551')\n console.error('\u2551 yarn mercato generate \u2551')\n console.error('\u2551 \u2551')\n console.error('\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D')\n process.exit(1)\n }\n }\n\n // Dynamic import (not a top-level static import) so the mercato entry \u2014 and the\n // Postgres driver it pulls in \u2014 loads only after initTelemetry() above.\n const { run } = await import('./mercato.js')\n const code = await run(process.argv)\n // Flush spans/logs for commands that return (workers block forever and flush via\n // their own shutdown handler instead).\n await flushTelemetry()\n process.exit(code ?? 0)\n}\n\nmain().catch((error: unknown) => {\n if (error instanceof Error) {\n console.error(error.message)\n } else {\n console.error(error)\n }\n process.exit(1)\n})\n"],
5
- "mappings": "AAMA,SAAS,iCAAiC;AAC1C,SAAS,+BAAsD;AAC/D,SAAS,sBAAsB;AAI/B,SAAS,sBAA4B;AACnC,QAAM,sBAAsB,QAAQ,SAAS;AAC7C,QAAM,eAAe,OAAO,SAAS,oBAAoB,MAAM,GAAG,EAAE,CAAC,KAAK,KAAK,EAAE;AACjF,MAAI,gBAAgB,IAAI;AACtB;AAAA,EACF;AACA,QAAM,IAAI;AAAA,IACR;AAAA,MACE;AAAA,MACA,wBAAwB,mBAAmB;AAAA,MAC3C;AAAA,IACF,EAAE,KAAK,GAAG;AAAA,EACZ;AACF;AAEA,eAAe,aAAa,MAA2D;AACrF,MAAI;AAEF,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,mBAAmB;AAC3D,UAAM,WAAW,eAAe;AAChC,UAAM,SAAS,SAAS,UAAU;AAElC,QAAI,SAAS,kBAAkB;AAC7B,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,MACF,IAAI,MAAM,OACR,kCACF;AACA,YAAM,WAAW,0BAA0B,MAAM;AACjD,UAAI,+BAA+B,QAAQ,GAAG;AAC5C,sCAA8B,QAAQ;AACtC,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,kDAAkD;AAChG,UAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,cAAc;AAC1D,UAAM,OAAO,MAAM,qBAAqB,MAAM;AAE9C,uBAAmB,KAAK,OAAO;AAC/B,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAE/D,QACE,QAAQ,SAAS,+BAA+B,KAE9C,QAAQ,SAAS,oBAAoB,MACpC,QAAQ,SAAS,aAAa,KAAK,QAAQ,SAAS,YAAY,KAAK,QAAQ,SAAS,UAAU,IAEnG;AACA,aAAO;AAAA,IACT;AAEA,UAAM;AAAA,EACR;AACF;AAEA,eAAe,OAAsB;AACnC,sBAAoB;AACpB,QAAM,gBAAgB,wBAAwB,QAAQ,IAAI;AAE1D,MAAI,kBAAkB,QAAQ;AAM5B,UAAM,EAAE,WAAW,IAAI,MAAM,OAAO,mBAAmB;AACvD,UAAM,WAAW;AASjB,QAAI,0BAA0B,GAAG;AAC/B,YAAM,EAAE,cAAc,IAAI,MAAM,OAAO,yBAAyB;AAChE,YAAM,cAAc;AAAA,IACtB;AAEA,UAAM,qBAAqB,MAAM,aAAa,aAAa;AAC3D,QAAI,CAAC,oBAAoB;AACvB,cAAQ,MAAM,gaAAuE;AACrF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gaAAuE;AACrF,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAIA,QAAM,EAAE,IAAI,IAAI,MAAM,OAAO,cAAc;AAC3C,QAAM,OAAO,MAAM,IAAI,QAAQ,IAAI;AAGnC,QAAM,eAAe;AACrB,UAAQ,KAAK,QAAQ,CAAC;AACxB;AAEA,KAAK,EAAE,MAAM,CAAC,UAAmB;AAC/B,MAAI,iBAAiB,OAAO;AAC1B,YAAQ,MAAM,MAAM,OAAO;AAAA,EAC7B,OAAO;AACL,YAAQ,MAAM,KAAK;AAAA,EACrB;AACA,UAAQ,KAAK,CAAC;AAChB,CAAC;",
4
+ "sourcesContent": ["/**\n * CLI binary entry point for @open-mercato/cli package.\n *\n * Called from within a Next.js app directory as: yarn mercato <command>\n * Uses dynamic app resolution to find generated files at .mercato/generated/\n */\nimport { isTelemetryBackendEnabled } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { resolveCliBootstrapMode, type CliBootstrapMode } from './lib/cli-bootstrap-mode.js'\nimport { flushBeforeExit } from './lib/flush-before-exit.js'\n// `run` is imported dynamically inside `main()` so telemetry can initialize\n// before the mercato entry (and its Postgres driver) loads \u2014 see main().\n\nfunction assertNode24Runtime(): void {\n const detectedNodeVersion = process.versions.node\n const majorVersion = Number.parseInt(detectedNodeVersion.split('.')[0] ?? '0', 10)\n if (majorVersion >= 24) {\n return\n }\n throw new Error(\n [\n 'Unsupported Node.js runtime.',\n `Cause: Detected Node ${detectedNodeVersion}, but Open Mercato requires Node 24.x.`,\n 'What to do: switch your shell to Node 24 (for example `nvm use 24`), run `yarn install`, then retry.',\n ].join(' '),\n )\n}\n\nasync function tryBootstrap(mode: Exclude<CliBootstrapMode, 'none'>): Promise<boolean> {\n try {\n // Use the CLI resolver to find the app directory (handles monorepo detection)\n const { createResolver } = await import('./lib/resolver.js')\n const resolver = createResolver()\n const appDir = resolver.getAppDir()\n\n if (mode === 'dev-supervisor') {\n const {\n canUseLightweightDevSupervisor,\n loadDevSupervisorManifest,\n registerDevSupervisorManifest,\n } = await import(\n './lib/dev-supervisor-manifest.js'\n )\n const manifest = loadDevSupervisorManifest(appDir)\n if (canUseLightweightDevSupervisor(manifest)) {\n registerDevSupervisorManifest(manifest)\n return true\n }\n }\n\n const { bootstrapFromAppRoot } = await import('@open-mercato/shared/lib/bootstrap/dynamicLoader')\n const { registerCliModules } = await import('./mercato.js')\n const data = await bootstrapFromAppRoot(appDir)\n // Register CLI modules directly to avoid module resolution issues\n registerCliModules(data.modules)\n return true\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n // Check if the error is about missing generated files\n if (\n message.includes('dev-supervisor.generated.json') ||\n (\n message.includes('Cannot find module') &&\n (message.includes('/generated/') || message.includes('.generated') || message.includes('.mercato'))\n )\n ) {\n return false\n }\n // Re-throw other errors\n throw err\n }\n}\n\nasync function main(): Promise<void> {\n assertNode24Runtime()\n const bootstrapMode = resolveCliBootstrapMode(process.argv)\n\n if (bootstrapMode !== 'none') {\n // Load the app's `.env` BEFORE initTelemetry(): `run()` only dotenv-loads it\n // later (ensureEnvLoaded), which is too late \u2014 TELEMETRY_BACKEND set only in\n // `.env` would silently resolve to `noop` for worker/scheduler processes.\n // The loader touches only the resolver + dotenv (no `pg`), preserving the\n // instrumentation load-order guarantee below.\n const { loadAppEnv } = await import('./lib/load-env.js')\n await loadAppEnv()\n\n // Initialize telemetry BEFORE bootstrapping the app graph. Bootstrap and the\n // per-command handlers load MikroORM's Postgres driver \u2192 `pg`, and the\n // OpenTelemetry pg/undici auto-instrumentation only records spans for a\n // driver required AFTER the SDK has started. Registering here \u2014 ahead of any\n // app module \u2014 is what lets long-running worker/scheduler processes emit DB\n // spans (not just the bullmq-otel add/process envelope). The package itself\n // is not imported unless an explicit supported backend is selected.\n if (isTelemetryBackendEnabled()) {\n const { initTelemetry } = await import('@open-mercato/telemetry')\n await initTelemetry()\n }\n\n const bootstrapSucceeded = await tryBootstrap(bootstrapMode)\n if (!bootstrapSucceeded) {\n console.error('\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557')\n console.error('\u2551 Generated files not found! \u2551')\n console.error('\u2551 \u2551')\n console.error('\u2551 The CLI requires generated files to discover modules. \u2551')\n console.error('\u2551 Please run the following command first: \u2551')\n console.error('\u2551 \u2551')\n console.error('\u2551 yarn mercato generate \u2551')\n console.error('\u2551 \u2551')\n console.error('\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D')\n process.exit(1)\n }\n }\n\n // Dynamic import (not a top-level static import) so the mercato entry \u2014 and the\n // Postgres driver it pulls in \u2014 loads only after initTelemetry() above.\n const { run } = await import('./mercato.js')\n const code = await run(process.argv)\n // Flush the coalesced browser broadcasts, then spans/logs, for commands that\n // return (workers block forever and flush via their own shutdown handler instead).\n await flushBeforeExit()\n process.exit(code ?? 0)\n}\n\nmain().catch((error: unknown) => {\n if (error instanceof Error) {\n console.error(error.message)\n } else {\n console.error(error)\n }\n process.exit(1)\n})\n"],
5
+ "mappings": "AAMA,SAAS,iCAAiC;AAC1C,SAAS,+BAAsD;AAC/D,SAAS,uBAAuB;AAIhC,SAAS,sBAA4B;AACnC,QAAM,sBAAsB,QAAQ,SAAS;AAC7C,QAAM,eAAe,OAAO,SAAS,oBAAoB,MAAM,GAAG,EAAE,CAAC,KAAK,KAAK,EAAE;AACjF,MAAI,gBAAgB,IAAI;AACtB;AAAA,EACF;AACA,QAAM,IAAI;AAAA,IACR;AAAA,MACE;AAAA,MACA,wBAAwB,mBAAmB;AAAA,MAC3C;AAAA,IACF,EAAE,KAAK,GAAG;AAAA,EACZ;AACF;AAEA,eAAe,aAAa,MAA2D;AACrF,MAAI;AAEF,UAAM,EAAE,eAAe,IAAI,MAAM,OAAO,mBAAmB;AAC3D,UAAM,WAAW,eAAe;AAChC,UAAM,SAAS,SAAS,UAAU;AAElC,QAAI,SAAS,kBAAkB;AAC7B,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA;AAAA,MACF,IAAI,MAAM,OACR,kCACF;AACA,YAAM,WAAW,0BAA0B,MAAM;AACjD,UAAI,+BAA+B,QAAQ,GAAG;AAC5C,sCAA8B,QAAQ;AACtC,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,kDAAkD;AAChG,UAAM,EAAE,mBAAmB,IAAI,MAAM,OAAO,cAAc;AAC1D,UAAM,OAAO,MAAM,qBAAqB,MAAM;AAE9C,uBAAmB,KAAK,OAAO;AAC/B,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAE/D,QACE,QAAQ,SAAS,+BAA+B,KAE9C,QAAQ,SAAS,oBAAoB,MACpC,QAAQ,SAAS,aAAa,KAAK,QAAQ,SAAS,YAAY,KAAK,QAAQ,SAAS,UAAU,IAEnG;AACA,aAAO;AAAA,IACT;AAEA,UAAM;AAAA,EACR;AACF;AAEA,eAAe,OAAsB;AACnC,sBAAoB;AACpB,QAAM,gBAAgB,wBAAwB,QAAQ,IAAI;AAE1D,MAAI,kBAAkB,QAAQ;AAM5B,UAAM,EAAE,WAAW,IAAI,MAAM,OAAO,mBAAmB;AACvD,UAAM,WAAW;AASjB,QAAI,0BAA0B,GAAG;AAC/B,YAAM,EAAE,cAAc,IAAI,MAAM,OAAO,yBAAyB;AAChE,YAAM,cAAc;AAAA,IACtB;AAEA,UAAM,qBAAqB,MAAM,aAAa,aAAa;AAC3D,QAAI,CAAC,oBAAoB;AACvB,cAAQ,MAAM,gaAAuE;AACrF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gFAAsE;AACpF,cAAQ,MAAM,iFAAuE;AACrF,cAAQ,MAAM,gaAAuE;AACrF,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAIA,QAAM,EAAE,IAAI,IAAI,MAAM,OAAO,cAAc;AAC3C,QAAM,OAAO,MAAM,IAAI,QAAQ,IAAI;AAGnC,QAAM,gBAAgB;AACtB,UAAQ,KAAK,QAAQ,CAAC;AACxB;AAEA,KAAK,EAAE,MAAM,CAAC,UAAmB;AAC/B,MAAI,iBAAiB,OAAO;AAC1B,YAAQ,MAAM,MAAM,OAAO;AAAA,EAC7B,OAAO;AACL,YAAQ,MAAM,KAAK;AAAA,EACrB;AACA,UAAQ,KAAK,CAAC;AAChB,CAAC;",
6
6
  "names": []
7
7
  }
@@ -7,6 +7,11 @@ import { ReflectMetadataProvider } from "@mikro-orm/decorators/legacy";
7
7
  import { PostgreSqlDriver } from "@mikro-orm/postgresql";
8
8
  import { getSslConfig } from "@open-mercato/shared/lib/db/ssl";
9
9
  import { quotePostgresIdentifier } from "./identifiers.js";
10
+ import {
11
+ checkEncryptionMapBackfills,
12
+ isEncryptionBackfillCheckEnabled,
13
+ readDeclaredEncryptionMaps
14
+ } from "./encryption-backfill-check.js";
10
15
  import {
11
16
  collectQueryIndexReindexEntityTypes,
12
17
  isMigrationReindexEnabled,
@@ -50,11 +55,19 @@ function getClientUrl() {
50
55
  function sortModules(mods) {
51
56
  return mods.slice().sort((a, b) => a.id.localeCompare(b.id));
52
57
  }
53
- async function dynamicImportProvider(id) {
54
- if (process.platform === "win32" && /^[a-zA-Z]:[\\/]/.test(id)) {
55
- id = pathToFileURL(id).href;
58
+ function toImportableModuleSpecifier(id) {
59
+ if (id.startsWith("file:") || id.startsWith("data:") || id.startsWith("node:") || id.startsWith("http:") || id.startsWith("https:")) {
60
+ return id;
56
61
  }
57
- return import(id);
62
+ const isWindowsAbsolute = /^[a-zA-Z]:[\\/]/.test(id);
63
+ const isFilesystemRelative = id.startsWith("./") || id.startsWith("../") || id.startsWith(".\\") || id.startsWith("..\\") || /(^|[\\/])node_modules([\\/]|$)/.test(id);
64
+ if (path.isAbsolute(id) || isWindowsAbsolute || isFilesystemRelative) {
65
+ return pathToFileURL(path.resolve(id)).href;
66
+ }
67
+ return id;
68
+ }
69
+ async function dynamicImportProvider(id) {
70
+ return import(toImportableModuleSpecifier(id));
58
71
  }
59
72
  function sanitizeModuleId(modId) {
60
73
  return modId.replace(/[^a-z0-9_]/gi, "_");
@@ -168,6 +181,69 @@ function getMigrationsPath(entry, resolver) {
168
181
  }
169
182
  return path.join(roots.pkgBase, "migrations").replace(/\\/g, "/");
170
183
  }
184
+ async function loadModuleEncryptionMaps(entry, resolver) {
185
+ const roots = resolver.getModulePaths(entry);
186
+ const appFile = path.join(roots.appBase, "encryption.ts");
187
+ const pkgFile = path.join(roots.pkgBase, "encryption.ts");
188
+ try {
189
+ if (fs.existsSync(appFile)) return readDeclaredEncryptionMaps(entry.id, await importWithTypeScriptFile(appFile));
190
+ if (entry.from === "@app" || resolver.isMonorepo()) {
191
+ return fs.existsSync(pkgFile) ? readDeclaredEncryptionMaps(entry.id, await importWithTypeScriptFile(pkgFile)) : [];
192
+ }
193
+ return readDeclaredEncryptionMaps(entry.id, await import(`${resolver.getModuleImportBase(entry).pkgBase}/encryption`));
194
+ } catch {
195
+ return [];
196
+ }
197
+ }
198
+ function readModuleMigrationSources(modules, resolver) {
199
+ const sources = [];
200
+ for (const entry of modules) {
201
+ const migrationsPath = getMigrationsPath(entry, resolver);
202
+ if (!fs.existsSync(migrationsPath)) continue;
203
+ for (const file of fs.readdirSync(migrationsPath)) {
204
+ if (!/^Migration.*\.(ts|js)$/.test(file) || file.endsWith(".d.ts")) continue;
205
+ sources.push(fs.readFileSync(path.join(migrationsPath, file), "utf8"));
206
+ }
207
+ }
208
+ return sources;
209
+ }
210
+ async function queryEncryptionMapRows() {
211
+ const { Client } = await import("pg");
212
+ const client = new Client({ connectionString: getClientUrl(), ssl: getSslConfig(), connectionTimeoutMillis: 5e3 });
213
+ await client.connect();
214
+ try {
215
+ const table = await client.query(`select to_regclass('encryption_maps') is not null as "exists"`);
216
+ if (!table.rows[0]?.exists) return null;
217
+ const result = await client.query(
218
+ `select "tenant_id", "organization_id", "entity_id", "fields_json", "is_active" from "encryption_maps" where "deleted_at" is null`
219
+ );
220
+ return result.rows.map((row) => ({
221
+ tenantId: row.tenant_id ?? null,
222
+ organizationId: row.organization_id ?? null,
223
+ entityId: String(row.entity_id),
224
+ fieldsJson: row.fields_json,
225
+ isActive: row.is_active === true
226
+ }));
227
+ } finally {
228
+ try {
229
+ await client.end();
230
+ } catch {
231
+ }
232
+ }
233
+ }
234
+ async function warnAboutMissingEncryptionMapBackfills(modules, resolver) {
235
+ if (!isEncryptionBackfillCheckEnabled()) return;
236
+ await checkEncryptionMapBackfills({
237
+ loadDeclaredMaps: async () => {
238
+ const declared = [];
239
+ for (const entry of modules) declared.push(...await loadModuleEncryptionMaps(entry, resolver));
240
+ return declared;
241
+ },
242
+ loadMigrationSources: () => readModuleMigrationSources(modules, resolver),
243
+ queryEncryptionMaps: queryEncryptionMapRows,
244
+ warn: (message) => console.warn(message)
245
+ });
246
+ }
171
247
  async function dbGenerate(resolver, options = {}) {
172
248
  const modules = resolver.loadEnabledModules();
173
249
  const ordered = sortModules(modules);
@@ -256,6 +332,7 @@ async function dbGenerate(resolver, options = {}) {
256
332
  }
257
333
  }
258
334
  console.log(results.join("\n"));
335
+ await warnAboutMissingEncryptionMapBackfills(ordered, resolver);
259
336
  }
260
337
  async function dbMigrate(resolver, options = {}) {
261
338
  const modules = resolver.loadEnabledModules();
@@ -490,6 +567,8 @@ export {
490
567
  resolveGeneratedMigrationPath,
491
568
  sanitizeModuleId,
492
569
  shouldCreateInitialModuleMigration,
493
- validateTableName
570
+ toImportableModuleSpecifier,
571
+ validateTableName,
572
+ warnAboutMissingEncryptionMapBackfills
494
573
  };
495
574
  //# sourceMappingURL=commands.js.map