@open-mercato/cli 0.6.7 → 0.6.8-canary.320.1.bf889f49e4

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 (335) hide show
  1. package/.turbo/turbo-build.log +77 -4
  2. package/AGENTS.md +6 -1
  3. package/build.mjs +68 -11
  4. package/dist/agentic/claude-code/hooks/entity-migration-check.ts +32 -88
  5. package/dist/agentic/codex/enforcement-rules.md +2 -16
  6. package/dist/agentic/cursor/hooks/entity-migration-check.mjs +19 -50
  7. package/dist/agentic/cursor/rules/entity-guard.mdc +9 -21
  8. package/dist/agentic/cursor/rules/generated-guard.mdc +3 -5
  9. package/dist/agentic/cursor/rules/open-mercato.mdc +3 -29
  10. package/dist/agentic/guides/ai-workflows.md +76 -0
  11. package/dist/agentic/guides/architecture.md +107 -0
  12. package/dist/agentic/guides/backend-ui.md +86 -0
  13. package/dist/agentic/guides/contracts.md +92 -0
  14. package/dist/agentic/guides/extensions.md +121 -0
  15. package/dist/agentic/guides/framework-extension-points.md +36 -0
  16. package/dist/agentic/guides/integrations.md +81 -0
  17. package/dist/agentic/guides/module-facts.json +116757 -3951
  18. package/dist/agentic/guides/module-system.md +12 -127
  19. package/dist/agentic/guides/modules/ai_assistant.md +227 -28
  20. package/dist/agentic/guides/modules/api_docs.md +58 -2
  21. package/dist/agentic/guides/modules/api_keys.md +92 -7
  22. package/dist/agentic/guides/modules/attachments.md +119 -14
  23. package/dist/agentic/guides/modules/audit_logs.md +131 -10
  24. package/dist/agentic/guides/modules/auth.md +276 -33
  25. package/dist/agentic/guides/modules/business_rules.md +149 -14
  26. package/dist/agentic/guides/modules/catalog.md +482 -43
  27. package/dist/agentic/guides/modules/channel_gmail.md +82 -3
  28. package/dist/agentic/guides/modules/channel_imap.md +82 -3
  29. package/dist/agentic/guides/modules/checkout.md +305 -27
  30. package/dist/agentic/guides/modules/communication_channels.md +315 -33
  31. package/dist/agentic/guides/modules/configs.md +104 -8
  32. package/dist/agentic/guides/modules/content.md +57 -2
  33. package/dist/agentic/guides/modules/currencies.md +152 -17
  34. package/dist/agentic/guides/modules/customer_accounts.md +387 -45
  35. package/dist/agentic/guides/modules/customers.md +746 -85
  36. package/dist/agentic/guides/modules/dashboards.md +155 -10
  37. package/dist/agentic/guides/modules/data_sync.md +149 -16
  38. package/dist/agentic/guides/modules/design_system.md +102 -0
  39. package/dist/agentic/guides/modules/dictionaries.md +107 -12
  40. package/dist/agentic/guides/modules/directory.md +148 -15
  41. package/dist/agentic/guides/modules/entities.md +145 -13
  42. package/dist/agentic/guides/modules/events.md +59 -2
  43. package/dist/agentic/guides/modules/feature_toggles.md +119 -9
  44. package/dist/agentic/guides/modules/gateway_stripe.md +92 -4
  45. package/dist/agentic/guides/modules/generators.md +45 -2
  46. package/dist/agentic/guides/modules/inbox_ops.md +207 -27
  47. package/dist/agentic/guides/modules/integrations.md +149 -16
  48. package/dist/agentic/guides/modules/messages.md +223 -24
  49. package/dist/agentic/guides/modules/notifications.md +96 -8
  50. package/dist/agentic/guides/modules/onboarding.md +77 -6
  51. package/dist/agentic/guides/modules/payment_gateways.md +168 -21
  52. package/dist/agentic/guides/modules/perspectives.md +77 -7
  53. package/dist/agentic/guides/modules/planner.md +136 -18
  54. package/dist/agentic/guides/modules/portal.md +93 -2
  55. package/dist/agentic/guides/modules/progress.md +104 -14
  56. package/dist/agentic/guides/modules/query_index.md +154 -15
  57. package/dist/agentic/guides/modules/record_locks.md +206 -21
  58. package/dist/agentic/guides/modules/resources.md +197 -30
  59. package/dist/agentic/guides/modules/sales.md +687 -84
  60. package/dist/agentic/guides/modules/scheduler.md +133 -14
  61. package/dist/agentic/guides/modules/search.md +170 -6
  62. package/dist/agentic/guides/modules/security.md +294 -35
  63. package/dist/agentic/guides/modules/shipping_carriers.md +134 -18
  64. package/dist/agentic/guides/modules/sso.md +151 -26
  65. package/dist/agentic/guides/modules/staff.md +372 -57
  66. package/dist/agentic/guides/modules/storage_s3.md +95 -5
  67. package/dist/agentic/guides/modules/sync_akeneo.md +85 -3
  68. package/dist/agentic/guides/modules/sync_excel.md +75 -6
  69. package/dist/agentic/guides/modules/system_status_overlays.md +60 -2
  70. package/dist/agentic/guides/modules/translations.md +104 -10
  71. package/dist/agentic/guides/modules/webhooks.md +203 -24
  72. package/dist/agentic/guides/modules/wms.md +469 -47
  73. package/dist/agentic/guides/modules/workflows.md +302 -43
  74. package/dist/agentic/guides/reference-module-facts.json +6942 -0
  75. package/dist/agentic/guides/reference-modules/example.md +462 -0
  76. package/dist/agentic/guides/spec-delivery.md +56 -0
  77. package/dist/agentic/guides/testing-debugging.md +80 -0
  78. package/dist/agentic/guides/upstream/AGENTS.md +300 -0
  79. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +334 -0
  80. package/dist/agentic/guides/upstream/manifest.json +8 -0
  81. package/dist/agentic/shared/AGENTS.md.template +120 -311
  82. package/dist/agentic/shared/ai/agentic.config.json +2 -0
  83. package/dist/agentic/shared/ai/harness/README.md +50 -0
  84. package/dist/agentic/shared/ai/harness/RELEASE.md +59 -0
  85. package/dist/agentic/shared/ai/harness/cases.json +20596 -0
  86. package/dist/agentic/shared/ai/harness/cases.schema.json +196 -0
  87. package/dist/agentic/shared/ai/harness/design-system-inventory.json +6326 -0
  88. package/dist/agentic/shared/ai/harness/fixtures/index.json +51 -0
  89. package/dist/agentic/shared/ai/harness/fixtures/seeds.json +166 -0
  90. package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +21 -0
  91. package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +96 -0
  92. package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +172 -0
  93. package/dist/agentic/shared/ai/harness/knowledge-change.schema.json +228 -0
  94. package/dist/agentic/shared/ai/harness/release-matrix.json +163 -0
  95. package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
  96. package/dist/agentic/shared/ai/harness/result.schema.json +136 -0
  97. package/dist/agentic/shared/ai/harness/routing-response.schema.json +22 -0
  98. package/dist/agentic/shared/ai/harness/source-link-inventory.json +1706 -0
  99. package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
  100. package/dist/agentic/shared/ai/harness/validators.json +82 -0
  101. package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +2121 -0
  102. package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +1096 -0
  103. package/dist/agentic/shared/ai/harness/writable-spec-oracles.mjs +602 -0
  104. package/dist/agentic/shared/ai/lessons/_template.md +16 -0
  105. package/dist/agentic/shared/ai/lessons.md +26 -3
  106. package/dist/agentic/shared/ai/review-checklist.md +36 -0
  107. package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +21 -0
  108. package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +4 -0
  109. package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +98 -0
  110. package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +21 -241
  111. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +31 -0
  112. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +53 -0
  113. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +15 -0
  114. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +11 -0
  115. package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +30 -0
  116. package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +12 -0
  117. package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +13 -0
  118. package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +14 -0
  119. package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +28 -0
  120. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +12 -0
  121. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +22 -0
  122. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +12 -0
  123. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +19 -0
  124. package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +17 -601
  125. package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +21 -0
  126. package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +15 -0
  127. package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +16 -0
  128. package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +10 -0
  129. package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +15 -310
  130. package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +19 -0
  131. package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +31 -0
  132. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +100 -0
  133. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +16 -0
  134. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/knowledge-change.md +133 -0
  135. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +17 -0
  136. package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +25 -0
  137. package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +11 -0
  138. package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +13 -0
  139. package/dist/agentic/shared/ai/skills/om-help/SKILL.md +15 -97
  140. package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +18 -0
  141. package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +20 -0
  142. package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +33 -152
  143. package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +33 -0
  144. package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +40 -0
  145. package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +27 -0
  146. package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +24 -0
  147. package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +25 -708
  148. package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +16 -0
  149. package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +25 -0
  150. package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +15 -0
  151. package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +12 -0
  152. package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +34 -0
  153. package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +4 -0
  154. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +11 -0
  155. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +31 -0
  156. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +39 -0
  157. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +43 -0
  158. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +11 -0
  159. package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +20 -820
  160. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +32 -0
  161. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +172 -0
  162. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +14 -0
  163. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +83 -0
  164. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +40 -0
  165. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +11 -0
  166. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +22 -0
  167. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +13 -0
  168. package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +41 -0
  169. package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +19 -0
  170. package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +44 -0
  171. package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +39 -0
  172. package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +20 -0
  173. package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +57 -0
  174. package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +11 -0
  175. package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +486 -0
  176. package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +20 -843
  177. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +42 -0
  178. package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +27 -0
  179. package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +13 -0
  180. package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +43 -0
  181. package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +11 -0
  182. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +15 -63
  183. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +15 -0
  184. package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +17 -467
  185. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +29 -0
  186. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +14 -0
  187. package/dist/agentic/shared/ai/skills/tiers.json +143 -21
  188. package/dist/agentic/shared/ai/skills/tiers.schema.json +52 -40
  189. package/dist/agentic/shared/ai/specs/2026-08-06-reference-module-activation.md +248 -0
  190. package/dist/agentic/shared/ai/specs/README.md +4 -3
  191. package/dist/agentic/shared/ai/specs/SPEC-000-template.md +226 -10
  192. package/dist/agentic/shared/ai/trackers/github.md +108 -4
  193. package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +224 -0
  194. package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
  195. package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +3812 -0
  196. package/dist/agentic/shared/scripts/execution-sandbox.mjs +329 -0
  197. package/dist/agentic/shared/scripts/framework-context.mjs +888 -0
  198. package/dist/agentic/shared/scripts/install-skills.mjs +1029 -0
  199. package/dist/agentic/shared/scripts/install-skills.sh +4 -694
  200. package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +222 -0
  201. package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1774 -0
  202. package/dist/agentic/shared/scripts/validate-knowledge-change.mjs +1172 -0
  203. package/dist/bin.js +32 -34
  204. package/dist/bin.js.map +2 -2
  205. package/dist/lib/__integration__/TC-INT-008.spec.js +88 -8
  206. package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
  207. package/dist/lib/agentic-init.js +13 -3
  208. package/dist/lib/agentic-init.js.map +2 -2
  209. package/dist/lib/agentic-setup.js +434 -186
  210. package/dist/lib/agentic-setup.js.map +2 -2
  211. package/dist/lib/cli-bootstrap-mode.js +32 -0
  212. package/dist/lib/cli-bootstrap-mode.js.map +7 -0
  213. package/dist/lib/dev-supervisor-manifest.js +105 -0
  214. package/dist/lib/dev-supervisor-manifest.js.map +7 -0
  215. package/dist/lib/generate-watch-events.js +15 -0
  216. package/dist/lib/generate-watch-events.js.map +2 -2
  217. package/dist/lib/generators/entity-ids.js +11 -24
  218. package/dist/lib/generators/entity-ids.js.map +2 -2
  219. package/dist/lib/generators/extensions/injection-widgets.js +20 -0
  220. package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
  221. package/dist/lib/generators/module-di.js +10 -23
  222. package/dist/lib/generators/module-di.js.map +2 -2
  223. package/dist/lib/generators/module-entities.js +10 -23
  224. package/dist/lib/generators/module-entities.js.map +2 -2
  225. package/dist/lib/generators/module-extension-facts.js +1882 -0
  226. package/dist/lib/generators/module-extension-facts.js.map +7 -0
  227. package/dist/lib/generators/module-fact-sources.js +70 -0
  228. package/dist/lib/generators/module-fact-sources.js.map +7 -0
  229. package/dist/lib/generators/module-facts-discovery.js +64 -7
  230. package/dist/lib/generators/module-facts-discovery.js.map +2 -2
  231. package/dist/lib/generators/module-facts.js +1913 -52
  232. package/dist/lib/generators/module-facts.js.map +3 -3
  233. package/dist/lib/generators/module-override-targets.js +712 -0
  234. package/dist/lib/generators/module-override-targets.js.map +7 -0
  235. package/dist/lib/generators/module-registry.js +405 -36
  236. package/dist/lib/generators/module-registry.js.map +2 -2
  237. package/dist/lib/generators/route-manifest-shards.js +170 -0
  238. package/dist/lib/generators/route-manifest-shards.js.map +7 -0
  239. package/dist/lib/in-process-generate-watcher.js +12 -3
  240. package/dist/lib/in-process-generate-watcher.js.map +2 -2
  241. package/dist/lib/load-env.js +24 -0
  242. package/dist/lib/load-env.js.map +7 -0
  243. package/dist/lib/queue-worker-supervisor.js.map +2 -2
  244. package/dist/lib/resolver.js +28 -3
  245. package/dist/lib/resolver.js.map +2 -2
  246. package/dist/lib/telemetry-init.js +358 -0
  247. package/dist/lib/telemetry-init.js.map +7 -0
  248. package/dist/lib/testing/integration.js +5 -2
  249. package/dist/lib/testing/integration.js.map +2 -2
  250. package/dist/lib/utils.js +15 -8
  251. package/dist/lib/utils.js.map +2 -2
  252. package/dist/mercato.js +35 -9
  253. package/dist/mercato.js.map +2 -2
  254. package/package.json +13 -11
  255. package/src/__tests__/mercato.test.ts +144 -0
  256. package/src/bin.ts +60 -42
  257. package/src/lib/__integration__/TC-INT-008.spec.ts +107 -9
  258. package/src/lib/__tests__/agentic-init.test.ts +41 -5
  259. package/src/lib/__tests__/agentic-setup.ownership.test.ts +386 -0
  260. package/src/lib/__tests__/cli-bootstrap-mode.test.ts +22 -0
  261. package/src/lib/__tests__/content-stable-writes.test.ts +74 -0
  262. package/src/lib/__tests__/dev-supervisor-manifest.test.ts +131 -0
  263. package/src/lib/__tests__/generate-watch-events.test.ts +76 -0
  264. package/src/lib/__tests__/in-process-generate-watcher.test.ts +52 -1
  265. package/src/lib/__tests__/injection-widget-id-validation.test.ts +184 -0
  266. package/src/lib/__tests__/load-env.test.ts +65 -0
  267. package/src/lib/__tests__/queue-worker-supervisor.test.ts +33 -0
  268. package/src/lib/__tests__/resolver.enterprise.test.ts +50 -0
  269. package/src/lib/__tests__/telemetry-init.test.ts +298 -0
  270. package/src/lib/agentic-init.ts +15 -3
  271. package/src/lib/agentic-setup.ts +573 -222
  272. package/src/lib/cli-bootstrap-mode.ts +31 -0
  273. package/src/lib/dev-supervisor-manifest.ts +142 -0
  274. package/src/lib/generate-watch-events.ts +14 -0
  275. package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
  276. package/src/lib/generators/__tests__/entity-ids.test.ts +32 -0
  277. package/src/lib/generators/__tests__/helpers/override-target-diagnostic-fixtures.ts +150 -0
  278. package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
  279. package/src/lib/generators/__tests__/module-extension-facts.test.ts +603 -0
  280. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +139 -2
  281. package/src/lib/generators/__tests__/module-facts.computed-di-token.test.ts +88 -0
  282. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +1 -0
  283. package/src/lib/generators/__tests__/module-facts.discovery.test.ts +54 -10
  284. package/src/lib/generators/__tests__/module-facts.example-fact-coverage.test.ts +414 -0
  285. package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +108 -0
  286. package/src/lib/generators/__tests__/module-facts.local-reference.test.ts +393 -0
  287. package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
  288. package/src/lib/generators/__tests__/module-facts.search-entity-id.test.ts +151 -0
  289. package/src/lib/generators/__tests__/module-facts.source-links.test.ts +160 -0
  290. package/src/lib/generators/__tests__/module-override-targets.diagnostic-wiring.test.ts +92 -0
  291. package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
  292. package/src/lib/generators/__tests__/module-override-targets.test.ts +328 -0
  293. package/src/lib/generators/__tests__/module-override-targets.unknown-mode.test.ts +117 -0
  294. package/src/lib/generators/__tests__/module-subset.test.ts +266 -0
  295. package/src/lib/generators/__tests__/output-snapshots.test.ts +3 -3
  296. package/src/lib/generators/__tests__/route-manifest-shards.test.ts +62 -0
  297. package/src/lib/generators/entity-ids.ts +10 -26
  298. package/src/lib/generators/extensions/injection-widgets.ts +51 -0
  299. package/src/lib/generators/module-di.ts +9 -25
  300. package/src/lib/generators/module-entities.ts +9 -25
  301. package/src/lib/generators/module-extension-facts.ts +2472 -0
  302. package/src/lib/generators/module-fact-sources.ts +108 -0
  303. package/src/lib/generators/module-facts-discovery.ts +104 -7
  304. package/src/lib/generators/module-facts.ts +2747 -138
  305. package/src/lib/generators/module-override-targets.ts +907 -0
  306. package/src/lib/generators/module-registry.ts +448 -35
  307. package/src/lib/generators/route-manifest-shards.ts +146 -0
  308. package/src/lib/in-process-generate-watcher.ts +13 -4
  309. package/src/lib/load-env.ts +30 -0
  310. package/src/lib/queue-worker-supervisor.ts +7 -2
  311. package/src/lib/resolver.ts +34 -3
  312. package/src/lib/telemetry-init.ts +461 -0
  313. package/src/lib/testing/__tests__/integration.test.ts +4 -0
  314. package/src/lib/testing/integration.ts +5 -2
  315. package/src/lib/utils.ts +24 -16
  316. package/src/mercato.ts +46 -9
  317. package/dist/agentic/guides/cache.md +0 -45
  318. package/dist/agentic/guides/core.md +0 -453
  319. package/dist/agentic/guides/events.md +0 -117
  320. package/dist/agentic/guides/queue.md +0 -53
  321. package/dist/agentic/guides/search.md +0 -115
  322. package/dist/agentic/guides/shared.md +0 -108
  323. package/dist/agentic/guides/ui.md +0 -347
  324. package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +0 -27
  325. package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +0 -27
  326. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +0 -260
  327. package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +0 -146
  328. package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +0 -84
  329. package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +0 -193
  330. package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +0 -34
  331. package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +0 -762
  332. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +0 -50
  333. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +0 -97
  334. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +0 -271
  335. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +0 -101
@@ -0,0 +1,907 @@
1
+ import crypto from 'node:crypto'
2
+ import type { ModuleOverrideDomain } from '@open-mercato/shared/modules/overrides'
3
+ import type { ModuleExtensionContributionFact } from '@open-mercato/shared/modules/widgets/extension-points'
4
+ import type {
5
+ ModuleFactRef,
6
+ ModuleFactSourceKind,
7
+ ModuleFactSourceRef,
8
+ ModuleFactsJsonEntry,
9
+ ModuleOwnedContractFact,
10
+ } from './module-facts'
11
+ import { buildFactSourceLookup, type FactSourceLookup } from './module-fact-sources'
12
+ import { getFrameworkOverrideHostOperations } from './module-extension-facts'
13
+
14
+ /**
15
+ * Exact module-specific unified override targets.
16
+ *
17
+ * Spec: `.ai/specs/2026-08-02-module-facts-exact-override-targets.md`.
18
+ *
19
+ * Projects every concrete key a downstream app can set under
20
+ * `modules.<moduleId>.overrides.<domain>` from the normalized owned facts
21
+ * (spec 1 provenance) and the extension-surface contributions (spec 2). Each
22
+ * target carries the structured runtime `path`, the applicable framework mode,
23
+ * a module-owned `factRef`, and a portable `source`. The framework catalog
24
+ * (`framework-extension-points`) stays the authority for domains/modes; this
25
+ * layer only emits the module's concrete keys. Framework-only settings such as
26
+ * `nav.groupOrder` are never module targets.
27
+ */
28
+
29
+ /**
30
+ * Closed mode/note/diagnostic sets, published as `as const` ledgers whose types
31
+ * derive from them. The `factCoverage` ledger enumerates these at runtime, so a
32
+ * value added here without a ledger row fails the coverage contract test instead
33
+ * of silently widening the public surface.
34
+ */
35
+ export const MODULE_OVERRIDE_MODES = ['disable-replace', 'replace', 'additive'] as const
36
+
37
+ export type ModuleOverrideMode = (typeof MODULE_OVERRIDE_MODES)[number]
38
+
39
+ export const MODULE_OVERRIDE_TARGET_NOTES = [
40
+ 'safe-metadata-only',
41
+ 'page-middleware-not-mutation-guard',
42
+ ] as const
43
+
44
+ export type ModuleOverrideTargetNote = (typeof MODULE_OVERRIDE_TARGET_NOTES)[number]
45
+
46
+ function isModuleOverrideMode(value: string): value is ModuleOverrideMode {
47
+ return (MODULE_OVERRIDE_MODES as readonly string[]).includes(value)
48
+ }
49
+
50
+ export type ModuleOverrideTarget = {
51
+ id: string
52
+ domain: ModuleOverrideDomain
53
+ key?: string
54
+ path: string[]
55
+ modes: ModuleOverrideMode[]
56
+ factRef: ModuleFactRef
57
+ source: ModuleFactSourceRef
58
+ notes?: ModuleOverrideTargetNote[]
59
+ }
60
+
61
+ export const MODULE_OVERRIDE_TARGET_DIAGNOSTIC_CODES = [
62
+ 'missing-owned-fact',
63
+ 'missing-source',
64
+ 'unsupported-dynamic-key',
65
+ 'unknown-framework-domain',
66
+ 'unknown-framework-mode',
67
+ ] as const
68
+
69
+ export type ModuleOverrideTargetDiagnosticCode = (typeof MODULE_OVERRIDE_TARGET_DIAGNOSTIC_CODES)[number]
70
+
71
+ export type ModuleOverrideTargetDiagnostic = {
72
+ code: ModuleOverrideTargetDiagnosticCode
73
+ moduleId: string
74
+ domain: ModuleOverrideDomain
75
+ candidatePath?: string[]
76
+ source?: ModuleFactSourceRef
77
+ }
78
+
79
+ /**
80
+ * Public, spec-shaped adapter view. `collect` mirrors the spec's illustrative
81
+ * signature (targets only); `diagnose` is the additive per-module diagnostic
82
+ * companion. Every adapter is bound to exactly one top-level override domain,
83
+ * or classified `frameworkOnly` (framework-catalog settings that never become
84
+ * module targets, e.g. `nav`).
85
+ */
86
+ export type OverrideTargetAdapter = {
87
+ domain: ModuleOverrideDomain
88
+ collect(moduleFacts: ModuleFactsJsonEntry): ModuleOverrideTarget[]
89
+ diagnose(moduleFacts: ModuleFactsJsonEntry): ModuleOverrideTargetDiagnostic[]
90
+ frameworkOnly?: boolean
91
+ }
92
+
93
+ type AdapterResult = {
94
+ targets: ModuleOverrideTarget[]
95
+ diagnostics: ModuleOverrideTargetDiagnostic[]
96
+ }
97
+
98
+ type InternalAdapter = {
99
+ domain: ModuleOverrideDomain
100
+ frameworkOnly?: boolean
101
+ run(moduleFacts: ModuleFactsJsonEntry, resolveFactSource: FactSourceLookup): AdapterResult
102
+ }
103
+
104
+ const OVERRIDE_HOST_OPERATIONS = getFrameworkOverrideHostOperations()
105
+
106
+ /**
107
+ * Why a dotted host produced no modes. The two failure causes are distinct and a
108
+ * downstream app needs to tell them apart: an absent host means the catalog never
109
+ * described this override surface, while an unrecognized mode means the catalog
110
+ * *does* describe it but names an operation this generator cannot map to a public
111
+ * {@link ModuleOverrideMode}. Collapsing the second into the first would report a
112
+ * catalog the generator has fallen behind as a surface that does not exist.
113
+ */
114
+ export type FrameworkOverrideModeResolution =
115
+ | { outcome: 'resolved'; modes: ModuleOverrideMode[] }
116
+ | { outcome: 'unknown-framework-domain' }
117
+ | { outcome: 'unknown-framework-mode'; operation: string }
118
+
119
+ /**
120
+ * Supported modes come from the framework catalog only. A host the catalog does not
121
+ * describe is never guessed, and neither is a host whose declared operation is not a
122
+ * known mode: both produce no target, so a downstream app is never told a key is
123
+ * overridable in a mode the runtime may not support.
124
+ */
125
+ export function resolveFrameworkOverrideModes(operation: string | undefined): FrameworkOverrideModeResolution {
126
+ if (operation === undefined) return { outcome: 'unknown-framework-domain' }
127
+ if (!isModuleOverrideMode(operation)) return { outcome: 'unknown-framework-mode', operation }
128
+ return { outcome: 'resolved', modes: [operation] }
129
+ }
130
+
131
+ function modesFor(dottedHost: string): FrameworkOverrideModeResolution {
132
+ return resolveFrameworkOverrideModes(OVERRIDE_HOST_OPERATIONS[dottedHost])
133
+ }
134
+
135
+ /**
136
+ * The module id equals the last segment of the portable `sourceRoot`
137
+ * (`node_modules/<pkg>/src/modules/<moduleId>`), which the provenance builder
138
+ * derives from the module id itself, so this round-trips losslessly.
139
+ */
140
+ function moduleIdOf(facts: ModuleFactsJsonEntry): string {
141
+ const segments = facts.sourceRoot.split('/').filter(Boolean)
142
+ return segments[segments.length - 1] ?? ''
143
+ }
144
+
145
+ /**
146
+ * Deterministic id derived from the JSON-serialized `[moduleId, domain, path]`
147
+ * tuple so segment boundaries cannot collide. `path` remains the lossless
148
+ * authority; this is a compact stable handle for consumers that key by id.
149
+ */
150
+ export function computeOverrideTargetId(moduleId: string, domain: ModuleOverrideDomain, path: string[]): string {
151
+ return crypto.createHash('sha1').update(JSON.stringify([moduleId, domain, path])).digest('hex').slice(0, 16)
152
+ }
153
+
154
+ function compareSourceRefs(left: ModuleFactSourceRef, right: ModuleFactSourceRef): number {
155
+ return (
156
+ left.sourcePath.localeCompare(right.sourcePath) ||
157
+ (left.line ?? 0) - (right.line ?? 0) ||
158
+ (left.exportName ?? '').localeCompare(right.exportName ?? '')
159
+ )
160
+ }
161
+
162
+ function comparePaths(left: string[], right: string[]): number {
163
+ const length = Math.max(left.length, right.length)
164
+ for (let index = 0; index < length; index += 1) {
165
+ const leftSegment = left[index]
166
+ const rightSegment = right[index]
167
+ if (leftSegment === undefined) return -1
168
+ if (rightSegment === undefined) return 1
169
+ const compared = leftSegment.localeCompare(rightSegment)
170
+ if (compared !== 0) return compared
171
+ }
172
+ return 0
173
+ }
174
+
175
+ type MakeTargetInput = {
176
+ moduleId: string
177
+ domain: ModuleOverrideDomain
178
+ path: string[]
179
+ dottedHost: string
180
+ factRef: ModuleFactRef
181
+ source: ModuleFactSourceRef
182
+ key?: string
183
+ notes?: ModuleOverrideTargetNote[]
184
+ }
185
+
186
+ /** The resolution outcomes that yield no target; each names its own diagnostic code. */
187
+ type UnresolvedOverrideMode = Exclude<FrameworkOverrideModeResolution, { outcome: 'resolved' }>
188
+
189
+ function makeTarget(input: MakeTargetInput): ModuleOverrideTarget | UnresolvedOverrideMode {
190
+ const resolution = modesFor(input.dottedHost)
191
+ if (resolution.outcome !== 'resolved') return resolution
192
+ const { modes } = resolution
193
+ const target: ModuleOverrideTarget = {
194
+ id: computeOverrideTargetId(input.moduleId, input.domain, input.path),
195
+ domain: input.domain,
196
+ ...(input.key !== undefined ? { key: input.key } : {}),
197
+ path: input.path,
198
+ modes,
199
+ factRef: input.factRef,
200
+ source: input.source,
201
+ ...(input.notes && input.notes.length > 0 ? { notes: input.notes } : {}),
202
+ }
203
+ return target
204
+ }
205
+
206
+ /**
207
+ * Emits the target, or the diagnostic matching why the framework catalog yielded no
208
+ * modes: `unknown-framework-domain` when the catalog does not describe the target's
209
+ * dotted host at all, `unknown-framework-mode` when it describes the host but names
210
+ * an operation that is not a public {@link ModuleOverrideMode}. Neither case guesses
211
+ * a target.
212
+ */
213
+ function pushTarget(
214
+ targets: ModuleOverrideTarget[],
215
+ diagnostics: ModuleOverrideTargetDiagnostic[],
216
+ input: MakeTargetInput,
217
+ ): void {
218
+ const result = makeTarget(input)
219
+ if (!('outcome' in result)) {
220
+ targets.push(result)
221
+ return
222
+ }
223
+ diagnostics.push({
224
+ code: result.outcome,
225
+ moduleId: input.moduleId,
226
+ domain: input.domain,
227
+ candidatePath: input.path,
228
+ source: input.source,
229
+ })
230
+ }
231
+
232
+ /** Portable contribution source ref → override target source ref. */
233
+ function contributionSource(contribution: ModuleExtensionContributionFact): ModuleFactSourceRef {
234
+ return {
235
+ sourcePath: contribution.source.path,
236
+ ...(contribution.source.symbol ? { exportName: contribution.source.symbol } : {}),
237
+ }
238
+ }
239
+
240
+ /** Owned-contract source ref, dropping the (constant) exportName for compactness. */
241
+ function ownedSource(fact: ModuleOwnedContractFact): ModuleFactSourceRef {
242
+ return {
243
+ sourcePath: fact.source.sourcePath,
244
+ ...(fact.source.line ? { line: fact.source.line } : {}),
245
+ }
246
+ }
247
+
248
+ function contributionsOfKind<K extends ModuleExtensionContributionFact['kind']>(
249
+ facts: ModuleFactsJsonEntry,
250
+ kind: K,
251
+ ): Array<Extract<ModuleExtensionContributionFact, { kind: K }>> {
252
+ const contributions = facts.extensionSurfaces?.contributions ?? []
253
+ return contributions.filter((contribution): contribution is Extract<ModuleExtensionContributionFact, { kind: K }> =>
254
+ contribution.kind === kind,
255
+ )
256
+ }
257
+
258
+ function indexedFactSourcePath(
259
+ resolveFactSource: FactSourceLookup,
260
+ kind: ModuleFactSourceKind,
261
+ id: string,
262
+ ): string | null {
263
+ return resolveFactSource(kind, id)?.sourcePath ?? null
264
+ }
265
+
266
+ // ---------------------------------------------------------------------------
267
+ // Local canonical override-key normalizers.
268
+ //
269
+ // These MIRROR the private `normalizeApiRouteOverrideKey` /
270
+ // `normalizePageRouteOverrideKey` in
271
+ // `packages/shared/src/modules/overrides.ts` (not exported by that module).
272
+ // Round-trip tests feed the emitted keys back through the exported runtime
273
+ // appliers (`applyApiOverridesToManifests` / `applyPageOverridesToManifests`)
274
+ // to prove equivalence. See the implementation report: exporting the shared
275
+ // normalizers would let this file import rather than mirror them.
276
+ // ---------------------------------------------------------------------------
277
+
278
+ const VALID_HTTP_METHODS = new Set(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
279
+
280
+ /**
281
+ * Canonical `'METHOD /api/path'` public override key for a route method. Route
282
+ * fact paths are stored without the `/api` prefix (e.g. `/customers/x`), so the
283
+ * public key downstream apps write is `METHOD /api<path>`.
284
+ */
285
+ function apiRouteOverrideKey(method: string, routePath: string): string | null {
286
+ const upper = method.trim().toUpperCase()
287
+ if (!VALID_HTTP_METHODS.has(upper)) return null
288
+ const withLead = routePath.startsWith('/') ? routePath : `/${routePath}`
289
+ const publicPath = withLead.startsWith('/api/') || withLead === '/api' ? withLead : `/api${withLead}`
290
+ const normalized = publicPath.replace(/\/+$/, '') || '/'
291
+ return `${upper} ${normalized}`
292
+ }
293
+
294
+ function pageRouteOverrideKey(routePath: string): string | null {
295
+ const trimmed = routePath.trim()
296
+ if (!trimmed) return null
297
+ if (trimmed.startsWith('backend:') || trimmed.startsWith('frontend:')) return trimmed
298
+ const withLead = trimmed.startsWith('/') ? trimmed : `/${trimmed}`
299
+ const stripped = withLead.replace(/\/+$/, '') || '/'
300
+ if (stripped === '/frontend') return 'frontend:/'
301
+ if (stripped.startsWith('/frontend/')) return `frontend:/${stripped.slice('/frontend/'.length)}`
302
+ if (stripped === '/backend' || stripped.startsWith('/backend/')) return `backend:${stripped}`
303
+ return `frontend:${stripped}`
304
+ }
305
+
306
+ // ---------------------------------------------------------------------------
307
+ // Adapters (one per top-level override domain).
308
+ // ---------------------------------------------------------------------------
309
+
310
+ const aiAdapter: InternalAdapter = {
311
+ domain: 'ai',
312
+ run(facts) {
313
+ const moduleId = moduleIdOf(facts)
314
+ const targets: ModuleOverrideTarget[] = []
315
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
316
+
317
+ for (const agent of facts.aiAgents) {
318
+ if (!agent.sourcePath) {
319
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'ai', candidatePath: ['ai', 'agents', agent.id] })
320
+ continue
321
+ }
322
+ const path = ['ai', 'agents', agent.id]
323
+ pushTarget(targets, diagnostics, {
324
+ moduleId, domain: 'ai', path, key: agent.id, dottedHost: 'ai.agents',
325
+ factRef: { factSection: 'aiAgents', factKey: agent.id },
326
+ source: { sourcePath: agent.sourcePath },
327
+ })
328
+ }
329
+ for (const tool of facts.aiTools) {
330
+ if (!tool.sourcePath) {
331
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'ai', candidatePath: ['ai', 'tools', tool.name] })
332
+ continue
333
+ }
334
+ const path = ['ai', 'tools', tool.name]
335
+ pushTarget(targets, diagnostics, {
336
+ moduleId, domain: 'ai', path, key: tool.name, dottedHost: 'ai.tools',
337
+ factRef: { factSection: 'aiTools', factKey: tool.name },
338
+ source: { sourcePath: tool.sourcePath },
339
+ })
340
+ }
341
+ // AI file contracts split by mode: keyed `aiAgentOverrides` / `aiToolOverrides`
342
+ // entries are exact `ai.agents.<id>` / `ai.tools.<id>` targets, while additive
343
+ // `aiAgentExtensions` patches belong to the keyless `ai.extensions` array.
344
+ const aiFileContracts = facts.ownedContracts?.['ai-extension'] ?? []
345
+ const additiveExtensions: ModuleOwnedContractFact[] = []
346
+ for (const fact of aiFileContracts) {
347
+ const metadata = fact.metadata ?? {}
348
+ if (metadata.mode !== 'override') {
349
+ additiveExtensions.push(fact)
350
+ continue
351
+ }
352
+ const overrideKey = typeof metadata.overrideKey === 'string' ? metadata.overrideKey : null
353
+ const target = metadata.target === 'agent' ? 'agents' : metadata.target === 'tool' ? 'tools' : null
354
+ if (!overrideKey || !target) {
355
+ diagnostics.push({
356
+ code: 'unsupported-dynamic-key',
357
+ moduleId,
358
+ domain: 'ai',
359
+ candidatePath: ['ai', target ?? 'extensions'],
360
+ source: ownedSource(fact),
361
+ })
362
+ continue
363
+ }
364
+ const path = ['ai', target, overrideKey]
365
+ pushTarget(targets, diagnostics, {
366
+ moduleId, domain: 'ai', path, key: overrideKey, dottedHost: `ai.${target}`,
367
+ factRef: { factSection: 'ownedContracts.ai-extension', factKey: fact.id },
368
+ source: ownedSource(fact),
369
+ })
370
+ }
371
+ if (additiveExtensions.length > 0) {
372
+ const anchor = [...additiveExtensions].sort((left, right) => left.id.localeCompare(right.id))[0]
373
+ pushTarget(targets, diagnostics, {
374
+ moduleId, domain: 'ai', path: ['ai', 'extensions'], dottedHost: 'ai.extensions',
375
+ factRef: { factSection: 'ownedContracts.ai-extension', factKey: anchor.id },
376
+ source: ownedSource(anchor),
377
+ })
378
+ }
379
+ return { targets, diagnostics }
380
+ },
381
+ }
382
+
383
+ const routesAdapter: InternalAdapter = {
384
+ domain: 'routes',
385
+ run(facts) {
386
+ const moduleId = moduleIdOf(facts)
387
+ const targets: ModuleOverrideTarget[] = []
388
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
389
+
390
+ for (const route of facts.apiRoutes) {
391
+ for (const method of route.methods) {
392
+ const key = apiRouteOverrideKey(method, route.path)
393
+ if (!key) {
394
+ diagnostics.push({ code: 'unsupported-dynamic-key', moduleId, domain: 'routes', candidatePath: ['routes', 'api', `${method} ${route.path}`] })
395
+ continue
396
+ }
397
+ if (!route.sourcePath) {
398
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'routes', candidatePath: ['routes', 'api', key] })
399
+ continue
400
+ }
401
+ const path = ['routes', 'api', key]
402
+ pushTarget(targets, diagnostics, {
403
+ moduleId, domain: 'routes', path, key, dottedHost: 'routes.api',
404
+ factRef: { factSection: 'apiRoutes', factKey: route.path },
405
+ source: { sourcePath: route.sourcePath },
406
+ })
407
+ }
408
+ }
409
+
410
+ const emitPage = (page: { path: string; sourcePath: string }, section: 'backendPages' | 'frontendPages'): void => {
411
+ const key = pageRouteOverrideKey(page.path)
412
+ if (!key) {
413
+ diagnostics.push({ code: 'unsupported-dynamic-key', moduleId, domain: 'routes', candidatePath: ['routes', 'pages', page.path] })
414
+ return
415
+ }
416
+ if (!page.sourcePath) {
417
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'routes', candidatePath: ['routes', 'pages', key] })
418
+ return
419
+ }
420
+ const path = ['routes', 'pages', key]
421
+ pushTarget(targets, diagnostics, {
422
+ moduleId, domain: 'routes', path, key, dottedHost: 'routes.pages',
423
+ factRef: { factSection: section, factKey: page.path },
424
+ source: { sourcePath: page.sourcePath },
425
+ })
426
+ }
427
+ for (const page of facts.backendPages) emitPage(page, 'backendPages')
428
+ for (const page of facts.frontendPages) emitPage(page, 'frontendPages')
429
+
430
+ return { targets, diagnostics }
431
+ },
432
+ }
433
+
434
+ const eventsAdapter: InternalAdapter = {
435
+ domain: 'events',
436
+ run(facts) {
437
+ const moduleId = moduleIdOf(facts)
438
+ const targets: ModuleOverrideTarget[] = []
439
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
440
+ for (const contribution of contributionsOfKind(facts, 'subscriber')) {
441
+ const subscriberId = contribution.details.subscriberId || contribution.id
442
+ const path = ['events', 'subscribers', subscriberId]
443
+ if (!contribution.source.path) {
444
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'events', candidatePath: path })
445
+ continue
446
+ }
447
+ pushTarget(targets, diagnostics, {
448
+ moduleId, domain: 'events', path, key: subscriberId, dottedHost: 'events.subscribers',
449
+ factRef: { factSection: 'extensionSurfaces.contributions', factKey: contribution.id },
450
+ source: contributionSource(contribution),
451
+ })
452
+ }
453
+ return { targets, diagnostics }
454
+ },
455
+ }
456
+
457
+ const workersAdapter: InternalAdapter = {
458
+ domain: 'workers',
459
+ run(facts) {
460
+ const moduleId = moduleIdOf(facts)
461
+ const targets: ModuleOverrideTarget[] = []
462
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
463
+ for (const fact of facts.ownedContracts?.worker ?? []) {
464
+ const path = ['workers', fact.id]
465
+ if (!fact.source.sourcePath) {
466
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'workers', candidatePath: path })
467
+ continue
468
+ }
469
+ pushTarget(targets, diagnostics, {
470
+ moduleId, domain: 'workers', path, key: fact.id, dottedHost: 'workers',
471
+ factRef: { factSection: 'ownedContracts.worker', factKey: fact.id },
472
+ source: ownedSource(fact),
473
+ })
474
+ }
475
+ return { targets, diagnostics }
476
+ },
477
+ }
478
+
479
+ const widgetsAdapter: InternalAdapter = {
480
+ domain: 'widgets',
481
+ run(facts) {
482
+ const moduleId = moduleIdOf(facts)
483
+ const targets: ModuleOverrideTarget[] = []
484
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
485
+
486
+ const emitInjection = (registryKey: string, contribution: ModuleExtensionContributionFact): void => {
487
+ const path = ['widgets', 'injection', registryKey]
488
+ pushTarget(targets, diagnostics, {
489
+ moduleId, domain: 'widgets', path, key: registryKey, dottedHost: 'widgets.injection',
490
+ factRef: { factSection: 'extensionSurfaces.contributions', factKey: contribution.id },
491
+ source: contributionSource(contribution),
492
+ })
493
+ }
494
+
495
+ for (const contribution of contributionsOfKind(facts, 'widget')) {
496
+ const registryKey = contribution.details.registryKey
497
+ if (!registryKey) {
498
+ diagnostics.push({ code: 'unsupported-dynamic-key', moduleId, domain: 'widgets', candidatePath: ['widgets', 'injection', contribution.id] })
499
+ continue
500
+ }
501
+ if (contribution.details.payload === 'dashboard') {
502
+ const path = ['widgets', 'dashboard', registryKey]
503
+ pushTarget(targets, diagnostics, {
504
+ moduleId, domain: 'widgets', path, key: registryKey, dottedHost: 'widgets.dashboard',
505
+ factRef: { factSection: 'extensionSurfaces.contributions', factKey: contribution.id },
506
+ source: contributionSource(contribution),
507
+ })
508
+ } else {
509
+ emitInjection(registryKey, contribution)
510
+ }
511
+ }
512
+
513
+ // data-table / crud-form contributions are injection-widget entries whose
514
+ // registry key is the id segment before the `@<spot>` suffix.
515
+ for (const contribution of [
516
+ ...contributionsOfKind(facts, 'data-table'),
517
+ ...contributionsOfKind(facts, 'crud-form'),
518
+ ]) {
519
+ const registryKey = contribution.id.split('@')[0]
520
+ if (!registryKey || registryKey === contribution.id) {
521
+ diagnostics.push({ code: 'unsupported-dynamic-key', moduleId, domain: 'widgets', candidatePath: ['widgets', 'injection', contribution.id] })
522
+ continue
523
+ }
524
+ emitInjection(registryKey, contribution)
525
+ }
526
+
527
+ for (const contribution of contributionsOfKind(facts, 'component-override')) {
528
+ const componentId = contribution.details.handle
529
+ const path = ['widgets', 'components', componentId]
530
+ pushTarget(targets, diagnostics, {
531
+ moduleId, domain: 'widgets', path, key: componentId, dottedHost: 'widgets.components',
532
+ factRef: { factSection: 'extensionSurfaces.contributions', factKey: contribution.id },
533
+ source: contributionSource(contribution),
534
+ })
535
+ }
536
+
537
+ return { targets, diagnostics }
538
+ },
539
+ }
540
+
541
+ const notificationsAdapter: InternalAdapter = {
542
+ domain: 'notifications',
543
+ run(facts, resolveFactSource) {
544
+ const moduleId = moduleIdOf(facts)
545
+ const targets: ModuleOverrideTarget[] = []
546
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
547
+
548
+ const registrySources = new Map<string, ModuleFactSourceRef>()
549
+ for (const contribution of contributionsOfKind(facts, 'specialized-registry')) {
550
+ if (contribution.details.registry !== 'notification') continue
551
+ registrySources.set(contribution.details.registryId, contributionSource(contribution))
552
+ }
553
+ for (const type of facts.notifications) {
554
+ const path = ['notifications', 'types', type]
555
+ const indexedPath = indexedFactSourcePath(resolveFactSource, 'notification', type)
556
+ const source = registrySources.get(type) ?? (indexedPath ? { sourcePath: indexedPath } : null)
557
+ if (!source) {
558
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'notifications', candidatePath: path })
559
+ continue
560
+ }
561
+ pushTarget(targets, diagnostics, {
562
+ moduleId, domain: 'notifications', path, key: type, dottedHost: 'notifications.types',
563
+ factRef: { factSection: 'notifications', factKey: type },
564
+ source,
565
+ })
566
+ }
567
+
568
+ // Reactive handlers (`notifications.handlers.ts`) are keyed by their declared
569
+ // handler id; a handler that declares none cannot be addressed by runtime.
570
+ for (const contribution of contributionsOfKind(facts, 'browser-reaction')) {
571
+ if (!contribution.details.transports.includes('notification-effect')) continue
572
+ const overrideKey = contribution.details.overrideKey
573
+ if (!overrideKey) {
574
+ diagnostics.push({
575
+ code: 'unsupported-dynamic-key',
576
+ moduleId,
577
+ domain: 'notifications',
578
+ candidatePath: ['notifications', 'handlers'],
579
+ source: contributionSource(contribution),
580
+ })
581
+ continue
582
+ }
583
+ pushTarget(targets, diagnostics, {
584
+ moduleId,
585
+ domain: 'notifications',
586
+ path: ['notifications', 'handlers', overrideKey],
587
+ key: overrideKey,
588
+ dottedHost: 'notifications.handlers',
589
+ factRef: { factSection: 'extensionSurfaces.contributions', factKey: contribution.id },
590
+ source: contributionSource(contribution),
591
+ })
592
+ }
593
+ return { targets, diagnostics }
594
+ },
595
+ }
596
+
597
+ function contributionIdAdapter(
598
+ domain: ModuleOverrideDomain,
599
+ kind: ModuleExtensionContributionFact['kind'],
600
+ dottedHost: string,
601
+ ): InternalAdapter {
602
+ return {
603
+ domain,
604
+ run(facts) {
605
+ const moduleId = moduleIdOf(facts)
606
+ const targets: ModuleOverrideTarget[] = []
607
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
608
+ for (const contribution of contributionsOfKind(facts, kind)) {
609
+ const path = [domain, contribution.id]
610
+ if (!contribution.source.path) {
611
+ diagnostics.push({ code: 'missing-source', moduleId, domain, candidatePath: path })
612
+ continue
613
+ }
614
+ pushTarget(targets, diagnostics, {
615
+ moduleId, domain, path, key: contribution.id, dottedHost,
616
+ factRef: { factSection: 'extensionSurfaces.contributions', factKey: contribution.id },
617
+ source: contributionSource(contribution),
618
+ })
619
+ }
620
+ return { targets, diagnostics }
621
+ },
622
+ }
623
+ }
624
+
625
+ const interceptorsAdapter = contributionIdAdapter('interceptors', 'api-interceptor', 'interceptors')
626
+ const commandInterceptorsAdapter = contributionIdAdapter('commandInterceptors', 'command-interceptor', 'commandInterceptors')
627
+ const enrichersAdapter = contributionIdAdapter('enrichers', 'response-enricher', 'enrichers')
628
+
629
+ /**
630
+ * `guards` targets ONLY backend/frontend page route middleware
631
+ * (`ownedContracts['page-middleware']`). `data/guards.ts` mutation guards
632
+ * (contribution kind `mutation-guard`) are a different runtime contract and
633
+ * MUST NEVER surface here — every target carries the closed
634
+ * `page-middleware-not-mutation-guard` note.
635
+ */
636
+ const guardsAdapter: InternalAdapter = {
637
+ domain: 'guards',
638
+ run(facts) {
639
+ const moduleId = moduleIdOf(facts)
640
+ const targets: ModuleOverrideTarget[] = []
641
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
642
+ for (const fact of facts.ownedContracts?.['page-middleware'] ?? []) {
643
+ const path = ['guards', fact.id]
644
+ if (!fact.source.sourcePath) {
645
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'guards', candidatePath: path })
646
+ continue
647
+ }
648
+ pushTarget(targets, diagnostics, {
649
+ moduleId, domain: 'guards', path, key: fact.id, dottedHost: 'guards',
650
+ factRef: { factSection: 'ownedContracts.page-middleware', factKey: fact.id },
651
+ source: ownedSource(fact),
652
+ notes: ['page-middleware-not-mutation-guard'],
653
+ })
654
+ }
655
+ return { targets, diagnostics }
656
+ },
657
+ }
658
+
659
+ const cliAdapter: InternalAdapter = {
660
+ domain: 'cli',
661
+ run(facts) {
662
+ const moduleId = moduleIdOf(facts)
663
+ const targets: ModuleOverrideTarget[] = []
664
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
665
+ for (const command of facts.cliCommands) {
666
+ const path = ['cli', command.command]
667
+ if (!command.sourcePath) {
668
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'cli', candidatePath: path })
669
+ continue
670
+ }
671
+ pushTarget(targets, diagnostics, {
672
+ moduleId, domain: 'cli', path, key: command.command, dottedHost: 'cli',
673
+ factRef: { factSection: 'cliCommands', factKey: command.command },
674
+ source: { sourcePath: command.sourcePath },
675
+ })
676
+ }
677
+ return { targets, diagnostics }
678
+ },
679
+ }
680
+
681
+ // `SetupOverridesShape` keys the runtime override resolver accepts. Only these
682
+ // supported hook/profile properties become `setup` targets.
683
+ const SUPPORTED_SETUP_HOOKS = new Set(['seedDefaults', 'seedExamples', 'onTenantCreated'])
684
+
685
+ const setupAdapter: InternalAdapter = {
686
+ domain: 'setup',
687
+ run(facts) {
688
+ const moduleId = moduleIdOf(facts)
689
+ const targets: ModuleOverrideTarget[] = []
690
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
691
+ const setupFacts = facts.ownedContracts?.setup ?? []
692
+ for (const fact of setupFacts) {
693
+ if (!fact.source.sourcePath) {
694
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'setup', candidatePath: ['setup'] })
695
+ continue
696
+ }
697
+ const source = ownedSource(fact)
698
+ const properties: string[] = []
699
+ const hooks = fact.metadata?.hooks
700
+ if (Array.isArray(hooks)) {
701
+ for (const hook of hooks) {
702
+ if (typeof hook === 'string' && SUPPORTED_SETUP_HOOKS.has(hook)) properties.push(hook)
703
+ }
704
+ }
705
+ const roles = fact.metadata?.roles
706
+ if (Array.isArray(roles) && roles.length > 0) properties.push('defaultRoleFeatures')
707
+ const customerRoles = fact.metadata?.customerRoles
708
+ if (Array.isArray(customerRoles) && customerRoles.length > 0) properties.push('defaultCustomerRoleFeatures')
709
+ for (const property of properties) {
710
+ const path = ['setup', property]
711
+ pushTarget(targets, diagnostics, {
712
+ moduleId, domain: 'setup', path, dottedHost: 'setup',
713
+ factRef: { factSection: 'ownedContracts.setup', factKey: fact.id },
714
+ source,
715
+ })
716
+ }
717
+ }
718
+ return { targets, diagnostics }
719
+ },
720
+ }
721
+
722
+ const aclAdapter: InternalAdapter = {
723
+ domain: 'acl',
724
+ run(facts, resolveFactSource) {
725
+ const moduleId = moduleIdOf(facts)
726
+ const targets: ModuleOverrideTarget[] = []
727
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
728
+ for (const feature of facts.aclFeatures) {
729
+ const path = ['acl', 'features', feature]
730
+ const sourcePath = indexedFactSourcePath(resolveFactSource, 'acl-feature', feature)
731
+ if (!sourcePath) {
732
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'acl', candidatePath: path })
733
+ continue
734
+ }
735
+ pushTarget(targets, diagnostics, {
736
+ moduleId, domain: 'acl', path, key: feature, dottedHost: 'acl.features',
737
+ factRef: { factSection: 'aclFeatures', factKey: feature },
738
+ source: { sourcePath },
739
+ })
740
+ }
741
+ return { targets, diagnostics }
742
+ },
743
+ }
744
+
745
+ /**
746
+ * `di` targets expose the exact registration token and correlate to the rich DI
747
+ * owned fact (which carries the registration kind). They NEVER include the
748
+ * `asValue` payload, constructed service, or factory/class body — the target
749
+ * schema has no such field and carries the `safe-metadata-only` note.
750
+ */
751
+ const diAdapter: InternalAdapter = {
752
+ domain: 'di',
753
+ run(facts) {
754
+ const moduleId = moduleIdOf(facts)
755
+ const targets: ModuleOverrideTarget[] = []
756
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
757
+ for (const fact of facts.ownedContracts?.['di-registration'] ?? []) {
758
+ const path = ['di', fact.id]
759
+ if (!fact.source.sourcePath) {
760
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'di', candidatePath: path })
761
+ continue
762
+ }
763
+ pushTarget(targets, diagnostics, {
764
+ moduleId, domain: 'di', path, key: fact.id, dottedHost: 'di',
765
+ factRef: { factSection: 'ownedContracts.di-registration', factKey: fact.id },
766
+ source: ownedSource(fact),
767
+ notes: ['safe-metadata-only'],
768
+ })
769
+ }
770
+ return { targets, diagnostics }
771
+ },
772
+ }
773
+
774
+ const encryptionAdapter: InternalAdapter = {
775
+ domain: 'encryption',
776
+ run(facts) {
777
+ const moduleId = moduleIdOf(facts)
778
+ const targets: ModuleOverrideTarget[] = []
779
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
780
+ for (const fact of facts.ownedContracts?.encryption ?? []) {
781
+ const path = ['encryption', 'maps', fact.id]
782
+ if (!fact.source.sourcePath) {
783
+ diagnostics.push({ code: 'missing-source', moduleId, domain: 'encryption', candidatePath: path })
784
+ continue
785
+ }
786
+ pushTarget(targets, diagnostics, {
787
+ moduleId, domain: 'encryption', path, key: fact.id, dottedHost: 'encryption.maps',
788
+ factRef: { factSection: 'ownedContracts.encryption', factKey: fact.id },
789
+ source: ownedSource(fact),
790
+ })
791
+ }
792
+ return { targets, diagnostics }
793
+ },
794
+ }
795
+
796
+ /** `nav.groupOrder` is a framework-only setting; never a module target. */
797
+ const navAdapter: InternalAdapter = {
798
+ domain: 'nav',
799
+ frameworkOnly: true,
800
+ run() {
801
+ return { targets: [], diagnostics: [] }
802
+ },
803
+ }
804
+
805
+ const INTERNAL_ADAPTERS: InternalAdapter[] = [
806
+ aiAdapter,
807
+ routesAdapter,
808
+ eventsAdapter,
809
+ workersAdapter,
810
+ widgetsAdapter,
811
+ notificationsAdapter,
812
+ interceptorsAdapter,
813
+ commandInterceptorsAdapter,
814
+ enrichersAdapter,
815
+ guardsAdapter,
816
+ cliAdapter,
817
+ setupAdapter,
818
+ aclAdapter,
819
+ diAdapter,
820
+ encryptionAdapter,
821
+ navAdapter,
822
+ ]
823
+
824
+ /**
825
+ * Runtime enumeration of every unified override domain. Typed as a
826
+ * `Record<ModuleOverrideDomain, true>` so adding a member to the runtime
827
+ * `ModuleOverrideDomain` union fails the build here until it is listed — and
828
+ * the coverage test then fails until an adapter (or `frameworkOnly`
829
+ * classification) is registered.
830
+ */
831
+ const OVERRIDE_DOMAIN_PRESENCE: Record<ModuleOverrideDomain, true> = {
832
+ ai: true,
833
+ routes: true,
834
+ events: true,
835
+ workers: true,
836
+ widgets: true,
837
+ notifications: true,
838
+ interceptors: true,
839
+ commandInterceptors: true,
840
+ enrichers: true,
841
+ guards: true,
842
+ cli: true,
843
+ setup: true,
844
+ acl: true,
845
+ di: true,
846
+ encryption: true,
847
+ nav: true,
848
+ }
849
+
850
+ export const ALL_OVERRIDE_DOMAINS = Object.keys(OVERRIDE_DOMAIN_PRESENCE) as ModuleOverrideDomain[]
851
+
852
+ export const MODULE_OVERRIDE_TARGET_ADAPTERS: OverrideTargetAdapter[] = INTERNAL_ADAPTERS.map((adapter) => ({
853
+ domain: adapter.domain,
854
+ ...(adapter.frameworkOnly ? { frameworkOnly: true } : {}),
855
+ collect: (moduleFacts) => adapter.run(moduleFacts, buildFactSourceLookup(moduleFacts)).targets,
856
+ diagnose: (moduleFacts) => adapter.run(moduleFacts, buildFactSourceLookup(moduleFacts)).diagnostics,
857
+ }))
858
+
859
+ export type ModuleOverrideTargetsResult = {
860
+ overrideTargets: ModuleOverrideTarget[]
861
+ overrideTargetDiagnostics: ModuleOverrideTargetDiagnostic[]
862
+ }
863
+
864
+ /**
865
+ * Project every exact override target (and per-module diagnostic) for one
866
+ * module's facts. Output sorts by domain, then path segments, then source;
867
+ * diagnostics sort by moduleId, domain, code, then path/source. Targets are
868
+ * deduped by `(domain, path)` keeping the source-first entry.
869
+ */
870
+ export function collectModuleOverrideTargets(
871
+ facts: ModuleFactsJsonEntry,
872
+ resolveFactSource: FactSourceLookup = buildFactSourceLookup(facts),
873
+ ): ModuleOverrideTargetsResult {
874
+ const collected: ModuleOverrideTarget[] = []
875
+ const diagnostics: ModuleOverrideTargetDiagnostic[] = []
876
+ for (const adapter of INTERNAL_ADAPTERS) {
877
+ if (adapter.frameworkOnly) continue
878
+ const result = adapter.run(facts, resolveFactSource)
879
+ collected.push(...result.targets)
880
+ diagnostics.push(...result.diagnostics)
881
+ }
882
+
883
+ const byPath = new Map<string, ModuleOverrideTarget>()
884
+ for (const target of collected) {
885
+ const pathKey = `${target.domain} ${JSON.stringify(target.path)}`
886
+ const existing = byPath.get(pathKey)
887
+ if (!existing || compareSourceRefs(target.source, existing.source) < 0) byPath.set(pathKey, target)
888
+ }
889
+
890
+ const overrideTargets = [...byPath.values()].sort(
891
+ (left, right) =>
892
+ left.domain.localeCompare(right.domain) ||
893
+ comparePaths(left.path, right.path) ||
894
+ compareSourceRefs(left.source, right.source),
895
+ )
896
+
897
+ const overrideTargetDiagnostics = diagnostics.sort(
898
+ (left, right) =>
899
+ left.moduleId.localeCompare(right.moduleId) ||
900
+ left.domain.localeCompare(right.domain) ||
901
+ left.code.localeCompare(right.code) ||
902
+ comparePaths(left.candidatePath ?? [], right.candidatePath ?? []) ||
903
+ compareSourceRefs(left.source ?? { sourcePath: '' }, right.source ?? { sourcePath: '' }),
904
+ )
905
+
906
+ return { overrideTargets, overrideTargetDiagnostics }
907
+ }