@open-mercato/cli 0.6.7 → 0.6.8-canary.319.1.5b4be4bc52

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 (287) hide show
  1. package/.turbo/turbo-build.log +77 -4
  2. package/build.mjs +68 -11
  3. package/dist/agentic/claude-code/hooks/entity-migration-check.ts +32 -88
  4. package/dist/agentic/codex/enforcement-rules.md +2 -16
  5. package/dist/agentic/cursor/hooks/entity-migration-check.mjs +19 -50
  6. package/dist/agentic/cursor/rules/entity-guard.mdc +9 -21
  7. package/dist/agentic/cursor/rules/generated-guard.mdc +3 -5
  8. package/dist/agentic/cursor/rules/open-mercato.mdc +3 -29
  9. package/dist/agentic/guides/ai-workflows.md +76 -0
  10. package/dist/agentic/guides/architecture.md +107 -0
  11. package/dist/agentic/guides/backend-ui.md +84 -0
  12. package/dist/agentic/guides/contracts.md +92 -0
  13. package/dist/agentic/guides/extensions.md +121 -0
  14. package/dist/agentic/guides/framework-extension-points.md +36 -0
  15. package/dist/agentic/guides/integrations.md +81 -0
  16. package/dist/agentic/guides/module-facts.json +116757 -3951
  17. package/dist/agentic/guides/module-system.md +12 -127
  18. package/dist/agentic/guides/modules/ai_assistant.md +227 -28
  19. package/dist/agentic/guides/modules/api_docs.md +58 -2
  20. package/dist/agentic/guides/modules/api_keys.md +92 -7
  21. package/dist/agentic/guides/modules/attachments.md +119 -14
  22. package/dist/agentic/guides/modules/audit_logs.md +131 -10
  23. package/dist/agentic/guides/modules/auth.md +276 -33
  24. package/dist/agentic/guides/modules/business_rules.md +149 -14
  25. package/dist/agentic/guides/modules/catalog.md +482 -43
  26. package/dist/agentic/guides/modules/channel_gmail.md +82 -3
  27. package/dist/agentic/guides/modules/channel_imap.md +82 -3
  28. package/dist/agentic/guides/modules/checkout.md +305 -27
  29. package/dist/agentic/guides/modules/communication_channels.md +315 -33
  30. package/dist/agentic/guides/modules/configs.md +104 -8
  31. package/dist/agentic/guides/modules/content.md +57 -2
  32. package/dist/agentic/guides/modules/currencies.md +152 -17
  33. package/dist/agentic/guides/modules/customer_accounts.md +387 -45
  34. package/dist/agentic/guides/modules/customers.md +746 -85
  35. package/dist/agentic/guides/modules/dashboards.md +155 -10
  36. package/dist/agentic/guides/modules/data_sync.md +149 -16
  37. package/dist/agentic/guides/modules/design_system.md +102 -0
  38. package/dist/agentic/guides/modules/dictionaries.md +107 -12
  39. package/dist/agentic/guides/modules/directory.md +148 -15
  40. package/dist/agentic/guides/modules/entities.md +145 -13
  41. package/dist/agentic/guides/modules/events.md +59 -2
  42. package/dist/agentic/guides/modules/feature_toggles.md +119 -9
  43. package/dist/agentic/guides/modules/gateway_stripe.md +92 -4
  44. package/dist/agentic/guides/modules/generators.md +45 -2
  45. package/dist/agentic/guides/modules/inbox_ops.md +207 -27
  46. package/dist/agentic/guides/modules/integrations.md +149 -16
  47. package/dist/agentic/guides/modules/messages.md +223 -24
  48. package/dist/agentic/guides/modules/notifications.md +96 -8
  49. package/dist/agentic/guides/modules/onboarding.md +77 -6
  50. package/dist/agentic/guides/modules/payment_gateways.md +168 -21
  51. package/dist/agentic/guides/modules/perspectives.md +77 -7
  52. package/dist/agentic/guides/modules/planner.md +136 -18
  53. package/dist/agentic/guides/modules/portal.md +93 -2
  54. package/dist/agentic/guides/modules/progress.md +104 -14
  55. package/dist/agentic/guides/modules/query_index.md +154 -15
  56. package/dist/agentic/guides/modules/record_locks.md +206 -21
  57. package/dist/agentic/guides/modules/resources.md +197 -30
  58. package/dist/agentic/guides/modules/sales.md +687 -84
  59. package/dist/agentic/guides/modules/scheduler.md +133 -14
  60. package/dist/agentic/guides/modules/search.md +170 -6
  61. package/dist/agentic/guides/modules/security.md +294 -35
  62. package/dist/agentic/guides/modules/shipping_carriers.md +134 -18
  63. package/dist/agentic/guides/modules/sso.md +151 -26
  64. package/dist/agentic/guides/modules/staff.md +372 -57
  65. package/dist/agentic/guides/modules/storage_s3.md +95 -5
  66. package/dist/agentic/guides/modules/sync_akeneo.md +85 -3
  67. package/dist/agentic/guides/modules/sync_excel.md +75 -6
  68. package/dist/agentic/guides/modules/system_status_overlays.md +60 -2
  69. package/dist/agentic/guides/modules/translations.md +104 -10
  70. package/dist/agentic/guides/modules/webhooks.md +203 -24
  71. package/dist/agentic/guides/modules/wms.md +469 -47
  72. package/dist/agentic/guides/modules/workflows.md +302 -43
  73. package/dist/agentic/guides/reference-module-facts.json +6370 -0
  74. package/dist/agentic/guides/reference-modules/example.md +451 -0
  75. package/dist/agentic/guides/spec-delivery.md +42 -0
  76. package/dist/agentic/guides/testing-debugging.md +80 -0
  77. package/dist/agentic/guides/upstream/AGENTS.md +299 -0
  78. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +316 -0
  79. package/dist/agentic/guides/upstream/manifest.json +8 -0
  80. package/dist/agentic/shared/AGENTS.md.template +120 -311
  81. package/dist/agentic/shared/ai/agentic.config.json +2 -0
  82. package/dist/agentic/shared/ai/harness/README.md +50 -0
  83. package/dist/agentic/shared/ai/harness/RELEASE.md +57 -0
  84. package/dist/agentic/shared/ai/harness/cases.json +15050 -0
  85. package/dist/agentic/shared/ai/harness/cases.schema.json +188 -0
  86. package/dist/agentic/shared/ai/harness/fixtures/index.json +51 -0
  87. package/dist/agentic/shared/ai/harness/fixtures/seeds.json +166 -0
  88. package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +21 -0
  89. package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +96 -0
  90. package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +172 -0
  91. package/dist/agentic/shared/ai/harness/knowledge-change.schema.json +228 -0
  92. package/dist/agentic/shared/ai/harness/release-matrix.json +163 -0
  93. package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
  94. package/dist/agentic/shared/ai/harness/result.schema.json +109 -0
  95. package/dist/agentic/shared/ai/harness/routing-response.schema.json +22 -0
  96. package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
  97. package/dist/agentic/shared/ai/harness/validators.json +82 -0
  98. package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +2121 -0
  99. package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +1096 -0
  100. package/dist/agentic/shared/ai/harness/writable-spec-oracles.mjs +412 -0
  101. package/dist/agentic/shared/ai/lessons/_template.md +16 -0
  102. package/dist/agentic/shared/ai/lessons.md +26 -3
  103. package/dist/agentic/shared/ai/review-checklist.md +36 -0
  104. package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +21 -0
  105. package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +4 -0
  106. package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +98 -0
  107. package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +21 -241
  108. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +31 -0
  109. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +53 -0
  110. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +15 -0
  111. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +11 -0
  112. package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +30 -0
  113. package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +12 -0
  114. package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +13 -0
  115. package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +14 -0
  116. package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +28 -0
  117. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +12 -0
  118. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +22 -0
  119. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +12 -0
  120. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +19 -0
  121. package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +17 -601
  122. package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +21 -0
  123. package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +15 -0
  124. package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +16 -0
  125. package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +10 -0
  126. package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +15 -310
  127. package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +19 -0
  128. package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +31 -0
  129. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +100 -0
  130. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +16 -0
  131. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/knowledge-change.md +115 -0
  132. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +17 -0
  133. package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +25 -0
  134. package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +11 -0
  135. package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +13 -0
  136. package/dist/agentic/shared/ai/skills/om-help/SKILL.md +15 -97
  137. package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +18 -0
  138. package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +20 -0
  139. package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +33 -152
  140. package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +33 -0
  141. package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +40 -0
  142. package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +27 -0
  143. package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +24 -0
  144. package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +25 -708
  145. package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +16 -0
  146. package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +25 -0
  147. package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +15 -0
  148. package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +12 -0
  149. package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +34 -0
  150. package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +4 -0
  151. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +11 -0
  152. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +31 -0
  153. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +39 -0
  154. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +43 -0
  155. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +11 -0
  156. package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +20 -820
  157. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +32 -0
  158. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +172 -0
  159. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +14 -0
  160. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +83 -0
  161. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +40 -0
  162. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +11 -0
  163. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +22 -0
  164. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +13 -0
  165. package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +41 -0
  166. package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +19 -0
  167. package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +44 -0
  168. package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +39 -0
  169. package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +20 -0
  170. package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +57 -0
  171. package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +11 -0
  172. package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +486 -0
  173. package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +20 -843
  174. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +42 -0
  175. package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +27 -0
  176. package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +13 -0
  177. package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +43 -0
  178. package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +11 -0
  179. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +15 -63
  180. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +15 -0
  181. package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +17 -467
  182. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +29 -0
  183. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +14 -0
  184. package/dist/agentic/shared/ai/skills/tiers.json +143 -21
  185. package/dist/agentic/shared/ai/skills/tiers.schema.json +52 -40
  186. package/dist/agentic/shared/ai/specs/README.md +4 -3
  187. package/dist/agentic/shared/ai/specs/SPEC-000-template.md +226 -10
  188. package/dist/agentic/shared/ai/trackers/github.md +108 -4
  189. package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +224 -0
  190. package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
  191. package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +3585 -0
  192. package/dist/agentic/shared/scripts/execution-sandbox.mjs +329 -0
  193. package/dist/agentic/shared/scripts/framework-context.mjs +888 -0
  194. package/dist/agentic/shared/scripts/install-skills.mjs +1029 -0
  195. package/dist/agentic/shared/scripts/install-skills.sh +4 -694
  196. package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +222 -0
  197. package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1774 -0
  198. package/dist/agentic/shared/scripts/validate-knowledge-change.mjs +911 -0
  199. package/dist/bin.js +13 -1
  200. package/dist/bin.js.map +2 -2
  201. package/dist/lib/__integration__/TC-INT-008.spec.js +88 -8
  202. package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
  203. package/dist/lib/agentic-init.js +13 -3
  204. package/dist/lib/agentic-init.js.map +2 -2
  205. package/dist/lib/agentic-setup.js +434 -186
  206. package/dist/lib/agentic-setup.js.map +2 -2
  207. package/dist/lib/generators/extensions/injection-widgets.js +20 -0
  208. package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
  209. package/dist/lib/generators/module-extension-facts.js +1872 -0
  210. package/dist/lib/generators/module-extension-facts.js.map +7 -0
  211. package/dist/lib/generators/module-fact-sources.js +70 -0
  212. package/dist/lib/generators/module-fact-sources.js.map +7 -0
  213. package/dist/lib/generators/module-facts-discovery.js +64 -7
  214. package/dist/lib/generators/module-facts-discovery.js.map +2 -2
  215. package/dist/lib/generators/module-facts.js +1629 -52
  216. package/dist/lib/generators/module-facts.js.map +3 -3
  217. package/dist/lib/generators/module-override-targets.js +688 -0
  218. package/dist/lib/generators/module-override-targets.js.map +7 -0
  219. package/dist/lib/generators/module-registry.js +11 -5
  220. package/dist/lib/generators/module-registry.js.map +2 -2
  221. package/dist/lib/load-env.js +24 -0
  222. package/dist/lib/load-env.js.map +7 -0
  223. package/dist/lib/resolver.js +2 -0
  224. package/dist/lib/resolver.js.map +2 -2
  225. package/dist/lib/telemetry-init.js +358 -0
  226. package/dist/lib/telemetry-init.js.map +7 -0
  227. package/dist/lib/testing/integration.js +5 -2
  228. package/dist/lib/testing/integration.js.map +2 -2
  229. package/dist/mercato.js +23 -1
  230. package/dist/mercato.js.map +2 -2
  231. package/package.json +13 -11
  232. package/src/bin.ts +33 -1
  233. package/src/lib/__integration__/TC-INT-008.spec.ts +107 -9
  234. package/src/lib/__tests__/agentic-init.test.ts +41 -5
  235. package/src/lib/__tests__/agentic-setup.ownership.test.ts +386 -0
  236. package/src/lib/__tests__/injection-widget-id-validation.test.ts +184 -0
  237. package/src/lib/__tests__/load-env.test.ts +65 -0
  238. package/src/lib/__tests__/telemetry-init.test.ts +298 -0
  239. package/src/lib/agentic-init.ts +15 -3
  240. package/src/lib/agentic-setup.ts +573 -222
  241. package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
  242. package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
  243. package/src/lib/generators/__tests__/module-extension-facts.test.ts +603 -0
  244. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +139 -2
  245. package/src/lib/generators/__tests__/module-facts.computed-di-token.test.ts +88 -0
  246. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +1 -0
  247. package/src/lib/generators/__tests__/module-facts.discovery.test.ts +54 -10
  248. package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +108 -0
  249. package/src/lib/generators/__tests__/module-facts.local-reference.test.ts +391 -0
  250. package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
  251. package/src/lib/generators/__tests__/module-facts.search-entity-id.test.ts +151 -0
  252. package/src/lib/generators/__tests__/module-facts.source-links.test.ts +160 -0
  253. package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
  254. package/src/lib/generators/__tests__/module-override-targets.test.ts +328 -0
  255. package/src/lib/generators/__tests__/module-subset.test.ts +18 -0
  256. package/src/lib/generators/extensions/injection-widgets.ts +51 -0
  257. package/src/lib/generators/module-extension-facts.ts +2433 -0
  258. package/src/lib/generators/module-fact-sources.ts +108 -0
  259. package/src/lib/generators/module-facts-discovery.ts +104 -7
  260. package/src/lib/generators/module-facts.ts +2387 -138
  261. package/src/lib/generators/module-override-targets.ts +863 -0
  262. package/src/lib/generators/module-registry.ts +16 -6
  263. package/src/lib/load-env.ts +30 -0
  264. package/src/lib/resolver.ts +3 -0
  265. package/src/lib/telemetry-init.ts +461 -0
  266. package/src/lib/testing/__tests__/integration.test.ts +4 -0
  267. package/src/lib/testing/integration.ts +5 -2
  268. package/src/mercato.ts +26 -1
  269. package/dist/agentic/guides/cache.md +0 -45
  270. package/dist/agentic/guides/core.md +0 -453
  271. package/dist/agentic/guides/events.md +0 -117
  272. package/dist/agentic/guides/queue.md +0 -53
  273. package/dist/agentic/guides/search.md +0 -115
  274. package/dist/agentic/guides/shared.md +0 -108
  275. package/dist/agentic/guides/ui.md +0 -347
  276. package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +0 -27
  277. package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +0 -27
  278. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +0 -260
  279. package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +0 -146
  280. package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +0 -84
  281. package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +0 -193
  282. package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +0 -34
  283. package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +0 -762
  284. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +0 -50
  285. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +0 -97
  286. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +0 -271
  287. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +0 -101
@@ -0,0 +1,603 @@
1
+ import fs from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+ import {
5
+ assertNoUnresolvedExtensionTargets,
6
+ correlateModuleExtensionFacts,
7
+ extractModuleExtensionFacts,
8
+ getFrameworkExtensionHosts,
9
+ renderFrameworkExtensionPointsMarkdown,
10
+ } from '../module-extension-facts'
11
+ import type { ModuleFactsJsonEntry } from '../module-facts'
12
+
13
+ function write(root: string, relativePath: string, source: string): void {
14
+ const filePath = path.join(root, relativePath)
15
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
16
+ fs.writeFileSync(filePath, source)
17
+ }
18
+
19
+ describe('module extension facts', () => {
20
+ let moduleRoot: string
21
+
22
+ beforeEach(() => {
23
+ moduleRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'module-extension-facts-'))
24
+ })
25
+
26
+ afterEach(() => {
27
+ fs.rmSync(moduleRoot, { recursive: true, force: true })
28
+ })
29
+
30
+ it('expands declared host families and preserves fact references and browser transports', () => {
31
+ write(moduleRoot, 'extension-points.ts', `
32
+ import { defineModuleExtensionPoints, dataTableExtensionHost, crudFormExtensionHost, injectionExtensionHost } from 'x'
33
+ export const extensionPoints = defineModuleExtensionPoints({
34
+ moduleId: 'alpha',
35
+ hosts: {
36
+ records: dataTableExtensionHost({ tableId: 'alpha.records.list', baseSpotId: 'data-table:alpha.records', source: 'Records.tsx' }),
37
+ editor: crudFormExtensionHost({ entityId: 'alpha.record', source: 'Edit.tsx' }),
38
+ detail: injectionExtensionHost({
39
+ family: 'detail', pattern: 'detail:alpha.record:{recordId}',
40
+ parameters: { recordId: { source: 'record.id', pattern: '^[a-z]+$' } },
41
+ supported: ['render-widget'], source: 'Detail.tsx', fallbacks: ['detail:alpha.record'],
42
+ }),
43
+ },
44
+ })
45
+ `)
46
+ write(moduleRoot, 'Records.tsx', 'export const tableId = extensionPoints.hosts.records.tableId')
47
+ write(moduleRoot, 'Edit.tsx', 'export const entityId = extensionPoints.hosts.editor.entityId')
48
+ write(moduleRoot, 'Detail.tsx', 'export const spotId = extensionPoints.hosts.detail.pattern')
49
+
50
+ const facts = extractModuleExtensionFacts({
51
+ moduleId: 'alpha',
52
+ moduleRoot,
53
+ sourceRoot: 'node_modules/pkg/src/modules/alpha',
54
+ entities: [{ id: 'alpha:record' }],
55
+ events: [{ id: 'alpha.record.updated', clientBroadcast: true, portalBroadcast: true }],
56
+ apiRoutes: [{ path: '/alpha/records', methods: ['GET', 'POST'] }],
57
+ searchEntities: ['alpha:record'],
58
+ })
59
+
60
+ expect(facts.hosts).toEqual(expect.arrayContaining([
61
+ expect.objectContaining({ id: 'data-table:alpha.records:header', bound: true }),
62
+ expect.objectContaining({ id: 'data-table:alpha.records.list:columns', capabilities: ['column-widget'] }),
63
+ expect.objectContaining({ id: 'data-table:alpha.records:empty-state', bound: false }),
64
+ expect.objectContaining({ id: 'crud-form:alpha.record:fields', capabilities: ['field-widget'] }),
65
+ expect.objectContaining({ id: 'detail:alpha.record:{recordId}', resolution: 'pattern' }),
66
+ expect.objectContaining({ id: 'alpha:record', resolution: 'fact-ref' }),
67
+ expect.objectContaining({
68
+ id: 'alpha.record.updated',
69
+ capabilities: ['async-subscriber', 'sync-subscriber', 'browser-client', 'browser-portal'],
70
+ }),
71
+ expect.objectContaining({ id: 'alpha.record.querying', activation: 'caller-opt-in' }),
72
+ expect.objectContaining({ id: 'alpha.record.queried', phases: ['result-transform', 'scope-reapply'] }),
73
+ ]))
74
+ expect(facts.unresolved).toEqual([])
75
+ const hostOrder = facts.hosts.map((host) => `${host.family}\u0000${host.id}`)
76
+ expect(hostOrder).toEqual([...hostOrder].sort((left, right) => left.localeCompare(right)))
77
+ })
78
+
79
+ it('extracts outgoing mechanisms with kind-specific contracts and correlates targets', () => {
80
+ write(moduleRoot, 'extension-points.ts', `
81
+ import { defineModuleExtensionPoints, dataTableExtensionHost } from 'x'
82
+ export const extensionPoints = defineModuleExtensionPoints({
83
+ moduleId: 'alpha', hosts: { records: dataTableExtensionHost({ tableId: 'alpha.records', source: 'Records.tsx' }) },
84
+ })
85
+ `)
86
+ write(moduleRoot, 'Records.tsx', 'export const tableId = extensionPoints.hosts.records.tableId')
87
+ write(moduleRoot, 'widgets/injection-table.ts', `
88
+ export const injectionTable = {
89
+ 'data-table:alpha.records:columns': [{ widgetId: 'alpha.columns', priority: 20, features: ['alpha.view'] }],
90
+ 'menu:backend.sidebar': [{ widgetId: 'alpha.menu' }],
91
+ }
92
+ `)
93
+ write(moduleRoot, 'data/enrichers.ts', `
94
+ const enricher = {
95
+ id: 'alpha.records.enricher', targetEntity: 'alpha:record', features: ['alpha.view'], timeout: 750,
96
+ fallback: {}, queryEngine: { enabled: true, engines: ['json'], applyOn: ['list'] },
97
+ async enrichOne() {}, async enrichMany() {},
98
+ }
99
+ export const enrichers = [enricher]
100
+ `)
101
+ write(moduleRoot, 'api/interceptors.ts', `
102
+ export const interceptors = [{
103
+ id: 'alpha.records.interceptor', targetRoute: 'alpha/records', methods: ['POST'],
104
+ async before() {}, async after() {},
105
+ }]
106
+ `)
107
+ write(moduleRoot, 'commands/interceptors.ts', `
108
+ export const interceptors = [{
109
+ id: 'alpha.records.command', targetCommand: 'alpha.records.update',
110
+ async beforeExecute() {}, async afterUndo() {},
111
+ }]
112
+ `)
113
+ write(moduleRoot, 'commands/update.ts', `
114
+ const updateCommand = { id: 'alpha.records.update', async execute() {} }
115
+ registerCommand(updateCommand)
116
+ `)
117
+ write(moduleRoot, 'data/guards.ts', `
118
+ const guard = { id: 'alpha.records.guard', targetEntity: 'alpha:record', operations: ['update', 'delete'], async validate() {}, async afterSuccess() {} }
119
+ export const guards = [guard]
120
+ `)
121
+ write(moduleRoot, 'data/extensions.ts', `
122
+ export const extensions = [{ base: 'alpha:record', extension: 'beta:record_meta', join: { baseKey: 'id', extensionKey: 'record_id' } }]
123
+ `)
124
+ write(moduleRoot, 'subscribers/updated.ts', `
125
+ export const metadata = { event: 'alpha.record.updated', id: 'alpha:record-updated', persistent: true, priority: 10 }
126
+ `)
127
+
128
+ const surface = extractModuleExtensionFacts({
129
+ moduleId: 'alpha',
130
+ moduleRoot,
131
+ sourceRoot: 'node_modules/pkg/src/modules/alpha',
132
+ entities: [{ id: 'alpha:record' }],
133
+ events: [{ id: 'alpha.record.updated', clientBroadcast: true }],
134
+ apiRoutes: [{ path: '/alpha/records', methods: ['POST'] }],
135
+ searchEntities: [],
136
+ })
137
+ const correlated = correlateModuleExtensionFacts({
138
+ surfacesByModule: { alpha: surface },
139
+ entityIds: new Set(['alpha:record']),
140
+ eventIds: new Set(['alpha.record.updated']),
141
+ apiRoutes: new Set(['/alpha/records']),
142
+ commandIds: new Set(['alpha.records.update']),
143
+ }).alpha
144
+
145
+ expect(correlated.contributions).toEqual(expect.arrayContaining([
146
+ expect.objectContaining({
147
+ id: 'alpha.records.enricher',
148
+ kind: 'response-enricher',
149
+ details: expect.objectContaining({
150
+ targetEntity: 'alpha:record', surfaces: ['list', 'detail'], timeoutMs: 750,
151
+ queryEngine: { engines: ['json'], applyOn: ['list'], activation: 'caller-opt-in' },
152
+ }),
153
+ }),
154
+ expect.objectContaining({
155
+ id: 'alpha.records.interceptor', kind: 'api-interceptor',
156
+ targets: [expect.objectContaining({
157
+ resolution: 'fact-ref',
158
+ factRef: { factSection: 'apiRoutes', factKey: '/alpha/records' },
159
+ })],
160
+ details: expect.objectContaining({ phases: ['before', 'after'] }),
161
+ }),
162
+ expect.objectContaining({
163
+ id: 'alpha.records.command',
164
+ targets: [expect.objectContaining({
165
+ factRef: { factSection: 'ownedContracts.command', factKey: 'alpha.records.update' },
166
+ })],
167
+ details: expect.objectContaining({ phases: ['before-execute', 'after-undo'] }),
168
+ }),
169
+ expect.objectContaining({ id: 'alpha.records.guard', details: expect.objectContaining({ optimisticLock: 'preserved' }) }),
170
+ expect.objectContaining({ kind: 'entity-extension', details: expect.objectContaining({ linkId: 'id:record_id' }) }),
171
+ expect.objectContaining({ id: 'alpha:record-updated', details: expect.objectContaining({ persistent: true, sync: false, priority: 10 }) }),
172
+ expect.objectContaining({ id: 'alpha.columns@data-table:alpha.records:columns', targets: [expect.objectContaining({ resolution: 'exact' })] }),
173
+ expect.objectContaining({ id: 'alpha.menu@menu:backend.sidebar', targets: [expect.objectContaining({ resolution: 'framework' })] }),
174
+ expect.objectContaining({ id: 'alpha.record.updated.browser', kind: 'browser-reaction' }),
175
+ ]))
176
+ expect(correlated.unresolved).toEqual([])
177
+ const contributionOrder = correlated.contributions.map((contribution) => `${contribution.kind}\u0000${contribution.id}`)
178
+ expect(contributionOrder).toEqual([...contributionOrder].sort((left, right) => left.localeCompare(right)))
179
+ })
180
+
181
+ it('renders a deterministic separate framework catalog', () => {
182
+ type ExtensionSurfacesAreOptional = {} extends Pick<ModuleFactsJsonEntry, 'extensionSurfaces'> ? true : false
183
+ const extensionSurfacesAreOptional: ExtensionSurfacesAreOptional = true
184
+ const first = renderFrameworkExtensionPointsMarkdown()
185
+ const second = renderFrameworkExtensionPointsMarkdown()
186
+ expect(second).toBe(first)
187
+ expect(first).toContain('# Framework extension points (generated, do not edit)')
188
+ expect(first).toContain('integrations.detail:{integrationId}')
189
+ expect(first).toContain('menu:backend.sidebar')
190
+ expect(extensionSurfacesAreOptional).toBe(true)
191
+ })
192
+
193
+ it('projects every wired unified override key with its supported mode', () => {
194
+ const overrides = getFrameworkExtensionHosts()
195
+ .filter((host) => host.family === 'module-override')
196
+ .map((host) => [host.id, host.operations?.[0]])
197
+
198
+ expect(overrides).toEqual([
199
+ ['module-override:acl.features', 'disable-replace'],
200
+ ['module-override:ai.agents', 'disable-replace'],
201
+ ['module-override:ai.extensions', 'additive'],
202
+ ['module-override:ai.tools', 'disable-replace'],
203
+ ['module-override:cli', 'disable-replace'],
204
+ ['module-override:commandInterceptors', 'disable-replace'],
205
+ ['module-override:di', 'disable-replace'],
206
+ ['module-override:encryption.maps', 'disable-replace'],
207
+ ['module-override:enrichers', 'disable-replace'],
208
+ ['module-override:events.subscribers', 'disable-replace'],
209
+ ['module-override:guards', 'disable-replace'],
210
+ ['module-override:interceptors', 'disable-replace'],
211
+ ['module-override:nav.groupOrder', 'additive'],
212
+ ['module-override:notifications.handlers', 'disable-replace'],
213
+ ['module-override:notifications.types', 'disable-replace'],
214
+ ['module-override:routes.api', 'disable-replace'],
215
+ ['module-override:routes.pages', 'disable-replace'],
216
+ ['module-override:setup', 'replace'],
217
+ ['module-override:widgets.components', 'disable-replace'],
218
+ ['module-override:widgets.dashboard', 'disable-replace'],
219
+ ['module-override:widgets.injection', 'disable-replace'],
220
+ ['module-override:workers', 'disable-replace'],
221
+ ])
222
+ })
223
+
224
+ it('projects dashboard, notification, integration, vector, provider, and workflow registries', () => {
225
+ write(moduleRoot, 'notifications.handlers.ts', `
226
+ export const notificationHandlers = [{
227
+ id: 'alpha.alert-toast', notificationType: 'alpha.alert', features: ['alpha.view'], priority: 80,
228
+ handle() {},
229
+ }]
230
+ `)
231
+ write(moduleRoot, 'integration.ts', `
232
+ export const integration = { id: 'alpha_gateway', category: 'payment', providerKey: 'alpha-pay' }
233
+ export const integrations = [integration]
234
+ `)
235
+ write(moduleRoot, 'vector.ts', `
236
+ export const vectorConfig = { entities: [{ entityId: 'alpha:record' }] }
237
+ export default vectorConfig
238
+ `)
239
+ write(moduleRoot, 'workflows.ts', `
240
+ const approval = defineWorkflow({ workflowId: 'alpha.approval', steps: [], transitions: [] })
241
+ export const workflowsConfig = createWorkflowsModuleConfig({ moduleId: 'alpha', workflows: [approval] })
242
+ `)
243
+ write(moduleRoot, 'di.ts', `
244
+ export function register() {
245
+ registerPaymentGatewayDescriptor({ providerKey: 'alpha-pay', label: 'Alpha Pay' })
246
+ registerShippingAdapter({ providerKey: 'alpha-ship' })
247
+ registerCurrencyProvider({ id: 'alpha-rates' })
248
+ }
249
+ `)
250
+ write(moduleRoot, 'widgets/dashboard/summary/widget.ts', `
251
+ const widget = { metadata: { id: 'alpha.dashboard.summary', features: ['alpha.view'] }, Widget() {} }
252
+ export default widget
253
+ `)
254
+
255
+ const facts = extractModuleExtensionFacts({
256
+ moduleId: 'alpha',
257
+ moduleRoot,
258
+ sourceRoot: 'node_modules/pkg/src/modules/alpha',
259
+ entities: [],
260
+ events: [],
261
+ apiRoutes: [],
262
+ searchEntities: ['alpha:record'],
263
+ notifications: ['alpha.alert'],
264
+ aiTools: [{ name: 'alpha_lookup', sourcePath: 'node_modules/pkg/src/modules/alpha/ai-tools.ts' }],
265
+ aiAgents: [{ id: 'alpha_agent', sourcePath: 'node_modules/pkg/src/modules/alpha/ai-agents.ts' }],
266
+ })
267
+
268
+ expect(facts.contributions).toEqual(expect.arrayContaining([
269
+ expect.objectContaining({
270
+ id: 'alpha.alert-toast',
271
+ kind: 'browser-reaction',
272
+ targets: [expect.objectContaining({ id: 'alpha.alert', resolution: 'fact-ref' })],
273
+ details: expect.objectContaining({ transports: ['notification-effect'], hooks: ['useNotificationEffect'] }),
274
+ }),
275
+ expect.objectContaining({ id: 'dashboard:alpha.dashboard.summary', kind: 'widget', details: expect.objectContaining({ payload: 'dashboard' }) }),
276
+ expect.objectContaining({ id: 'integration:alpha_gateway', details: expect.objectContaining({ specialistRoute: 'integrations' }) }),
277
+ expect.objectContaining({ id: 'notification:alpha.alert', details: expect.objectContaining({ registry: 'notification' }) }),
278
+ expect.objectContaining({ id: 'search:alpha:record', details: expect.objectContaining({ registry: 'search' }) }),
279
+ expect.objectContaining({ id: 'vector:alpha:record', details: expect.objectContaining({ registry: 'vector' }) }),
280
+ expect.objectContaining({ id: 'ai:alpha_lookup', details: expect.objectContaining({ specialistRoute: 'aiTools' }) }),
281
+ expect.objectContaining({ id: 'ai:alpha_agent', details: expect.objectContaining({ specialistRoute: 'aiAgents' }) }),
282
+ expect.objectContaining({ id: 'payment:alpha-pay', source: expect.objectContaining({ symbol: 'registerPaymentGatewayDescriptor' }) }),
283
+ expect.objectContaining({ id: 'shipping:alpha-ship', details: expect.objectContaining({ specialistRoute: 'shippingCarriers' }) }),
284
+ expect.objectContaining({ id: 'currency:alpha-rates', details: expect.objectContaining({ specialistRoute: 'currencies' }) }),
285
+ expect.objectContaining({ id: 'workflow:alpha.approval', details: expect.objectContaining({ specialistRoute: 'workflows' }) }),
286
+ ]))
287
+ expect(facts.contributions.filter((fact) => fact.id === 'payment:alpha-pay')).toHaveLength(1)
288
+ })
289
+
290
+ it('resolves factory-built contributions and generated entity registry references', () => {
291
+ write(moduleRoot, 'data/enrichers.ts', `
292
+ function makeEnricher(targetEntity) {
293
+ return { id: \`alpha.image:\${targetEntity}\`, targetEntity, async enrichMany(records) { return records } }
294
+ }
295
+ const registryEnricher = { id: 'alpha.registry', targetEntity: E.alpha.record, async enrichOne(record) { return record } }
296
+ export const enrichers = [makeEnricher('alpha:line'), registryEnricher]
297
+ `)
298
+
299
+ const facts = extractModuleExtensionFacts({
300
+ moduleId: 'alpha',
301
+ moduleRoot,
302
+ sourceRoot: 'node_modules/pkg/src/modules/alpha',
303
+ entities: [],
304
+ events: [],
305
+ apiRoutes: [],
306
+ searchEntities: [],
307
+ })
308
+
309
+ expect(facts.contributions).toEqual(expect.arrayContaining([
310
+ expect.objectContaining({ id: 'alpha.image:alpha:line', targets: [expect.objectContaining({ id: 'alpha:line' })] }),
311
+ expect.objectContaining({ id: 'alpha.registry', targets: [expect.objectContaining({ id: 'alpha:record' })] }),
312
+ ]))
313
+ })
314
+
315
+ it('fails closed on an unknown member call instead of publishing its first argument', () => {
316
+ // The generic call fallback used to forward the FIRST ARGUMENT of any call expression.
317
+ // For a member call that COMPUTES its value from the arguments — a handle builder, an
318
+ // enum lookup, anything namespaced — that publishes an id the argument merely contributed
319
+ // to, which resolves to nothing. `ComponentReplacementHandles.*` is intercepted by an
320
+ // explicit formula above, so only a builder the reader does NOT know exercises this path.
321
+ // The failure is silent (the fact vanishes rather than erroring), so it needs its own test.
322
+ write(moduleRoot, 'widgets/components.ts', `
323
+ import { SomeOtherRegistry } from '@open-mercato/shared/modules/widgets/component-registry'
324
+ export const componentOverrides = [
325
+ {
326
+ target: { componentId: SomeOtherRegistry.derive('alpha.records.list') },
327
+ priority: 50,
328
+ wrapper: (Original) => Original,
329
+ },
330
+ ]
331
+ `)
332
+
333
+ const facts = extractModuleExtensionFacts({
334
+ moduleId: 'alpha',
335
+ moduleRoot,
336
+ sourceRoot: 'node_modules/pkg/src/modules/alpha',
337
+ entities: [],
338
+ events: [],
339
+ apiRoutes: [],
340
+ searchEntities: [],
341
+ })
342
+
343
+ const overrides = facts.contributions.filter((contribution) => contribution.kind === 'component-override')
344
+ expect(overrides).toEqual([])
345
+ expect(JSON.stringify(facts)).not.toContain('alpha.records.list')
346
+ })
347
+
348
+ it('classifies component overrides by the ComponentOverride discriminant and resolves handle builders', () => {
349
+ write(moduleRoot, 'widgets/components.ts', `
350
+ import { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'
351
+ function wrapRecords(Original) { return Original }
352
+ export const componentOverrides = [
353
+ {
354
+ target: { componentId: ComponentReplacementHandles.section('ui.detail', 'NotesSection') },
355
+ priority: 50,
356
+ wrapper: (Original) => Original,
357
+ },
358
+ {
359
+ target: { componentId: ComponentReplacementHandles.dataTable('alpha.records.list') },
360
+ priority: 50,
361
+ wrapper: wrapRecords,
362
+ },
363
+ {
364
+ target: { componentId: ComponentReplacementHandles.crudForm('alpha.record') },
365
+ priority: 50,
366
+ propsTransform: (props) => props,
367
+ },
368
+ {
369
+ target: { componentId: ComponentReplacementHandles.page('/backend/alpha') },
370
+ priority: 50,
371
+ replacement: AlphaPage,
372
+ propsSchema: alphaPropsSchema,
373
+ },
374
+ {
375
+ target: { componentId: ComponentReplacementHandles.mystery('alpha') },
376
+ priority: 50,
377
+ wrapper: (Original) => Original,
378
+ },
379
+ ]
380
+ `)
381
+
382
+ const facts = extractModuleExtensionFacts({
383
+ moduleId: 'alpha',
384
+ moduleRoot,
385
+ sourceRoot: 'node_modules/pkg/src/modules/alpha',
386
+ entities: [],
387
+ events: [],
388
+ apiRoutes: [],
389
+ searchEntities: [],
390
+ })
391
+
392
+ const overrides = facts.contributions
393
+ .filter((contribution) => contribution.kind === 'component-override')
394
+ .map((contribution) => contribution.kind === 'component-override'
395
+ ? { id: contribution.id, ...contribution.details }
396
+ : null)
397
+
398
+ expect(overrides).toEqual([
399
+ {
400
+ id: 'alpha.component-override.0:section:ui.detail.NotesSection',
401
+ handle: 'section:ui.detail.NotesSection',
402
+ mode: 'wrapper',
403
+ propsContract: 'component-props-schema',
404
+ },
405
+ {
406
+ id: 'alpha.component-override.1:data-table:alpha.records.list',
407
+ handle: 'data-table:alpha.records.list',
408
+ mode: 'wrapper',
409
+ propsContract: 'component-props-schema',
410
+ },
411
+ {
412
+ id: 'alpha.component-override.2:crud-form:alpha.record',
413
+ handle: 'crud-form:alpha.record',
414
+ mode: 'props',
415
+ propsContract: 'component-props-schema',
416
+ },
417
+ {
418
+ id: 'alpha.component-override.3:page:/backend/alpha',
419
+ handle: 'page:/backend/alpha',
420
+ mode: 'replace',
421
+ propsContract: 'component-props-schema',
422
+ },
423
+ ])
424
+ // A handle the framework builder catalog does not describe never becomes a
425
+ // guessed id built from the call's first argument.
426
+ expect(JSON.stringify(facts.contributions)).not.toContain('ui.detail"')
427
+ expect(JSON.stringify(facts.contributions)).not.toContain('"alpha"')
428
+ })
429
+
430
+ it('reads arrow-function convention hooks as declared surfaces', () => {
431
+ write(moduleRoot, 'data/enrichers.ts', `
432
+ const enrichList = async (records) => records
433
+ export const enrichers = [{
434
+ id: 'alpha.arrow', targetEntity: 'alpha:record',
435
+ enrichMany: enrichList,
436
+ enrichOne: async (record) => record,
437
+ }]
438
+ `)
439
+
440
+ const facts = extractModuleExtensionFacts({
441
+ moduleId: 'alpha',
442
+ moduleRoot,
443
+ sourceRoot: 'node_modules/pkg/src/modules/alpha',
444
+ entities: [{ id: 'alpha:record' }],
445
+ events: [],
446
+ apiRoutes: [],
447
+ searchEntities: [],
448
+ })
449
+
450
+ expect(facts.contributions).toEqual(expect.arrayContaining([
451
+ expect.objectContaining({
452
+ id: 'alpha.arrow',
453
+ details: expect.objectContaining({ surfaces: ['list', 'detail'] }),
454
+ }),
455
+ ]))
456
+ })
457
+
458
+ it('reads every injection-table slot form the runtime loader accepts', () => {
459
+ write(moduleRoot, 'extension-points.ts', `
460
+ export const extensionPoints = defineModuleExtensionPoints({
461
+ moduleId: 'alpha',
462
+ hosts: {
463
+ records: dataTableExtensionHost({ tableId: 'alpha.records', source: 'Records.tsx' }),
464
+ editor: crudFormExtensionHost({ entityId: 'alpha.record', source: 'Edit.tsx' }),
465
+ },
466
+ })
467
+ `)
468
+ write(moduleRoot, 'Records.tsx', 'export const tableId = extensionPoints.hosts.records.tableId')
469
+ write(moduleRoot, 'Edit.tsx', 'export const entityId = extensionPoints.hosts.editor.entityId')
470
+ write(moduleRoot, 'widgets/injection-table.ts', `
471
+ export const injectionTable = {
472
+ 'crud-form:alpha.record': 'alpha.bare-string',
473
+ 'data-table:alpha.records:bulk-actions': { widgetId: 'alpha.single-object', priority: 40, features: ['alpha.manage'] },
474
+ 'data-table:alpha.records:columns': [{ widgetId: 'alpha.array-entry', priority: 20 }],
475
+ }
476
+ `)
477
+
478
+ const facts = extractModuleExtensionFacts({
479
+ moduleId: 'alpha',
480
+ moduleRoot,
481
+ sourceRoot: 'node_modules/pkg/src/modules/alpha',
482
+ entities: [],
483
+ events: [],
484
+ apiRoutes: [],
485
+ searchEntities: [],
486
+ })
487
+ const injected = facts.contributions.filter((contribution) => contribution.source.symbol === 'injectionTable')
488
+
489
+ expect(injected).toEqual(expect.arrayContaining([
490
+ expect.objectContaining({
491
+ id: 'alpha.bare-string@crud-form:alpha.record',
492
+ kind: 'crud-form',
493
+ features: [],
494
+ placement: undefined,
495
+ details: expect.objectContaining({ entityId: 'alpha.record', payload: 'render' }),
496
+ }),
497
+ expect.objectContaining({
498
+ id: 'alpha.single-object@data-table:alpha.records:bulk-actions',
499
+ kind: 'data-table',
500
+ features: ['alpha.manage'],
501
+ placement: { priority: 40 },
502
+ details: expect.objectContaining({ payload: 'bulk-action', tableId: 'alpha.records', executionGuard: 'both' }),
503
+ }),
504
+ expect.objectContaining({
505
+ id: 'alpha.array-entry@data-table:alpha.records:columns',
506
+ kind: 'data-table',
507
+ placement: { priority: 20 },
508
+ details: expect.objectContaining({ payload: 'column', executionGuard: 'host' }),
509
+ }),
510
+ ]))
511
+ expect(injected).toHaveLength(3)
512
+ })
513
+
514
+ it('reports declarations whose authoritative source no longer binds the host key', () => {
515
+ write(moduleRoot, 'extension-points.ts', `
516
+ export const extensionPoints = defineModuleExtensionPoints({
517
+ moduleId: 'alpha',
518
+ hosts: { records: dataTableExtensionHost({ tableId: 'alpha.records', source: 'Records.tsx' }) },
519
+ })
520
+ `)
521
+ write(moduleRoot, 'Records.tsx', "export const tableId = 'alpha.records'")
522
+
523
+ const facts = extractModuleExtensionFacts({
524
+ moduleId: 'alpha',
525
+ moduleRoot,
526
+ sourceRoot: 'node_modules/pkg/src/modules/alpha',
527
+ entities: [],
528
+ events: [],
529
+ apiRoutes: [],
530
+ searchEntities: [],
531
+ })
532
+
533
+ expect(facts.unresolved).toEqual([
534
+ expect.objectContaining({ key: 'alpha.records', reason: 'unbound-declaration' }),
535
+ ])
536
+ expect(facts.hosts.filter((host) => host.key.startsWith('records.')).every((host) => !host.bound)).toBe(true)
537
+ })
538
+
539
+ it('fails closed when a correlated first-party target remains unresolved', () => {
540
+ expect(() => assertNoUnresolvedExtensionTargets({
541
+ alpha: {
542
+ hosts: [],
543
+ contributions: [],
544
+ unresolved: [{
545
+ key: 'alpha.widget:data-table:alpha.missing',
546
+ source: { path: 'widgets/injection-table.ts' },
547
+ reason: 'unresolved-first-party-target',
548
+ }],
549
+ },
550
+ })).toThrow('alpha:alpha.widget:data-table:alpha.missing')
551
+ })
552
+
553
+ it('does not trust a claimed fact reference before matching a known first-party host', () => {
554
+ write(moduleRoot, 'data/enrichers.ts', `
555
+ export const enrichers = [{
556
+ id: 'alpha.typo', targetEntity: 'alpha.missing', async enrichOne(record) { return record },
557
+ }]
558
+ `)
559
+ const surface = extractModuleExtensionFacts({
560
+ moduleId: 'alpha',
561
+ moduleRoot,
562
+ sourceRoot: 'node_modules/pkg/src/modules/alpha',
563
+ entities: [{ id: 'alpha:record' }],
564
+ events: [],
565
+ apiRoutes: [],
566
+ searchEntities: [],
567
+ })
568
+ const correlated = correlateModuleExtensionFacts({
569
+ surfacesByModule: { alpha: surface },
570
+ entityIds: new Set(['alpha:record']),
571
+ eventIds: new Set(),
572
+ apiRoutes: new Set(),
573
+ commandIds: new Set(),
574
+ })
575
+
576
+ expect(() => assertNoUnresolvedExtensionTargets(correlated)).toThrow('alpha.typo:alpha.missing')
577
+ })
578
+
579
+ it('rejects wildcard targets that match no known first-party contract', () => {
580
+ const correlated = correlateModuleExtensionFacts({
581
+ surfacesByModule: {
582
+ alpha: {
583
+ hosts: [],
584
+ contributions: [{
585
+ id: 'alpha.typo-pattern',
586
+ kind: 'command-interceptor',
587
+ targets: [{ id: 'alpha.typo.*', resolution: 'unresolved' }],
588
+ scopeContract: 'tenant-and-organization',
589
+ source: { path: 'commands/interceptors.ts' },
590
+ details: { targetCommand: 'alpha.typo.*', phases: ['before-execute'] },
591
+ }],
592
+ unresolved: [],
593
+ },
594
+ },
595
+ entityIds: new Set(),
596
+ eventIds: new Set(),
597
+ apiRoutes: new Set(),
598
+ commandIds: new Set(['alpha.records.update']),
599
+ })
600
+
601
+ expect(() => assertNoUnresolvedExtensionTargets(correlated)).toThrow('alpha.typo-pattern:alpha.typo.*')
602
+ })
603
+ })