@open-mercato/cli 0.6.8-develop.6931.1.4a1e1c786b → 0.6.8-develop.6941.1.3c26284542
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 +64 -0
- package/build.mjs +57 -7
- package/dist/agentic/guides/backend-ui.md +7 -1
- package/dist/agentic/guides/module-facts.json +565 -224
- package/dist/agentic/guides/module-facts.v2.json +122933 -0
- package/dist/agentic/guides/modules/ai_assistant.md +2 -2
- package/dist/agentic/guides/modules/api_docs.md +2 -2
- package/dist/agentic/guides/modules/api_keys.md +2 -2
- package/dist/agentic/guides/modules/attachments.md +2 -2
- package/dist/agentic/guides/modules/audit_logs.md +2 -2
- package/dist/agentic/guides/modules/auth.md +2 -2
- package/dist/agentic/guides/modules/business_rules.md +2 -2
- package/dist/agentic/guides/modules/catalog.md +11 -4
- package/dist/agentic/guides/modules/channel_gmail.md +2 -2
- package/dist/agentic/guides/modules/channel_imap.md +2 -2
- package/dist/agentic/guides/modules/checkout.md +8 -2
- package/dist/agentic/guides/modules/communication_channels.md +6 -6
- package/dist/agentic/guides/modules/configs.md +2 -2
- package/dist/agentic/guides/modules/content.md +2 -2
- package/dist/agentic/guides/modules/currencies.md +2 -2
- package/dist/agentic/guides/modules/customer_accounts.md +2 -2
- package/dist/agentic/guides/modules/customers.md +8 -8
- package/dist/agentic/guides/modules/dashboards.md +12 -12
- package/dist/agentic/guides/modules/data_sync.md +4 -4
- package/dist/agentic/guides/modules/design_system.md +2 -2
- package/dist/agentic/guides/modules/dictionaries.md +2 -2
- package/dist/agentic/guides/modules/directory.md +2 -2
- package/dist/agentic/guides/modules/entities.md +3 -3
- package/dist/agentic/guides/modules/eudr.md +3 -3
- package/dist/agentic/guides/modules/events.md +2 -2
- package/dist/agentic/guides/modules/feature_toggles.md +2 -2
- package/dist/agentic/guides/modules/gateway_stripe.md +2 -2
- package/dist/agentic/guides/modules/generators.md +2 -2
- package/dist/agentic/guides/modules/inbox_ops.md +2 -2
- package/dist/agentic/guides/modules/integrations.md +5 -2
- package/dist/agentic/guides/modules/messages.md +2 -2
- package/dist/agentic/guides/modules/notifications.md +2 -2
- package/dist/agentic/guides/modules/onboarding.md +2 -2
- package/dist/agentic/guides/modules/payment_gateways.md +7 -3
- package/dist/agentic/guides/modules/perspectives.md +2 -2
- package/dist/agentic/guides/modules/planner.md +2 -2
- package/dist/agentic/guides/modules/portal.md +2 -2
- package/dist/agentic/guides/modules/progress.md +2 -2
- package/dist/agentic/guides/modules/query_index.md +2 -2
- package/dist/agentic/guides/modules/record_locks.md +3 -3
- package/dist/agentic/guides/modules/resources.md +2 -2
- package/dist/agentic/guides/modules/sales.md +13 -4
- package/dist/agentic/guides/modules/scheduler.md +2 -2
- package/dist/agentic/guides/modules/search.md +4 -4
- package/dist/agentic/guides/modules/security.md +3 -3
- package/dist/agentic/guides/modules/shipping_carriers.md +2 -2
- package/dist/agentic/guides/modules/sso.md +2 -2
- package/dist/agentic/guides/modules/staff.md +7 -4
- package/dist/agentic/guides/modules/storage_s3.md +2 -2
- package/dist/agentic/guides/modules/sync_akeneo.md +4 -4
- package/dist/agentic/guides/modules/sync_excel.md +2 -2
- package/dist/agentic/guides/modules/system_status_overlays.md +2 -2
- package/dist/agentic/guides/modules/translations.md +2 -2
- package/dist/agentic/guides/modules/webhooks.md +2 -2
- package/dist/agentic/guides/modules/wms.md +31 -4
- package/dist/agentic/guides/modules/workflows.md +2 -2
- package/dist/agentic/guides/reference-module-facts.json +8335 -0
- package/dist/agentic/guides/reference-modules/example.md +489 -0
- package/dist/agentic/guides/spec-delivery.md +34 -0
- package/dist/agentic/guides/testing-debugging.md +2 -1
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +3 -2
- package/dist/agentic/guides/upstream/manifest.json +2 -2
- package/dist/agentic/shared/AGENTS.md.template +10 -14
- package/dist/agentic/shared/ai/harness/README.md +4 -4
- package/dist/agentic/shared/ai/harness/RELEASE.md +4 -4
- package/dist/agentic/shared/ai/harness/cases.json +11350 -5088
- package/dist/agentic/shared/ai/harness/cases.schema.json +70 -7
- package/dist/agentic/shared/ai/harness/design-system-inventory.json +6697 -0
- package/dist/agentic/shared/ai/harness/fixtures/index.json +3 -1
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +4 -0
- package/dist/agentic/shared/ai/harness/knowledge-change.schema.json +228 -0
- package/dist/agentic/shared/ai/harness/release-matrix.json +9 -4
- package/dist/agentic/shared/ai/harness/result.schema.json +60 -0
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +10 -0
- package/dist/agentic/shared/ai/harness/source-link-inventory.json +3231 -0
- package/dist/agentic/shared/ai/harness/validators.json +8 -4
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +5 -2
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +3 -0
- package/dist/agentic/shared/ai/harness/writable-spec-oracles.mjs +879 -0
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.7-to-0.7.0/SKILL.md +102 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +15 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +6 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +4 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +2 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +2 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +3 -1
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +2 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +2 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +3 -3
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +1 -1
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/knowledge-change.md +133 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +11 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +1 -1
- package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +2 -2
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +14 -1
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +2 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +19 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +1 -4
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +24 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +3 -1
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +1 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +13 -0
- package/dist/agentic/shared/ai/skills/tiers.json +2 -1
- package/dist/agentic/shared/ai/specs/2026-08-06-reference-module-activation.md +248 -0
- package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +39 -2
- package/dist/agentic/shared/scripts/design-source-contract.mjs +108 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +935 -28
- package/dist/agentic/shared/scripts/framework-context.mjs +4 -1
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1 -1
- package/dist/agentic/shared/scripts/validate-knowledge-change.mjs +1511 -0
- package/dist/lib/__integration__/TC-INT-008.spec.js +1 -0
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-setup.js +9 -1
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/module-extension-facts.js +93 -26
- package/dist/lib/generators/module-extension-facts.js.map +3 -3
- package/dist/lib/generators/module-facts-discovery.js +23 -0
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +969 -41
- package/dist/lib/generators/module-facts.js.map +2 -2
- package/dist/lib/generators/module-override-targets.js +35 -11
- package/dist/lib/generators/module-override-targets.js.map +2 -2
- package/dist/lib/generators/module-registry.js +105 -2
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/resolver.js +5 -0
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/testing/integration-discovery.js +34 -0
- package/dist/lib/testing/integration-discovery.js.map +2 -2
- package/dist/lib/testing/integration.js +0 -2
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +4 -4
- package/dist/mercato.js.map +2 -2
- package/package.json +6 -6
- package/src/lib/__integration__/TC-INT-008.spec.ts +4 -2
- package/src/lib/__tests__/resolve-environment.test.ts +18 -0
- package/src/lib/agentic-setup.ts +10 -2
- package/src/lib/generators/__tests__/helpers/override-target-diagnostic-fixtures.ts +150 -0
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +290 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +72 -7
- package/src/lib/generators/__tests__/module-facts.computed-di-token.test.ts +88 -0
- package/src/lib/generators/__tests__/module-facts.example-fact-coverage.test.ts +502 -0
- package/src/lib/generators/__tests__/module-facts.local-reference.test.ts +414 -0
- package/src/lib/generators/__tests__/module-facts.search-entity-id.test.ts +151 -0
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +42 -1
- package/src/lib/generators/__tests__/module-override-targets.diagnostic-wiring.test.ts +92 -0
- package/src/lib/generators/__tests__/module-override-targets.test.ts +24 -0
- package/src/lib/generators/__tests__/module-override-targets.unknown-mode.test.ts +117 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +60 -0
- package/src/lib/generators/module-extension-facts.ts +181 -25
- package/src/lib/generators/module-facts-discovery.ts +46 -0
- package/src/lib/generators/module-facts.ts +1330 -86
- package/src/lib/generators/module-override-targets.ts +70 -26
- package/src/lib/generators/module-registry.ts +135 -2
- package/src/lib/resolver.ts +6 -1
- package/src/lib/testing/__tests__/integration-discovery.test.ts +35 -0
- package/src/lib/testing/integration-discovery.ts +50 -0
- package/src/lib/testing/integration.ts +0 -2
- package/src/mercato.ts +3 -3
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { extractModuleFacts } from '../module-facts'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A DI token registered through a computed key must still reach module facts.
|
|
8
|
+
*
|
|
9
|
+
* `container.register({ [SERVICE_TOKEN]: asFunction(...) })` — reading the token from an
|
|
10
|
+
* exported const — is the idiomatic, type-safe registration. `getPropertyName` returned
|
|
11
|
+
* undefined for a `ts.ComputedPropertyName`, so the whole registration vanished with NO
|
|
12
|
+
* diagnostic: the unresolved-token path is only reached for a NAMED token whose Awilix kind
|
|
13
|
+
* is unrecognised, so a computed key failed silently rather than being reported.
|
|
14
|
+
*
|
|
15
|
+
* That produced a capability the inventory claimed and the extractor scored zero for. Found
|
|
16
|
+
* by an agent that ran the real extractor instead of trusting the row.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
function write(filePath: string, source: string): void {
|
|
20
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
|
21
|
+
fs.writeFileSync(filePath, source)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function factsFor(diSource: string): Array<{ id: string; metadata?: Record<string, unknown> }> {
|
|
25
|
+
const moduleRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'om-computed-di-'))
|
|
26
|
+
try {
|
|
27
|
+
write(path.join(moduleRoot, 'di.ts'), diSource)
|
|
28
|
+
const facts = extractModuleFacts({
|
|
29
|
+
moduleId: 'probe',
|
|
30
|
+
moduleRoot,
|
|
31
|
+
sourceRoot: 'src/modules/probe',
|
|
32
|
+
}) as { ownedContracts?: Record<string, Array<{ id: string; metadata?: Record<string, unknown> }>> }
|
|
33
|
+
return facts.ownedContracts?.['di-registration'] ?? []
|
|
34
|
+
} finally {
|
|
35
|
+
fs.rmSync(moduleRoot, { recursive: true, force: true })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('module-facts DI registration with a computed token', () => {
|
|
40
|
+
it('resolves a token declared as a same-file const', () => {
|
|
41
|
+
const facts = factsFor(`
|
|
42
|
+
import { asFunction } from 'awilix'
|
|
43
|
+
export const PROBE_SERVICE = 'probeService' as const
|
|
44
|
+
export function register(container: any) {
|
|
45
|
+
container.register({ [PROBE_SERVICE]: asFunction(createProbeService).scoped() })
|
|
46
|
+
}
|
|
47
|
+
`)
|
|
48
|
+
|
|
49
|
+
expect(facts).toHaveLength(1)
|
|
50
|
+
expect(facts[0].id).toBe('probeService')
|
|
51
|
+
expect(facts[0].metadata).toMatchObject({ registrationKind: 'function', lifetime: 'scoped' })
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('resolves a token written as a computed string literal', () => {
|
|
55
|
+
const facts = factsFor(`
|
|
56
|
+
import { asValue } from 'awilix'
|
|
57
|
+
export function register(container: any) {
|
|
58
|
+
container.register({ ['literalToken']: asValue(42) })
|
|
59
|
+
}
|
|
60
|
+
`)
|
|
61
|
+
|
|
62
|
+
expect(facts.map((fact) => fact.id)).toEqual(['literalToken'])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('still resolves plain identifier and string keys', () => {
|
|
66
|
+
const facts = factsFor(`
|
|
67
|
+
import { asValue } from 'awilix'
|
|
68
|
+
export function register(container: any) {
|
|
69
|
+
container.register({ plainKey: asValue(1), 'quotedKey': asValue(2) })
|
|
70
|
+
}
|
|
71
|
+
`)
|
|
72
|
+
|
|
73
|
+
expect(facts.map((fact) => fact.id).sort()).toEqual(['plainKey', 'quotedKey'])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('leaves a genuinely dynamic token unresolved rather than inventing one', () => {
|
|
77
|
+
// A key the reader cannot know statically must NOT be guessed. Emitting a wrong token
|
|
78
|
+
// would be worse than emitting none.
|
|
79
|
+
const facts = factsFor(`
|
|
80
|
+
import { asValue } from 'awilix'
|
|
81
|
+
export function register(container: any, name: string) {
|
|
82
|
+
container.register({ [name]: asValue(1) })
|
|
83
|
+
}
|
|
84
|
+
`)
|
|
85
|
+
|
|
86
|
+
expect(facts).toEqual([])
|
|
87
|
+
})
|
|
88
|
+
})
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import {
|
|
4
|
+
MODULE_FACT_SOURCE_KINDS,
|
|
5
|
+
MODULE_FACT_DIAGNOSTIC_CODES,
|
|
6
|
+
MODULE_OWNED_CONTRACT_KINDS,
|
|
7
|
+
buildCoverageFamily,
|
|
8
|
+
buildModuleFactCoverageLedger,
|
|
9
|
+
countModuleFactCoverage,
|
|
10
|
+
extractAllModuleFacts,
|
|
11
|
+
extractModuleFacts,
|
|
12
|
+
type ModuleFactCoverageFamily,
|
|
13
|
+
type ModuleFactCoverageRow,
|
|
14
|
+
type ModuleFacts,
|
|
15
|
+
} from '../module-facts'
|
|
16
|
+
import {
|
|
17
|
+
COMPONENT_OVERRIDE_MODES,
|
|
18
|
+
CRUD_FORM_EXTENSION_SURFACE_KEYS,
|
|
19
|
+
CRUD_FORM_LIFECYCLE_PHASES,
|
|
20
|
+
CRUD_FORM_OPERATIONS,
|
|
21
|
+
DATA_TABLE_EXTENSION_SURFACE_KEYS,
|
|
22
|
+
EXTENSION_HOST_ACTIVATIONS,
|
|
23
|
+
EXTENSION_HOST_CAPABILITIES,
|
|
24
|
+
EXTENSION_HOST_FAMILIES,
|
|
25
|
+
MODULE_EXTENSION_ACTIVATION_KINDS,
|
|
26
|
+
MODULE_EXTENSION_CONTRIBUTION_KINDS,
|
|
27
|
+
MODULE_EXTENSION_HOST_RESOLUTIONS,
|
|
28
|
+
MODULE_EXTENSION_RESOLUTIONS,
|
|
29
|
+
MODULE_EXTENSION_TARGET_KINDS,
|
|
30
|
+
MODULE_EXTENSION_TARGET_RESOLUTIONS,
|
|
31
|
+
MODULE_EXTENSION_UNRESOLVED_REASONS,
|
|
32
|
+
MODULE_SPECIALIZED_REGISTRIES,
|
|
33
|
+
} from '@open-mercato/shared/modules/widgets/extension-points'
|
|
34
|
+
import {
|
|
35
|
+
ALL_OVERRIDE_DOMAINS,
|
|
36
|
+
MODULE_OVERRIDE_MODES,
|
|
37
|
+
MODULE_OVERRIDE_TARGET_DIAGNOSTIC_CODES,
|
|
38
|
+
MODULE_OVERRIDE_TARGET_NOTES,
|
|
39
|
+
} from '../module-override-targets'
|
|
40
|
+
import { OVERRIDE_TARGET_DIAGNOSTIC_FIXTURES } from './helpers/override-target-diagnostic-fixtures'
|
|
41
|
+
import { discoverPackageModuleSources } from '../module-facts-discovery'
|
|
42
|
+
import { createResolver } from '../../resolver'
|
|
43
|
+
|
|
44
|
+
function findRepoRoot(): string {
|
|
45
|
+
let dir = __dirname
|
|
46
|
+
for (let depth = 0; depth < 10; depth += 1) {
|
|
47
|
+
if (fs.existsSync(path.join(dir, 'packages', 'core', 'src', 'modules'))) return dir
|
|
48
|
+
dir = path.dirname(dir)
|
|
49
|
+
}
|
|
50
|
+
throw new Error('[internal] could not locate repo root from the test directory')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const CLOSED_LEDGER_STATUSES = [
|
|
54
|
+
'emitted-example',
|
|
55
|
+
'framework-only',
|
|
56
|
+
'catalog-only',
|
|
57
|
+
'currently-unbound',
|
|
58
|
+
'pending-emission',
|
|
59
|
+
'negative-fixture',
|
|
60
|
+
] as const
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A route the example module really ships, addressed the way the generated module
|
|
64
|
+
* registry addresses it. `api-route` facts are read from that registry rather than
|
|
65
|
+
* from module source, so this fixture is what turns the `requiresGeneratedRegistry`
|
|
66
|
+
* flag from an unchecked claim into a proven one: the same extraction reports zero
|
|
67
|
+
* without it and a real count with it.
|
|
68
|
+
*/
|
|
69
|
+
const REGISTRY_BACKED_ROUTE = {
|
|
70
|
+
path: '/example/todos',
|
|
71
|
+
sourceFile: 'apps/mercato/src/modules/example/api/todos/route.ts',
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function exampleRegistrySource(): string {
|
|
75
|
+
return [
|
|
76
|
+
'export const modules = [',
|
|
77
|
+
" { id: 'example', apis: [",
|
|
78
|
+
` { path: '${REGISTRY_BACKED_ROUTE.path}', handlers: { GET: getTodos, POST: createTodo } },`,
|
|
79
|
+
' ] },',
|
|
80
|
+
']',
|
|
81
|
+
].join('\n')
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function extractExampleFacts(registrySource?: string): ModuleFacts {
|
|
85
|
+
return extractModuleFacts({
|
|
86
|
+
moduleId: 'example',
|
|
87
|
+
moduleRoot: path.join(findRepoRoot(), 'apps', 'mercato', 'src', 'modules', 'example'),
|
|
88
|
+
portableSourceRoot: 'src/modules/example',
|
|
89
|
+
...(registrySource === undefined ? {} : { registrySource }),
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function familyOf(ledger: ModuleFactCoverageFamily[], name: string): ModuleFactCoverageFamily {
|
|
94
|
+
const family = ledger.find((entry) => entry.family === name)
|
|
95
|
+
if (!family) throw new Error(`[internal] factCoverage ledger has no family "${name}"`)
|
|
96
|
+
return family
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
describe('factCoverage ledger — enum derivation', () => {
|
|
100
|
+
const ledger = buildModuleFactCoverageLedger()
|
|
101
|
+
|
|
102
|
+
it('classifies every value of every enumerated family exactly once', () => {
|
|
103
|
+
const expectedSizes: Record<string, number> = {
|
|
104
|
+
ModuleFactSourceKind: MODULE_FACT_SOURCE_KINDS.length,
|
|
105
|
+
ModuleOwnedContractKind: MODULE_OWNED_CONTRACT_KINDS.length,
|
|
106
|
+
ModuleOverrideDomain: ALL_OVERRIDE_DOMAINS.length,
|
|
107
|
+
ModuleOverrideMode: MODULE_OVERRIDE_MODES.length,
|
|
108
|
+
ModuleOverrideTargetNote: MODULE_OVERRIDE_TARGET_NOTES.length,
|
|
109
|
+
ModuleOverrideTargetDiagnosticCode: MODULE_OVERRIDE_TARGET_DIAGNOSTIC_CODES.length,
|
|
110
|
+
ModuleExtensionContributionKind: MODULE_EXTENSION_CONTRIBUTION_KINDS.length,
|
|
111
|
+
ModuleExtensionActivationKind: MODULE_EXTENSION_ACTIVATION_KINDS.length,
|
|
112
|
+
ExtensionHostFamily: EXTENSION_HOST_FAMILIES.length,
|
|
113
|
+
ExtensionHostCapability: EXTENSION_HOST_CAPABILITIES.length,
|
|
114
|
+
ExtensionHostActivation: EXTENSION_HOST_ACTIVATIONS.length,
|
|
115
|
+
ModuleExtensionTargetKind: MODULE_EXTENSION_TARGET_KINDS.length,
|
|
116
|
+
ModuleExtensionHostResolution: MODULE_EXTENSION_HOST_RESOLUTIONS.length,
|
|
117
|
+
ModuleExtensionTargetResolution: MODULE_EXTENSION_TARGET_RESOLUTIONS.length,
|
|
118
|
+
ModuleExtensionResolution: MODULE_EXTENSION_RESOLUTIONS.length,
|
|
119
|
+
SpecializedRegistry: MODULE_SPECIALIZED_REGISTRIES.length,
|
|
120
|
+
ComponentOverrideMode: COMPONENT_OVERRIDE_MODES.length,
|
|
121
|
+
ModuleFactDiagnosticCode: MODULE_FACT_DIAGNOSTIC_CODES.length,
|
|
122
|
+
ModuleExtensionUnresolvedReason: MODULE_EXTENSION_UNRESOLVED_REASONS.length,
|
|
123
|
+
DataTableExtensionSurface: DATA_TABLE_EXTENSION_SURFACE_KEYS.length,
|
|
124
|
+
CrudFormExtensionSurface: CRUD_FORM_EXTENSION_SURFACE_KEYS.length,
|
|
125
|
+
CrudFormLifecyclePhase: CRUD_FORM_LIFECYCLE_PHASES.length,
|
|
126
|
+
CrudFormOperation: CRUD_FORM_OPERATIONS.length,
|
|
127
|
+
}
|
|
128
|
+
expect(ledger.map((family) => family.family).sort()).toEqual(Object.keys(expectedSizes).sort())
|
|
129
|
+
for (const family of ledger) {
|
|
130
|
+
expect(family.rows).toHaveLength(expectedSizes[family.family])
|
|
131
|
+
const values = family.rows.map((row) => row.value)
|
|
132
|
+
expect(new Set(values).size).toBe(values.length)
|
|
133
|
+
expect(family.enumSource).toMatch(/^packages\/(?:cli\/src\/lib\/generators|shared\/src\/modules\/widgets)\/.+#[A-Z_]+$/)
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('resolves every enumSource to a file that really exports that symbol with those values', () => {
|
|
138
|
+
for (const family of ledger) {
|
|
139
|
+
const [relativePath, symbol] = family.enumSource.split('#')
|
|
140
|
+
const absolute = path.join(findRepoRoot(), relativePath)
|
|
141
|
+
expect(fs.existsSync(absolute)).toBe(true)
|
|
142
|
+
expect(fs.readFileSync(absolute, 'utf8')).toMatch(new RegExp(`^export const ${symbol}\\b`, 'm'))
|
|
143
|
+
const exported = (require(absolute) as Record<string, unknown>)[symbol]
|
|
144
|
+
expect(Array.isArray(exported)).toBe(true)
|
|
145
|
+
expect([...(exported as string[])]).toEqual(family.rows.map((row) => row.value))
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('derives each family from the exported runtime constant, value for value', () => {
|
|
150
|
+
expect(familyOf(ledger, 'ModuleFactSourceKind').rows.map((row) => row.value)).toEqual([
|
|
151
|
+
...MODULE_FACT_SOURCE_KINDS,
|
|
152
|
+
])
|
|
153
|
+
expect(familyOf(ledger, 'ModuleOwnedContractKind').rows.map((row) => row.value)).toEqual([
|
|
154
|
+
...MODULE_OWNED_CONTRACT_KINDS,
|
|
155
|
+
])
|
|
156
|
+
expect(familyOf(ledger, 'ModuleOverrideDomain').rows.map((row) => row.value)).toEqual([...ALL_OVERRIDE_DOMAINS])
|
|
157
|
+
expect(familyOf(ledger, 'ModuleOverrideMode').rows.map((row) => row.value)).toEqual([...MODULE_OVERRIDE_MODES])
|
|
158
|
+
expect(familyOf(ledger, 'ModuleOverrideTargetNote').rows.map((row) => row.value)).toEqual([
|
|
159
|
+
...MODULE_OVERRIDE_TARGET_NOTES,
|
|
160
|
+
])
|
|
161
|
+
expect(familyOf(ledger, 'ModuleOverrideTargetDiagnosticCode').rows.map((row) => row.value)).toEqual([
|
|
162
|
+
...MODULE_OVERRIDE_TARGET_DIAGNOSTIC_CODES,
|
|
163
|
+
])
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('locks the ten ModuleOwnedContractKind values the spec enumerates', () => {
|
|
167
|
+
expect([...MODULE_OWNED_CONTRACT_KINDS]).toEqual([
|
|
168
|
+
'module-metadata',
|
|
169
|
+
'command',
|
|
170
|
+
'worker',
|
|
171
|
+
'page-middleware',
|
|
172
|
+
'setup',
|
|
173
|
+
'encryption',
|
|
174
|
+
'di-registration',
|
|
175
|
+
'custom-entity',
|
|
176
|
+
'ai-extension',
|
|
177
|
+
'generator-plugin',
|
|
178
|
+
])
|
|
179
|
+
expect(familyOf(ledger, 'ModuleOwnedContractKind').rows).toHaveLength(10)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('uses only the six closed statuses and keeps status self-sufficient', () => {
|
|
183
|
+
const allowed = new Set<string>(CLOSED_LEDGER_STATUSES)
|
|
184
|
+
for (const family of ledger) {
|
|
185
|
+
for (const row of family.rows) {
|
|
186
|
+
expect(allowed.has(row.status)).toBe(true)
|
|
187
|
+
expect(row.note.length).toBeGreaterThan(0)
|
|
188
|
+
// A reader keying on `status` alone must never be told the example emits
|
|
189
|
+
// something it does not: the pending gap lives in the status itself.
|
|
190
|
+
if (row.pendingEmission) expect(row.status).toBe('pending-emission')
|
|
191
|
+
if (row.status === 'pending-emission') expect(row.pendingEmission?.length ?? 0).toBeGreaterThan(0)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
describe('buildCoverageFamily — both directions of the enum/ledger contract', () => {
|
|
198
|
+
const row = (value: string): ModuleFactCoverageRow => ({ value, status: 'emitted-example', note: 'fixture' })
|
|
199
|
+
|
|
200
|
+
it('classifies every enumerated value when the ledger is complete', () => {
|
|
201
|
+
const family = buildCoverageFamily('Fixture', 'packages/cli/src/lib/generators/module-facts.ts#FIXTURE', ['a', 'b'], {
|
|
202
|
+
a: row('a'),
|
|
203
|
+
b: row('b'),
|
|
204
|
+
})
|
|
205
|
+
expect(family.rows.map((entry) => entry.value)).toEqual(['a', 'b'])
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('throws when an enumerated value has no row', () => {
|
|
209
|
+
expect(() =>
|
|
210
|
+
buildCoverageFamily('Fixture', 'packages/cli/src/lib/generators/module-facts.ts#FIXTURE', ['a', 'b'], {
|
|
211
|
+
a: row('a'),
|
|
212
|
+
}),
|
|
213
|
+
).toThrow(/has no row for: b/)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('throws on a stale row for a value the enum no longer carries', () => {
|
|
217
|
+
expect(() =>
|
|
218
|
+
buildCoverageFamily('Fixture', 'packages/cli/src/lib/generators/module-facts.ts#FIXTURE', ['a'], {
|
|
219
|
+
a: row('a'),
|
|
220
|
+
removed: row('removed'),
|
|
221
|
+
}),
|
|
222
|
+
).toThrow(/classifies values absent from the enum: removed/)
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
describe('factCoverage ledger — built by fact generation, not only by this test', () => {
|
|
227
|
+
it('is built and returned on every extractAllModuleFacts run', () => {
|
|
228
|
+
const generated = extractAllModuleFacts({ sources: [] })
|
|
229
|
+
expect(generated.factCoverage).toEqual(buildModuleFactCoverageLedger())
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
describe('factCoverage ledger — checked against the real example extraction', () => {
|
|
234
|
+
const ledger = buildModuleFactCoverageLedger()
|
|
235
|
+
const repoRoot = findRepoRoot()
|
|
236
|
+
const facts = extractAllModuleFacts({
|
|
237
|
+
sources: [...discoverPackageModuleSources(createResolver(repoRoot)), {
|
|
238
|
+
moduleId: 'example',
|
|
239
|
+
moduleRoot: path.join(repoRoot, 'apps', 'mercato', 'src', 'modules', 'example'),
|
|
240
|
+
portableSourceRoot: 'src/modules/example',
|
|
241
|
+
sourceKind: 'local-reference' as const,
|
|
242
|
+
}],
|
|
243
|
+
}).factsByModule.example
|
|
244
|
+
const counts = countModuleFactCoverage(facts)
|
|
245
|
+
|
|
246
|
+
it('extracts the real canonical example module', () => {
|
|
247
|
+
expect(facts.module).toBe('example')
|
|
248
|
+
expect(facts.sourceRoot).toBe('src/modules/example')
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('emits a clean, portable extension graph for the canonical example', () => {
|
|
252
|
+
expect(facts.diagnostics ?? []).toEqual([])
|
|
253
|
+
expect(facts.extensionSurfaces?.unresolved ?? []).toEqual([])
|
|
254
|
+
for (const source of facts.factSources) {
|
|
255
|
+
if (!source.source) continue
|
|
256
|
+
expect(source.source.sourcePath).toMatch(/^src\/modules\/example\//)
|
|
257
|
+
expect(path.isAbsolute(source.source.sourcePath)).toBe(false)
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('resolves lifecycle, guard, event and declared injection targets without fallbacks', () => {
|
|
262
|
+
const targets = facts.extensionSurfaces?.contributions.flatMap((contribution) => contribution.targets) ?? []
|
|
263
|
+
for (const id of [
|
|
264
|
+
'example.todo.creating',
|
|
265
|
+
'example.todo.updating',
|
|
266
|
+
'example.todo',
|
|
267
|
+
'example.ping',
|
|
268
|
+
'example:phase-c-handlers',
|
|
269
|
+
]) {
|
|
270
|
+
expect(targets).toContainEqual(expect.objectContaining({ id }))
|
|
271
|
+
expect(targets.find((target) => target.id === id)?.resolution).not.toBe('unresolved')
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('emits exact command and component caller activations', () => {
|
|
276
|
+
const activations = facts.extensionSurfaces?.activations ?? []
|
|
277
|
+
expect(activations).toContainEqual(expect.objectContaining({
|
|
278
|
+
kind: 'command-interceptor-bridge',
|
|
279
|
+
host: expect.objectContaining({ id: 'example.todos.update' }),
|
|
280
|
+
}))
|
|
281
|
+
expect(activations).toContainEqual(expect.objectContaining({
|
|
282
|
+
kind: 'component-extension-consumer',
|
|
283
|
+
host: expect.objectContaining({ id: 'section:example.overrides.showcase' }),
|
|
284
|
+
}))
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('connects the Todo subscriber to the portal broadcast reaction', () => {
|
|
288
|
+
const contributions = facts.extensionSurfaces?.contributions ?? []
|
|
289
|
+
expect(contributions).toContainEqual(expect.objectContaining({
|
|
290
|
+
id: 'example:announce-todo-to-portal',
|
|
291
|
+
kind: 'subscriber',
|
|
292
|
+
targets: [expect.objectContaining({ id: 'example.todo.*', resolution: 'pattern' })],
|
|
293
|
+
}))
|
|
294
|
+
expect(contributions).toContainEqual(expect.objectContaining({
|
|
295
|
+
id: 'example.todo_announcement.published.browser',
|
|
296
|
+
kind: 'browser-reaction',
|
|
297
|
+
details: expect.objectContaining({ transports: ['portal'] }),
|
|
298
|
+
}))
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
it('proves a non-zero real count for every emitted-example row', () => {
|
|
302
|
+
const unproven: string[] = []
|
|
303
|
+
let proven = 0
|
|
304
|
+
for (const family of ledger) {
|
|
305
|
+
const familyCounts = counts[family.family] ?? {}
|
|
306
|
+
for (const row of family.rows) {
|
|
307
|
+
if (row.status !== 'emitted-example') continue
|
|
308
|
+
if (row.requiresGeneratedRegistry) continue
|
|
309
|
+
const count = familyCounts[row.value] ?? 0
|
|
310
|
+
if (count <= 0) unproven.push(`${family.family}.${row.value}`)
|
|
311
|
+
else proven += 1
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
expect(unproven).toEqual([])
|
|
315
|
+
expect(proven).toBeGreaterThan(0)
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('keeps every pending-emission row at a real count of zero so it cannot go stale', () => {
|
|
319
|
+
const wronglyPending: string[] = []
|
|
320
|
+
const pending: string[] = []
|
|
321
|
+
for (const family of ledger) {
|
|
322
|
+
const familyCounts = counts[family.family] ?? {}
|
|
323
|
+
for (const row of family.rows) {
|
|
324
|
+
if (row.status !== 'pending-emission') continue
|
|
325
|
+
pending.push(`${family.family}.${row.value}`)
|
|
326
|
+
if ((familyCounts[row.value] ?? 0) > 0) wronglyPending.push(`${family.family}.${row.value}`)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
expect(wronglyPending).toEqual([])
|
|
330
|
+
expect(pending).toEqual([])
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
it('keeps every catalog-only row at a real count of zero, since the example contributes nothing for it', () => {
|
|
334
|
+
const contributing: string[] = []
|
|
335
|
+
for (const family of ledger) {
|
|
336
|
+
const familyCounts = counts[family.family] ?? {}
|
|
337
|
+
for (const row of family.rows) {
|
|
338
|
+
if (row.status !== 'catalog-only') continue
|
|
339
|
+
if ((familyCounts[row.value] ?? 0) > 0) contributing.push(`${family.family}.${row.value}`)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
expect(contributing).toEqual([])
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it('publishes all ten owned-contract kinds', () => {
|
|
346
|
+
const ownedCounts = counts.ModuleOwnedContractKind
|
|
347
|
+
for (const kind of MODULE_OWNED_CONTRACT_KINDS) {
|
|
348
|
+
expect(ownedCounts[kind] ?? 0).toBeGreaterThan(0)
|
|
349
|
+
}
|
|
350
|
+
expect(fs.existsSync(path.join(findRepoRoot(), 'apps/mercato/src/modules/example/generators.ts'))).toBe(true)
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it('emits a real override target for all fifteen module-owned domains and none for framework-only nav', () => {
|
|
354
|
+
const domainCounts = counts.ModuleOverrideDomain
|
|
355
|
+
for (const domain of ALL_OVERRIDE_DOMAINS) {
|
|
356
|
+
if (domain === 'nav') {
|
|
357
|
+
expect(domainCounts[domain] ?? 0).toBe(0)
|
|
358
|
+
continue
|
|
359
|
+
}
|
|
360
|
+
expect(domainCounts[domain] ?? 0).toBeGreaterThan(0)
|
|
361
|
+
}
|
|
362
|
+
expect(Object.keys(domainCounts)).toHaveLength(ALL_OVERRIDE_DOMAINS.length - 1)
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
it('round-trips all three modes and both closed notes through real targets', () => {
|
|
366
|
+
for (const mode of MODULE_OVERRIDE_MODES) {
|
|
367
|
+
expect(counts.ModuleOverrideMode[mode] ?? 0).toBeGreaterThan(0)
|
|
368
|
+
}
|
|
369
|
+
for (const note of MODULE_OVERRIDE_TARGET_NOTES) {
|
|
370
|
+
expect(counts.ModuleOverrideTargetNote[note] ?? 0).toBeGreaterThan(0)
|
|
371
|
+
}
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
it('emits zero override-target diagnostics from activated canonical output', () => {
|
|
375
|
+
expect(facts.overrideTargetDiagnostics ?? []).toEqual([])
|
|
376
|
+
for (const code of MODULE_OVERRIDE_TARGET_DIAGNOSTIC_CODES) {
|
|
377
|
+
expect(counts.ModuleOverrideTargetDiagnosticCode[code] ?? 0).toBe(0)
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it('never classifies a diagnostic code as canonical coverage', () => {
|
|
382
|
+
for (const row of familyOf(ledger, 'ModuleOverrideTargetDiagnosticCode').rows) {
|
|
383
|
+
expect(['negative-fixture', 'currently-unbound']).toContain(row.status)
|
|
384
|
+
}
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
it('backs every negative-fixture claim with a fixture that really produces that code', () => {
|
|
388
|
+
const claimed = familyOf(ledger, 'ModuleOverrideTargetDiagnosticCode')
|
|
389
|
+
.rows.filter((row) => row.status === 'negative-fixture')
|
|
390
|
+
.map((row) => row.value)
|
|
391
|
+
.sort()
|
|
392
|
+
const produced = OVERRIDE_TARGET_DIAGNOSTIC_FIXTURES.flatMap((fixture) =>
|
|
393
|
+
fixture.run().map((diagnostic) => diagnostic.code),
|
|
394
|
+
)
|
|
395
|
+
expect([...new Set(produced)].sort()).toEqual(claimed)
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it('classifies a code no fixture can reach as currently-unbound, not as a negative fixture', () => {
|
|
399
|
+
const unbound = familyOf(ledger, 'ModuleOverrideTargetDiagnosticCode')
|
|
400
|
+
.rows.filter((row) => row.status === 'currently-unbound')
|
|
401
|
+
.map((row) => row.value)
|
|
402
|
+
expect(unbound).toEqual(['missing-owned-fact'])
|
|
403
|
+
const produced = new Set(
|
|
404
|
+
OVERRIDE_TARGET_DIAGNOSTIC_FIXTURES.flatMap((fixture) => fixture.run().map((diagnostic) => diagnostic.code)),
|
|
405
|
+
)
|
|
406
|
+
for (const code of unbound) expect(produced.has(code)).toBe(false)
|
|
407
|
+
const projectorSource = fs.readFileSync(
|
|
408
|
+
path.join(findRepoRoot(), 'packages/cli/src/lib/generators/module-override-targets.ts'),
|
|
409
|
+
'utf8',
|
|
410
|
+
)
|
|
411
|
+
// Reserved by the closed set, but no adapter names it as a `code:` value today.
|
|
412
|
+
expect(projectorSource).not.toMatch(/code: 'missing-owned-fact'/)
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* `requiresGeneratedRegistry` is the only way a row can skip the non-zero proof, so
|
|
418
|
+
* the flag itself is proven here rather than trusted: a row carrying it must report
|
|
419
|
+
* zero from a source-only extraction (otherwise the row is extractable from source
|
|
420
|
+
* and the skip is unwarranted) AND a real count once the same extraction is handed a
|
|
421
|
+
* module registry (otherwise nothing emits it at all and the row is not
|
|
422
|
+
* `emitted-example`). Setting the flag on a row that fails either side fails here.
|
|
423
|
+
*/
|
|
424
|
+
describe('requiresGeneratedRegistry — the only skip out of the non-zero proof is itself proven', () => {
|
|
425
|
+
const ledger = buildModuleFactCoverageLedger()
|
|
426
|
+
const sourceOnlyCounts = countModuleFactCoverage(extractExampleFacts())
|
|
427
|
+
const registryBackedCounts = countModuleFactCoverage(extractExampleFacts(exampleRegistrySource()))
|
|
428
|
+
const claims = ledger.flatMap((family) =>
|
|
429
|
+
family.rows
|
|
430
|
+
.filter((row) => row.requiresGeneratedRegistry)
|
|
431
|
+
.map((row) => ({ family: family.family, row })),
|
|
432
|
+
)
|
|
433
|
+
const label = (claim: { family: string; row: ModuleFactCoverageRow }): string =>
|
|
434
|
+
`${claim.family}.${claim.row.value}`
|
|
435
|
+
|
|
436
|
+
it('is claimed by exactly the rows the rest of this suite proves', () => {
|
|
437
|
+
expect(claims.map(label)).toEqual(['ModuleFactSourceKind.api-route'])
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
it('is claimed only where it changes anything: on an emitted-example row', () => {
|
|
441
|
+
for (const claim of claims) expect(claim.row.status).toBe('emitted-example')
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
it('is claimed only by a value a source-only extraction genuinely cannot see', () => {
|
|
445
|
+
const visibleFromSource = claims
|
|
446
|
+
.filter((claim) => (sourceOnlyCounts[claim.family]?.[claim.row.value] ?? 0) > 0)
|
|
447
|
+
.map(label)
|
|
448
|
+
expect(visibleFromSource).toEqual([])
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
it('is claimed only by a value the generated registry really does yield', () => {
|
|
452
|
+
const stillZeroWithRegistry = claims
|
|
453
|
+
.filter((claim) => (registryBackedCounts[claim.family]?.[claim.row.value] ?? 0) <= 0)
|
|
454
|
+
.map(label)
|
|
455
|
+
expect(stillZeroWithRegistry).toEqual([])
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
it('drives the registry-backed count from a route the example module really ships', () => {
|
|
459
|
+
expect(fs.existsSync(path.join(findRepoRoot(), REGISTRY_BACKED_ROUTE.sourceFile))).toBe(true)
|
|
460
|
+
expect(registryBackedCounts.ModuleFactSourceKind['api-route']).toBe(1)
|
|
461
|
+
expect(sourceOnlyCounts.ModuleFactSourceKind['api-route'] ?? 0).toBe(0)
|
|
462
|
+
})
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
describe('factCoverage published into the example surface inventories', () => {
|
|
466
|
+
const ledger = buildModuleFactCoverageLedger()
|
|
467
|
+
const inventoryPaths = [
|
|
468
|
+
'apps/mercato/src/modules/example/references/surface-inventory.json',
|
|
469
|
+
'packages/create-app/template/src/modules/example/references/surface-inventory.json',
|
|
470
|
+
]
|
|
471
|
+
|
|
472
|
+
function readInventory(relativePath: string): {
|
|
473
|
+
factCoverage?: { generatedNote?: string; families?: ModuleFactCoverageFamily[] }
|
|
474
|
+
} {
|
|
475
|
+
return JSON.parse(fs.readFileSync(path.join(findRepoRoot(), relativePath), 'utf8'))
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
it.each(inventoryPaths)('%s publishes the ledger verbatim', (relativePath) => {
|
|
479
|
+
const inventory = readInventory(relativePath)
|
|
480
|
+
expect(inventory.factCoverage?.families).toEqual(ledger)
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
it.each(inventoryPaths)('%s warns that a ledger status is not proof on its own', (relativePath) => {
|
|
484
|
+
const note = readInventory(relativePath).factCoverage?.generatedNote ?? ''
|
|
485
|
+
expect(note).toContain('STATUS IS NOT PROOF ON ITS OWN')
|
|
486
|
+
expect(note).toContain('pendingEmission')
|
|
487
|
+
expect(note).toContain('requiresGeneratedRegistry')
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
it.each(inventoryPaths)('%s documents every status the closed set can publish', (relativePath) => {
|
|
491
|
+
const note = readInventory(relativePath).factCoverage?.generatedNote ?? ''
|
|
492
|
+
const undocumented = CLOSED_LEDGER_STATUSES.filter((status) => !note.includes(`\`${status}\``))
|
|
493
|
+
expect(undocumented).toEqual([])
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
it('keeps the app inventory and the create-app template mirror byte-identical', () => {
|
|
497
|
+
const [appInventory, templateInventory] = inventoryPaths.map((relativePath) =>
|
|
498
|
+
fs.readFileSync(path.join(findRepoRoot(), relativePath), 'utf8'),
|
|
499
|
+
)
|
|
500
|
+
expect(templateInventory).toBe(appInventory)
|
|
501
|
+
})
|
|
502
|
+
})
|