@open-mercato/cli 0.6.7 → 0.6.8-develop.6875.1.871a4afc94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +4 -4
- package/build.mjs +24 -7
- package/dist/agentic/claude-code/hooks/entity-migration-check.ts +32 -88
- package/dist/agentic/codex/enforcement-rules.md +2 -16
- package/dist/agentic/cursor/hooks/entity-migration-check.mjs +19 -50
- package/dist/agentic/cursor/rules/entity-guard.mdc +9 -21
- package/dist/agentic/cursor/rules/generated-guard.mdc +3 -5
- package/dist/agentic/cursor/rules/open-mercato.mdc +3 -29
- package/dist/agentic/guides/ai-workflows.md +76 -0
- package/dist/agentic/guides/architecture.md +107 -0
- package/dist/agentic/guides/backend-ui.md +80 -0
- package/dist/agentic/guides/contracts.md +92 -0
- package/dist/agentic/guides/extensions.md +121 -0
- package/dist/agentic/guides/framework-extension-points.md +36 -0
- package/dist/agentic/guides/integrations.md +81 -0
- package/dist/agentic/guides/module-facts.json +115537 -3951
- package/dist/agentic/guides/module-system.md +12 -127
- package/dist/agentic/guides/modules/ai_assistant.md +227 -28
- package/dist/agentic/guides/modules/api_docs.md +58 -2
- package/dist/agentic/guides/modules/api_keys.md +92 -7
- package/dist/agentic/guides/modules/attachments.md +119 -14
- package/dist/agentic/guides/modules/audit_logs.md +131 -10
- package/dist/agentic/guides/modules/auth.md +276 -33
- package/dist/agentic/guides/modules/business_rules.md +149 -14
- package/dist/agentic/guides/modules/catalog.md +475 -43
- package/dist/agentic/guides/modules/channel_gmail.md +82 -3
- package/dist/agentic/guides/modules/channel_imap.md +82 -3
- package/dist/agentic/guides/modules/checkout.md +299 -27
- package/dist/agentic/guides/modules/communication_channels.md +315 -33
- package/dist/agentic/guides/modules/configs.md +104 -8
- package/dist/agentic/guides/modules/content.md +57 -2
- package/dist/agentic/guides/modules/currencies.md +152 -17
- package/dist/agentic/guides/modules/customer_accounts.md +387 -45
- package/dist/agentic/guides/modules/customers.md +746 -85
- package/dist/agentic/guides/modules/dashboards.md +155 -10
- package/dist/agentic/guides/modules/data_sync.md +149 -16
- package/dist/agentic/guides/modules/design_system.md +102 -0
- package/dist/agentic/guides/modules/dictionaries.md +107 -12
- package/dist/agentic/guides/modules/directory.md +148 -15
- package/dist/agentic/guides/modules/entities.md +145 -13
- package/dist/agentic/guides/modules/events.md +59 -2
- package/dist/agentic/guides/modules/feature_toggles.md +119 -9
- package/dist/agentic/guides/modules/gateway_stripe.md +92 -4
- package/dist/agentic/guides/modules/generators.md +45 -2
- package/dist/agentic/guides/modules/inbox_ops.md +207 -27
- package/dist/agentic/guides/modules/integrations.md +146 -16
- package/dist/agentic/guides/modules/messages.md +223 -24
- package/dist/agentic/guides/modules/notifications.md +96 -8
- package/dist/agentic/guides/modules/onboarding.md +77 -6
- package/dist/agentic/guides/modules/payment_gateways.md +164 -21
- package/dist/agentic/guides/modules/perspectives.md +77 -7
- package/dist/agentic/guides/modules/planner.md +136 -18
- package/dist/agentic/guides/modules/portal.md +93 -2
- package/dist/agentic/guides/modules/progress.md +104 -14
- package/dist/agentic/guides/modules/query_index.md +154 -15
- package/dist/agentic/guides/modules/record_locks.md +206 -21
- package/dist/agentic/guides/modules/resources.md +197 -30
- package/dist/agentic/guides/modules/sales.md +678 -84
- package/dist/agentic/guides/modules/scheduler.md +133 -14
- package/dist/agentic/guides/modules/search.md +170 -6
- package/dist/agentic/guides/modules/security.md +294 -35
- package/dist/agentic/guides/modules/shipping_carriers.md +134 -18
- package/dist/agentic/guides/modules/sso.md +151 -26
- package/dist/agentic/guides/modules/staff.md +369 -57
- package/dist/agentic/guides/modules/storage_s3.md +95 -5
- package/dist/agentic/guides/modules/sync_akeneo.md +85 -3
- package/dist/agentic/guides/modules/sync_excel.md +75 -6
- package/dist/agentic/guides/modules/system_status_overlays.md +60 -2
- package/dist/agentic/guides/modules/translations.md +104 -10
- package/dist/agentic/guides/modules/webhooks.md +203 -24
- package/dist/agentic/guides/modules/wms.md +441 -46
- package/dist/agentic/guides/modules/workflows.md +302 -43
- package/dist/agentic/guides/spec-delivery.md +22 -0
- package/dist/agentic/guides/testing-debugging.md +80 -0
- package/dist/agentic/guides/upstream/AGENTS.md +299 -0
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +316 -0
- package/dist/agentic/guides/upstream/manifest.json +8 -0
- package/dist/agentic/shared/AGENTS.md.template +124 -311
- package/dist/agentic/shared/ai/agentic.config.json +2 -0
- package/dist/agentic/shared/ai/harness/README.md +50 -0
- package/dist/agentic/shared/ai/harness/RELEASE.md +57 -0
- package/dist/agentic/shared/ai/harness/cases.json +13680 -0
- package/dist/agentic/shared/ai/harness/cases.schema.json +133 -0
- package/dist/agentic/shared/ai/harness/fixtures/index.json +49 -0
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +162 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +21 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +96 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +172 -0
- package/dist/agentic/shared/ai/harness/release-matrix.json +158 -0
- package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
- package/dist/agentic/shared/ai/harness/result.schema.json +76 -0
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +12 -0
- package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
- package/dist/agentic/shared/ai/harness/validators.json +78 -0
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +2121 -0
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +1096 -0
- package/dist/agentic/shared/ai/lessons/_template.md +16 -0
- package/dist/agentic/shared/ai/lessons.md +26 -3
- package/dist/agentic/shared/ai/review-checklist.md +36 -0
- package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +21 -0
- package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +4 -0
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +98 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +20 -241
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +16 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +53 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +13 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +11 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +30 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +12 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +13 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +14 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +28 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +22 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +19 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +16 -601
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +19 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +13 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +14 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +10 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +14 -310
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +17 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +29 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +100 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +16 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +17 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +25 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +11 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +13 -0
- package/dist/agentic/shared/ai/skills/om-help/SKILL.md +15 -97
- package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +18 -0
- package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +20 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +33 -152
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +33 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +40 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +27 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +24 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +24 -708
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +16 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +14 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +15 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +12 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +34 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +4 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +11 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +31 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +39 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +43 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +11 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +20 -820
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +19 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +172 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +12 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +83 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +21 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +11 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +22 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +13 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +41 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +19 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +44 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +39 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +20 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +57 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +11 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +486 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +19 -843
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +18 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +27 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +13 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +41 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +11 -0
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +15 -63
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +15 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +16 -467
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +16 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +14 -0
- package/dist/agentic/shared/ai/skills/tiers.json +143 -21
- package/dist/agentic/shared/ai/skills/tiers.schema.json +52 -40
- package/dist/agentic/shared/ai/specs/README.md +4 -3
- package/dist/agentic/shared/ai/specs/SPEC-000-template.md +226 -10
- package/dist/agentic/shared/ai/trackers/github.md +108 -4
- package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +187 -0
- package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +2958 -0
- package/dist/agentic/shared/scripts/execution-sandbox.mjs +329 -0
- package/dist/agentic/shared/scripts/framework-context.mjs +888 -0
- package/dist/agentic/shared/scripts/install-skills.mjs +1029 -0
- package/dist/agentic/shared/scripts/install-skills.sh +4 -694
- package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +222 -0
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1774 -0
- package/dist/bin.js +13 -1
- package/dist/bin.js.map +2 -2
- package/dist/lib/__integration__/TC-INT-008.spec.js +88 -8
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-init.js +13 -3
- package/dist/lib/agentic-init.js.map +2 -2
- package/dist/lib/agentic-setup.js +434 -186
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/extensions/injection-widgets.js +20 -0
- package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
- package/dist/lib/generators/module-extension-facts.js +1840 -0
- package/dist/lib/generators/module-extension-facts.js.map +7 -0
- package/dist/lib/generators/module-fact-sources.js +70 -0
- package/dist/lib/generators/module-fact-sources.js.map +7 -0
- package/dist/lib/generators/module-facts-discovery.js +41 -7
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +1387 -45
- package/dist/lib/generators/module-facts.js.map +3 -3
- package/dist/lib/generators/module-override-targets.js +688 -0
- package/dist/lib/generators/module-override-targets.js.map +7 -0
- package/dist/lib/generators/module-registry.js +11 -5
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/load-env.js +24 -0
- package/dist/lib/load-env.js.map +7 -0
- package/dist/lib/resolver.js +2 -0
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/telemetry-init.js +358 -0
- package/dist/lib/telemetry-init.js.map +7 -0
- package/dist/lib/testing/integration.js +5 -0
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +23 -1
- package/dist/mercato.js.map +2 -2
- package/package.json +13 -11
- package/src/bin.ts +33 -1
- package/src/lib/__integration__/TC-INT-008.spec.ts +107 -9
- package/src/lib/__tests__/agentic-init.test.ts +41 -5
- package/src/lib/__tests__/agentic-setup.ownership.test.ts +386 -0
- package/src/lib/__tests__/injection-widget-id-validation.test.ts +184 -0
- package/src/lib/__tests__/load-env.test.ts +65 -0
- package/src/lib/__tests__/telemetry-init.test.ts +298 -0
- package/src/lib/agentic-init.ts +15 -3
- package/src/lib/agentic-setup.ts +573 -222
- package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
- package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +404 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +88 -2
- package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +1 -0
- package/src/lib/generators/__tests__/module-facts.discovery.test.ts +54 -10
- package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +108 -0
- package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +119 -0
- package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
- package/src/lib/generators/__tests__/module-override-targets.test.ts +304 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +18 -0
- package/src/lib/generators/extensions/injection-widgets.ts +51 -0
- package/src/lib/generators/module-extension-facts.ts +2365 -0
- package/src/lib/generators/module-fact-sources.ts +108 -0
- package/src/lib/generators/module-facts-discovery.ts +58 -7
- package/src/lib/generators/module-facts.ts +1851 -46
- package/src/lib/generators/module-override-targets.ts +863 -0
- package/src/lib/generators/module-registry.ts +16 -6
- package/src/lib/load-env.ts +30 -0
- package/src/lib/resolver.ts +3 -0
- package/src/lib/telemetry-init.ts +461 -0
- package/src/lib/testing/__tests__/integration.test.ts +4 -0
- package/src/lib/testing/integration.ts +5 -0
- package/src/mercato.ts +26 -1
- package/dist/agentic/guides/cache.md +0 -45
- package/dist/agentic/guides/core.md +0 -453
- package/dist/agentic/guides/events.md +0 -117
- package/dist/agentic/guides/queue.md +0 -53
- package/dist/agentic/guides/search.md +0 -115
- package/dist/agentic/guides/shared.md +0 -108
- package/dist/agentic/guides/ui.md +0 -347
- package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +0 -27
- package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +0 -27
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +0 -260
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +0 -146
- package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +0 -84
- package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +0 -193
- package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +0 -34
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +0 -762
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +0 -50
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +0 -97
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +0 -271
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +0 -101
|
@@ -0,0 +1,404 @@
|
|
|
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('reports declarations whose authoritative source no longer binds the host key', () => {
|
|
316
|
+
write(moduleRoot, 'extension-points.ts', `
|
|
317
|
+
export const extensionPoints = defineModuleExtensionPoints({
|
|
318
|
+
moduleId: 'alpha',
|
|
319
|
+
hosts: { records: dataTableExtensionHost({ tableId: 'alpha.records', source: 'Records.tsx' }) },
|
|
320
|
+
})
|
|
321
|
+
`)
|
|
322
|
+
write(moduleRoot, 'Records.tsx', "export const tableId = 'alpha.records'")
|
|
323
|
+
|
|
324
|
+
const facts = extractModuleExtensionFacts({
|
|
325
|
+
moduleId: 'alpha',
|
|
326
|
+
moduleRoot,
|
|
327
|
+
sourceRoot: 'node_modules/pkg/src/modules/alpha',
|
|
328
|
+
entities: [],
|
|
329
|
+
events: [],
|
|
330
|
+
apiRoutes: [],
|
|
331
|
+
searchEntities: [],
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
expect(facts.unresolved).toEqual([
|
|
335
|
+
expect.objectContaining({ key: 'alpha.records', reason: 'unbound-declaration' }),
|
|
336
|
+
])
|
|
337
|
+
expect(facts.hosts.filter((host) => host.key.startsWith('records.')).every((host) => !host.bound)).toBe(true)
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
it('fails closed when a correlated first-party target remains unresolved', () => {
|
|
341
|
+
expect(() => assertNoUnresolvedExtensionTargets({
|
|
342
|
+
alpha: {
|
|
343
|
+
hosts: [],
|
|
344
|
+
contributions: [],
|
|
345
|
+
unresolved: [{
|
|
346
|
+
key: 'alpha.widget:data-table:alpha.missing',
|
|
347
|
+
source: { path: 'widgets/injection-table.ts' },
|
|
348
|
+
reason: 'unresolved-first-party-target',
|
|
349
|
+
}],
|
|
350
|
+
},
|
|
351
|
+
})).toThrow('alpha:alpha.widget:data-table:alpha.missing')
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
it('does not trust a claimed fact reference before matching a known first-party host', () => {
|
|
355
|
+
write(moduleRoot, 'data/enrichers.ts', `
|
|
356
|
+
export const enrichers = [{
|
|
357
|
+
id: 'alpha.typo', targetEntity: 'alpha.missing', async enrichOne(record) { return record },
|
|
358
|
+
}]
|
|
359
|
+
`)
|
|
360
|
+
const surface = extractModuleExtensionFacts({
|
|
361
|
+
moduleId: 'alpha',
|
|
362
|
+
moduleRoot,
|
|
363
|
+
sourceRoot: 'node_modules/pkg/src/modules/alpha',
|
|
364
|
+
entities: [{ id: 'alpha:record' }],
|
|
365
|
+
events: [],
|
|
366
|
+
apiRoutes: [],
|
|
367
|
+
searchEntities: [],
|
|
368
|
+
})
|
|
369
|
+
const correlated = correlateModuleExtensionFacts({
|
|
370
|
+
surfacesByModule: { alpha: surface },
|
|
371
|
+
entityIds: new Set(['alpha:record']),
|
|
372
|
+
eventIds: new Set(),
|
|
373
|
+
apiRoutes: new Set(),
|
|
374
|
+
commandIds: new Set(),
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
expect(() => assertNoUnresolvedExtensionTargets(correlated)).toThrow('alpha.typo:alpha.missing')
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
it('rejects wildcard targets that match no known first-party contract', () => {
|
|
381
|
+
const correlated = correlateModuleExtensionFacts({
|
|
382
|
+
surfacesByModule: {
|
|
383
|
+
alpha: {
|
|
384
|
+
hosts: [],
|
|
385
|
+
contributions: [{
|
|
386
|
+
id: 'alpha.typo-pattern',
|
|
387
|
+
kind: 'command-interceptor',
|
|
388
|
+
targets: [{ id: 'alpha.typo.*', resolution: 'unresolved' }],
|
|
389
|
+
scopeContract: 'tenant-and-organization',
|
|
390
|
+
source: { path: 'commands/interceptors.ts' },
|
|
391
|
+
details: { targetCommand: 'alpha.typo.*', phases: ['before-execute'] },
|
|
392
|
+
}],
|
|
393
|
+
unresolved: [],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
entityIds: new Set(),
|
|
397
|
+
eventIds: new Set(),
|
|
398
|
+
apiRoutes: new Set(),
|
|
399
|
+
commandIds: new Set(['alpha.records.update']),
|
|
400
|
+
})
|
|
401
|
+
|
|
402
|
+
expect(() => assertNoUnresolvedExtensionTargets(correlated)).toThrow('alpha.typo-pattern:alpha.typo.*')
|
|
403
|
+
})
|
|
404
|
+
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
-
import { extractAllModuleFacts } from '../module-facts'
|
|
3
|
+
import { extractAllModuleFacts, renderModuleFactsJson } from '../module-facts'
|
|
4
4
|
import { discoverPackageModuleSources } from '../module-facts-discovery'
|
|
5
5
|
import { createResolver } from '../../resolver'
|
|
6
6
|
|
|
@@ -20,7 +20,77 @@ function isUnique(values: string[]): boolean {
|
|
|
20
20
|
describe('module-facts BC resolve guard (T2)', () => {
|
|
21
21
|
const repoRoot = findRepoRoot()
|
|
22
22
|
const sources = discoverPackageModuleSources(createResolver(repoRoot))
|
|
23
|
-
const
|
|
23
|
+
const extractionStartedAt = process.cpuUsage()
|
|
24
|
+
const { factsByModule, markdownByModule, frameworkMarkdown } = extractAllModuleFacts({ sources })
|
|
25
|
+
const extractionCpuUsage = process.cpuUsage(extractionStartedAt)
|
|
26
|
+
const extractionCpuDurationMs = (extractionCpuUsage.user + extractionCpuUsage.system) / 1_000
|
|
27
|
+
|
|
28
|
+
it('emits complete, deterministic extension catalogs for every resolved module', () => {
|
|
29
|
+
const repeated = extractAllModuleFacts({ sources })
|
|
30
|
+
expect(renderModuleFactsJson(repeated.factsByModule)).toBe(renderModuleFactsJson(factsByModule))
|
|
31
|
+
expect(repeated.markdownByModule).toEqual(markdownByModule)
|
|
32
|
+
expect(repeated.frameworkMarkdown).toBe(frameworkMarkdown)
|
|
33
|
+
for (const facts of Object.values(factsByModule)) {
|
|
34
|
+
expect(facts.extensionSurfaces).toBeDefined()
|
|
35
|
+
expect(facts.extensionSurfaces?.unresolved).toEqual([])
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('keeps generated extension facts within bounded build-time and context budgets', () => {
|
|
40
|
+
const completeJson = renderModuleFactsJson(factsByModule)
|
|
41
|
+
const legacyJson = renderModuleFactsJson(Object.fromEntries(
|
|
42
|
+
Object.entries(factsByModule).map(([moduleId, facts]) => [moduleId, { ...facts, extensionSurfaces: undefined }]),
|
|
43
|
+
))
|
|
44
|
+
const markdownBytes = Object.values(markdownByModule)
|
|
45
|
+
.reduce((total, markdown) => total + Buffer.byteLength(markdown), Buffer.byteLength(frameworkMarkdown))
|
|
46
|
+
|
|
47
|
+
// Budget raised by the bidirectional-topology spec
|
|
48
|
+
// (2026-08-02-module-facts-extension-activation-and-incoming-index): the
|
|
49
|
+
// additive `activations`, cross-module `incoming`, and per-contribution
|
|
50
|
+
// `contributionResolutions` layers add ~210KB of compact references (no
|
|
51
|
+
// contribution payloads are duplicated). Incoming rows are cross-module only;
|
|
52
|
+
// resolution rows are required one-per-contribution by the spec's acceptance
|
|
53
|
+
// criteria and are the dominant term.
|
|
54
|
+
//
|
|
55
|
+
// JSON cap raised again by the exact-override-targets spec
|
|
56
|
+
// (2026-08-02-module-facts-exact-override-targets): the additive per-module
|
|
57
|
+
// `overrideTargets` project one exact key per real override entry (acl
|
|
58
|
+
// features, di tokens, subscribers, pages, workers, encryption, widgets,
|
|
59
|
+
// notifications, cli, setup, ai, interceptors/enrichers, page guards). These
|
|
60
|
+
// are required exhaustively by the spec's acceptance criteria; targets carry
|
|
61
|
+
// only compact structured path/key/factRef/source refs (no runtime values or
|
|
62
|
+
// contribution payloads). The delta cap is unchanged because `overrideTargets`
|
|
63
|
+
// live in both the complete and legacy renders (only `extensionSurfaces` is
|
|
64
|
+
// stripped for the legacy comparison).
|
|
65
|
+
//
|
|
66
|
+
// JSON cap raised a third time by the uniform provenance index
|
|
67
|
+
// (2026-08-02-module-facts-source-provenance-and-contract-inventory): every
|
|
68
|
+
// proven `(kind, id)` now reaches `factSources` (~630KB across the repo), so a
|
|
69
|
+
// consumer resolves any fact's origin through one lookup. Entries whose
|
|
70
|
+
// declaration site is already serialized inline (routes, pages, CLI commands,
|
|
71
|
+
// AI tools/agents, owned contracts, hosts, contributions) emit a typed
|
|
72
|
+
// `factRef` pointer instead of a duplicated source ref, and `factKey` is
|
|
73
|
+
// omitted when it equals the entry `id` — so the index costs references, never
|
|
74
|
+
// copied provenance payloads. The cap also covers the newly reachable
|
|
75
|
+
// framework-host activations (dashboard/menu/notification contributions now
|
|
76
|
+
// resolve as bound instead of silently falling back to capability-only).
|
|
77
|
+
// This is a blow-up detector, not a performance target. It measures CPU time
|
|
78
|
+
// for a whole-repo extraction, and CPU time for fixed work varies with the
|
|
79
|
+
// machine: the same extraction measures ~7.3s on a developer workstation and
|
|
80
|
+
// ~30.0s on a CI runner. At the previous 30s cap CI sat exactly on the line
|
|
81
|
+
// (an observed failure at 30,052.8ms), so the guard could not tell a genuine
|
|
82
|
+
// pathological regression from ordinary hardware variance and failed
|
|
83
|
+
// unrelated PRs at random. 90s keeps it meaningful — a real blow-up here is
|
|
84
|
+
// multiplicative, not a few percent — while leaving CI roughly 3x headroom.
|
|
85
|
+
expect(extractionCpuDurationMs).toBeLessThan(90_000)
|
|
86
|
+
expect(Buffer.byteLength(completeJson)).toBeLessThan(3_500_000)
|
|
87
|
+
expect(Buffer.byteLength(completeJson) - Buffer.byteLength(legacyJson)).toBeLessThan(1_800_000)
|
|
88
|
+
// Markdown cap raised with the source-link contract: entities, events, ACL
|
|
89
|
+
// features, DI tokens, search entities, notifications, UMES hosts and UMES
|
|
90
|
+
// contributions all render a resolved Source cell, and contribution
|
|
91
|
+
// resolutions render as their own source-linked section.
|
|
92
|
+
expect(markdownBytes).toBeLessThan(1_550_000)
|
|
93
|
+
})
|
|
24
94
|
|
|
25
95
|
it('discovers a superset of the historical core modules', () => {
|
|
26
96
|
const discovered = new Set(Object.keys(factsByModule))
|
|
@@ -30,11 +100,27 @@ describe('module-facts BC resolve guard (T2)', () => {
|
|
|
30
100
|
expect(discovered.size).toBeGreaterThan(9)
|
|
31
101
|
})
|
|
32
102
|
|
|
103
|
+
it('keeps factory-built and generated-registry enricher contributions visible', () => {
|
|
104
|
+
expect(factsByModule.sales.extensionSurfaces?.contributions).toEqual(expect.arrayContaining([
|
|
105
|
+
expect.objectContaining({ id: 'sales.catalog-image:sales:sales_quote_line', kind: 'response-enricher' }),
|
|
106
|
+
expect.objectContaining({ id: 'sales.catalog-image:sales:sales_order_line', kind: 'response-enricher' }),
|
|
107
|
+
]))
|
|
108
|
+
expect(factsByModule.wms.extensionSurfaces?.contributions).toEqual(expect.arrayContaining([
|
|
109
|
+
expect.objectContaining({ id: 'wms.sales-order-inventory', kind: 'response-enricher' }),
|
|
110
|
+
]))
|
|
111
|
+
})
|
|
112
|
+
|
|
33
113
|
for (const source of sources) {
|
|
34
114
|
const moduleId = source.moduleId
|
|
35
115
|
describe(`${moduleId}`, () => {
|
|
36
116
|
const facts = factsByModule[moduleId]
|
|
37
117
|
|
|
118
|
+
it('stamps the exact providing package and version without removing coreVersion', () => {
|
|
119
|
+
expect(facts.sourcePackage).toBe(source.from ?? null)
|
|
120
|
+
expect(facts.sourceVersion).toBe(source.packageVersion ?? null)
|
|
121
|
+
expect(facts).toHaveProperty('coreVersion')
|
|
122
|
+
})
|
|
123
|
+
|
|
38
124
|
// Entity / search / host ids are colon-namespaced under the module by construction
|
|
39
125
|
// and convention; drift here means the builder or a module's data model broke.
|
|
40
126
|
it('colon-namespaces entity / search / host ids under the module and keeps ids unique', () => {
|
|
@@ -52,6 +52,7 @@ describe('module-facts customers fixture (T1 anti-drift guard)', () => {
|
|
|
52
52
|
'customers.companies.list',
|
|
53
53
|
'customers.deals.list',
|
|
54
54
|
'customers.people.list',
|
|
55
|
+
'customers.todos.list',
|
|
55
56
|
])
|
|
56
57
|
expect(facts.hostTokens.entityIds).toEqual(['customers:customer_entity'])
|
|
57
58
|
expect(facts.diTokens).toEqual([])
|
|
@@ -4,11 +4,13 @@ import path from 'node:path'
|
|
|
4
4
|
import type { PackageResolver } from '../../resolver'
|
|
5
5
|
import { discoverPackageModuleSources, hasReadableModuleSource } from '../module-facts-discovery'
|
|
6
6
|
|
|
7
|
-
type FakePackage = { name: string; path: string; modulesPath: string }
|
|
7
|
+
type FakePackage = { name: string; version?: string | null; path: string; modulesPath: string }
|
|
8
|
+
type FakeModuleEntry = { id: string; from?: string }
|
|
8
9
|
|
|
9
|
-
function makeResolver(packages: FakePackage[]): PackageResolver {
|
|
10
|
+
function makeResolver(packages: FakePackage[], enabledModules: FakeModuleEntry[] = []): PackageResolver {
|
|
10
11
|
return {
|
|
11
12
|
discoverPackages: () => packages,
|
|
13
|
+
loadEnabledModules: () => enabledModules,
|
|
12
14
|
} as unknown as PackageResolver
|
|
13
15
|
}
|
|
14
16
|
|
|
@@ -62,27 +64,69 @@ describe('module-facts discovery (T1)', () => {
|
|
|
62
64
|
writeModule(path.join(pkgSrc, 'src', 'modules'), 'gamma', 'events.ts')
|
|
63
65
|
|
|
64
66
|
const resolver = makeResolver([
|
|
65
|
-
{ name: '@open-mercato/tagged', path: pkgSrc, modulesPath: path.join(pkgSrc, 'src', 'modules') },
|
|
67
|
+
{ name: '@open-mercato/tagged', version: '0.6.6', path: pkgSrc, modulesPath: path.join(pkgSrc, 'src', 'modules') },
|
|
66
68
|
])
|
|
67
69
|
|
|
68
70
|
const [source] = discoverPackageModuleSources(resolver)
|
|
69
|
-
expect(source).toMatchObject({
|
|
71
|
+
expect(source).toMatchObject({
|
|
72
|
+
moduleId: 'gamma',
|
|
73
|
+
from: '@open-mercato/tagged',
|
|
74
|
+
packageVersion: '0.6.6',
|
|
75
|
+
})
|
|
70
76
|
expect(source.moduleRoot).toBe(path.join(pkgSrc, 'src', 'modules', 'gamma'))
|
|
71
77
|
})
|
|
72
78
|
|
|
73
|
-
it('
|
|
79
|
+
it('fails closed on duplicate module ids and reports every provider independent of discovery order', () => {
|
|
74
80
|
const pkgA = path.join(tmp, 'dup-a')
|
|
75
81
|
writeModule(path.join(pkgA, 'src', 'modules'), 'shared', 'index.ts')
|
|
76
82
|
const pkgB = path.join(tmp, 'dup-b')
|
|
77
83
|
writeModule(path.join(pkgB, 'src', 'modules'), 'shared', 'index.ts')
|
|
78
84
|
|
|
79
|
-
const
|
|
85
|
+
const packages = [
|
|
80
86
|
{ name: '@open-mercato/dup-a', path: pkgA, modulesPath: path.join(pkgA, 'src', 'modules') },
|
|
81
87
|
{ name: '@open-mercato/dup-b', path: pkgB, modulesPath: path.join(pkgB, 'src', 'modules') },
|
|
82
|
-
]
|
|
88
|
+
]
|
|
89
|
+
const expected = '[module-facts] duplicate module id "shared" is provided by '
|
|
90
|
+
+ '@open-mercato/dup-a, @open-mercato/dup-b; src/modules.ts does not select a provider. '
|
|
91
|
+
+ 'Select exactly one matching provider in src/modules.ts.'
|
|
92
|
+
|
|
93
|
+
expect(() => discoverPackageModuleSources(makeResolver(packages))).toThrow(expected)
|
|
94
|
+
expect(() => discoverPackageModuleSources(makeResolver([...packages].reverse()))).toThrow(expected)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('uses the exact src/modules.ts provider even when package discovery order is reversed', () => {
|
|
98
|
+
const pkgA = path.join(tmp, 'selected-a')
|
|
99
|
+
writeModule(path.join(pkgA, 'src', 'modules'), 'shared', 'index.ts')
|
|
100
|
+
const pkgB = path.join(tmp, 'selected-b')
|
|
101
|
+
writeModule(path.join(pkgB, 'src', 'modules'), 'shared', 'index.ts')
|
|
102
|
+
|
|
103
|
+
const packages = [
|
|
104
|
+
{ name: '@open-mercato/selected-a', path: pkgA, modulesPath: path.join(pkgA, 'src', 'modules') },
|
|
105
|
+
{ name: '@open-mercato/selected-b', path: pkgB, modulesPath: path.join(pkgB, 'src', 'modules') },
|
|
106
|
+
]
|
|
107
|
+
const enabled = [{ id: 'shared', from: '@open-mercato/selected-b' }]
|
|
108
|
+
|
|
109
|
+
for (const discoveredPackages of [packages, [...packages].reverse()]) {
|
|
110
|
+
const sources = discoverPackageModuleSources(makeResolver(discoveredPackages, enabled))
|
|
111
|
+
expect(sources).toHaveLength(1)
|
|
112
|
+
expect(sources[0]).toMatchObject({ moduleId: 'shared', from: '@open-mercato/selected-b' })
|
|
113
|
+
expect(sources[0].moduleRoot).toBe(path.join(pkgB, 'src', 'modules', 'shared'))
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('explains that an omitted provider implicitly selects core', () => {
|
|
118
|
+
const pkgA = path.join(tmp, 'implicit-a')
|
|
119
|
+
writeModule(path.join(pkgA, 'src', 'modules'), 'shared', 'index.ts')
|
|
120
|
+
const pkgB = path.join(tmp, 'implicit-b')
|
|
121
|
+
writeModule(path.join(pkgB, 'src', 'modules'), 'shared', 'index.ts')
|
|
122
|
+
|
|
123
|
+
const packages = [
|
|
124
|
+
{ name: '@open-mercato/dup-a', path: pkgA, modulesPath: path.join(pkgA, 'src', 'modules') },
|
|
125
|
+
{ name: '@open-mercato/dup-b', path: pkgB, modulesPath: path.join(pkgB, 'src', 'modules') },
|
|
126
|
+
]
|
|
83
127
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
128
|
+
expect(() => discoverPackageModuleSources(makeResolver(packages, [{ id: 'shared' }]))).toThrow(
|
|
129
|
+
'src/modules.ts omits "from", which defaults to @open-mercato/core',
|
|
130
|
+
)
|
|
87
131
|
})
|
|
88
132
|
})
|