@canonical/pragma-cli 0.36.0 → 0.38.0
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/README.md +3 -2
- package/bundled-skills/adoption-a1-styles/SKILL.md +24 -0
- package/bundled-skills/adoption-a2-components/SKILL.md +22 -6
- package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +1397 -327
- package/bundled-skills/anatomy-author/SKILL.md +840 -137
- package/bundled-skills/design-auditor/SKILL.md +133 -9
- package/bundled-skills/implement-component/SKILL.md +192 -23
- package/bundled-skills/specify-component/SKILL.md +124 -12
- package/bundled-skills/specify-pattern/SKILL.md +47 -8
- package/dist/package.json +23 -20
- package/dist/pragma.conf.js +963 -40
- package/dist/pragma.conf.js.map +1 -1
- package/dist/src/capabilities/capabilities/catalog.js +14 -5
- package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
- package/dist/src/capabilities/capabilities/hints.js +33 -5
- package/dist/src/capabilities/capabilities/hints.js.map +1 -1
- package/dist/src/capabilities/config/show.render.js +30 -2
- package/dist/src/capabilities/config/show.render.js.map +1 -1
- package/dist/src/capabilities/create/create.verb.js +2 -1
- package/dist/src/capabilities/create/create.verb.js.map +1 -1
- package/dist/src/capabilities/create/createSurface.generated.js +23 -1
- package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +11 -6
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js +38 -0
- package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/targetHealth.js +104 -14
- package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
- package/dist/src/capabilities/graph/connect.render.js +101 -0
- package/dist/src/capabilities/graph/connect.render.js.map +1 -0
- package/dist/src/capabilities/graph/connect.types.js +21 -0
- package/dist/src/capabilities/graph/connect.types.js.map +1 -0
- package/dist/src/capabilities/graph/connect.verb.js +147 -0
- package/dist/src/capabilities/graph/connect.verb.js.map +1 -0
- package/dist/src/capabilities/graph/index.js +5 -3
- package/dist/src/capabilities/graph/index.js.map +1 -1
- package/dist/src/capabilities/graph/queryText.js +206 -0
- package/dist/src/capabilities/graph/queryText.js.map +1 -0
- package/dist/src/capabilities/graph/relationIndex.js +302 -0
- package/dist/src/capabilities/graph/relationIndex.js.map +1 -0
- package/dist/src/capabilities/graph/runConnect.js +456 -0
- package/dist/src/capabilities/graph/runConnect.js.map +1 -0
- package/dist/src/capabilities/graph/runQuery.js +21 -4
- package/dist/src/capabilities/graph/runQuery.js.map +1 -1
- package/dist/src/capabilities/setup/buildPlan.js +6 -2
- package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
- package/dist/src/capabilities/setup/operations/index.js +7 -2
- package/dist/src/capabilities/setup/operations/index.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupCompletions.js +6 -2
- package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupConfig.js +4 -2
- package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupGenerator.js +72 -14
- package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupLsp.js +372 -62
- package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupMcp.js +150 -5
- package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupSkills.js +4 -2
- package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
- package/dist/src/capabilities/setup/operations/writability.js +114 -0
- package/dist/src/capabilities/setup/operations/writability.js.map +1 -0
- package/dist/src/capabilities/setup/plan.js.map +1 -1
- package/dist/src/capabilities/setup/setup.verb.js +80 -31
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
- package/dist/src/capabilities/setup/targets.js +124 -42
- package/dist/src/capabilities/setup/targets.js.map +1 -1
- package/dist/src/capabilities/shared/index.js +1 -1
- package/dist/src/capabilities/shared/index.js.map +1 -1
- package/dist/src/constants.js +7 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/kernel/error/renderError.js +3 -2
- package/dist/src/kernel/error/renderError.js.map +1 -1
- package/dist/src/kernel/error/validOptions.js +41 -0
- package/dist/src/kernel/error/validOptions.js.map +1 -0
- package/dist/src/kernel/packs/compile.js +90 -6
- package/dist/src/kernel/packs/compile.js.map +1 -1
- package/dist/src/kernel/packs/cursor.js +135 -0
- package/dist/src/kernel/packs/cursor.js.map +1 -0
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js +9 -2
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js.map +1 -1
- package/dist/src/kernel/packs/paging.js +108 -0
- package/dist/src/kernel/packs/paging.js.map +1 -0
- package/dist/src/kernel/packs/renderPack.js +154 -5
- package/dist/src/kernel/packs/renderPack.js.map +1 -1
- package/dist/src/kernel/packs/resolveEntity.js +118 -19
- package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
- package/dist/src/kernel/packs/runBodies.js +156 -18
- package/dist/src/kernel/packs/runBodies.js.map +1 -1
- package/dist/src/kernel/packs/schema.js +160 -12
- package/dist/src/kernel/packs/schema.js.map +1 -1
- package/dist/src/kernel/packs/sparql/authorQuery.js +227 -0
- package/dist/src/kernel/packs/sparql/authorQuery.js.map +1 -0
- package/dist/src/kernel/packs/sparql/buildListQuery.js +251 -0
- package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -0
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js +125 -22
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
- package/dist/src/kernel/packs/sparql/filterValues.js +145 -0
- package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -0
- package/dist/src/kernel/packs/sparql/index.js +24 -14
- package/dist/src/kernel/packs/sparql/index.js.map +1 -1
- package/dist/src/kernel/packs/sparql/runSelect.js +10 -4
- package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
- package/dist/src/kernel/packs/sparql/searchTerm.js +25 -0
- package/dist/src/kernel/packs/sparql/searchTerm.js.map +1 -0
- package/dist/src/kernel/packs/storyRules.js +151 -0
- package/dist/src/kernel/packs/storyRules.js.map +1 -0
- package/dist/src/kernel/packs/tierScope.js +310 -0
- package/dist/src/kernel/packs/tierScope.js.map +1 -0
- package/dist/src/kernel/packs/types.js +80 -0
- package/dist/src/kernel/packs/types.js.map +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js +10 -1
- package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
- package/dist/src/kernel/project/cli/dispatch.js +104 -3
- package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
- package/dist/src/kernel/project/cli/rootHelp.js +8 -5
- package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
- package/dist/src/kernel/project/cli/suggestNames.js +31 -7
- package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
- package/dist/src/kernel/project/mcp/registerVerb.js +5 -1
- package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
- package/dist/src/kernel/render/renderers.js +34 -2
- package/dist/src/kernel/render/renderers.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +2 -3
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js +4 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/embedded.js +13 -6
- package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/read.js +17 -10
- package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
- package/dist/src/kernel/runtime/loadSession.js +3 -1
- package/dist/src/kernel/runtime/loadSession.js.map +1 -1
- package/dist/src/kernel/runtime/probe.js +1 -1
- package/dist/src/kernel/runtime/probe.js.map +1 -1
- package/dist/src/kernel/runtime/readEntity.js +19 -4
- package/dist/src/kernel/runtime/readEntity.js.map +1 -1
- package/dist/src/kernel/runtime/refs/parseRef.js +17 -1
- package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
- package/dist/src/kernel/runtime/refs/resolve.js +38 -5
- package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
- package/dist/src/kernel/spec/emitReference.js +1 -1
- package/dist/src/kernel/spec/emitReference.js.map +1 -1
- package/package.json +23 -20
- package/pragma.conf.ts +983 -40
- package/src/capabilities/block.parity.test.ts +207 -27
- package/src/capabilities/block.shipped.exec.test.ts +156 -14
- package/src/capabilities/block.tierRank.exec.test.ts +46 -9
- package/src/capabilities/capabilities/catalog.ts +14 -5
- package/src/capabilities/capabilities/hints.ts +40 -5
- package/src/capabilities/config/collectConfigShow.test.ts +1 -0
- package/src/capabilities/config/set.test.ts +24 -25
- package/src/capabilities/config/show.render.test.ts +9 -4
- package/src/capabilities/config/show.render.ts +30 -2
- package/src/capabilities/create/byteEquality.test.ts +28 -1
- package/src/capabilities/create/create.test.ts +1 -0
- package/src/capabilities/create/create.verb.ts +2 -1
- package/src/capabilities/create/createGrammar.test.ts +4 -1
- package/src/capabilities/create/createSurface.generated.ts +23 -1
- package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
- package/src/capabilities/distribution.parity.test.ts +12 -11
- package/src/capabilities/distribution.test.ts +77 -7
- package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
- package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
- package/src/capabilities/doctor/checks/harnessInventory.test.ts +13 -6
- package/src/capabilities/doctor/checks/harnessInventory.ts +8 -4
- package/src/capabilities/doctor/checks/isSupportedNodeVersion.test.ts +111 -0
- package/src/capabilities/doctor/checks/isSupportedNodeVersion.ts +34 -0
- package/src/capabilities/doctor/checks/mcpCommand.test.ts +7 -1
- package/src/capabilities/doctor/checks/targetHealth.ts +114 -13
- package/src/capabilities/doctor/doctor.test.ts +327 -36
- package/src/capabilities/emptyRecovery.test.ts +84 -28
- package/src/capabilities/graph/connect.render.ts +141 -0
- package/src/capabilities/graph/connect.shipped.exec.test.ts +370 -0
- package/src/capabilities/graph/connect.test.ts +361 -0
- package/src/capabilities/graph/connect.types.ts +136 -0
- package/src/capabilities/graph/connect.verb.ts +165 -0
- package/src/capabilities/graph/index.ts +5 -3
- package/src/capabilities/graph/query.shipped.exec.test.ts +66 -0
- package/src/capabilities/graph/query.test.ts +17 -0
- package/src/capabilities/graph/queryText.test.ts +121 -0
- package/src/capabilities/graph/queryText.ts +240 -0
- package/src/capabilities/graph/relationIndex.ts +372 -0
- package/src/capabilities/graph/runConnect.ts +577 -0
- package/src/capabilities/graph/runQuery.ts +21 -6
- package/src/capabilities/lazy.test.ts +32 -2
- package/src/capabilities/listBudget.shipped.exec.test.ts +239 -0
- package/src/capabilities/listLookup.shipped.exec.test.ts +387 -0
- package/src/capabilities/listQuery.shipped.exec.test.ts +400 -0
- package/src/capabilities/resources/resources.test.ts +38 -2
- package/src/capabilities/setup/buildPlan.ts +7 -1
- package/src/capabilities/setup/operations/index.ts +14 -0
- package/src/capabilities/setup/operations/setupCompletions.ts +9 -2
- package/src/capabilities/setup/operations/setupConfig.ts +7 -2
- package/src/capabilities/setup/operations/setupGenerator.ts +93 -15
- package/src/capabilities/setup/operations/setupLsp.ts +544 -85
- package/src/capabilities/setup/operations/setupMcp.ts +212 -4
- package/src/capabilities/setup/operations/setupSkills.ts +7 -2
- package/src/capabilities/setup/operations/writability.test.ts +151 -0
- package/src/capabilities/setup/operations/writability.ts +135 -0
- package/src/capabilities/setup/plan.ts +11 -0
- package/src/capabilities/setup/setup.test.ts +992 -16
- package/src/capabilities/setup/setup.verb.ts +84 -36
- package/src/capabilities/setup/targets.ts +152 -48
- package/src/capabilities/setup/types.ts +41 -5
- package/src/capabilities/shared/index.ts +1 -0
- package/src/capabilities/sources/sources.test.ts +55 -0
- package/src/capabilities/surface.test.ts +59 -11
- package/src/capabilities/tier.parity.test.ts +2 -2
- package/src/capabilities/token.parity.test.ts +784 -0
- package/src/capabilities/tokenGraph.cli.test.ts +279 -0
- package/src/capabilities/tokenGraph.shipped.exec.test.ts +653 -0
- package/src/capabilities/variable.parity.test.ts +448 -0
- package/src/constants.ts +8 -0
- package/src/kernel/completion/shellDrive.test.ts +32 -30
- package/src/kernel/config/defaults.test.ts +21 -2
- package/src/kernel/config/readConfig.test.ts +1 -1
- package/src/kernel/error/renderError.ts +3 -2
- package/src/kernel/error/validOptions.test.ts +57 -0
- package/src/kernel/error/validOptions.ts +45 -0
- package/src/kernel/packs/collect.test.ts +2 -0
- package/src/kernel/packs/compile.test.ts +188 -15
- package/src/kernel/packs/compile.ts +109 -14
- package/src/kernel/packs/cursor.ts +135 -0
- package/src/kernel/packs/filteredEmpty.test.ts +213 -0
- package/src/kernel/packs/graphql/buildLookupDocument.ts +16 -9
- package/src/kernel/packs/graphqlSource.test.ts +152 -0
- package/src/kernel/packs/listNotice.test.ts +156 -0
- package/src/kernel/packs/paging.ts +110 -0
- package/src/kernel/packs/renderPack.ts +186 -13
- package/src/kernel/packs/resolveEntity.test.ts +62 -3
- package/src/kernel/packs/resolveEntity.ts +186 -26
- package/src/kernel/packs/runBodies.ts +196 -26
- package/src/kernel/packs/schema.ts +180 -13
- package/src/kernel/packs/sparql/authorQuery.test.ts +179 -0
- package/src/kernel/packs/sparql/authorQuery.ts +257 -0
- package/src/kernel/packs/sparql/buildListQuery.test.ts +344 -0
- package/src/kernel/packs/sparql/buildListQuery.ts +344 -0
- package/src/kernel/packs/sparql/buildLookupQuery.test.ts +138 -0
- package/src/kernel/packs/sparql/buildLookupQuery.ts +140 -20
- package/src/kernel/packs/sparql/filterValues.test.ts +86 -0
- package/src/kernel/packs/sparql/filterValues.ts +182 -0
- package/src/kernel/packs/sparql/index.ts +30 -14
- package/src/kernel/packs/sparql/runSelect.test.ts +63 -0
- package/src/kernel/packs/sparql/runSelect.ts +42 -2
- package/src/kernel/packs/sparql/searchTerm.ts +29 -0
- package/src/kernel/packs/storyRules.ts +180 -0
- package/src/kernel/packs/tierScope.test.ts +487 -0
- package/src/kernel/packs/tierScope.ts +409 -0
- package/src/kernel/packs/toolDescription.test.ts +13 -10
- package/src/kernel/packs/types.ts +311 -9
- package/src/kernel/project/cli/buildProgram.ts +17 -1
- package/src/kernel/project/cli/dispatch.test.ts +87 -0
- package/src/kernel/project/cli/dispatch.ts +114 -4
- package/src/kernel/project/cli/rootHelp.ts +8 -5
- package/src/kernel/project/cli/suggest.test.ts +35 -0
- package/src/kernel/project/cli/suggestNames.ts +34 -7
- package/src/kernel/project/mcp/registerVerb.ts +5 -1
- package/src/kernel/render/contracts.ts +2 -0
- package/src/kernel/render/prefixes.test.ts +2 -0
- package/src/kernel/render/renderers.test.ts +43 -0
- package/src/kernel/render/renderers.ts +34 -1
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +2 -3
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
- package/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.ts +3 -0
- package/src/kernel/runtime/graphpack/embedded.ts +14 -6
- package/src/kernel/runtime/graphpack/graphpack.test.ts +31 -5
- package/src/kernel/runtime/graphpack/read.ts +17 -13
- package/src/kernel/runtime/loadSession.ts +3 -1
- package/src/kernel/runtime/probe.ts +1 -1
- package/src/kernel/runtime/readEntity.ts +19 -4
- package/src/kernel/runtime/refs/parseRef.test.ts +49 -1
- package/src/kernel/runtime/refs/parseRef.ts +25 -1
- package/src/kernel/runtime/refs/resolve.ts +39 -5
- package/src/kernel/runtime/types.ts +30 -0
- package/src/kernel/spec/emitReference.ts +1 -1
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/types.ts +17 -0
- package/src/testing/PARITY_GAPS.ts +4 -4
- package/src/testing/behavioral/agentSession.mcp.test.ts +43 -39
- package/src/testing/behavioral/completion.test.ts +7 -6
- package/src/testing/behavioral/disclosure.test.ts +39 -37
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +82 -64
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +11 -10
- package/src/testing/behavioral/journeys.cli.test.ts +71 -32
- package/src/testing/behavioral/journeys.defaultPack.test.ts +6 -4
- package/src/testing/behavioral/journeys.firstInstall.test.ts +70 -5
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +4 -3
- package/src/testing/behavioral/journeys.packageStories.test.ts +2 -1
- package/src/testing/behavioral/mcpSurface.test.ts +28 -25
- package/src/testing/behavioral/parity.test.ts +25 -23
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +20 -0
- package/src/testing/eval/cases/effectful.ts +7 -1
- package/src/testing/eval/cases/readNouns.ts +100 -11
- package/src/testing/fixtures/blockGraph.ts +89 -5
- package/src/testing/fixtures/connectGraph.ts +159 -0
- package/src/testing/fixtures/graph/canonical.ts +128 -32
- package/src/testing/helpers/fsProbe.ts +88 -0
- package/src/testing/helpers/runCli.test.ts +44 -0
- package/src/testing/helpers/runCli.ts +27 -3
- package/src/testing/perf/budgets.test.ts +3 -1
- package/src/testing/perf/budgets.ts +138 -54
- package/dist/src/kernel/packs/sparql/applyFilters.js +0 -167
- package/dist/src/kernel/packs/sparql/applyFilters.js.map +0 -1
- package/dist/src/kernel/packs/sparql/applySearch.js +0 -25
- package/dist/src/kernel/packs/sparql/applySearch.js.map +0 -1
- package/src/kernel/packs/sparql/applyFilters.ts +0 -203
- package/src/kernel/packs/sparql/applySearch.ts +0 -33
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `token` and `variable` stories, EXECUTED against the shipped pack
|
|
3
|
+
* (PROTECTED).
|
|
4
|
+
*
|
|
5
|
+
* `token.parity.test.ts` and `variable.parity.test.ts` assert the stories'
|
|
6
|
+
* semantics over fixture graphs this repository authors. That is a real check,
|
|
7
|
+
* but a fixture obligingly asserts whatever the story reads — it can prove the
|
|
8
|
+
* story agrees with the fixture and nothing about the corpus a release actually
|
|
9
|
+
* ships. So this file boots the shipped graph and asserts the two things a
|
|
10
|
+
* fixture structurally cannot.
|
|
11
|
+
*
|
|
12
|
+
* FIRST, that every graph term the two stories name is DEFINED by the shipped
|
|
13
|
+
* ontology. Defined, not asserted, and the difference is the whole point: a
|
|
14
|
+
* term the ontology never defines is a story bug that can never render, on any
|
|
15
|
+
* entity, on any install — the defect that let `block lookup` publish no usage
|
|
16
|
+
* narrative for every block on every install, silently, because a name that
|
|
17
|
+
* maps onto no property is exactly what OPTIONAL parity swallows. A term the
|
|
18
|
+
* ontology defines that no instance asserts yet is a content gap and not this
|
|
19
|
+
* suite's business.
|
|
20
|
+
*
|
|
21
|
+
* SECOND, the corpus INVARIANTS the two nouns' addressability rests on: that
|
|
22
|
+
* every symbol and every variable carries exactly one name literal, that the
|
|
23
|
+
* dash-stripping which makes a variable typable is injective and collides with
|
|
24
|
+
* no symbol name, and that a resolved value carries exactly one of a chain or a
|
|
25
|
+
* derivation. Each of those is a property of the whole population, so each is
|
|
26
|
+
* asserted as a count of VIOLATIONS being zero rather than as a row count the
|
|
27
|
+
* next upstream release would move. The populations themselves are deliberately
|
|
28
|
+
* not pinned: upstream adds symbols, and a test that failed because it did
|
|
29
|
+
* would be a test of the wrong thing.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
33
|
+
import { compileStoryModule } from "../kernel/packs/compile.js";
|
|
34
|
+
import { MAX_LIST_WINDOW } from "../kernel/packs/paging.js";
|
|
35
|
+
import {
|
|
36
|
+
distributionSource,
|
|
37
|
+
isNestedExpand,
|
|
38
|
+
type PackExpandSelect,
|
|
39
|
+
type PackLookup,
|
|
40
|
+
type PackPage,
|
|
41
|
+
} from "../kernel/packs/types.js";
|
|
42
|
+
import { verbKey } from "../kernel/packs/uniqueness.js";
|
|
43
|
+
import { bootRuntime } from "../kernel/runtime/boot.js";
|
|
44
|
+
import type { PragmaRuntime } from "../kernel/runtime/types.js";
|
|
45
|
+
import type { CapabilityModule, VerbSpec } from "../kernel/spec/types.js";
|
|
46
|
+
import { TEST_FLAGS } from "../testing/helpers/projectCli.js";
|
|
47
|
+
import { declaredStories } from "./distribution.js";
|
|
48
|
+
|
|
49
|
+
const SOURCE = distributionSource("pragma.conf.ts");
|
|
50
|
+
|
|
51
|
+
/** The two stories this file owns, and their compiled modules. */
|
|
52
|
+
const NOUNS = ["token", "variable"] as const;
|
|
53
|
+
|
|
54
|
+
const modules = new Map<string, CapabilityModule>(
|
|
55
|
+
NOUNS.map((noun) => {
|
|
56
|
+
const story = declaredStories.get(noun);
|
|
57
|
+
if (!story) throw new Error(`pragma.conf.ts declares no "${noun}" story`);
|
|
58
|
+
return [noun, compileStoryModule(story, SOURCE, {})];
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const lookups = new Map<string, PackLookup>(
|
|
63
|
+
NOUNS.map((noun) => {
|
|
64
|
+
const lookup = declaredStories.get(noun)?.lookup;
|
|
65
|
+
if (!lookup) throw new Error(`the "${noun}" story declares no lookup`);
|
|
66
|
+
return [noun, lookup];
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
function verb(noun: string, name: string): VerbSpec {
|
|
71
|
+
const found = modules
|
|
72
|
+
.get(noun)
|
|
73
|
+
?.verbs.find((v) => verbKey(v.path) === `${noun} ${name}`);
|
|
74
|
+
if (!found) throw new Error(`no compiled "${noun} ${name}" verb`);
|
|
75
|
+
return found;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** One graph term a story names, paired with the site that declares it. */
|
|
79
|
+
interface DeclaredTerm {
|
|
80
|
+
readonly what: string;
|
|
81
|
+
readonly term: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Standard RDF vocabulary, which no domain ontology declares and every graph
|
|
86
|
+
* carries. The distribution's own vocabulary declaration leaves `rdfs:label`
|
|
87
|
+
* and `rdfs:comment` out for exactly this reason — "the kernel treats standard
|
|
88
|
+
* vocabulary as universal" — and the list walk that reads an `rdf:List` is the
|
|
89
|
+
* same case: `rdf:first` and `rdf:rest` are the list's own structure, not terms
|
|
90
|
+
* a token ontology could define.
|
|
91
|
+
*
|
|
92
|
+
* Skipped here rather than admitted by a looser ASK, so the sweep below stays a
|
|
93
|
+
* hard requirement on every DOMAIN term and this exemption is a readable list
|
|
94
|
+
* rather than a widened predicate.
|
|
95
|
+
*/
|
|
96
|
+
const STANDARD_VOCABULARY = new Set([
|
|
97
|
+
"rdf:first",
|
|
98
|
+
"rdf:rest",
|
|
99
|
+
"rdfs:label",
|
|
100
|
+
"rdfs:comment",
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The individual property steps of a declared term.
|
|
105
|
+
*
|
|
106
|
+
* A declared term may be a property PATH, and the shipped stories use three
|
|
107
|
+
* shapes of one: an inverse step (`^dt:symbol`, from a symbol back to the
|
|
108
|
+
* definitions that point at it), a sequence (`dt:under/dt-web:selector`), and a
|
|
109
|
+
* list walk, whose middle step carries a zero-or-more marker. An ASK over the path
|
|
110
|
+
* itself is not the question — the question is whether each STEP names a
|
|
111
|
+
* property the ontology defines — and a path spliced into an ASK's subject
|
|
112
|
+
* position would not even parse.
|
|
113
|
+
*/
|
|
114
|
+
function steps(term: string): string[] {
|
|
115
|
+
return term
|
|
116
|
+
.split("/")
|
|
117
|
+
.map((step) => step.replace(/^\^/, "").replace(/[*+?]$/, ""))
|
|
118
|
+
.filter((step) => step !== "" && !STANDARD_VOCABULARY.has(step));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Every term an `expand.select` names, to whatever depth it nests. */
|
|
122
|
+
function selectedTerms(
|
|
123
|
+
site: string,
|
|
124
|
+
select: readonly PackExpandSelect[],
|
|
125
|
+
): readonly DeclaredTerm[] {
|
|
126
|
+
return select.flatMap((entry) =>
|
|
127
|
+
isNestedExpand(entry)
|
|
128
|
+
? [
|
|
129
|
+
{ what: `${site} → nested "${entry.name}"`, term: entry.relation },
|
|
130
|
+
...selectedTerms(`${site} → "${entry.name}"`, entry.select),
|
|
131
|
+
]
|
|
132
|
+
: [{ what: `${site} → field "${entry.name}"`, term: entry.property }],
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Every property the two stories name in the graph, read from the declaration
|
|
138
|
+
* rather than listed — a term added to any field, section or expand is covered
|
|
139
|
+
* the moment it is declared, by the author who declares it.
|
|
140
|
+
*
|
|
141
|
+
* The LOOKUP half only. A list query is author TEXT the kernel does not
|
|
142
|
+
* compose, so its terms cannot be enumerated from the declaration; what guards
|
|
143
|
+
* those is that the list must answer rows at all, which the cross-noun
|
|
144
|
+
* round-trip sweep asserts for every noun with both halves.
|
|
145
|
+
*/
|
|
146
|
+
const DECLARED_TERMS: readonly DeclaredTerm[] = NOUNS.flatMap((noun) => {
|
|
147
|
+
const lookup = lookups.get(noun) as PackLookup;
|
|
148
|
+
return [
|
|
149
|
+
{ what: `${noun} lookup \`by\``, term: lookup.by },
|
|
150
|
+
...(lookup.fields ?? []).map((f) => ({
|
|
151
|
+
what: `${noun} field "${f.name}"`,
|
|
152
|
+
term: f.property,
|
|
153
|
+
})),
|
|
154
|
+
...(lookup.sections ?? []).map((s) => ({
|
|
155
|
+
what: `${noun} section "${s.name}"`,
|
|
156
|
+
term: s.property,
|
|
157
|
+
})),
|
|
158
|
+
...(lookup.expand ?? []).flatMap((e) => [
|
|
159
|
+
{ what: `${noun} expand "${e.name}"`, term: e.relation },
|
|
160
|
+
...selectedTerms(`${noun} expand "${e.name}"`, e.select),
|
|
161
|
+
]),
|
|
162
|
+
];
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
/** One case per (declaration site, property step). */
|
|
166
|
+
const DECLARED_STEPS: readonly { what: string; property: string }[] =
|
|
167
|
+
DECLARED_TERMS.flatMap(({ what, term }) =>
|
|
168
|
+
steps(term).map((property) => ({ what, property })),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// ONE store for the file — booting the shipped pack is the expensive part, and
|
|
172
|
+
// this package already contends under parallel runs.
|
|
173
|
+
let rt: PragmaRuntime;
|
|
174
|
+
beforeAll(async () => {
|
|
175
|
+
rt = bootRuntime(TEST_FLAGS);
|
|
176
|
+
await rt.store.get();
|
|
177
|
+
}, 60_000);
|
|
178
|
+
afterAll(async () => {
|
|
179
|
+
(await rt.store.get()).store.dispose();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/** One scalar from the shipped graph, queried directly. */
|
|
183
|
+
async function count(query: string): Promise<number> {
|
|
184
|
+
const result = await rt.query.sparql(query);
|
|
185
|
+
if (result.type !== "select") throw new Error("expected a SELECT");
|
|
186
|
+
return Number(Object.values(result.bindings[0] ?? {})[0] ?? Number.NaN);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** The rows of a list-shaped verb over the shipped pack. */
|
|
190
|
+
async function rows(
|
|
191
|
+
noun: string,
|
|
192
|
+
name: string,
|
|
193
|
+
params: Record<string, unknown> = {},
|
|
194
|
+
): Promise<Record<string, string>[]> {
|
|
195
|
+
const page = (await verb(noun, name).run(params, rt)) as PackPage;
|
|
196
|
+
return [...page.rows];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** One looked-up entity from the shipped pack. */
|
|
200
|
+
async function lookup(
|
|
201
|
+
noun: string,
|
|
202
|
+
name: string,
|
|
203
|
+
): Promise<Record<string, unknown>> {
|
|
204
|
+
const out = (await verb(noun, "lookup").run({ name: [name] }, rt)) as {
|
|
205
|
+
errors: unknown[];
|
|
206
|
+
results: Record<string, unknown>[];
|
|
207
|
+
};
|
|
208
|
+
expect(out.errors).toEqual([]);
|
|
209
|
+
return out.results[0] as Record<string, unknown>;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
describe("every declared term is DEFINED by the shipped ontology (PROTECTED)", () => {
|
|
213
|
+
it("derives the sweep from the declarations rather than a list", () => {
|
|
214
|
+
// A guard, not a no-op: an empty derivation would make every case below
|
|
215
|
+
// vacuous. Both nouns declare a `by`, fields and expands, so the floor is
|
|
216
|
+
// comfortably above zero without pinning a number that moves whenever a
|
|
217
|
+
// field is added.
|
|
218
|
+
expect(DECLARED_STEPS.length).toBeGreaterThanOrEqual(15);
|
|
219
|
+
expect(DECLARED_TERMS.map((t) => t.what)).toContain("token lookup `by`");
|
|
220
|
+
expect(DECLARED_TERMS.map((t) => t.what)).toContain("variable lookup `by`");
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it.each(DECLARED_STEPS)(
|
|
224
|
+
"$what reads $property, which the shipped ontology defines",
|
|
225
|
+
async ({ what, property }) => {
|
|
226
|
+
const result = await rt.query.sparql(
|
|
227
|
+
[
|
|
228
|
+
"ASK {",
|
|
229
|
+
` ${property} a ?kind .`,
|
|
230
|
+
" VALUES ?kind { owl:DatatypeProperty owl:ObjectProperty }",
|
|
231
|
+
"}",
|
|
232
|
+
].join("\n"),
|
|
233
|
+
);
|
|
234
|
+
expect(result.type).toBe("ask");
|
|
235
|
+
expect(
|
|
236
|
+
result.type === "ask" ? result.result : false,
|
|
237
|
+
`${what} reads ${property}, which the shipped ontology does not define — it can never render, for any entity, on any install`,
|
|
238
|
+
).toBe(true);
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("the name literal both nouns key on (PROTECTED)", () => {
|
|
244
|
+
it("every symbol carries exactly one label", async () => {
|
|
245
|
+
// Both halves of the grammar REQUIRE it and neither declares a fallback,
|
|
246
|
+
// so a symbol without one is published by neither and a symbol with two
|
|
247
|
+
// would be published twice under different names.
|
|
248
|
+
expect(
|
|
249
|
+
await count(
|
|
250
|
+
`SELECT (COUNT(DISTINCT ?s) AS ?n) WHERE {
|
|
251
|
+
?s a dt:TokenSymbol .
|
|
252
|
+
FILTER NOT EXISTS { ?s rdfs:label ?l }
|
|
253
|
+
}`,
|
|
254
|
+
),
|
|
255
|
+
).toBe(0);
|
|
256
|
+
expect(
|
|
257
|
+
await count(
|
|
258
|
+
`SELECT (COUNT(?s) AS ?n) WHERE {
|
|
259
|
+
SELECT ?s (COUNT(DISTINCT ?l) AS ?labels) WHERE {
|
|
260
|
+
?s a dt:TokenSymbol ; rdfs:label ?l .
|
|
261
|
+
} GROUP BY ?s HAVING (COUNT(DISTINCT ?l) > 1)
|
|
262
|
+
}`,
|
|
263
|
+
),
|
|
264
|
+
).toBe(0);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("every variable carries exactly one label", async () => {
|
|
268
|
+
expect(
|
|
269
|
+
await count(
|
|
270
|
+
`SELECT (COUNT(DISTINCT ?v) AS ?n) WHERE {
|
|
271
|
+
?v a dt:Variable .
|
|
272
|
+
FILTER NOT EXISTS { ?v rdfs:label ?l }
|
|
273
|
+
}`,
|
|
274
|
+
),
|
|
275
|
+
).toBe(0);
|
|
276
|
+
expect(
|
|
277
|
+
await count(
|
|
278
|
+
`SELECT (COUNT(?v) AS ?n) WHERE {
|
|
279
|
+
SELECT ?v (COUNT(DISTINCT ?l) AS ?labels) WHERE {
|
|
280
|
+
?v a dt:Variable ; rdfs:label ?l .
|
|
281
|
+
} GROUP BY ?v HAVING (COUNT(DISTINCT ?l) > 1)
|
|
282
|
+
}`,
|
|
283
|
+
),
|
|
284
|
+
).toBe(0);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("a variable's label IS its CSS name with the leading dashes stripped", async () => {
|
|
288
|
+
// This is what makes the noun addressable at all — a `--`-prefixed
|
|
289
|
+
// positional cannot be typed — and it is upstream's promise rather than
|
|
290
|
+
// this repository's derivation. Asserted as zero violations, so it holds
|
|
291
|
+
// however many variables upstream ships.
|
|
292
|
+
expect(
|
|
293
|
+
await count(
|
|
294
|
+
`SELECT (COUNT(?v) AS ?n) WHERE {
|
|
295
|
+
?v a dt:Variable ; rdfs:label ?l .
|
|
296
|
+
BIND(REPLACE(REPLACE(STR(?v), "^.*[/#]", ""), "^--", "") AS ?stripped)
|
|
297
|
+
FILTER(?l != ?stripped)
|
|
298
|
+
}`,
|
|
299
|
+
),
|
|
300
|
+
).toBe(0);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("a symbol's label IS its dotted local name, dots intact", async () => {
|
|
304
|
+
// The reason no IRI-derived fallback is declared: the kernel's derivation
|
|
305
|
+
// publishes these dots as SLASHES, which would disagree with the notation
|
|
306
|
+
// the label uses and the anatomy spells. Pinning the agreement is what
|
|
307
|
+
// makes that a considered omission rather than a guess.
|
|
308
|
+
expect(
|
|
309
|
+
await count(
|
|
310
|
+
`SELECT (COUNT(?s) AS ?n) WHERE {
|
|
311
|
+
?s a dt:TokenSymbol ; rdfs:label ?l .
|
|
312
|
+
BIND(REPLACE(STR(?s), "^.*[/#]", "") AS ?derived)
|
|
313
|
+
FILTER(?l != ?derived)
|
|
314
|
+
}`,
|
|
315
|
+
),
|
|
316
|
+
).toBe(0);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it("the stripping is INJECTIVE — no two variables answer to one name", async () => {
|
|
320
|
+
// If two ever collided, a lookup would answer one and silently hide the
|
|
321
|
+
// other, which is the shape of defect the round-trip guard exists for.
|
|
322
|
+
const variables = await count(
|
|
323
|
+
"SELECT (COUNT(DISTINCT ?v) AS ?n) WHERE { ?v a dt:Variable }",
|
|
324
|
+
);
|
|
325
|
+
const names = await count(
|
|
326
|
+
"SELECT (COUNT(DISTINCT ?l) AS ?n) WHERE { ?v a dt:Variable ; rdfs:label ?l }",
|
|
327
|
+
);
|
|
328
|
+
expect(names).toBe(variables);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("no variable name collides with a symbol name", async () => {
|
|
332
|
+
// The two nouns share one flat namespace as far as a caller is concerned,
|
|
333
|
+
// so a shared name would make `token lookup X` and `variable lookup X`
|
|
334
|
+
// answer different entities under one spelling.
|
|
335
|
+
expect(
|
|
336
|
+
await count(
|
|
337
|
+
`SELECT (COUNT(*) AS ?n) WHERE {
|
|
338
|
+
?v a dt:Variable ; rdfs:label ?name .
|
|
339
|
+
?s a dt:TokenSymbol ; rdfs:label ?name .
|
|
340
|
+
}`,
|
|
341
|
+
),
|
|
342
|
+
).toBe(0);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
describe("a resolved value carries a chain XOR a derivation (PROTECTED)", () => {
|
|
347
|
+
it("never both, and never neither", async () => {
|
|
348
|
+
// The shape's own rule, and the reason both S3 surfaces select both: a
|
|
349
|
+
// surface reading only the chain renders every derived routing blank, and
|
|
350
|
+
// one reading only the derivation renders every ordinary value blank.
|
|
351
|
+
expect(
|
|
352
|
+
await count(
|
|
353
|
+
`SELECT (COUNT(?r) AS ?n) WHERE {
|
|
354
|
+
?r a dt:ResolvedValue .
|
|
355
|
+
OPTIONAL { ?r dt:resolutionChain ?c }
|
|
356
|
+
OPTIONAL { ?r dt:derivedFrom ?d }
|
|
357
|
+
FILTER((BOUND(?c) && BOUND(?d)) || (!BOUND(?c) && !BOUND(?d)))
|
|
358
|
+
}`,
|
|
359
|
+
),
|
|
360
|
+
).toBe(0);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("a derived routing carries NO value of its own", async () => {
|
|
364
|
+
expect(
|
|
365
|
+
await count(
|
|
366
|
+
`SELECT (COUNT(?r) AS ?n) WHERE {
|
|
367
|
+
?r a dt:ResolvedValue ; dt:derivedFrom ?d ; dt:resolvesTo ?v .
|
|
368
|
+
}`,
|
|
369
|
+
),
|
|
370
|
+
).toBe(0);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("both kinds are present, so neither branch is untested here", async () => {
|
|
374
|
+
// Without this the two assertions above would pass vacuously on a corpus
|
|
375
|
+
// that had lost one kind entirely.
|
|
376
|
+
expect(
|
|
377
|
+
await count(
|
|
378
|
+
"SELECT (COUNT(?r) AS ?n) WHERE { ?r dt:resolutionChain ?c }",
|
|
379
|
+
),
|
|
380
|
+
).toBeGreaterThan(0);
|
|
381
|
+
expect(
|
|
382
|
+
await count("SELECT (COUNT(?r) AS ?n) WHERE { ?r dt:derivedFrom ?d }"),
|
|
383
|
+
).toBeGreaterThan(0);
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
describe("a symbol's own definition is its base chain's HEAD (PROTECTED)", () => {
|
|
388
|
+
// What `token list`'s two definition-level columns rest on, and what a
|
|
389
|
+
// fixture cannot vouch for. The story reads the head of the symbol's BASE
|
|
390
|
+
// resolved value — the one with no `dt:coordinate` — so three things have to
|
|
391
|
+
// hold over the corpus: that there is at most one such value per symbol, that
|
|
392
|
+
// its head really is a definition OF that symbol, and that the heads of a
|
|
393
|
+
// symbol's OTHER values never name a different type or description, because
|
|
394
|
+
// `token lookup` reads the same pair as a property path and a path cannot
|
|
395
|
+
// spell the base restriction.
|
|
396
|
+
it("no symbol has two base resolved values", async () => {
|
|
397
|
+
// If one ever did, the list would publish the symbol twice — or, with the
|
|
398
|
+
// `DISTINCT` that guards it, silently drop whichever head sorted second.
|
|
399
|
+
expect(
|
|
400
|
+
await count(
|
|
401
|
+
`SELECT (COUNT(*) AS ?n) WHERE {
|
|
402
|
+
SELECT ?s WHERE {
|
|
403
|
+
?v a dt:ResolvedValue ; dt:forSymbol ?s .
|
|
404
|
+
FILTER NOT EXISTS { ?v dt:coordinate ?c }
|
|
405
|
+
} GROUP BY ?s HAVING (COUNT(?v) > 1)
|
|
406
|
+
}`,
|
|
407
|
+
),
|
|
408
|
+
).toBe(0);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("a chain head is a definition OF the symbol the value is for", async () => {
|
|
412
|
+
// The claim that makes "its own definition" true rather than merely
|
|
413
|
+
// convenient: the head is reached through the value, not through
|
|
414
|
+
// `dt:symbol`, so nothing in the path itself guarantees it points back.
|
|
415
|
+
expect(
|
|
416
|
+
await count(
|
|
417
|
+
`SELECT (COUNT(*) AS ?n) WHERE {
|
|
418
|
+
?v a dt:ResolvedValue ;
|
|
419
|
+
dt:forSymbol ?s ;
|
|
420
|
+
dt:resolutionChain/rdf:first ?head .
|
|
421
|
+
FILTER NOT EXISTS { ?head dt:symbol ?s }
|
|
422
|
+
}`,
|
|
423
|
+
),
|
|
424
|
+
).toBe(0);
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("a symbol's chain heads never name two types, or two descriptions", async () => {
|
|
428
|
+
// This is what licenses the lookup's unrestricted path. A symbol with
|
|
429
|
+
// values at several positions has several heads (354 of them do today); as
|
|
430
|
+
// long as they agree on these two fields, the path is one value repeated
|
|
431
|
+
// and the first row the resolver keeps is the same answer the list gives.
|
|
432
|
+
for (const property of [
|
|
433
|
+
"dt:tokenType/rdfs:label",
|
|
434
|
+
"w3c-tokens:description",
|
|
435
|
+
]) {
|
|
436
|
+
expect(
|
|
437
|
+
await count(
|
|
438
|
+
`SELECT (COUNT(*) AS ?n) WHERE {
|
|
439
|
+
SELECT ?s WHERE {
|
|
440
|
+
?v dt:forSymbol ?s ; dt:resolutionChain/rdf:first/${property} ?o .
|
|
441
|
+
} GROUP BY ?s HAVING (COUNT(DISTINCT ?o) > 1)
|
|
442
|
+
}`,
|
|
443
|
+
),
|
|
444
|
+
property,
|
|
445
|
+
).toBe(0);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it("the population is there, so none of the three passes vacuously", async () => {
|
|
450
|
+
expect(
|
|
451
|
+
await count(
|
|
452
|
+
`SELECT (COUNT(DISTINCT ?s) AS ?n) WHERE {
|
|
453
|
+
?v a dt:ResolvedValue ;
|
|
454
|
+
dt:forSymbol ?s ;
|
|
455
|
+
dt:resolutionChain/rdf:first ?head .
|
|
456
|
+
FILTER NOT EXISTS { ?v dt:coordinate ?c }
|
|
457
|
+
}`,
|
|
458
|
+
),
|
|
459
|
+
).toBeGreaterThan(0);
|
|
460
|
+
expect(
|
|
461
|
+
await count(
|
|
462
|
+
`SELECT (COUNT(*) AS ?n) WHERE {
|
|
463
|
+
SELECT ?s WHERE { ?v dt:forSymbol ?s ; dt:resolutionChain ?ch }
|
|
464
|
+
GROUP BY ?s HAVING (COUNT(?v) > 1)
|
|
465
|
+
}`,
|
|
466
|
+
),
|
|
467
|
+
).toBeGreaterThan(0);
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it("token list and token lookup publish the SAME pair for one symbol", async () => {
|
|
471
|
+
// One rule, two spellings — the list's own SPARQL and the lookup's
|
|
472
|
+
// property path — so this is the seam a divergence would open at.
|
|
473
|
+
const listed = (
|
|
474
|
+
(await verb("token", "list").run(
|
|
475
|
+
{ search: "color.text" },
|
|
476
|
+
rt,
|
|
477
|
+
)) as PackPage
|
|
478
|
+
).rows.find((row) => row.name === "color.text");
|
|
479
|
+
const looked = await lookup("token", "color.text");
|
|
480
|
+
expect(listed?.type).toBeTruthy();
|
|
481
|
+
expect(listed?.description).toBeTruthy();
|
|
482
|
+
expect(looked.type).toBe(listed?.type);
|
|
483
|
+
expect(looked.description).toBe(listed?.description);
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
describe("the shipped nouns answer, end to end (PROTECTED)", () => {
|
|
488
|
+
it("token list publishes rows, and every row is named and typed or blank", async () => {
|
|
489
|
+
const page = (await verb("token", "list").run(
|
|
490
|
+
{ limit: 50 },
|
|
491
|
+
rt,
|
|
492
|
+
)) as PackPage;
|
|
493
|
+
expect(page.rows.length).toBeGreaterThan(0);
|
|
494
|
+
for (const row of page.rows) {
|
|
495
|
+
expect(row.name, JSON.stringify(row)).toBeTruthy();
|
|
496
|
+
// The column's contract: `type` is the label its own definition carries,
|
|
497
|
+
// or nothing at all. It is never a partial guess, and never an IRI.
|
|
498
|
+
expect(row.type ?? "").not.toContain("://");
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it("a dotted symbol name resolves, with its definitions shown apart", async () => {
|
|
503
|
+
const symbol = await lookup("token", "color.text");
|
|
504
|
+
expect(symbol).toMatchObject({ name: "color.text" });
|
|
505
|
+
const definitions = symbol.definitions as Record<string, string>[];
|
|
506
|
+
// Many definitions is the fact that makes "the symbol's own" a real
|
|
507
|
+
// choice, and this expand is the only surface that shows them apart.
|
|
508
|
+
// Asserted as a floor, not a count: upstream adds modifier contexts.
|
|
509
|
+
expect(definitions.length).toBeGreaterThan(1);
|
|
510
|
+
expect(new Set(definitions.map((d) => d.type))).toEqual(new Set(["color"]));
|
|
511
|
+
// Coverage reads the family end of the relation; values read the other.
|
|
512
|
+
expect((symbol.coverage as unknown[]).length).toBeGreaterThan(0);
|
|
513
|
+
expect((symbol.values as unknown[]).length).toBeGreaterThan(0);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it("a channel names the symbol it provisions and resolves by derivation", async () => {
|
|
517
|
+
const channel = await lookup("token", "modifier.color.text");
|
|
518
|
+
expect(channel).toMatchObject({ channelOf: "color.text" });
|
|
519
|
+
const values = channel.values as Record<string, string>[];
|
|
520
|
+
expect(values.length).toBeGreaterThan(0);
|
|
521
|
+
for (const value of values) {
|
|
522
|
+
expect(value.derivedFrom, JSON.stringify(value)).toBeTruthy();
|
|
523
|
+
expect(value.value).toBeUndefined();
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
it("token values answers a symbol's materialised positions", async () => {
|
|
528
|
+
const answered = await rows("token", "values", { symbol: "color.text" });
|
|
529
|
+
expect(answered.length).toBeGreaterThan(0);
|
|
530
|
+
for (const row of answered) expect(row.symbol).toBe("color.text");
|
|
531
|
+
// The default position is a row with no coordinate; the mode coordinate is
|
|
532
|
+
// one the resolver materialises. Both spellings, neither an IRI.
|
|
533
|
+
expect(answered.some((row) => row.position === undefined)).toBe(true);
|
|
534
|
+
for (const row of answered) {
|
|
535
|
+
expect(row.position ?? "").not.toContain("://");
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("a dash-stripped variable name resolves, with its declarations", async () => {
|
|
540
|
+
const variable = await lookup("variable", "color-text");
|
|
541
|
+
expect(variable).toMatchObject({
|
|
542
|
+
name: "color-text",
|
|
543
|
+
symbol: "color.text",
|
|
544
|
+
});
|
|
545
|
+
const declarations = variable.declarations as Record<string, string>[];
|
|
546
|
+
expect(declarations.length).toBeGreaterThan(0);
|
|
547
|
+
// The web's own bridge fields, which nothing read before this story: the
|
|
548
|
+
// selector a declaration sits under and the at-rule stack around it.
|
|
549
|
+
expect(declarations.some((d) => d.selector !== undefined)).toBe(true);
|
|
550
|
+
expect(declarations.some((d) => d.inAtRule !== undefined)).toBe(true);
|
|
551
|
+
expect(declarations.some((d) => d.emits !== undefined)).toBe(true);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it("two spellings of one symbol each answer their own row", async () => {
|
|
555
|
+
const out = (await verb("variable", "lookup").run(
|
|
556
|
+
{ name: ["typography-weight-semiBold", "typography-weight-semi-bold"] },
|
|
557
|
+
rt,
|
|
558
|
+
)) as { errors: unknown[]; results: Record<string, unknown>[] };
|
|
559
|
+
expect(out.errors).toEqual([]);
|
|
560
|
+
expect(out.results).toHaveLength(2);
|
|
561
|
+
for (const result of out.results) {
|
|
562
|
+
expect(result.symbol).toBe("typography.weight.semiBold");
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it("variable chain walks transitively, through variables standing for nothing", async () => {
|
|
567
|
+
// The walk's whole value: the chain passes through hops that stand for no
|
|
568
|
+
// symbol, so a one-hop join would answer nothing for these.
|
|
569
|
+
const walk = await rows("variable", "chain", {
|
|
570
|
+
variable: "disabled--color-text",
|
|
571
|
+
});
|
|
572
|
+
expect(walk.length).toBeGreaterThan(1);
|
|
573
|
+
for (const row of walk) {
|
|
574
|
+
expect(row.variable).toBe("disabled--color-text");
|
|
575
|
+
expect(row.symbol).toBeTruthy();
|
|
576
|
+
}
|
|
577
|
+
// The starting variable stands for nothing itself — which is exactly why
|
|
578
|
+
// it is not reachable through the token noun.
|
|
579
|
+
expect(
|
|
580
|
+
await count(
|
|
581
|
+
`SELECT (COUNT(*) AS ?n) WHERE {
|
|
582
|
+
?v rdfs:label "disabled--color-text" ; dt:ofSymbol ?s .
|
|
583
|
+
}`,
|
|
584
|
+
),
|
|
585
|
+
).toBe(0);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
it("token consumers answers rows with every identity column, or its recovery", async () => {
|
|
589
|
+
// Deliberately NOT "is empty": the design-system packs will record
|
|
590
|
+
// bindings, and a test pinning the emptiness would fail on that correct
|
|
591
|
+
// change. What is invariant is the SHAPE of the two legal answers.
|
|
592
|
+
const page = (await verb("token", "consumers").run({}, rt)) as PackPage;
|
|
593
|
+
if (page.rows.length === 0) {
|
|
594
|
+
const notice = verb("token", "consumers").output.formatters.notice?.(
|
|
595
|
+
page as never,
|
|
596
|
+
);
|
|
597
|
+
expect(notice).toContain(
|
|
598
|
+
"no component is recorded as using any token yet",
|
|
599
|
+
);
|
|
600
|
+
expect(notice).toContain("sources update");
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
for (const row of page.rows) {
|
|
604
|
+
// All seven identity columns, because two bindings differing only in
|
|
605
|
+
// state or rank are two different facts — a narrower row would publish
|
|
606
|
+
// rows a caller cannot tell apart.
|
|
607
|
+
expect(Object.keys(row).sort()).toEqual(
|
|
608
|
+
expect.arrayContaining(["block", "symbol", "uri"]),
|
|
609
|
+
);
|
|
610
|
+
expect(row.block).toBeTruthy();
|
|
611
|
+
expect(row.symbol).toBeTruthy();
|
|
612
|
+
// The via is the block whose anatomy the binding was authored in, and it
|
|
613
|
+
// is BLANK whenever that is the consuming block itself — which the data
|
|
614
|
+
// asserts on every own-tree record. A via equal to the block would mean
|
|
615
|
+
// the two columns had collapsed back into one, which is the defect that
|
|
616
|
+
// printed six identical `Button` rows for one symbol.
|
|
617
|
+
if (row.via) expect(row.via).not.toBe(row.block);
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
it("token consumers names the CONSUMING block, not the tree it came from", async () => {
|
|
622
|
+
// The corpus invariant a fixture cannot reach: over the WHOLE population,
|
|
623
|
+
// every row whose record is inherited must name a block DIFFERENT from the
|
|
624
|
+
// via, and every own-tree row must leave the via empty. Asserted as a
|
|
625
|
+
// count of violations being zero rather than as row counts upstream moves.
|
|
626
|
+
//
|
|
627
|
+
// Read straight from the graph, because the story's own answer is what is
|
|
628
|
+
// under test: `ds:viaBlock` is present on every record and the term's
|
|
629
|
+
// definition says it equals the block on the block's own tree, so a column
|
|
630
|
+
// that coalesced the two printed a block's inherited rows under the name
|
|
631
|
+
// of the block they came from.
|
|
632
|
+
const inherited = await count(
|
|
633
|
+
`SELECT (COUNT(*) AS ?n) WHERE {
|
|
634
|
+
?block ds:hasTokenBinding ?r .
|
|
635
|
+
?r ds:viaBlock ?via .
|
|
636
|
+
FILTER(?via != ?block)
|
|
637
|
+
}`,
|
|
638
|
+
);
|
|
639
|
+
// The WHOLE population in one page, which is what makes this an invariant
|
|
640
|
+
// over the corpus rather than over whatever the default page held.
|
|
641
|
+
const page = (await verb("token", "consumers").run(
|
|
642
|
+
{ limit: MAX_LIST_WINDOW },
|
|
643
|
+
rt,
|
|
644
|
+
)) as PackPage;
|
|
645
|
+
if (page.rows.length === 0) return;
|
|
646
|
+
|
|
647
|
+
const withVia = page.rows.filter((row) => Boolean(row.via));
|
|
648
|
+
expect(withVia).toHaveLength(inherited);
|
|
649
|
+
// And the consuming block is never the via — the whole point of splitting
|
|
650
|
+
// the column.
|
|
651
|
+
expect(withVia.filter((row) => row.via === row.block)).toEqual([]);
|
|
652
|
+
});
|
|
653
|
+
});
|