@canonical/pragma-cli 0.30.0 → 0.31.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 +64 -35
- package/dist/pragma +0 -0
- package/package.json +15 -13
- package/src/completions/constants.ts +7 -0
- package/src/completions/handleQuery.test.ts +32 -0
- package/src/completions/handleQuery.ts +20 -1
- package/src/completions/queryCompletions.test.ts +5 -0
- package/src/completions/queryCompletions.ts +17 -3
- package/src/completions/querySocket.test.ts +31 -0
- package/src/completions/querySocket.ts +36 -9
- package/src/config/parseConfigValues.ts +2 -0
- package/src/config/readConfigLayers.test.ts +2 -0
- package/src/config/readConfigLayers.ts +3 -0
- package/src/config/types.ts +7 -0
- package/src/constants.ts +3 -1
- package/src/domains/block/commands/sample.ts +2 -1
- package/src/domains/block/formatters/list.test.ts +3 -3
- package/src/domains/block/formatters/lookup.test.ts +5 -5
- package/src/domains/block/orchestration/enrichBlocks.ts +6 -3
- package/src/domains/block/stories.ts +9 -3
- package/src/domains/config/commands/channel.test.ts +16 -0
- package/src/domains/config/commands/channel.ts +14 -1
- package/src/domains/config/commands/framework.test.ts +14 -0
- package/src/domains/config/commands/framework.ts +13 -1
- package/src/domains/config/commands/tier.test.ts +7 -0
- package/src/domains/config/commands/tier.ts +14 -1
- package/src/domains/config/operations/validateTier.test.ts +9 -0
- package/src/domains/config/operations/validateTier.ts +10 -2
- package/src/domains/create/generatorPack/bundled.ts +49 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.test.ts +159 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.ts +312 -0
- package/src/domains/create/index.ts +13 -7
- package/src/domains/create/mcp/specs.ts +6 -161
- package/src/domains/create/renderGeneratorUi.test.ts +103 -0
- package/src/domains/create/renderGeneratorUi.ts +65 -0
- package/src/domains/doctor/formatters/doctor.test.ts +50 -2
- package/src/domains/doctor/formatters/doctor.ts +90 -38
- package/src/domains/doctor/operations/checks/checkMcpCommands.ts +9 -6
- package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -12
- package/src/domains/doctor/operations/checks.test.ts +6 -4
- package/src/domains/doctor/operations/types.ts +23 -1
- package/src/domains/graph/commands/inspect.test.ts +2 -2
- package/src/domains/graph/commands/query.test.ts +5 -3
- package/src/domains/graph/formatters/inspect.ts +7 -7
- package/src/domains/graph/formatters/query.test.ts +87 -0
- package/src/domains/graph/formatters/query.ts +42 -5
- package/src/domains/graph/operations/inspectUri.test.ts +5 -5
- package/src/domains/graphql/commands/serve.test.ts +12 -6
- package/src/domains/graphql/commands/serve.ts +16 -6
- package/src/domains/graphql/helpers/parsePrefixes.test.ts +3 -3
- package/src/domains/graphql/helpers/parsePrefixes.ts +3 -3
- package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +9 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.ts +24 -3
- package/src/domains/graphql/operations/compileSchema.test.ts +2 -2
- package/src/domains/graphql/operations/createSchemaServer.test.ts +2 -2
- package/src/domains/graphql/operations/ensureSchemaArtifact.test.ts +167 -0
- package/src/domains/graphql/operations/ensureSchemaArtifact.ts +178 -0
- package/src/domains/graphql/operations/index.ts +5 -0
- package/src/domains/info/formatters/upgrade.test.ts +7 -4
- package/src/domains/info/formatters/upgrade.ts +3 -1
- package/src/domains/info/operations/collectEntityCounts.test.ts +156 -0
- package/src/domains/info/operations/collectEntityCounts.ts +123 -0
- package/src/domains/info/operations/index.ts +1 -0
- package/src/domains/info/types.ts +13 -0
- package/src/domains/llm/capabilities.test.ts +23 -3
- package/src/domains/llm/commands/capabilities.ts +12 -3
- package/src/domains/llm/commands/llm.ts +5 -1
- package/src/domains/llm/data/commandReference.ts +20 -11
- package/src/domains/llm/data/decisionTrees.ts +10 -5
- package/src/domains/llm/data/toolCatalog.ts +49 -18
- package/src/domains/llm/formatters/orientation.ts +5 -1
- package/src/domains/llm/llmCommand.test.ts +11 -3
- package/src/domains/llm/mcp/specs.ts +3 -4
- package/src/domains/llm/operations/collectContext.test.ts +64 -0
- package/src/domains/llm/operations/collectContext.ts +7 -17
- package/src/domains/modifier/commands/sample.ts +2 -1
- package/src/domains/modifier/operations/lookup.ts +5 -3
- package/src/domains/modifier/stories.ts +9 -0
- package/src/domains/ontology/commands/list.test.ts +5 -3
- package/src/domains/ontology/commands/show.test.ts +85 -9
- package/src/domains/ontology/formatters/index.ts +1 -0
- package/src/domains/ontology/formatters/list.ts +37 -12
- package/src/domains/ontology/formatters/show.ts +358 -67
- package/src/domains/ontology/helpers/buildClassTree.ts +89 -0
- package/src/domains/ontology/helpers/completeClassNames.ts +43 -0
- package/src/domains/ontology/helpers/expandOntologyIris.ts +79 -0
- package/src/domains/ontology/helpers/index.ts +14 -0
- package/src/domains/ontology/helpers/queryClasses.ts +32 -16
- package/src/domains/ontology/helpers/queryProperties.ts +39 -6
- package/src/domains/ontology/helpers/queryTboxFacts.ts +160 -0
- package/src/domains/ontology/operations/listOntologies.test.ts +80 -3
- package/src/domains/ontology/operations/listOntologies.ts +126 -30
- package/src/domains/ontology/operations/showOntology.test.ts +131 -12
- package/src/domains/ontology/operations/showOntology.ts +289 -7
- package/src/domains/ontology/operations/showOntologyRaw.test.ts +4 -2
- package/src/domains/ontology/stories.ts +53 -8
- package/src/domains/refs/operations/updateRefs.ts +0 -1
- package/src/domains/setup/commands/index.ts +1 -0
- package/src/domains/setup/commands/setup.ts +70 -0
- package/src/domains/setup/index.ts +7 -3
- package/src/domains/setup/operations/setupLsp.test.ts +3 -2
- package/src/domains/setup/operations/setupLsp.ts +7 -2
- package/src/domains/shared/bootStore.bareCore.test.ts +63 -0
- package/src/domains/shared/bootStore.test.ts +82 -0
- package/src/domains/shared/bootStore.ts +56 -20
- package/src/domains/shared/emptyErrorRecovery.test.ts +23 -26
- package/src/domains/shared/extractLocalName.test.ts +3 -3
- package/src/domains/shared/formatters.ts +23 -2
- package/src/domains/shared/listTiers.ts +40 -0
- package/src/domains/shared/loaders/bundledLoader.ts +48 -0
- package/src/domains/shared/loaders/gitLoader.ts +1 -2
- package/src/domains/shared/loaders/localLoader.ts +2 -18
- package/src/domains/shared/loaders/readPackageDir.test.ts +58 -0
- package/src/domains/shared/loaders/readPackageDir.ts +46 -8
- package/src/domains/shared/lookupMany.test.ts +15 -0
- package/src/domains/shared/lookupMany.ts +22 -6
- package/src/domains/shared/namespaces.ts +13 -5
- package/src/domains/shared/parseSampleCount.test.ts +47 -0
- package/src/domains/shared/parseSampleCount.ts +45 -0
- package/src/domains/shared/prefixes.test.ts +211 -0
- package/src/domains/shared/prefixes.ts +182 -15
- package/src/domains/shared/renderers.ts +5 -5
- package/src/domains/shared/resolveLookupExitCode.test.ts +48 -0
- package/src/domains/shared/resolveLookupExitCode.ts +42 -0
- package/src/domains/shared/semanticPackage.test.ts +89 -0
- package/src/domains/shared/semanticPackage.ts +17 -2
- package/src/domains/shared/stories/compileLookupCommand.ts +3 -1
- package/src/domains/shared/stories/compileLookupTool.ts +1 -1
- package/src/domains/shared/stories/pack/applyPackFilters.test.ts +49 -0
- package/src/domains/shared/stories/pack/applyPackFilters.ts +46 -11
- package/src/domains/shared/stories/pack/applyPackSearch.test.ts +57 -0
- package/src/domains/shared/stories/pack/applyPackSearch.ts +41 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +30 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.ts +75 -1
- package/src/domains/shared/stories/pack/bundled/index.ts +23 -0
- package/src/domains/shared/stories/pack/bundled/standardPack.ts +164 -0
- package/src/domains/shared/stories/pack/bundled/tierPack.ts +50 -0
- package/src/domains/shared/stories/pack/collectPackStories.test.ts +305 -7
- package/src/domains/shared/stories/pack/collectPackStories.ts +90 -8
- package/src/domains/shared/stories/pack/compilePackCommands.ts +17 -7
- package/src/domains/shared/stories/pack/compilePackStories.test.ts +607 -2
- package/src/domains/shared/stories/pack/compilePackStories.ts +544 -73
- package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +20 -8
- package/src/domains/shared/stories/pack/index.ts +14 -0
- package/src/domains/shared/stories/pack/reservedVerbs.test.ts +213 -0
- package/src/domains/shared/stories/pack/reservedVerbs.ts +170 -0
- package/src/domains/shared/stories/pack/standardParity.test.ts +293 -220
- package/src/domains/shared/stories/pack/types.ts +123 -3
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +502 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +475 -24
- package/src/domains/shared/stories/types.ts +9 -1
- package/src/domains/shared/types/index.ts +5 -9
- package/src/domains/shared/types/ontology.ts +119 -24
- package/src/domains/shared/types/standard.ts +9 -52
- package/src/domains/token/commands/lookup.test.ts +8 -0
- package/src/domains/token/commands/lookup.ts +12 -0
- package/src/domains/token/commands/sample.ts +2 -1
- package/src/domains/token/featureFlag.ts +17 -0
- package/src/domains/token/index.ts +5 -0
- package/src/domains/token/mcp/specs.ts +13 -1
- package/src/domains/trace/commands/resolveTraceLimit.test.ts +31 -0
- package/src/domains/trace/commands/show.ts +30 -4
- package/src/index.ts +1 -13
- package/src/mcp/registerResources.test.ts +2 -2
- package/src/mcp/resources/pickPrimaryType.test.ts +5 -5
- package/src/mcp/tools/index.ts +18 -10
- package/src/mcp/tools/registerTools.test.ts +106 -30
- package/src/pipeline/collectCommands.test.ts +151 -1
- package/src/pipeline/collectCommands.ts +34 -19
- package/src/pipeline/createProgram.test.ts +5 -1
- package/src/pipeline/createProgram.ts +2 -2
- package/src/pipeline/firstRun.test.ts +80 -0
- package/src/pipeline/firstRun.ts +99 -0
- package/src/pipeline/parseGlobalFlags.test.ts +102 -1
- package/src/pipeline/parseGlobalFlags.ts +98 -9
- package/src/pipeline/resolveCommandKind.test.ts +12 -0
- package/src/pipeline/resolveCommandKind.ts +4 -0
- package/src/pipeline/rootHelp.test.ts +87 -0
- package/src/pipeline/rootHelp.ts +265 -0
- package/src/pipeline/runCli.orchestration.test.ts +28 -12
- package/src/pipeline/runCli.test.ts +17 -4
- package/src/pipeline/runCli.ts +162 -19
- package/src/testing/cli.ts +30 -2
- package/src/testing/dsFixtures.ts +21 -4
- package/src/testing/index.ts +1 -4
- package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +4 -4
- package/src/testing/integration/cli-user-stories.test.ts +63 -0
- package/src/testing/integration/cross-binary-byte-equality.test.ts +2 -2
- package/src/testing/integration/edge-cases.test.ts +62 -50
- package/src/testing/integration/executor-execution.test.ts +6 -4
- package/src/testing/integration/executor-golden.test.ts +2 -2
- package/src/testing/integration/mcp-surface.test.ts +40 -15
- package/src/testing/integration/parity.test.ts +119 -70
- package/src/testing/setupXdgIsolation.ts +16 -5
- package/src/testing/standardParityFixtures.ts +26 -83
- package/src/testing/store.ts +4 -4
- package/src/domains/create/commands/component.test.ts +0 -166
- package/src/domains/create/commands/component.ts +0 -123
- package/src/domains/create/commands/index.ts +0 -3
- package/src/domains/create/commands/package.test.ts +0 -35
- package/src/domains/create/commands/package.ts +0 -21
- package/src/domains/create/generators.ts +0 -8
- package/src/domains/standard/commands/categories.test.ts +0 -70
- package/src/domains/standard/commands/categories.ts +0 -48
- package/src/domains/standard/commands/index.ts +0 -6
- package/src/domains/standard/commands/list.test.ts +0 -105
- package/src/domains/standard/commands/list.ts +0 -11
- package/src/domains/standard/commands/lookup.test.ts +0 -135
- package/src/domains/standard/commands/lookup.ts +0 -14
- package/src/domains/standard/commands/sample.ts +0 -57
- package/src/domains/standard/formatters/categories.test.ts +0 -44
- package/src/domains/standard/formatters/categories.ts +0 -38
- package/src/domains/standard/formatters/index.ts +0 -10
- package/src/domains/standard/formatters/list.test.ts +0 -169
- package/src/domains/standard/formatters/list.ts +0 -184
- package/src/domains/standard/formatters/lookup.test.ts +0 -95
- package/src/domains/standard/formatters/lookup.ts +0 -101
- package/src/domains/standard/formatters/sample.ts +0 -15
- package/src/domains/standard/formatters/types.ts +0 -31
- package/src/domains/standard/index.ts +0 -40
- package/src/domains/standard/mcp/index.ts +0 -2
- package/src/domains/standard/mcp/specs.ts +0 -89
- package/src/domains/standard/operations/categories.test.ts +0 -61
- package/src/domains/standard/operations/categories.ts +0 -38
- package/src/domains/standard/operations/index.ts +0 -6
- package/src/domains/standard/operations/list.test.ts +0 -75
- package/src/domains/standard/operations/list.ts +0 -64
- package/src/domains/standard/operations/lookup.test.ts +0 -58
- package/src/domains/standard/operations/lookup.ts +0 -151
- package/src/domains/standard/operations/sample.ts +0 -35
- package/src/domains/standard/orchestration/buildStandardFilters.ts +0 -11
- package/src/domains/standard/orchestration/index.ts +0 -4
- package/src/domains/standard/orchestration/resolveStandardList.ts +0 -45
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +0 -33
- package/src/domains/standard/orchestration/standardEmptyError.test.ts +0 -33
- package/src/domains/standard/orchestration/standardEmptyError.ts +0 -29
- package/src/domains/standard/standardConfig.ts +0 -42
- package/src/domains/standard/stories.test.ts +0 -24
- package/src/domains/standard/stories.ts +0 -151
- package/src/domains/standard/types.ts +0 -15
- package/src/domains/tier/commands/index.ts +0 -3
- package/src/domains/tier/commands/list.test.ts +0 -67
- package/src/domains/tier/commands/list.ts +0 -9
- package/src/domains/tier/formatters/index.ts +0 -3
- package/src/domains/tier/formatters/list.test.ts +0 -33
- package/src/domains/tier/formatters/list.ts +0 -45
- package/src/domains/tier/index.ts +0 -17
- package/src/domains/tier/mcp/index.ts +0 -2
- package/src/domains/tier/mcp/specs.ts +0 -12
- package/src/domains/tier/operations/index.ts +0 -3
- package/src/domains/tier/operations/list.test.ts +0 -32
- package/src/domains/tier/operations/list.ts +0 -33
- package/src/domains/tier/stories.test.ts +0 -22
- package/src/domains/tier/stories.ts +0 -52
|
@@ -1,21 +1,74 @@
|
|
|
1
1
|
import { PragmaError } from "#error";
|
|
2
|
+
import { MAX_SAMPLE_COUNT, MIN_SAMPLE_COUNT } from "../../parseSampleCount.js";
|
|
2
3
|
import type {
|
|
3
4
|
StoryPackColumn,
|
|
4
5
|
StoryPackDefinition,
|
|
6
|
+
StoryPackDisclosure,
|
|
7
|
+
StoryPackExpand,
|
|
8
|
+
StoryPackExpandField,
|
|
5
9
|
StoryPackField,
|
|
6
10
|
StoryPackFilter,
|
|
11
|
+
StoryPackList,
|
|
7
12
|
StoryPackLookup,
|
|
13
|
+
StoryPackSample,
|
|
14
|
+
StoryPackSearch,
|
|
8
15
|
StoryPackSection,
|
|
16
|
+
StoryPackVerb,
|
|
9
17
|
} from "./types.js";
|
|
10
18
|
|
|
11
19
|
const NOUN_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
12
20
|
const FIELD_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Object-prototype key names no pack identifier may take.
|
|
24
|
+
*
|
|
25
|
+
* Pack identifiers become plain-object keys at run time — SELECT
|
|
26
|
+
* variables become row keys, expand names become entity keys, and filter
|
|
27
|
+
* params become parameter keys — so `__proto__`/`constructor`/`prototype`
|
|
28
|
+
* are rejected at boot, before any row or params object could be built
|
|
29
|
+
* with a prototype-polluting key.
|
|
30
|
+
*/
|
|
31
|
+
const RESERVED_OBJECT_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Reject an identifier that names a reserved object-prototype key.
|
|
35
|
+
*
|
|
36
|
+
* Shared guard for every FIELD_PATTERN/FILTER_PARAM_PATTERN-shaped
|
|
37
|
+
* identifier (column fields, field/section/expand/select names, filter
|
|
38
|
+
* params and variables, search variables, disclosure levels).
|
|
39
|
+
*
|
|
40
|
+
* @throws PragmaError with code `CONFIG_ERROR` when the value is reserved.
|
|
41
|
+
*/
|
|
42
|
+
function rejectReservedObjectKey(
|
|
43
|
+
value: string,
|
|
44
|
+
where: string,
|
|
45
|
+
source: string,
|
|
46
|
+
): void {
|
|
47
|
+
if (RESERVED_OBJECT_KEYS.has(value)) {
|
|
48
|
+
throw buildStoryConfigError(
|
|
49
|
+
source,
|
|
50
|
+
`"${where}" must not be the reserved object key "${value}".`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
13
54
|
/** A prefixed name (`ex:Recipe`) — prefix + local part, no path slashes. */
|
|
14
55
|
const PREFIXED_NAME_PATTERN = /^[A-Za-z][\w-]*:[^/<>"\s]+$/;
|
|
15
56
|
|
|
16
57
|
/** An absolute IRI that can be safely embedded as `<iri>` in SPARQL. */
|
|
17
58
|
const EMBEDDABLE_IRI_PATTERN = /^[A-Za-z][\w+.-]*:\/\/[^<>"\s]+$/;
|
|
18
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Whether a resolved IRI matches the embeddable shape pack terms are held
|
|
62
|
+
* to (no whitespace or `<>"` characters, `scheme://` form).
|
|
63
|
+
*
|
|
64
|
+
* Shared with the lookup path so an IRI-addressed lookup applies the same
|
|
65
|
+
* strictness to resolved user queries as this validator applies to
|
|
66
|
+
* authored pack terms before either is embedded as `<iri>` in SPARQL.
|
|
67
|
+
*/
|
|
68
|
+
export function isEmbeddableIri(value: string): boolean {
|
|
69
|
+
return EMBEDDABLE_IRI_PATTERN.test(value);
|
|
70
|
+
}
|
|
71
|
+
|
|
19
72
|
/**
|
|
20
73
|
* A filter parameter name: a single lowercase word.
|
|
21
74
|
*
|
|
@@ -30,16 +83,40 @@ const FILTER_PARAM_PATTERN = /^[a-z][a-z0-9]*$/;
|
|
|
30
83
|
|
|
31
84
|
/**
|
|
32
85
|
* Parameter names taken by global flags or parameters the kernel appends
|
|
33
|
-
* to every read story — `condensed` is added to every MCP list tool,
|
|
34
|
-
* `detailed` to every lookup
|
|
86
|
+
* to every read story — `condensed` is added to every MCP list tool,
|
|
87
|
+
* `detailed` to every lookup, `search` is the compiled free-text search
|
|
88
|
+
* parameter, and `detail` the disclosure level selector — so a filter may
|
|
89
|
+
* not reuse them.
|
|
35
90
|
*/
|
|
36
91
|
const RESERVED_FILTER_PARAMS = new Set([
|
|
37
92
|
"llm",
|
|
38
93
|
"format",
|
|
39
94
|
"verbose",
|
|
95
|
+
"detail",
|
|
40
96
|
"detailed",
|
|
41
97
|
"condensed",
|
|
42
98
|
"names",
|
|
99
|
+
"search",
|
|
100
|
+
"help",
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Names a disclosure level may not take. Levels derive parameters — the
|
|
105
|
+
* `--detail <level>` selector plus one boolean alias flag per non-base
|
|
106
|
+
* level — so a level must not collide with `detail` itself, the lookup's
|
|
107
|
+
* `names` positional, or kernel/global parameter names. `detailed` is
|
|
108
|
+
* deliberately absent: it is the expected name for a pack's legacy
|
|
109
|
+
* detailed level and derives the compatible `--detailed`/`detailed`
|
|
110
|
+
* alias.
|
|
111
|
+
*/
|
|
112
|
+
const RESERVED_LEVEL_NAMES = new Set([
|
|
113
|
+
"detail",
|
|
114
|
+
"names",
|
|
115
|
+
"condensed",
|
|
116
|
+
"search",
|
|
117
|
+
"llm",
|
|
118
|
+
"format",
|
|
119
|
+
"verbose",
|
|
43
120
|
"help",
|
|
44
121
|
]);
|
|
45
122
|
|
|
@@ -78,6 +155,7 @@ export default function validateStoryPackDefinition(
|
|
|
78
155
|
: requireString(obj.toolDescription, "toolDescription", source);
|
|
79
156
|
|
|
80
157
|
const list = validateList(obj.list, source);
|
|
158
|
+
const verbs = validateVerbs(obj.verbs, source);
|
|
81
159
|
const lookup =
|
|
82
160
|
obj.lookup === undefined ? undefined : validateLookup(obj.lookup, source);
|
|
83
161
|
|
|
@@ -86,16 +164,87 @@ export default function validateStoryPackDefinition(
|
|
|
86
164
|
...(description !== undefined ? { description } : {}),
|
|
87
165
|
...(toolDescription !== undefined ? { toolDescription } : {}),
|
|
88
166
|
list,
|
|
167
|
+
...(verbs ? { verbs } : {}),
|
|
89
168
|
...(lookup ? { lookup } : {}),
|
|
90
169
|
};
|
|
91
170
|
}
|
|
92
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Verbs the compiler itself emits for a pack noun — an extra verb may not
|
|
174
|
+
* collide with them.
|
|
175
|
+
*/
|
|
176
|
+
const COMPILED_VERBS = new Set(["list", "lookup", "sample"]);
|
|
177
|
+
|
|
178
|
+
function validateVerbs(
|
|
179
|
+
raw: unknown,
|
|
180
|
+
source: string,
|
|
181
|
+
): readonly StoryPackVerb[] | undefined {
|
|
182
|
+
if (raw === undefined) return undefined;
|
|
183
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
184
|
+
throw buildStoryConfigError(source, '"verbs" must be a non-empty array.');
|
|
185
|
+
}
|
|
186
|
+
const seen = new Set<string>();
|
|
187
|
+
return raw.map((entry, index) => {
|
|
188
|
+
const where = `verbs[${index}]`;
|
|
189
|
+
const obj = requireObject(entry, where, source);
|
|
190
|
+
const verb = requireString(obj.verb, `${where}.verb`, source);
|
|
191
|
+
if (!NOUN_PATTERN.test(verb)) {
|
|
192
|
+
throw buildStoryConfigError(
|
|
193
|
+
source,
|
|
194
|
+
`"${where}.verb" must be kebab-case, got "${verb}".`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
if (COMPILED_VERBS.has(verb)) {
|
|
198
|
+
throw buildStoryConfigError(
|
|
199
|
+
source,
|
|
200
|
+
`"${where}.verb" "${verb}" collides with a compiled verb.`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
if (seen.has(verb)) {
|
|
204
|
+
throw buildStoryConfigError(source, `duplicate verb "${verb}".`);
|
|
205
|
+
}
|
|
206
|
+
seen.add(verb);
|
|
207
|
+
const description =
|
|
208
|
+
obj.description === undefined
|
|
209
|
+
? undefined
|
|
210
|
+
: requireString(obj.description, `${where}.description`, source);
|
|
211
|
+
const toolDescription =
|
|
212
|
+
obj.toolDescription === undefined
|
|
213
|
+
? undefined
|
|
214
|
+
: requireString(
|
|
215
|
+
obj.toolDescription,
|
|
216
|
+
`${where}.toolDescription`,
|
|
217
|
+
source,
|
|
218
|
+
);
|
|
219
|
+
return {
|
|
220
|
+
verb,
|
|
221
|
+
...(description !== undefined ? { description } : {}),
|
|
222
|
+
...(toolDescription !== undefined ? { toolDescription } : {}),
|
|
223
|
+
...validateListShape(entry, where, source),
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
93
228
|
function validateList(
|
|
94
229
|
raw: unknown,
|
|
95
230
|
source: string,
|
|
96
231
|
): StoryPackDefinition["list"] {
|
|
97
|
-
|
|
98
|
-
|
|
232
|
+
return validateListShape(raw, "list", source);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Validate a list-shaped story half: query, columns, filters, and search.
|
|
237
|
+
*
|
|
238
|
+
* Shared by `list` and (pack v1) extra list verbs, which compile through
|
|
239
|
+
* the same machinery. `where` names the field path for error messages.
|
|
240
|
+
*/
|
|
241
|
+
function validateListShape(
|
|
242
|
+
raw: unknown,
|
|
243
|
+
where: string,
|
|
244
|
+
source: string,
|
|
245
|
+
): StoryPackList {
|
|
246
|
+
const obj = requireObject(raw, where, source);
|
|
247
|
+
const query = requireString(obj.query, `${where}.query`, source);
|
|
99
248
|
// Strip leading PREFIX declarations so the real query verb is checked —
|
|
100
249
|
// "PREFIX ex: <…> CONSTRUCT {…}" must fail here, not at first use.
|
|
101
250
|
const afterPrefixes = query.replace(
|
|
@@ -105,31 +254,104 @@ function validateList(
|
|
|
105
254
|
if (!/^\s*SELECT\s/i.test(afterPrefixes)) {
|
|
106
255
|
throw buildStoryConfigError(
|
|
107
256
|
source,
|
|
108
|
-
|
|
257
|
+
`"${where}.query" must be a SPARQL SELECT query.`,
|
|
109
258
|
);
|
|
110
259
|
}
|
|
111
260
|
|
|
112
261
|
if (!Array.isArray(obj.columns) || obj.columns.length === 0) {
|
|
113
262
|
throw buildStoryConfigError(
|
|
114
263
|
source,
|
|
115
|
-
|
|
264
|
+
`"${where}.columns" must be a non-empty array.`,
|
|
116
265
|
);
|
|
117
266
|
}
|
|
118
267
|
const columns = obj.columns.map((column, index) =>
|
|
119
|
-
validateColumn(column,
|
|
268
|
+
validateColumn(column, `${where}.columns[${index}]`, source),
|
|
120
269
|
);
|
|
270
|
+
// Like filters and search below: a column naming a variable the query
|
|
271
|
+
// never binds is a typo that should fail at boot, not render an
|
|
272
|
+
// always-empty column at first use.
|
|
273
|
+
for (const column of columns) {
|
|
274
|
+
if (!referencesVariable(query, column.field)) {
|
|
275
|
+
throw buildStoryConfigError(
|
|
276
|
+
source,
|
|
277
|
+
`column field "?${column.field}" does not appear in "${where}.query".`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
121
281
|
|
|
122
|
-
const filters = validateFilterArray(obj.filters, source);
|
|
282
|
+
const filters = validateFilterArray(obj.filters, where, source);
|
|
123
283
|
for (const filter of filters ?? []) {
|
|
124
284
|
if (!referencesVariable(query, filter.variable)) {
|
|
125
285
|
throw buildStoryConfigError(
|
|
126
286
|
source,
|
|
127
|
-
`filter variable "?${filter.variable}" does not appear in "
|
|
287
|
+
`filter variable "?${filter.variable}" does not appear in "${where}.query".`,
|
|
128
288
|
);
|
|
129
289
|
}
|
|
130
290
|
}
|
|
131
291
|
|
|
132
|
-
|
|
292
|
+
const search = validateSearch(obj.search, query, where, source);
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
query,
|
|
296
|
+
columns,
|
|
297
|
+
...(filters ? { filters } : {}),
|
|
298
|
+
...(search ? { search } : {}),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Validate a list's free-text `search` declaration.
|
|
304
|
+
*
|
|
305
|
+
* Every searched variable must appear in the query — like filters, this
|
|
306
|
+
* fails a typo at boot instead of a search that silently never matches.
|
|
307
|
+
*/
|
|
308
|
+
function validateSearch(
|
|
309
|
+
raw: unknown,
|
|
310
|
+
query: string,
|
|
311
|
+
where: string,
|
|
312
|
+
source: string,
|
|
313
|
+
): StoryPackSearch | undefined {
|
|
314
|
+
if (raw === undefined) return undefined;
|
|
315
|
+
const obj = requireObject(raw, `${where}.search`, source);
|
|
316
|
+
if (!Array.isArray(obj.variables) || obj.variables.length === 0) {
|
|
317
|
+
throw buildStoryConfigError(
|
|
318
|
+
source,
|
|
319
|
+
`"${where}.search.variables" must be a non-empty array.`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
const variables = obj.variables.map((value, index) => {
|
|
323
|
+
const variable = requireString(
|
|
324
|
+
value,
|
|
325
|
+
`${where}.search.variables[${index}]`,
|
|
326
|
+
source,
|
|
327
|
+
);
|
|
328
|
+
if (!FIELD_PATTERN.test(variable)) {
|
|
329
|
+
throw buildStoryConfigError(
|
|
330
|
+
source,
|
|
331
|
+
`"${where}.search.variables[${index}]" must name a SELECT variable.`,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
rejectReservedObjectKey(
|
|
335
|
+
variable,
|
|
336
|
+
`${where}.search.variables[${index}]`,
|
|
337
|
+
source,
|
|
338
|
+
);
|
|
339
|
+
if (!referencesVariable(query, variable)) {
|
|
340
|
+
throw buildStoryConfigError(
|
|
341
|
+
source,
|
|
342
|
+
`search variable "?${variable}" does not appear in "${where}.query".`,
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
return variable;
|
|
346
|
+
});
|
|
347
|
+
const description =
|
|
348
|
+
obj.description === undefined
|
|
349
|
+
? undefined
|
|
350
|
+
: requireString(obj.description, `${where}.search.description`, source);
|
|
351
|
+
return {
|
|
352
|
+
variables,
|
|
353
|
+
...(description !== undefined ? { description } : {}),
|
|
354
|
+
};
|
|
133
355
|
}
|
|
134
356
|
|
|
135
357
|
/**
|
|
@@ -145,17 +367,18 @@ function referencesVariable(query: string, variable: string): boolean {
|
|
|
145
367
|
|
|
146
368
|
function validateFilterArray(
|
|
147
369
|
raw: unknown,
|
|
370
|
+
where: string,
|
|
148
371
|
source: string,
|
|
149
372
|
): readonly StoryPackFilter[] | undefined {
|
|
150
373
|
if (raw === undefined) return undefined;
|
|
151
374
|
if (!Array.isArray(raw) || raw.length === 0) {
|
|
152
375
|
throw buildStoryConfigError(
|
|
153
376
|
source,
|
|
154
|
-
|
|
377
|
+
`"${where}.filters" must be a non-empty array.`,
|
|
155
378
|
);
|
|
156
379
|
}
|
|
157
380
|
const filters = raw.map((entry, index) =>
|
|
158
|
-
validateFilter(entry,
|
|
381
|
+
validateFilter(entry, `${where}.filters[${index}]`, source),
|
|
159
382
|
);
|
|
160
383
|
const seen = new Set<string>();
|
|
161
384
|
for (const filter of filters) {
|
|
@@ -189,6 +412,7 @@ function validateFilter(
|
|
|
189
412
|
`"${where}.param" "${param}" is a reserved name.`,
|
|
190
413
|
);
|
|
191
414
|
}
|
|
415
|
+
rejectReservedObjectKey(param, `${where}.param`, source);
|
|
192
416
|
const variable = requireString(obj.variable, `${where}.variable`, source);
|
|
193
417
|
if (!FIELD_PATTERN.test(variable)) {
|
|
194
418
|
throw buildStoryConfigError(
|
|
@@ -196,13 +420,38 @@ function validateFilter(
|
|
|
196
420
|
`"${where}.variable" must name a SELECT variable.`,
|
|
197
421
|
);
|
|
198
422
|
}
|
|
199
|
-
|
|
423
|
+
rejectReservedObjectKey(variable, `${where}.variable`, source);
|
|
424
|
+
// `values` is optional (pack v1): without it the filter is a free-string
|
|
425
|
+
// parameter matched case-insensitively against the variable — used when
|
|
426
|
+
// the value set is data-driven and cannot be declared.
|
|
427
|
+
const values =
|
|
428
|
+
obj.values === undefined
|
|
429
|
+
? undefined
|
|
430
|
+
: validateFilterValues(obj.values, where, source);
|
|
431
|
+
const description =
|
|
432
|
+
obj.description === undefined
|
|
433
|
+
? undefined
|
|
434
|
+
: requireString(obj.description, `${where}.description`, source);
|
|
435
|
+
return {
|
|
436
|
+
param,
|
|
437
|
+
variable,
|
|
438
|
+
...(values ? { values } : {}),
|
|
439
|
+
...(description !== undefined ? { description } : {}),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function validateFilterValues(
|
|
444
|
+
raw: unknown,
|
|
445
|
+
where: string,
|
|
446
|
+
source: string,
|
|
447
|
+
): readonly string[] {
|
|
448
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
200
449
|
throw buildStoryConfigError(
|
|
201
450
|
source,
|
|
202
451
|
`"${where}.values" must be a non-empty array.`,
|
|
203
452
|
);
|
|
204
453
|
}
|
|
205
|
-
const values =
|
|
454
|
+
const values = raw.map((value, index) =>
|
|
206
455
|
requireString(value, `${where}.values[${index}]`, source),
|
|
207
456
|
);
|
|
208
457
|
const lowered = new Set<string>();
|
|
@@ -217,16 +466,7 @@ function validateFilter(
|
|
|
217
466
|
}
|
|
218
467
|
lowered.add(key);
|
|
219
468
|
}
|
|
220
|
-
|
|
221
|
-
obj.description === undefined
|
|
222
|
-
? undefined
|
|
223
|
-
: requireString(obj.description, `${where}.description`, source);
|
|
224
|
-
return {
|
|
225
|
-
param,
|
|
226
|
-
variable,
|
|
227
|
-
values,
|
|
228
|
-
...(description !== undefined ? { description } : {}),
|
|
229
|
-
};
|
|
469
|
+
return values;
|
|
230
470
|
}
|
|
231
471
|
|
|
232
472
|
function validateColumn(
|
|
@@ -242,6 +482,7 @@ function validateColumn(
|
|
|
242
482
|
`"${where}.field" must name a SELECT variable.`,
|
|
243
483
|
);
|
|
244
484
|
}
|
|
485
|
+
rejectReservedObjectKey(field, `${where}.field`, source);
|
|
245
486
|
const label =
|
|
246
487
|
obj.label === undefined
|
|
247
488
|
? undefined
|
|
@@ -256,6 +497,14 @@ function validateLookup(raw: unknown, source: string): StoryPackLookup {
|
|
|
256
497
|
obj.type === undefined
|
|
257
498
|
? undefined
|
|
258
499
|
: requireTerm(obj.type, "lookup.type", source);
|
|
500
|
+
const description =
|
|
501
|
+
obj.description === undefined
|
|
502
|
+
? undefined
|
|
503
|
+
: requireString(obj.description, "lookup.description", source);
|
|
504
|
+
const toolDescription =
|
|
505
|
+
obj.toolDescription === undefined
|
|
506
|
+
? undefined
|
|
507
|
+
: requireString(obj.toolDescription, "lookup.toolDescription", source);
|
|
259
508
|
|
|
260
509
|
const fields = validateFieldArray(obj.fields, "lookup.fields", source);
|
|
261
510
|
const sections = validateSectionArray(
|
|
@@ -263,12 +512,213 @@ function validateLookup(raw: unknown, source: string): StoryPackLookup {
|
|
|
263
512
|
"lookup.sections",
|
|
264
513
|
source,
|
|
265
514
|
);
|
|
515
|
+
const expand = validateExpandArray(obj.expand, "lookup.expand", source);
|
|
516
|
+
const disclosure = validateDisclosure(obj.disclosure, source);
|
|
517
|
+
const sample = validateSample(obj.sample, source);
|
|
518
|
+
|
|
519
|
+
// Cross-check: every expand `level` must name a declared disclosure level.
|
|
520
|
+
const levels = new Set(disclosure?.levels ?? []);
|
|
521
|
+
for (const entry of expand ?? []) {
|
|
522
|
+
if (entry.level !== undefined && !levels.has(entry.level)) {
|
|
523
|
+
throw buildStoryConfigError(
|
|
524
|
+
source,
|
|
525
|
+
`expand "${entry.name}" level "${entry.level}" is not a declared disclosure level.`,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
266
529
|
|
|
267
530
|
return {
|
|
268
531
|
by,
|
|
269
532
|
...(type !== undefined ? { type } : {}),
|
|
533
|
+
...(description !== undefined ? { description } : {}),
|
|
534
|
+
...(toolDescription !== undefined ? { toolDescription } : {}),
|
|
270
535
|
...(fields ? { fields } : {}),
|
|
271
536
|
...(sections ? { sections } : {}),
|
|
537
|
+
...(expand ? { expand } : {}),
|
|
538
|
+
...(disclosure ? { disclosure } : {}),
|
|
539
|
+
...(sample !== undefined ? { sample } : {}),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function validateSample(
|
|
544
|
+
raw: unknown,
|
|
545
|
+
source: string,
|
|
546
|
+
): true | StoryPackSample | undefined {
|
|
547
|
+
if (raw === undefined) return undefined;
|
|
548
|
+
if (raw === true) return true;
|
|
549
|
+
const obj = requireObject(raw, "lookup.sample", source);
|
|
550
|
+
if (obj.count !== undefined) {
|
|
551
|
+
// Authors fail fast on an out-of-range default; user-provided counts
|
|
552
|
+
// are clamped at run time by parseSampleCount instead.
|
|
553
|
+
if (
|
|
554
|
+
typeof obj.count !== "number" ||
|
|
555
|
+
!Number.isInteger(obj.count) ||
|
|
556
|
+
obj.count < MIN_SAMPLE_COUNT ||
|
|
557
|
+
obj.count > MAX_SAMPLE_COUNT
|
|
558
|
+
) {
|
|
559
|
+
throw buildStoryConfigError(
|
|
560
|
+
source,
|
|
561
|
+
`"lookup.sample.count" must be an integer between ${MIN_SAMPLE_COUNT} and ${MAX_SAMPLE_COUNT}.`,
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
const description =
|
|
566
|
+
obj.description === undefined
|
|
567
|
+
? undefined
|
|
568
|
+
: requireString(obj.description, "lookup.sample.description", source);
|
|
569
|
+
const toolDescription =
|
|
570
|
+
obj.toolDescription === undefined
|
|
571
|
+
? undefined
|
|
572
|
+
: requireString(
|
|
573
|
+
obj.toolDescription,
|
|
574
|
+
"lookup.sample.toolDescription",
|
|
575
|
+
source,
|
|
576
|
+
);
|
|
577
|
+
return {
|
|
578
|
+
...(obj.count !== undefined ? { count: obj.count as number } : {}),
|
|
579
|
+
...(description !== undefined ? { description } : {}),
|
|
580
|
+
...(toolDescription !== undefined ? { toolDescription } : {}),
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function validateDisclosure(
|
|
585
|
+
raw: unknown,
|
|
586
|
+
source: string,
|
|
587
|
+
): StoryPackDisclosure | undefined {
|
|
588
|
+
if (raw === undefined) return undefined;
|
|
589
|
+
const obj = requireObject(raw, "lookup.disclosure", source);
|
|
590
|
+
if (!Array.isArray(obj.levels) || obj.levels.length === 0) {
|
|
591
|
+
throw buildStoryConfigError(
|
|
592
|
+
source,
|
|
593
|
+
'"lookup.disclosure.levels" must be a non-empty array.',
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
const seen = new Set<string>();
|
|
597
|
+
const levels = obj.levels.map((value, index) => {
|
|
598
|
+
const level = requireString(
|
|
599
|
+
value,
|
|
600
|
+
`lookup.disclosure.levels[${index}]`,
|
|
601
|
+
source,
|
|
602
|
+
);
|
|
603
|
+
// Levels derive parameter names (`--detail` enum values and the alias
|
|
604
|
+
// flags), so they are held to the same single-word rule as filter
|
|
605
|
+
// params and may not shadow reserved parameter names.
|
|
606
|
+
if (!FILTER_PARAM_PATTERN.test(level)) {
|
|
607
|
+
throw buildStoryConfigError(
|
|
608
|
+
source,
|
|
609
|
+
`disclosure level "${level}" must be a single lowercase word (letters and digits, no hyphens).`,
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
if (RESERVED_LEVEL_NAMES.has(level)) {
|
|
613
|
+
throw buildStoryConfigError(
|
|
614
|
+
source,
|
|
615
|
+
`disclosure level "${level}" is a reserved name.`,
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
rejectReservedObjectKey(
|
|
619
|
+
level,
|
|
620
|
+
`lookup.disclosure.levels[${index}]`,
|
|
621
|
+
source,
|
|
622
|
+
);
|
|
623
|
+
if (seen.has(level)) {
|
|
624
|
+
throw buildStoryConfigError(
|
|
625
|
+
source,
|
|
626
|
+
`duplicate disclosure level "${level}".`,
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
seen.add(level);
|
|
630
|
+
return level;
|
|
631
|
+
});
|
|
632
|
+
const defaultLevel =
|
|
633
|
+
obj.default === undefined
|
|
634
|
+
? undefined
|
|
635
|
+
: requireString(obj.default, "lookup.disclosure.default", source);
|
|
636
|
+
if (defaultLevel !== undefined && !seen.has(defaultLevel)) {
|
|
637
|
+
throw buildStoryConfigError(
|
|
638
|
+
source,
|
|
639
|
+
`disclosure default "${defaultLevel}" is not one of the declared levels.`,
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
return {
|
|
643
|
+
levels,
|
|
644
|
+
...(defaultLevel !== undefined ? { default: defaultLevel } : {}),
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function validateExpandArray(
|
|
649
|
+
raw: unknown,
|
|
650
|
+
where: string,
|
|
651
|
+
source: string,
|
|
652
|
+
): readonly StoryPackExpand[] | undefined {
|
|
653
|
+
if (raw === undefined) return undefined;
|
|
654
|
+
if (!Array.isArray(raw)) {
|
|
655
|
+
throw buildStoryConfigError(source, `"${where}" must be an array.`);
|
|
656
|
+
}
|
|
657
|
+
const names = new Set<string>();
|
|
658
|
+
return raw.map((entry, index) => {
|
|
659
|
+
const expand = validateExpand(entry, `${where}[${index}]`, source);
|
|
660
|
+
if (names.has(expand.name)) {
|
|
661
|
+
throw buildStoryConfigError(
|
|
662
|
+
source,
|
|
663
|
+
`duplicate expand name "${expand.name}" in "${where}".`,
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
names.add(expand.name);
|
|
667
|
+
return expand;
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function validateExpand(
|
|
672
|
+
raw: unknown,
|
|
673
|
+
where: string,
|
|
674
|
+
source: string,
|
|
675
|
+
): StoryPackExpand {
|
|
676
|
+
const obj = requireObject(raw, where, source);
|
|
677
|
+
const name = requireString(obj.name, `${where}.name`, source);
|
|
678
|
+
if (!FIELD_PATTERN.test(name)) {
|
|
679
|
+
throw buildStoryConfigError(
|
|
680
|
+
source,
|
|
681
|
+
`"${where}.name" must be a simple identifier.`,
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
rejectReservedObjectKey(name, `${where}.name`, source);
|
|
685
|
+
const relation = requirePredicateTerm(
|
|
686
|
+
obj.relation,
|
|
687
|
+
`${where}.relation`,
|
|
688
|
+
source,
|
|
689
|
+
);
|
|
690
|
+
if (obj.kind !== undefined && obj.kind !== "list" && obj.kind !== "table") {
|
|
691
|
+
throw buildStoryConfigError(
|
|
692
|
+
source,
|
|
693
|
+
`"${where}.kind" must be "list" or "table".`,
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
if (!Array.isArray(obj.select) || obj.select.length === 0) {
|
|
697
|
+
throw buildStoryConfigError(
|
|
698
|
+
source,
|
|
699
|
+
`"${where}.select" must be a non-empty array.`,
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
const select: StoryPackExpandField[] = obj.select.map((entry, index) =>
|
|
703
|
+
validateField(entry, `${where}.select[${index}]`, source),
|
|
704
|
+
);
|
|
705
|
+
const heading =
|
|
706
|
+
obj.heading === undefined
|
|
707
|
+
? undefined
|
|
708
|
+
: requireString(obj.heading, `${where}.heading`, source);
|
|
709
|
+
// Membership in the declared levels is cross-checked in validateLookup.
|
|
710
|
+
const level =
|
|
711
|
+
obj.level === undefined
|
|
712
|
+
? undefined
|
|
713
|
+
: requireString(obj.level, `${where}.level`, source);
|
|
714
|
+
return {
|
|
715
|
+
name,
|
|
716
|
+
relation,
|
|
717
|
+
select,
|
|
718
|
+
...(heading !== undefined ? { heading } : {}),
|
|
719
|
+
...(obj.kind !== undefined ? { kind: obj.kind as "list" | "table" } : {}),
|
|
720
|
+
...(obj.showWhenEmpty === true ? { showWhenEmpty: true } : {}),
|
|
721
|
+
...(level !== undefined ? { level } : {}),
|
|
272
722
|
};
|
|
273
723
|
}
|
|
274
724
|
|
|
@@ -324,6 +774,7 @@ function validateField(
|
|
|
324
774
|
`"${where}.name" must be a simple identifier.`,
|
|
325
775
|
);
|
|
326
776
|
}
|
|
777
|
+
rejectReservedObjectKey(name, `${where}.name`, source);
|
|
327
778
|
const property = requirePredicateTerm(
|
|
328
779
|
obj.property,
|
|
329
780
|
`${where}.property`,
|
|
@@ -141,11 +141,19 @@ export interface LookupStory<TDetailed, TFmtInput> {
|
|
|
141
141
|
/** Named groups for organizing CLI parameters in help output. */
|
|
142
142
|
readonly parameterGroups?: Readonly<Record<string, readonly string[]>>;
|
|
143
143
|
readonly examples: readonly string[];
|
|
144
|
-
/**
|
|
144
|
+
/**
|
|
145
|
+
* Resolve all queries, collecting per-query failures.
|
|
146
|
+
*
|
|
147
|
+
* The compilers pass the per-invocation `view` so surface-sensitive
|
|
148
|
+
* resolution (e.g. pack disclosure defaults, which differ between the
|
|
149
|
+
* CLI and MCP per the ratified surface contract) can see which surface
|
|
150
|
+
* is calling; most stories ignore it.
|
|
151
|
+
*/
|
|
145
152
|
readonly resolve: (
|
|
146
153
|
rt: PragmaRuntime,
|
|
147
154
|
names: readonly string[],
|
|
148
155
|
params: Record<string, unknown>,
|
|
156
|
+
view?: LookupStoryView,
|
|
149
157
|
) => Promise<LookupResult<TDetailed>>;
|
|
150
158
|
/**
|
|
151
159
|
* Effective `detailed` for a surface. Defaults to the plain flag value
|
|
@@ -26,21 +26,17 @@ export type {
|
|
|
26
26
|
} from "./modifier.js";
|
|
27
27
|
export type {
|
|
28
28
|
OntologyClass,
|
|
29
|
+
OntologyClassFocus,
|
|
30
|
+
OntologyConstraint,
|
|
29
31
|
OntologyDetailed,
|
|
32
|
+
OntologyMeta,
|
|
30
33
|
OntologyProperty,
|
|
34
|
+
OntologyQueryHint,
|
|
31
35
|
OntologySummary,
|
|
32
36
|
} from "./ontology.js";
|
|
33
37
|
export type { PragmaRuntime } from "./runtime.js";
|
|
34
38
|
export type { SampleOutput, SampleResult } from "./sample.js";
|
|
35
|
-
export type {
|
|
36
|
-
CategorySummary,
|
|
37
|
-
CodeBlock,
|
|
38
|
-
StandardDetailed,
|
|
39
|
-
StandardDigest,
|
|
40
|
-
StandardListFilters,
|
|
41
|
-
StandardRef,
|
|
42
|
-
StandardSummary,
|
|
43
|
-
} from "./standard.js";
|
|
39
|
+
export type { StandardRef } from "./standard.js";
|
|
44
40
|
export type { TierEntry } from "./tier.js";
|
|
45
41
|
export type {
|
|
46
42
|
TokenDetailed,
|