@open-mercato/cli 0.6.7-develop.6795.1.8a3f27921c → 0.6.7-develop.6824.1.21fff90682

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 (102) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/build.mjs +1 -1
  3. package/dist/agentic/guides/module-facts.json +90266 -20749
  4. package/dist/agentic/guides/modules/ai_assistant.md +170 -60
  5. package/dist/agentic/guides/modules/api_docs.md +26 -1
  6. package/dist/agentic/guides/modules/api_keys.md +57 -17
  7. package/dist/agentic/guides/modules/attachments.md +72 -19
  8. package/dist/agentic/guides/modules/audit_logs.md +95 -29
  9. package/dist/agentic/guides/modules/auth.md +218 -83
  10. package/dist/agentic/guides/modules/business_rules.md +108 -34
  11. package/dist/agentic/guides/modules/catalog.md +386 -157
  12. package/dist/agentic/guides/modules/channel_gmail.md +55 -6
  13. package/dist/agentic/guides/modules/channel_imap.md +55 -6
  14. package/dist/agentic/guides/modules/checkout.md +254 -129
  15. package/dist/agentic/guides/modules/communication_channels.md +280 -109
  16. package/dist/agentic/guides/modules/configs.md +68 -11
  17. package/dist/agentic/guides/modules/content.md +26 -1
  18. package/dist/agentic/guides/modules/currencies.md +109 -44
  19. package/dist/agentic/guides/modules/customer_accounts.md +350 -156
  20. package/dist/agentic/guides/modules/customers.md +642 -294
  21. package/dist/agentic/guides/modules/dashboards.md +122 -28
  22. package/dist/agentic/guides/modules/data_sync.md +117 -37
  23. package/dist/agentic/guides/modules/design_system.md +36 -2
  24. package/dist/agentic/guides/modules/dictionaries.md +78 -20
  25. package/dist/agentic/guides/modules/directory.md +110 -45
  26. package/dist/agentic/guides/modules/entities.md +95 -38
  27. package/dist/agentic/guides/modules/events.md +31 -1
  28. package/dist/agentic/guides/modules/feature_toggles.md +79 -12
  29. package/dist/agentic/guides/modules/gateway_stripe.md +61 -6
  30. package/dist/agentic/guides/modules/generators.md +17 -1
  31. package/dist/agentic/guides/modules/inbox_ops.md +171 -60
  32. package/dist/agentic/guides/modules/integrations.md +116 -30
  33. package/dist/agentic/guides/modules/messages.md +187 -66
  34. package/dist/agentic/guides/modules/notifications.md +64 -9
  35. package/dist/agentic/guides/modules/onboarding.md +47 -8
  36. package/dist/agentic/guides/modules/payment_gateways.md +133 -48
  37. package/dist/agentic/guides/modules/perspectives.md +50 -12
  38. package/dist/agentic/guides/modules/planner.md +102 -32
  39. package/dist/agentic/guides/modules/portal.md +58 -15
  40. package/dist/agentic/guides/modules/progress.md +78 -30
  41. package/dist/agentic/guides/modules/query_index.md +115 -53
  42. package/dist/agentic/guides/modules/record_locks.md +178 -59
  43. package/dist/agentic/guides/modules/resources.md +155 -78
  44. package/dist/agentic/guides/modules/sales.md +620 -257
  45. package/dist/agentic/guides/modules/scheduler.md +95 -19
  46. package/dist/agentic/guides/modules/search.md +124 -20
  47. package/dist/agentic/guides/modules/security.md +251 -105
  48. package/dist/agentic/guides/modules/shipping_carriers.md +106 -37
  49. package/dist/agentic/guides/modules/sso.md +121 -49
  50. package/dist/agentic/guides/modules/staff.md +314 -126
  51. package/dist/agentic/guides/modules/storage_s3.md +55 -6
  52. package/dist/agentic/guides/modules/sync_akeneo.md +54 -4
  53. package/dist/agentic/guides/modules/sync_excel.md +49 -11
  54. package/dist/agentic/guides/modules/system_status_overlays.md +33 -5
  55. package/dist/agentic/guides/modules/translations.md +76 -21
  56. package/dist/agentic/guides/modules/webhooks.md +166 -82
  57. package/dist/agentic/guides/modules/wms.md +383 -223
  58. package/dist/agentic/guides/modules/workflows.md +248 -112
  59. package/dist/agentic/guides/upstream/AGENTS.md +1 -1
  60. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +2 -0
  61. package/dist/agentic/guides/upstream/manifest.json +3 -3
  62. package/dist/agentic/shared/AGENTS.md.template +6 -5
  63. package/dist/agentic/shared/ai/harness/README.md +2 -2
  64. package/dist/agentic/shared/ai/harness/RELEASE.md +4 -4
  65. package/dist/agentic/shared/ai/harness/cases.json +94 -1
  66. package/dist/agentic/shared/ai/harness/cases.schema.json +3 -3
  67. package/dist/agentic/shared/ai/harness/validators.json +1 -1
  68. package/dist/agentic/shared/ai/lessons/_template.md +16 -0
  69. package/dist/agentic/shared/ai/lessons.md +26 -3
  70. package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +7 -6
  71. package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +1 -1
  72. package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +4 -2
  73. package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
  74. package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +37 -12
  75. package/dist/lib/agentic-setup.js +2 -2
  76. package/dist/lib/agentic-setup.js.map +2 -2
  77. package/dist/lib/generators/module-extension-facts.js +652 -24
  78. package/dist/lib/generators/module-extension-facts.js.map +3 -3
  79. package/dist/lib/generators/module-fact-sources.js +70 -0
  80. package/dist/lib/generators/module-fact-sources.js.map +7 -0
  81. package/dist/lib/generators/module-facts.js +1001 -26
  82. package/dist/lib/generators/module-facts.js.map +3 -3
  83. package/dist/lib/generators/module-override-targets.js +688 -0
  84. package/dist/lib/generators/module-override-targets.js.map +7 -0
  85. package/dist/lib/testing/integration.js +5 -0
  86. package/dist/lib/testing/integration.js.map +2 -2
  87. package/package.json +5 -5
  88. package/src/lib/__tests__/agentic-setup.ownership.test.ts +6 -0
  89. package/src/lib/agentic-setup.ts +6 -2
  90. package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
  91. package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
  92. package/src/lib/generators/__tests__/module-extension-facts.test.ts +4 -2
  93. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +37 -3
  94. package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
  95. package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
  96. package/src/lib/generators/__tests__/module-override-targets.test.ts +304 -0
  97. package/src/lib/generators/module-extension-facts.ts +923 -27
  98. package/src/lib/generators/module-fact-sources.ts +108 -0
  99. package/src/lib/generators/module-facts.ts +1360 -25
  100. package/src/lib/generators/module-override-targets.ts +863 -0
  101. package/src/lib/testing/__tests__/integration.test.ts +4 -0
  102. package/src/lib/testing/integration.ts +5 -0
@@ -493,7 +493,7 @@ function factRefHost(options) {
493
493
  ...options.scopeContract ? { scopeContract: options.scopeContract } : {},
494
494
  bound: true,
495
495
  stability: "stable",
496
- source: { kind: "fact-ref", factSection: options.factSection, factKey: options.id }
496
+ source: { kind: "fact-ref", factSection: options.factSection, factKey: options.factKey ?? options.id }
497
497
  };
498
498
  }
499
499
  function sourceFilesBelow(directory) {
@@ -527,6 +527,7 @@ function apiExtensionHostIds(moduleId, moduleRoot) {
527
527
  const cached = activeExtractionCache?.apiExtensionHostIds.get(cacheKey);
528
528
  if (cached) return cached;
529
529
  const entityIds = /* @__PURE__ */ new Set();
530
+ const entitySourceFiles = {};
530
531
  const commandIds = /* @__PURE__ */ new Set();
531
532
  const routeIds = /* @__PURE__ */ new Set();
532
533
  for (const filePath of sourceFilesBelow(path.join(moduleRoot, "api"))) {
@@ -545,13 +546,19 @@ function apiExtensionHostIds(moduleId, moduleRoot) {
545
546
  if (name === "enrichers") {
546
547
  const config = staticValue(node.initializer, context);
547
548
  const entityId = isStaticObject(config) ? stringValue(config.entityId) : void 0;
548
- if (entityId) entityIds.add(entityId);
549
+ if (entityId) {
550
+ entityIds.add(entityId);
551
+ if (!entitySourceFiles[entityId]) entitySourceFiles[entityId] = filePath;
552
+ }
549
553
  }
550
554
  }
551
555
  if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "validateCrudMutationGuard") {
552
556
  const input = node.arguments[1] ? staticValue(node.arguments[1], context) : void 0;
553
557
  const entityId = isStaticObject(input) ? stringValue(input.resourceKind) : void 0;
554
- if (entityId) entityIds.add(entityId);
558
+ if (entityId) {
559
+ entityIds.add(entityId);
560
+ if (!entitySourceFiles[entityId]) entitySourceFiles[entityId] = filePath;
561
+ }
555
562
  }
556
563
  node.forEachChild(visit);
557
564
  };
@@ -559,6 +566,7 @@ function apiExtensionHostIds(moduleId, moduleRoot) {
559
566
  }
560
567
  const result = {
561
568
  entityIds: [...entityIds].sort((left, right) => left.localeCompare(right)),
569
+ entitySourceFiles,
562
570
  commandIds: [...commandIds].sort((left, right) => left.localeCompare(right)),
563
571
  routeIds: [...routeIds].sort((left, right) => left.localeCompare(right))
564
572
  };
@@ -580,6 +588,104 @@ function extractKnownCommandIds(moduleId, moduleRoot) {
580
588
  const apiHosts = apiExtensionHostIds(moduleId, moduleRoot);
581
589
  return moduleCommandIds(moduleRoot, apiHosts.commandIds);
582
590
  }
591
+ const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
592
+ const API_INTERCEPTOR_BRIDGE_CALLS = {
593
+ runApiInterceptorsBefore: "before",
594
+ runApiInterceptorsAfter: "after"
595
+ };
596
+ function isHttpMethodName(value) {
597
+ return HTTP_METHODS.includes(value);
598
+ }
599
+ function exportedHttpMethods(file) {
600
+ const methods = /* @__PURE__ */ new Set();
601
+ const consider = (name) => {
602
+ if (name && isHttpMethodName(name)) methods.add(name);
603
+ };
604
+ const considerBindingName = (name) => {
605
+ if (ts.isIdentifier(name)) {
606
+ consider(name.text);
607
+ return;
608
+ }
609
+ for (const element of name.elements) {
610
+ if (ts.isBindingElement(element)) considerBindingName(element.name);
611
+ }
612
+ };
613
+ const visit = (node) => {
614
+ if (ts.isVariableStatement(node) && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
615
+ for (const declaration of node.declarationList.declarations) {
616
+ considerBindingName(declaration.name);
617
+ }
618
+ }
619
+ if (ts.isFunctionDeclaration(node) && node.name && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
620
+ consider(node.name.text);
621
+ }
622
+ if (ts.isExportDeclaration(node) && node.exportClause && ts.isNamedExports(node.exportClause)) {
623
+ for (const element of node.exportClause.elements) consider(element.name.text);
624
+ }
625
+ node.forEachChild(visit);
626
+ };
627
+ file.forEachChild(visit);
628
+ return [...methods].sort((left, right) => left.localeCompare(right));
629
+ }
630
+ function extractApiRouteInterceptorBridges(file, context) {
631
+ const exportedMethods = exportedHttpMethods(file);
632
+ const phasesByMethod = /* @__PURE__ */ new Map();
633
+ const addPhase = (method, phase) => {
634
+ const phases = phasesByMethod.get(method) ?? /* @__PURE__ */ new Set();
635
+ phases.add(phase);
636
+ phasesByMethod.set(method, phases);
637
+ };
638
+ const visit = (node) => {
639
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
640
+ const calleeName = node.expression.text;
641
+ if (calleeName === "makeCrudRoute") {
642
+ for (const method of exportedMethods) {
643
+ addPhase(method, "before");
644
+ addPhase(method, "after");
645
+ }
646
+ }
647
+ const phase = API_INTERCEPTOR_BRIDGE_CALLS[calleeName];
648
+ if (phase) {
649
+ const callOptions = node.arguments[0] ? staticValue(node.arguments[0], context) : void 0;
650
+ const declaredMethod = isStaticObject(callOptions) ? stringValue(callOptions.method) : void 0;
651
+ const methods = declaredMethod && isHttpMethodName(declaredMethod) ? [declaredMethod] : exportedMethods;
652
+ for (const method of methods) addPhase(method, phase);
653
+ }
654
+ }
655
+ node.forEachChild(visit);
656
+ };
657
+ file.forEachChild(visit);
658
+ return [...phasesByMethod.entries()].map(([method, phases]) => ({
659
+ method,
660
+ phases: [...phases].sort((left, right) => left.localeCompare(right))
661
+ })).sort((left, right) => left.method.localeCompare(right.method));
662
+ }
663
+ function extractActivationTargetOwners(options) {
664
+ const apiRoutes = [];
665
+ for (const filePath of sourceFilesBelow(path.join(options.moduleRoot, "api"))) {
666
+ const routeId = apiRouteId(options.moduleId, options.moduleRoot, filePath);
667
+ if (!routeId) continue;
668
+ const file = sourceFile(filePath);
669
+ const bridges = file ? extractApiRouteInterceptorBridges(file, buildStaticContext(file)) : [];
670
+ apiRoutes.push({
671
+ id: routeId,
672
+ source: { sourcePath: portablePath(options.moduleRoot, options.sourceRoot, filePath) },
673
+ bridges
674
+ });
675
+ }
676
+ const commands = [];
677
+ const seenCommands = /* @__PURE__ */ new Set();
678
+ for (const filePath of sourceFilesBelow(path.join(options.moduleRoot, "commands"))) {
679
+ if (path.basename(filePath).replace(/\.(?:ts|tsx)$/, "") === "interceptors") continue;
680
+ const source = { sourcePath: portablePath(options.moduleRoot, options.sourceRoot, filePath) };
681
+ for (const id of extractCommandIdsFromSource(filePath)) {
682
+ if (seenCommands.has(id)) continue;
683
+ seenCommands.add(id);
684
+ commands.push({ id, source });
685
+ }
686
+ }
687
+ return { apiRoutes, commands };
688
+ }
583
689
  function extractFactRefHosts(options) {
584
690
  const hosts = [];
585
691
  for (const entity of options.entities) {
@@ -631,6 +737,7 @@ function extractFactRefHosts(options) {
631
737
  family: "query-lifecycle",
632
738
  capabilities: ["sync-subscriber"],
633
739
  factSection: "searchEntities",
740
+ factKey: entityId,
634
741
  phases: phase === "querying" ? ["block", "query-transform"] : ["result-transform", "scope-reapply"],
635
742
  activation: "caller-opt-in",
636
743
  scopeContract: "tenant-and-organization-reapplied-after-result"
@@ -639,7 +746,8 @@ function extractFactRefHosts(options) {
639
746
  }
640
747
  const apiHosts = apiExtensionHostIds(options.moduleId, options.moduleRoot);
641
748
  for (const entityId of apiHosts.entityIds) {
642
- hosts.push(factRefHost({
749
+ const declaringFile = apiHosts.entitySourceFiles[entityId];
750
+ const host = factRefHost({
643
751
  key: `api-entity.${entityId}`,
644
752
  id: entityId,
645
753
  moduleId: options.moduleId,
@@ -648,7 +756,15 @@ function extractFactRefHosts(options) {
648
756
  factSection: "apiRoutes",
649
757
  activation: "host-opt-in",
650
758
  scopeContract: "tenant-and-organization"
651
- }));
759
+ });
760
+ hosts.push(declaringFile ? {
761
+ ...host,
762
+ source: {
763
+ kind: "declaration",
764
+ path: portablePath(options.moduleRoot, options.sourceRoot, declaringFile),
765
+ symbol: entityId
766
+ }
767
+ } : host);
652
768
  }
653
769
  return sortHosts([...new Map(hosts.map((host) => [`${host.family}:${host.id}`, host])).values()]);
654
770
  }
@@ -738,7 +854,8 @@ function extractEnrichers(options) {
738
854
  const targetEntity = stringValue(entry.targetEntity);
739
855
  if (!targetEntity) return null;
740
856
  const timeoutMs = numberValue(entry.timeout) ?? 2e3;
741
- const queryEngine = isStaticObject(entry.queryEngine) ? entry.queryEngine : null;
857
+ const queryEngineConfig = isStaticObject(entry.queryEngine) ? entry.queryEngine : null;
858
+ const queryEngine = queryEngineConfig && booleanValue(queryEngineConfig.enabled) === true ? queryEngineConfig : null;
742
859
  const surfaces = [entry.enrichMany === true ? "list" : null, entry.enrichOne === true ? "detail" : null].filter((value) => value !== null);
743
860
  const base = contributionBase(id, sourcePath, id);
744
861
  return {
@@ -879,14 +996,13 @@ function extractSubscribers(options) {
879
996
  const directory = path.join(options.moduleRoot, "subscribers");
880
997
  if (!fs.existsSync(directory)) return [];
881
998
  const facts = [];
882
- for (const entry of fs.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name))) {
883
- if (!entry.isFile() || !/\.(?:ts|tsx)$/.test(entry.name) || /\.(?:test|spec)\./.test(entry.name)) continue;
884
- const filePath = path.join(directory, entry.name);
999
+ for (const filePath of sourceFilesBelow(directory)) {
885
1000
  const metadata = readRootObject(filePath, "metadata");
886
1001
  if (!metadata) continue;
887
1002
  const event = stringValue(metadata.event);
888
1003
  if (!event) continue;
889
- const subscriberId = stringValue(metadata.id) ?? `${options.moduleId}:${entry.name.replace(/\.tsx?$/, "")}`;
1004
+ const relativeStem = path.relative(directory, filePath).replace(/\.tsx?$/, "").split(path.sep).join("/");
1005
+ const subscriberId = stringValue(metadata.id) ?? `${options.moduleId}:${relativeStem}`;
890
1006
  const persistent = booleanValue(metadata.persistent) ?? false;
891
1007
  const sync = booleanValue(metadata.sync) ?? !persistent;
892
1008
  const contribution = contributionBase(subscriberId, portablePath(options.moduleRoot, options.sourceRoot, filePath), "metadata");
@@ -931,7 +1047,8 @@ function extractNotificationReactions(options) {
931
1047
  build(entry, sourcePath, index) {
932
1048
  const notificationType = stringValue(entry.notificationType);
933
1049
  if (!notificationType) return null;
934
- const id = stringValue(entry.id) ?? `${options.moduleId}.notification-handler.${index}`;
1050
+ const declaredId = stringValue(entry.id);
1051
+ const id = declaredId ?? `${options.moduleId}.notification-handler.${index}`;
935
1052
  const contribution = contributionBase(id, sourcePath, "notificationHandlers");
936
1053
  return {
937
1054
  ...contribution,
@@ -942,7 +1059,8 @@ function extractNotificationReactions(options) {
942
1059
  details: {
943
1060
  transports: ["notification-effect"],
944
1061
  hooks: ["useNotificationEffect"],
945
- audienceScopeContract: "tenant-organization-user-role-and-customer"
1062
+ audienceScopeContract: "tenant-organization-user-role-and-customer",
1063
+ ...declaredId ? { overrideKey: declaredId } : {}
946
1064
  }
947
1065
  };
948
1066
  }
@@ -1007,16 +1125,32 @@ function extractSpecializedRegistries(options) {
1007
1125
  }
1008
1126
  const integrationPath = conventionPath(options.moduleRoot, "integration.ts");
1009
1127
  if (integrationPath) {
1010
- const integration = readRootObject(integrationPath, "integration");
1011
1128
  const sourcePath = portablePath(options.moduleRoot, options.sourceRoot, integrationPath);
1012
- const integrationId = integration ? stringValue(integration.id) : void 0;
1013
- if (integrationId) add(integrationId, "integration", sourcePath, "integrations", "integration");
1014
- const providerKey = integration ? stringValue(integration.providerKey) : void 0;
1015
- const category = integration ? stringValue(integration.category) : void 0;
1016
- const categoryRegistry = category === "payment" ? "payment" : category === "shipping" ? "shipping" : category === "currency" ? "currency" : null;
1017
- if (providerKey && categoryRegistry) {
1018
- const specialistRoute = categoryRegistry === "payment" ? "paymentGateways" : categoryRegistry === "shipping" ? "shippingCarriers" : "currencies";
1019
- add(providerKey, categoryRegistry, sourcePath, specialistRoute, "integration.providerKey");
1129
+ const addIntegration = (integration, symbol, bundleId) => {
1130
+ const integrationId = stringValue(integration.id);
1131
+ if (integrationId) {
1132
+ add(integrationId, "integration", sourcePath, "integrations", bundleId ? `${symbol}[${bundleId}]` : symbol);
1133
+ }
1134
+ const providerKey = stringValue(integration.providerKey);
1135
+ const category = stringValue(integration.category);
1136
+ const categoryRegistry = category === "payment" ? "payment" : category === "shipping" ? "shipping" : category === "currency" ? "currency" : null;
1137
+ if (providerKey && categoryRegistry) {
1138
+ const specialistRoute = categoryRegistry === "payment" ? "paymentGateways" : categoryRegistry === "shipping" ? "shippingCarriers" : "currencies";
1139
+ add(providerKey, categoryRegistry, sourcePath, specialistRoute, `${symbol}.providerKey`);
1140
+ }
1141
+ };
1142
+ const singular = readRootObject(integrationPath, "integration");
1143
+ if (singular) addIntegration(singular, "integration");
1144
+ for (const integration of readConventionObjectArray(integrationPath, "integrations")) {
1145
+ addIntegration(integration, "integrations");
1146
+ }
1147
+ const bundle = readRootObject(integrationPath, "integrationBundle") ?? readRootObject(integrationPath, "bundle");
1148
+ if (bundle) {
1149
+ const bundleId = stringValue(bundle.id) ?? "bundle";
1150
+ const members = Array.isArray(bundle.integrations) ? bundle.integrations : [];
1151
+ for (const member of members) {
1152
+ if (isStaticObject(member)) addIntegration(member, "integrationBundle", bundleId);
1153
+ }
1020
1154
  }
1021
1155
  }
1022
1156
  const workflowsPath = conventionPath(options.moduleRoot, "workflows.ts");
@@ -1071,6 +1205,147 @@ function extractSpecializedRegistries(options) {
1071
1205
  }
1072
1206
  return facts;
1073
1207
  }
1208
+ const ACTIVATION_ADAPTERS = {
1209
+ "crud-response-enricher": { contributionKinds: ["response-enricher"], mode: "call-site-object" },
1210
+ "query-enricher": { contributionKinds: ["response-enricher"], mode: "call-site-object" },
1211
+ "mutation-guard": { contributionKinds: ["mutation-guard"], mode: "call-site-object" },
1212
+ "api-interceptor-bridge": { contributionKinds: ["api-interceptor"], mode: "host-reference" },
1213
+ "command-interceptor-bridge": { contributionKinds: ["command-interceptor"], mode: "owner-reference" },
1214
+ "widget-injection-consumer": { contributionKinds: ["widget", "data-table", "crud-form"], mode: "host-reference" },
1215
+ "component-extension-consumer": { contributionKinds: ["component-override"], mode: "host-reference" },
1216
+ "dashboard-host-consumer": { contributionKinds: ["widget"], mode: "host-reference" }
1217
+ };
1218
+ const CONTRIBUTION_ACTIVATION_CLASSIFICATION = {
1219
+ "response-enricher": ["crud-response-enricher", "query-enricher"],
1220
+ "mutation-guard": ["mutation-guard"],
1221
+ "api-interceptor": ["api-interceptor-bridge"],
1222
+ "command-interceptor": ["command-interceptor-bridge"],
1223
+ "widget": ["widget-injection-consumer", "dashboard-host-consumer"],
1224
+ "data-table": ["widget-injection-consumer"],
1225
+ "crud-form": ["widget-injection-consumer"],
1226
+ "component-override": ["component-extension-consumer"],
1227
+ "entity-extension": "capability-only",
1228
+ "subscriber": "capability-only",
1229
+ "browser-reaction": "capability-only",
1230
+ "specialized-registry": "capability-only",
1231
+ "module-override": "capability-only"
1232
+ };
1233
+ const ALL_ACTIVATION_KINDS = Object.keys(ACTIVATION_ADAPTERS);
1234
+ const ALL_CONTRIBUTION_KINDS = Object.keys(CONTRIBUTION_ACTIVATION_CLASSIFICATION);
1235
+ const MUTATION_GUARD_BRIDGE_ADAPTERS = {
1236
+ runRouteMutationGuards: { resourceArgument: "input-property" },
1237
+ validateCrudMutationGuard: { resourceArgument: "any-argument" },
1238
+ runMutationGuards: { resourceArgument: "any-argument" }
1239
+ };
1240
+ const MUTATION_GUARD_SOURCE_DIRECTORIES = ["api", "lib"];
1241
+ const MUTATION_GUARD_OPERATIONS = /* @__PURE__ */ new Set(["create", "update", "delete", "custom"]);
1242
+ function toRegistryGuardOperation(operation) {
1243
+ return operation === "custom" ? "update" : operation;
1244
+ }
1245
+ function readMutationGuardBridge(node, context) {
1246
+ const callee = node.expression;
1247
+ const calleeName = ts.isIdentifier(callee) ? callee.text : ts.isPropertyAccessExpression(callee) ? callee.name.text : null;
1248
+ const adapter = calleeName ? MUTATION_GUARD_BRIDGE_ADAPTERS[calleeName] : void 0;
1249
+ if (!adapter) return null;
1250
+ const argumentObjects = node.arguments.map((argument) => staticValue(argument, context)).filter(isStaticObject);
1251
+ const resourceObjects = adapter.resourceArgument === "input-property" ? argumentObjects.flatMap((argumentObject) => isStaticObject(argumentObject.input) ? [argumentObject.input] : []) : argumentObjects;
1252
+ for (const resourceObject of resourceObjects) {
1253
+ const entityId = stringValue(resourceObject.resourceKind) ?? stringValue(resourceObject.entityId);
1254
+ if (!entityId) continue;
1255
+ const declaredOperation = stringValue(resourceObject.operation);
1256
+ const operations = declaredOperation && MUTATION_GUARD_OPERATIONS.has(declaredOperation) ? [toRegistryGuardOperation(declaredOperation)] : [];
1257
+ return { entityId, operations };
1258
+ }
1259
+ return null;
1260
+ }
1261
+ function nodeLine(file, node) {
1262
+ return file.getLineAndCharacterOfPosition(node.getStart(file)).line + 1;
1263
+ }
1264
+ function activationId(host, kind) {
1265
+ const method = host.method ? `:${host.method}` : "";
1266
+ return `${host.kind}:${host.id}${method}:${kind}`;
1267
+ }
1268
+ function sortActivations(activations) {
1269
+ return [...activations].sort((left, right) => left.id.localeCompare(right.id));
1270
+ }
1271
+ function isQueryWithExtensionsCall(node, context) {
1272
+ if (!ts.isCallExpression(node)) return false;
1273
+ const callee = node.expression;
1274
+ const calleeName = ts.isPropertyAccessExpression(callee) ? callee.name.text : ts.isIdentifier(callee) ? callee.text : null;
1275
+ if (calleeName !== "query") return false;
1276
+ const queryOptions = node.arguments[1] ? staticValue(node.arguments[1], context) : void 0;
1277
+ return isStaticObject(queryOptions) && queryOptions.extensions !== void 0;
1278
+ }
1279
+ function extractCallSiteActivations(options) {
1280
+ const activations = [];
1281
+ const byId = /* @__PURE__ */ new Map();
1282
+ const push = (activation) => {
1283
+ const existing = byId.get(activation.id);
1284
+ if (existing) {
1285
+ if (!existing.operations || !activation.operations) {
1286
+ delete existing.operations;
1287
+ } else {
1288
+ existing.operations = [.../* @__PURE__ */ new Set([...existing.operations, ...activation.operations])].sort((left, right) => left.localeCompare(right));
1289
+ }
1290
+ return;
1291
+ }
1292
+ byId.set(activation.id, activation);
1293
+ activations.push(activation);
1294
+ };
1295
+ const scannedFiles = MUTATION_GUARD_SOURCE_DIRECTORIES.flatMap((directory) => sourceFilesBelow(path.join(options.moduleRoot, directory)));
1296
+ for (const filePath of scannedFiles) {
1297
+ const file = sourceFile(filePath);
1298
+ if (!file) continue;
1299
+ const context = buildStaticContext(file);
1300
+ const sourcePath = portablePath(options.moduleRoot, options.sourceRoot, filePath);
1301
+ const visit = (node) => {
1302
+ if (ts.isPropertyAssignment(node) && propertyName(node.name) === "enrichers") {
1303
+ const config = staticValue(node.initializer, context);
1304
+ const entityId = isStaticObject(config) ? stringValue(config.entityId) : void 0;
1305
+ if (entityId) {
1306
+ const host = { kind: "entity", id: entityId, moduleId: options.moduleId };
1307
+ push({
1308
+ id: activationId(host, "crud-response-enricher"),
1309
+ kind: "crud-response-enricher",
1310
+ host,
1311
+ contributionKinds: ["response-enricher"],
1312
+ source: { sourcePath, line: nodeLine(file, node) }
1313
+ });
1314
+ }
1315
+ }
1316
+ if (isQueryWithExtensionsCall(node, context)) {
1317
+ const entityId = stringValue(staticValue(node.arguments[0], context));
1318
+ if (entityId) {
1319
+ const host = { kind: "entity", id: entityId, moduleId: options.moduleId };
1320
+ push({
1321
+ id: activationId(host, "query-enricher"),
1322
+ kind: "query-enricher",
1323
+ host,
1324
+ contributionKinds: ["response-enricher"],
1325
+ source: { sourcePath, line: nodeLine(file, node) }
1326
+ });
1327
+ }
1328
+ }
1329
+ if (ts.isCallExpression(node)) {
1330
+ const bridge = readMutationGuardBridge(node, context);
1331
+ if (bridge) {
1332
+ const host = { kind: "entity", id: bridge.entityId, moduleId: options.moduleId };
1333
+ push({
1334
+ id: activationId(host, "mutation-guard"),
1335
+ kind: "mutation-guard",
1336
+ host,
1337
+ contributionKinds: ["mutation-guard"],
1338
+ ...bridge.operations.length > 0 ? { operations: bridge.operations } : {},
1339
+ source: { sourcePath, line: nodeLine(file, node) }
1340
+ });
1341
+ }
1342
+ }
1343
+ node.forEachChild(visit);
1344
+ };
1345
+ file.forEachChild(visit);
1346
+ }
1347
+ return sortActivations(activations);
1348
+ }
1074
1349
  function extractModuleExtensionFacts(options) {
1075
1350
  const declared = extractDeclaredHosts(options);
1076
1351
  const hosts = sortHosts([...declared.hosts, ...extractFactRefHosts(options)]);
@@ -1087,7 +1362,12 @@ function extractModuleExtensionFacts(options) {
1087
1362
  ...extractComponentOverrides(options),
1088
1363
  ...extractSpecializedRegistries(options)
1089
1364
  ]);
1090
- return { hosts, contributions, unresolved: declared.unresolved };
1365
+ return {
1366
+ hosts,
1367
+ contributions,
1368
+ unresolved: declared.unresolved,
1369
+ activations: extractCallSiteActivations(options)
1370
+ };
1091
1371
  }
1092
1372
  function patternMatches(pattern, targetId) {
1093
1373
  const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\\\{[^}]+\\\}/g, "[^:]+").replace(/\*/g, ".*");
@@ -1128,7 +1408,7 @@ function correlateExtensionTarget(targetFact, options) {
1128
1408
  return { id: targetFact.id, resolution: "fact-ref", factRef: { factSection: "events", factKey: targetFact.id } };
1129
1409
  }
1130
1410
  if (options.commandIds?.has(targetFact.id)) {
1131
- return factTarget(targetFact.id, "commands");
1411
+ return factTarget(targetFact.id, "ownedContracts.command");
1132
1412
  }
1133
1413
  const route = [...options.apiRoutes].find((apiRoute) => apiRoute === targetFact.id || apiRoute.endsWith(`/${targetFact.id}`));
1134
1414
  if (route) return { id: targetFact.id, resolution: "fact-ref", factRef: { factSection: "apiRoutes", factKey: route } };
@@ -1164,11 +1444,343 @@ function correlateModuleExtensionFacts(options) {
1164
1444
  result[moduleId] = {
1165
1445
  hosts: sortHosts([...surface.hosts]),
1166
1446
  contributions: sortContributions(contributions),
1167
- unresolved: unresolved.sort((left, right) => left.key.localeCompare(right.key))
1447
+ unresolved: unresolved.sort((left, right) => left.key.localeCompare(right.key)),
1448
+ activations: sortActivations([...surface.activations ?? []])
1449
+ };
1450
+ }
1451
+ return result;
1452
+ }
1453
+ function normalizeRouteId(routeId) {
1454
+ return routeId.replace(/^\//, "");
1455
+ }
1456
+ const FRAMEWORK_HOST_OWNER = "framework";
1457
+ const FRAMEWORK_HOST_FAMILIES_BY_ACTIVATION = {
1458
+ "dashboard-host-consumer": ["dashboard"],
1459
+ "widget-injection-consumer": ["menu", "generic", "crud-form", "data-table", "notification", "integration"],
1460
+ "component-extension-consumer": ["component-handle"]
1461
+ };
1462
+ function findFrameworkHost(targetId, activationKind) {
1463
+ const families = FRAMEWORK_HOST_FAMILIES_BY_ACTIVATION[activationKind];
1464
+ if (!families) return null;
1465
+ const candidates = FRAMEWORK_HOSTS.filter((host) => families.includes(host.family));
1466
+ const exact = candidates.find((host) => host.id === targetId);
1467
+ if (exact) return exact;
1468
+ return candidates.find((host) => host.resolution === "pattern" && patternMatches(host.id, targetId)) ?? null;
1469
+ }
1470
+ function operationsIntersect(activationOperations, contributionOperations) {
1471
+ if (!activationOperations || activationOperations.length === 0) return true;
1472
+ if (!contributionOperations || contributionOperations.length === 0) return true;
1473
+ return contributionOperations.some((operation) => activationOperations.includes(operation));
1474
+ }
1475
+ function contributionSourceRef(contribution) {
1476
+ return {
1477
+ sourcePath: contribution.source.path,
1478
+ ...contribution.source.symbol ? { exportName: contribution.source.symbol } : {}
1479
+ };
1480
+ }
1481
+ function targetRefKindFor(contribution, targetId) {
1482
+ if (targetId === "*" || targetId.includes("*") || targetId.includes("{")) return "wildcard";
1483
+ switch (contribution.kind) {
1484
+ case "response-enricher":
1485
+ case "mutation-guard":
1486
+ case "entity-extension":
1487
+ return "entity";
1488
+ case "api-interceptor":
1489
+ return "api-route";
1490
+ case "command-interceptor":
1491
+ return "command";
1492
+ case "component-override":
1493
+ return "component";
1494
+ case "subscriber":
1495
+ return "event";
1496
+ case "browser-reaction":
1497
+ return contribution.details.transports.includes("notification-effect") ? "notification" : "event";
1498
+ case "widget":
1499
+ case "data-table":
1500
+ case "crud-form":
1501
+ return "widget-spot";
1502
+ case "specialized-registry":
1503
+ case "module-override":
1504
+ return "module";
1505
+ default:
1506
+ return "module";
1507
+ }
1508
+ }
1509
+ function correlateIncomingExtensions(options) {
1510
+ const moduleIds = Object.keys(options.surfacesByModule).sort((left, right) => left.localeCompare(right));
1511
+ const apiRouteOwners = options.apiRouteOwners ?? /* @__PURE__ */ new Map();
1512
+ const commandOwners = options.commandOwners ?? /* @__PURE__ */ new Map();
1513
+ const activationIndex = /* @__PURE__ */ new Map();
1514
+ const activationKey = (kind, hostKind, id) => {
1515
+ const normId = hostKind === "api-route" ? normalizeRouteId(id) : id;
1516
+ return JSON.stringify([kind, hostKind, normId]);
1517
+ };
1518
+ const boundHostIndex = /* @__PURE__ */ new Map();
1519
+ for (const moduleId of moduleIds) {
1520
+ const surface = options.surfacesByModule[moduleId];
1521
+ for (const activation of surface.activations ?? []) {
1522
+ const key = activationKey(activation.kind, activation.host.kind, activation.host.id);
1523
+ const bucket = activationIndex.get(key);
1524
+ if (bucket) bucket.push({ moduleId, activation });
1525
+ else activationIndex.set(key, [{ moduleId, activation }]);
1526
+ }
1527
+ for (const host of surface.hosts) {
1528
+ if (!host.bound) continue;
1529
+ if (!boundHostIndex.has(host.id)) boundHostIndex.set(host.id, { moduleId, host });
1530
+ }
1531
+ }
1532
+ const incomingByModule = {};
1533
+ const resolutionsByModule = {};
1534
+ const syntheticActivations = {};
1535
+ for (const moduleId of moduleIds) {
1536
+ incomingByModule[moduleId] = [];
1537
+ resolutionsByModule[moduleId] = [];
1538
+ syntheticActivations[moduleId] = [];
1539
+ }
1540
+ const registerSyntheticActivation = (ownerModule, activation) => {
1541
+ if (!syntheticActivations[ownerModule]) syntheticActivations[ownerModule] = [];
1542
+ if (!syntheticActivations[ownerModule].some((entry) => entry.id === activation.id)) {
1543
+ syntheticActivations[ownerModule].push(activation);
1544
+ }
1545
+ };
1546
+ const findCallSiteActivations = (contributionKind, activationKinds, targetRef, contributionOperations) => {
1547
+ const matches = [];
1548
+ for (const kind of activationKinds) {
1549
+ if (ACTIVATION_ADAPTERS[kind].mode !== "call-site-object") continue;
1550
+ const bucket = activationIndex.get(activationKey(kind, targetRef.kind, targetRef.id)) ?? [];
1551
+ for (const entry of bucket) {
1552
+ if (!entry.activation.contributionKinds.includes(contributionKind)) continue;
1553
+ if (!operationsIntersect(entry.activation.operations, contributionOperations)) continue;
1554
+ matches.push(entry);
1555
+ }
1556
+ }
1557
+ return matches.sort((left, right) => left.activation.id.localeCompare(right.activation.id));
1558
+ };
1559
+ for (const contributorModuleId of moduleIds) {
1560
+ const surface = options.surfacesByModule[contributorModuleId];
1561
+ for (const contribution of surface.contributions) {
1562
+ const classification = CONTRIBUTION_ACTIVATION_CLASSIFICATION[contribution.kind];
1563
+ for (const resolvedTarget of contribution.targets) {
1564
+ const targetKind = targetRefKindFor(contribution, resolvedTarget.id);
1565
+ const baseTarget = { kind: targetKind, id: resolvedTarget.id };
1566
+ let resolution;
1567
+ let ownerModule = null;
1568
+ let boundActivationIds = [];
1569
+ if (targetKind === "wildcard" || resolvedTarget.resolution === "pattern") {
1570
+ resolution = "wildcard";
1571
+ } else if (resolvedTarget.resolution === "optional-external") {
1572
+ resolution = "optional-target-missing";
1573
+ } else if (resolvedTarget.resolution === "unresolved") {
1574
+ resolution = "unresolved";
1575
+ } else {
1576
+ const activationKinds = classification === "capability-only" ? [] : classification;
1577
+ const callSiteMatches = findCallSiteActivations(
1578
+ contribution.kind,
1579
+ activationKinds,
1580
+ baseTarget,
1581
+ contribution.operations
1582
+ );
1583
+ if (callSiteMatches.length > 0) {
1584
+ resolution = "bound";
1585
+ ownerModule = callSiteMatches[0].moduleId;
1586
+ boundActivationIds = callSiteMatches.map((entry) => entry.activation.id);
1587
+ } else {
1588
+ const referenceBinding = resolveReferenceBinding({
1589
+ contribution,
1590
+ activationKinds,
1591
+ targetRef: baseTarget,
1592
+ boundHostIndex,
1593
+ apiRouteOwners,
1594
+ commandOwners
1595
+ });
1596
+ if (referenceBinding) {
1597
+ resolution = "bound";
1598
+ const isFrameworkHost = referenceBinding.ownerModule === FRAMEWORK_HOST_OWNER;
1599
+ ownerModule = isFrameworkHost ? null : referenceBinding.ownerModule;
1600
+ const activationOwner = isFrameworkHost ? contributorModuleId : referenceBinding.ownerModule;
1601
+ for (const activation of referenceBinding.activations) {
1602
+ registerSyntheticActivation(activationOwner, activation);
1603
+ }
1604
+ boundActivationIds = referenceBinding.activations.map((activation) => activation.id);
1605
+ } else {
1606
+ const hostOwner = boundHostIndex.get(resolvedTarget.id);
1607
+ if (hostOwner) {
1608
+ resolution = "capability-only";
1609
+ ownerModule = hostOwner.moduleId;
1610
+ } else {
1611
+ resolution = "capability-only";
1612
+ ownerModule = null;
1613
+ }
1614
+ }
1615
+ }
1616
+ }
1617
+ const resolutionTarget = ownerModule ? { ...baseTarget, moduleId: ownerModule } : baseTarget;
1618
+ resolutionsByModule[contributorModuleId].push({
1619
+ contributionId: contribution.id,
1620
+ target: resolutionTarget,
1621
+ resolution,
1622
+ activationIds: [...boundActivationIds].sort((left, right) => left.localeCompare(right))
1623
+ });
1624
+ if (ownerModule && ownerModule !== contributorModuleId && (resolution === "bound" || resolution === "capability-only")) {
1625
+ const source = contributionSourceRef(contribution);
1626
+ const incomingTarget = { ...baseTarget, moduleId: ownerModule };
1627
+ if (resolution === "bound" && boundActivationIds.length > 0) {
1628
+ for (const activationRefId of boundActivationIds) {
1629
+ incomingByModule[ownerModule].push({
1630
+ contributionId: contribution.id,
1631
+ contributionKind: contribution.kind,
1632
+ contributorModuleId,
1633
+ target: incomingTarget,
1634
+ activationId: activationRefId,
1635
+ resolution: "bound",
1636
+ source
1637
+ });
1638
+ }
1639
+ } else {
1640
+ incomingByModule[ownerModule].push({
1641
+ contributionId: contribution.id,
1642
+ contributionKind: contribution.kind,
1643
+ contributorModuleId,
1644
+ target: incomingTarget,
1645
+ resolution,
1646
+ source
1647
+ });
1648
+ }
1649
+ }
1650
+ }
1651
+ }
1652
+ }
1653
+ const result = {};
1654
+ for (const moduleId of moduleIds) {
1655
+ const surface = options.surfacesByModule[moduleId];
1656
+ result[moduleId] = {
1657
+ ...surface,
1658
+ activations: sortActivations([...surface.activations ?? [], ...syntheticActivations[moduleId]]),
1659
+ incoming: dedupeIncoming(incomingByModule[moduleId]),
1660
+ contributionResolutions: sortResolutions(resolutionsByModule[moduleId])
1168
1661
  };
1169
1662
  }
1170
1663
  return result;
1171
1664
  }
1665
+ function matchApiInterceptorBridges(contribution, owner) {
1666
+ if (contribution.kind !== "api-interceptor") return [];
1667
+ const bridges = owner.bridges ?? [];
1668
+ const declaredMethods = contribution.details.methods ?? [];
1669
+ const declaredPhases = contribution.phases ?? [];
1670
+ const matched = [];
1671
+ for (const bridge of bridges) {
1672
+ if (declaredMethods.length > 0 && !declaredMethods.includes(bridge.method)) continue;
1673
+ const phases = declaredPhases.length > 0 ? bridge.phases.filter((phase) => declaredPhases.includes(phase)) : bridge.phases;
1674
+ if (phases.length === 0) continue;
1675
+ matched.push({ method: bridge.method, phases });
1676
+ }
1677
+ return matched;
1678
+ }
1679
+ function resolveReferenceBinding(input) {
1680
+ for (const kind of input.activationKinds) {
1681
+ const adapter = ACTIVATION_ADAPTERS[kind];
1682
+ if (adapter.mode === "call-site-object") continue;
1683
+ if (!adapter.contributionKinds.includes(input.contribution.kind)) continue;
1684
+ if (adapter.mode === "host-reference" && kind === "api-interceptor-bridge") {
1685
+ const routeId = normalizeRouteId(input.targetRef.id);
1686
+ const owner = input.apiRouteOwners.get(routeId);
1687
+ if (!owner) continue;
1688
+ const matchedBridges = matchApiInterceptorBridges(input.contribution, owner);
1689
+ if (matchedBridges.length === 0) continue;
1690
+ return {
1691
+ ownerModule: owner.moduleId,
1692
+ activations: matchedBridges.map((bridge) => {
1693
+ const host = {
1694
+ kind: "api-route",
1695
+ id: routeId,
1696
+ moduleId: owner.moduleId,
1697
+ method: bridge.method
1698
+ };
1699
+ return {
1700
+ id: activationId(host, kind),
1701
+ kind,
1702
+ host,
1703
+ contributionKinds: ["api-interceptor"],
1704
+ phases: bridge.phases,
1705
+ source: owner.source,
1706
+ bridge: { factSection: "apiRoutes", factKey: `/${routeId}` }
1707
+ };
1708
+ })
1709
+ };
1710
+ }
1711
+ if (adapter.mode === "owner-reference" && kind === "command-interceptor-bridge") {
1712
+ const owner = input.commandOwners.get(input.targetRef.id);
1713
+ if (!owner) continue;
1714
+ const host = { kind: "command", id: input.targetRef.id, moduleId: owner.moduleId };
1715
+ return {
1716
+ ownerModule: owner.moduleId,
1717
+ activations: [{
1718
+ id: activationId(host, kind),
1719
+ kind,
1720
+ host,
1721
+ contributionKinds: ["command-interceptor"],
1722
+ source: owner.source,
1723
+ bridge: { factSection: "ownedContracts.command", factKey: input.targetRef.id }
1724
+ }]
1725
+ };
1726
+ }
1727
+ if (adapter.mode === "host-reference" && (kind === "widget-injection-consumer" || kind === "component-extension-consumer" || kind === "dashboard-host-consumer")) {
1728
+ const frameworkHostFact = input.boundHostIndex.has(input.targetRef.id) ? null : findFrameworkHost(input.targetRef.id, kind);
1729
+ const hostEntry = input.boundHostIndex.get(input.targetRef.id) ?? (frameworkHostFact ? { moduleId: FRAMEWORK_HOST_OWNER, host: frameworkHostFact } : void 0);
1730
+ if (!hostEntry) continue;
1731
+ const hostSource = hostReferenceSource(hostEntry.host);
1732
+ if (!hostSource) continue;
1733
+ const host = { ...input.targetRef, moduleId: hostEntry.moduleId };
1734
+ return {
1735
+ ownerModule: hostEntry.moduleId,
1736
+ activations: [{
1737
+ id: activationId(host, kind),
1738
+ kind,
1739
+ host,
1740
+ contributionKinds: adapter.contributionKinds,
1741
+ source: hostSource,
1742
+ bridge: { factSection: "hosts", factKey: hostEntry.host.key }
1743
+ }]
1744
+ };
1745
+ }
1746
+ }
1747
+ return null;
1748
+ }
1749
+ function hostReferenceSource(host) {
1750
+ if (host.source.kind === "declaration" || host.source.kind === "framework") {
1751
+ return { sourcePath: host.source.path, exportName: host.source.symbol };
1752
+ }
1753
+ return null;
1754
+ }
1755
+ function incomingIdentity(entry) {
1756
+ const target2 = JSON.stringify([entry.target.kind, entry.target.id]);
1757
+ return JSON.stringify([entry.contributorModuleId, entry.contributionId, target2, entry.activationId ?? entry.resolution]);
1758
+ }
1759
+ function dedupeIncoming(entries) {
1760
+ const seen = /* @__PURE__ */ new Set();
1761
+ const unique = [];
1762
+ for (const entry of entries) {
1763
+ const identity = incomingIdentity(entry);
1764
+ if (seen.has(identity)) continue;
1765
+ seen.add(identity);
1766
+ unique.push(entry);
1767
+ }
1768
+ return unique.sort((left, right) => incomingIdentity(left).localeCompare(incomingIdentity(right)));
1769
+ }
1770
+ function resolutionIdentity(entry) {
1771
+ return JSON.stringify([entry.contributionId, entry.target.kind, entry.target.id, entry.resolution]);
1772
+ }
1773
+ function sortResolutions(entries) {
1774
+ const seen = /* @__PURE__ */ new Set();
1775
+ const unique = [];
1776
+ for (const entry of entries) {
1777
+ const identity = resolutionIdentity(entry);
1778
+ if (seen.has(identity)) continue;
1779
+ seen.add(identity);
1780
+ unique.push(entry);
1781
+ }
1782
+ return unique.sort((left, right) => resolutionIdentity(left).localeCompare(resolutionIdentity(right)));
1783
+ }
1172
1784
  function assertNoUnresolvedExtensionTargets(surfacesByModule) {
1173
1785
  const unresolved = Object.entries(surfacesByModule).flatMap(
1174
1786
  ([moduleId, surface]) => surface.unresolved.filter((entry) => entry.reason === "unresolved-first-party-target").map((entry) => `${moduleId}:${entry.key}`)
@@ -1177,6 +1789,15 @@ function assertNoUnresolvedExtensionTargets(surfacesByModule) {
1177
1789
  throw new Error(`[module-facts] unresolved first-party extension targets: ${unresolved.join(", ")}`);
1178
1790
  }
1179
1791
  }
1792
+ function getFrameworkOverrideModes() {
1793
+ const modes = {};
1794
+ for (const host of FRAMEWORK_OVERRIDE_HOSTS) {
1795
+ const dotted = host.key.replace(/^framework\.module-override\./, "");
1796
+ const mode = host.operations?.[0];
1797
+ if (mode === "disable-replace" || mode === "replace" || mode === "additive") modes[dotted] = mode;
1798
+ }
1799
+ return modes;
1800
+ }
1180
1801
  function getFrameworkExtensionHosts() {
1181
1802
  return sortHosts(allFrameworkHosts().map((host) => ({
1182
1803
  ...host,
@@ -1198,13 +1819,20 @@ function renderFrameworkExtensionPointsMarkdown() {
1198
1819
  ].join("\n");
1199
1820
  }
1200
1821
  export {
1822
+ ACTIVATION_ADAPTERS,
1823
+ ALL_ACTIVATION_KINDS,
1824
+ ALL_CONTRIBUTION_KINDS,
1825
+ CONTRIBUTION_ACTIVATION_CLASSIFICATION,
1201
1826
  assertNoUnresolvedExtensionTargets,
1202
1827
  correlateExtensionTarget,
1828
+ correlateIncomingExtensions,
1203
1829
  correlateModuleExtensionFacts,
1830
+ extractActivationTargetOwners,
1204
1831
  extractKnownApiRouteIds,
1205
1832
  extractKnownCommandIds,
1206
1833
  extractModuleExtensionFacts,
1207
1834
  getFrameworkExtensionHosts,
1835
+ getFrameworkOverrideModes,
1208
1836
  readConventionObjectArray,
1209
1837
  renderFrameworkExtensionPointsMarkdown,
1210
1838
  withModuleExtensionFactExtractionCache