@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
|
@@ -36,14 +36,16 @@ import {
|
|
|
36
36
|
buildLookupNamesQuery,
|
|
37
37
|
buildLookupQuery,
|
|
38
38
|
buildNameResolveQuery,
|
|
39
|
+
SCOPE_TIER_VARIABLE,
|
|
39
40
|
} from "./sparql/buildLookupQuery.js";
|
|
40
41
|
import { runSelect } from "./sparql/runSelect.js";
|
|
41
|
-
import
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
import {
|
|
43
|
+
expandIsSparql,
|
|
44
|
+
type PackChildRow,
|
|
45
|
+
type PackEntity,
|
|
46
|
+
type PackLookup,
|
|
47
|
+
type PackRow,
|
|
48
|
+
type StorySource,
|
|
47
49
|
} from "./types.js";
|
|
48
50
|
|
|
49
51
|
/** A structured per-query lookup failure (never rejects the whole batch). */
|
|
@@ -54,6 +56,32 @@ export interface LookupError {
|
|
|
54
56
|
readonly suggestions?: readonly string[];
|
|
55
57
|
}
|
|
56
58
|
|
|
59
|
+
/**
|
|
60
|
+
* The tier scope a name resolve answers under, as the resolver needs it.
|
|
61
|
+
*
|
|
62
|
+
* Built by the run body from the noun's declaration and the caller's arguments
|
|
63
|
+
* ({@link ../tierScope}); the resolver only has to know which tiers count and
|
|
64
|
+
* how to say so.
|
|
65
|
+
*/
|
|
66
|
+
export interface LookupScope {
|
|
67
|
+
/** The entity → tier edge, a validated pack term. */
|
|
68
|
+
readonly via: string;
|
|
69
|
+
/** The in-scope tier IRIs. */
|
|
70
|
+
readonly tiers: readonly string[];
|
|
71
|
+
/** The scope in the words a reader can type back (`global, apps`). */
|
|
72
|
+
readonly label: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** A name whose only matches were outside the tier scope. */
|
|
76
|
+
export interface OutOfScopeAnswer {
|
|
77
|
+
/** The name (or IRI, or glob expansion) that was looked up. */
|
|
78
|
+
readonly query: string;
|
|
79
|
+
/** The tiers the answers actually came from, as local names. */
|
|
80
|
+
readonly tiers: readonly string[];
|
|
81
|
+
/** The scope that held none of them, in the words `--tier` accepts. */
|
|
82
|
+
readonly scope: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
57
85
|
/**
|
|
58
86
|
* The result of a (possibly multi-name) lookup.
|
|
59
87
|
*
|
|
@@ -62,10 +90,23 @@ export interface LookupError {
|
|
|
62
90
|
* naming the ones not answered with, because there are none — an earlier draft
|
|
63
91
|
* kept the single-entity arity and carried the rest as IRIs in a notice, which
|
|
64
92
|
* is a sentence about an address where the payload is the address.
|
|
93
|
+
*
|
|
94
|
+
* The tier scope narrows that: for a SCOPED noun the in-scope matches are the
|
|
95
|
+
* answer, and the out-of-scope ones are not returned — `block lookup button`
|
|
96
|
+
* answers with the global Button alone. A name whose matches are ALL out of
|
|
97
|
+
* scope is answered anyway, from the tiers it does live in, and says so through
|
|
98
|
+
* {@link outOfScope}: refusing it would be answering "no such block" about a
|
|
99
|
+
* block that exists, which is the one thing a lookup must never do.
|
|
65
100
|
*/
|
|
66
101
|
export interface LookupOutput {
|
|
67
102
|
readonly results: PackEntity[];
|
|
68
103
|
readonly errors: LookupError[];
|
|
104
|
+
/**
|
|
105
|
+
* The names answered from OUTSIDE the tier scope, because the scope held
|
|
106
|
+
* nothing they reached. Absent when every answer was in scope — the ordinary
|
|
107
|
+
* case, and the case the payload speaks for itself in.
|
|
108
|
+
*/
|
|
109
|
+
readonly outOfScope?: OutOfScopeAnswer[];
|
|
69
110
|
}
|
|
70
111
|
|
|
71
112
|
/** What the resolver needs from the runtime: the store + the query facade. */
|
|
@@ -84,6 +125,7 @@ export async function resolveLookup(
|
|
|
84
125
|
source: StorySource,
|
|
85
126
|
prefixes: Readonly<Record<string, string>>,
|
|
86
127
|
level: string | undefined,
|
|
128
|
+
scope?: LookupScope,
|
|
87
129
|
): Promise<LookupOutput> {
|
|
88
130
|
if (queries.length === 0) {
|
|
89
131
|
throw PragmaError.invalidInput("names", "(empty)", {
|
|
@@ -106,16 +148,18 @@ export async function resolveLookup(
|
|
|
106
148
|
);
|
|
107
149
|
const results: PackEntity[] = [];
|
|
108
150
|
const errors: LookupError[] = [...expanded.globErrors];
|
|
151
|
+
const outOfScope: OutOfScopeAnswer[] = [];
|
|
109
152
|
const settled = await Promise.allSettled(
|
|
110
153
|
expanded.names.map((query) =>
|
|
111
|
-
lookupOne(rt, lookup, noun, query, source, prefixes, level),
|
|
154
|
+
lookupOne(rt, lookup, noun, query, source, prefixes, level, scope),
|
|
112
155
|
),
|
|
113
156
|
);
|
|
114
157
|
for (const [index, outcome] of settled.entries()) {
|
|
115
158
|
const query = expanded.names[index];
|
|
116
159
|
if (query === undefined) continue;
|
|
117
160
|
if (outcome.status === "fulfilled") {
|
|
118
|
-
results.push(...outcome.value);
|
|
161
|
+
results.push(...outcome.value.entities);
|
|
162
|
+
if (outcome.value.outOfScope) outOfScope.push(outcome.value.outOfScope);
|
|
119
163
|
continue;
|
|
120
164
|
}
|
|
121
165
|
const error = outcome.reason;
|
|
@@ -136,7 +180,11 @@ export async function resolveLookup(
|
|
|
136
180
|
});
|
|
137
181
|
}
|
|
138
182
|
}
|
|
139
|
-
return {
|
|
183
|
+
return {
|
|
184
|
+
results,
|
|
185
|
+
errors,
|
|
186
|
+
...(outOfScope.length > 0 ? { outOfScope } : {}),
|
|
187
|
+
};
|
|
140
188
|
}
|
|
141
189
|
|
|
142
190
|
/**
|
|
@@ -235,15 +283,16 @@ async function lookupOne(
|
|
|
235
283
|
source: StorySource,
|
|
236
284
|
prefixes: Readonly<Record<string, string>>,
|
|
237
285
|
level: string | undefined,
|
|
238
|
-
|
|
286
|
+
scope?: LookupScope,
|
|
287
|
+
): Promise<{ entities: PackEntity[]; outOfScope?: OutOfScopeAnswer }> {
|
|
239
288
|
const graphqlSourced = lookup.source === "graphql";
|
|
240
289
|
const rows = await runSelect(
|
|
241
290
|
rt,
|
|
242
|
-
buildResolveQuery(lookup, query, prefixes, level),
|
|
291
|
+
buildResolveQuery(lookup, query, prefixes, level, scope),
|
|
243
292
|
source,
|
|
244
293
|
);
|
|
245
|
-
const
|
|
246
|
-
if (
|
|
294
|
+
const resolved = firstRowPerEntity(rows);
|
|
295
|
+
if (resolved.length === 0) {
|
|
247
296
|
const candidates = await listEntityNames(rt, lookup, source);
|
|
248
297
|
throw PragmaError.notFound(noun, query, {
|
|
249
298
|
suggestions: suggestNames(query, candidates),
|
|
@@ -256,8 +305,12 @@ async function lookupOne(
|
|
|
256
305
|
});
|
|
257
306
|
}
|
|
258
307
|
|
|
308
|
+
const chosen = applyScope(resolved, query, scope);
|
|
309
|
+
const bases = chosen.rows.map(withoutScopeVariable);
|
|
310
|
+
const fallback = chosen.outOfScope ? { outOfScope: chosen.outOfScope } : {};
|
|
311
|
+
|
|
259
312
|
if (graphqlSourced) {
|
|
260
|
-
|
|
313
|
+
const fetched = await Promise.all(
|
|
261
314
|
bases.map((base) =>
|
|
262
315
|
fetchGraphqlLookup(
|
|
263
316
|
rt,
|
|
@@ -273,21 +326,123 @@ async function lookupOne(
|
|
|
273
326
|
),
|
|
274
327
|
),
|
|
275
328
|
);
|
|
329
|
+
// A graphql lookup may still declare SPARQL-lane expands (see
|
|
330
|
+
// `PackExpand.source`), and they are fetched HERE rather than inside the
|
|
331
|
+
// GraphQL lane: one entity, two lanes, merged on the way out. The document
|
|
332
|
+
// generator skips exactly what this loop claims, so no expand is fetched
|
|
333
|
+
// twice and none is dropped.
|
|
334
|
+
const entities = await Promise.all(
|
|
335
|
+
fetched.map(async (entity) =>
|
|
336
|
+
addSparqlExpands(rt, lookup, entity, source, level),
|
|
337
|
+
),
|
|
338
|
+
);
|
|
339
|
+
return { entities, ...fallback };
|
|
276
340
|
}
|
|
277
341
|
|
|
278
342
|
const entities: PackEntity[] = [];
|
|
279
343
|
for (const base of bases) {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
344
|
+
entities.push(
|
|
345
|
+
await addSparqlExpands(rt, lookup, { ...base }, source, level),
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
return { entities, ...fallback };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Narrow a resolve to the tier scope — or, when the scope holds none of what
|
|
353
|
+
* the name reached, answer from outside it and say so.
|
|
354
|
+
*
|
|
355
|
+
* THE FALLBACK IS THE POINT, and it is why the scope is not a `FILTER` in the
|
|
356
|
+
* resolve query. A scoped filter would turn `block lookup back-link` — an LXD
|
|
357
|
+
* component, out of the default scope — into ENTITY_NOT_FOUND with suggestions,
|
|
358
|
+
* i.e. into "no such block" about a block the store holds and `block list
|
|
359
|
+
* --tier apps_lxd` prints. A name a reader typed is evidence they mean
|
|
360
|
+
* something; the scope decides WHICH of several it means, never whether it
|
|
361
|
+
* exists.
|
|
362
|
+
*
|
|
363
|
+
* An entity with no tier at all is treated as IN scope. It cannot be placed, so
|
|
364
|
+
* it cannot be placed outside — and being unplaceable is not grounds for being
|
|
365
|
+
* unfindable.
|
|
366
|
+
*/
|
|
367
|
+
function applyScope(
|
|
368
|
+
rows: readonly PackRow[],
|
|
369
|
+
query: string,
|
|
370
|
+
scope: LookupScope | undefined,
|
|
371
|
+
): { rows: readonly PackRow[]; outOfScope?: OutOfScopeAnswer } {
|
|
372
|
+
if (!scope) return { rows };
|
|
373
|
+
const inScope = new Set(scope.tiers);
|
|
374
|
+
const kept = rows.filter((row) => {
|
|
375
|
+
const tier = row[SCOPE_TIER_VARIABLE];
|
|
376
|
+
return tier === undefined || tier === "" || inScope.has(tier);
|
|
377
|
+
});
|
|
378
|
+
if (kept.length > 0) return { rows: kept };
|
|
379
|
+
const tiers = [
|
|
380
|
+
...new Set(
|
|
381
|
+
rows
|
|
382
|
+
.map((row) => row[SCOPE_TIER_VARIABLE] ?? "")
|
|
383
|
+
.filter((tier) => tier !== "")
|
|
384
|
+
.map(scopeTierName),
|
|
385
|
+
),
|
|
386
|
+
];
|
|
387
|
+
return { rows, outOfScope: { query, tiers, scope: scope.label } };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** A tier IRI as the scope reports it: the local name `--tier` accepts. */
|
|
391
|
+
function scopeTierName(iri: string): string {
|
|
392
|
+
return iri.replace(/^.*[#/]/, "");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Drop the kernel's scope variable from a resolved row.
|
|
397
|
+
*
|
|
398
|
+
* The sparql-sourced lane spreads its resolve row straight into the entity it
|
|
399
|
+
* answers with, so a variable the kernel added for its own decision would land
|
|
400
|
+
* in `--format json` as a field the story never declared.
|
|
401
|
+
*/
|
|
402
|
+
function withoutScopeVariable(row: PackRow): PackRow {
|
|
403
|
+
if (!(SCOPE_TIER_VARIABLE in row)) return row;
|
|
404
|
+
const { [SCOPE_TIER_VARIABLE]: _tier, ...rest } = row;
|
|
405
|
+
return rest;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Fetch every SPARQL-lane expand active at this level onto one entity.
|
|
410
|
+
*
|
|
411
|
+
* The lane test is the expand's, not the lookup's, so this serves both callers:
|
|
412
|
+
* on a sparql lookup every expand is one of these, and on a graphql lookup only
|
|
413
|
+
* those that opted out of the document.
|
|
414
|
+
*
|
|
415
|
+
* @param rt - The runtime (store + query facade).
|
|
416
|
+
* @param lookup - The validated lookup declaration.
|
|
417
|
+
* @param entity - The entity to add child arrays to (mutated and returned).
|
|
418
|
+
* @param source - Pack source, for error attribution.
|
|
419
|
+
* @param level - Active canonical disclosure level.
|
|
420
|
+
* @returns The same entity, with one array per fetched expand.
|
|
421
|
+
* @note Impure — queries the store, once per expand.
|
|
422
|
+
*/
|
|
423
|
+
async function addSparqlExpands(
|
|
424
|
+
rt: LookupRuntime,
|
|
425
|
+
lookup: PackLookup,
|
|
426
|
+
entity: PackEntity,
|
|
427
|
+
source: StorySource,
|
|
428
|
+
level: string | undefined,
|
|
429
|
+
): Promise<PackEntity> {
|
|
430
|
+
for (const expand of activeExpands(lookup, level)) {
|
|
431
|
+
if (!expandIsSparql(lookup, expand)) continue;
|
|
432
|
+
entity[expand.name] = (await runSelect(
|
|
433
|
+
rt,
|
|
434
|
+
buildExpandQuery(expand, String(entity.uri), lookup),
|
|
435
|
+
source,
|
|
436
|
+
// A section whose vocabulary this store does not bind renders EMPTY
|
|
437
|
+
// rather than taking the whole entity down with it — see
|
|
438
|
+
// `RunSelectOptions.degradeOnUnboundPrefix`. The block lookup's tokens
|
|
439
|
+
// read the anatomy DSL's style key and state, which a store built
|
|
440
|
+
// without that pack does not bind, and every other section of that
|
|
441
|
+
// block is still perfectly answerable.
|
|
442
|
+
{ degradeOnUnboundPrefix: true },
|
|
443
|
+
)) as readonly PackChildRow[];
|
|
289
444
|
}
|
|
290
|
-
return
|
|
445
|
+
return entity;
|
|
291
446
|
}
|
|
292
447
|
|
|
293
448
|
/** Collapse a ranked resolve to one row per entity, keeping the best-ranked. */
|
|
@@ -316,13 +471,18 @@ function buildResolveQuery(
|
|
|
316
471
|
query: string,
|
|
317
472
|
prefixes: Readonly<Record<string, string>>,
|
|
318
473
|
level: string | undefined,
|
|
474
|
+
scope?: LookupScope,
|
|
319
475
|
): string {
|
|
320
476
|
const graphqlSourced = lookup.source === "graphql";
|
|
321
477
|
if (!looksLikeIri(query)) {
|
|
322
478
|
return graphqlSourced
|
|
323
|
-
? buildNameResolveQuery(lookup, query)
|
|
324
|
-
: buildLookupQuery(lookup, query, level);
|
|
479
|
+
? buildNameResolveQuery(lookup, query, scope?.via)
|
|
480
|
+
: buildLookupQuery(lookup, query, level, scope?.via);
|
|
325
481
|
}
|
|
482
|
+
// An IRI-ADDRESSED form is never scoped, and that is not an omission. An IRI
|
|
483
|
+
// reaches exactly one entity, so there is nothing for a scope to choose
|
|
484
|
+
// between — the whole job the scope does. A caller who pastes
|
|
485
|
+
// `ds:apps_lxd.component.back_link` has already said which tier they mean.
|
|
326
486
|
const resolved = resolveUri(query, prefixes);
|
|
327
487
|
if (!isEmbeddableIri(resolved)) {
|
|
328
488
|
throw PragmaError.invalidInput("name", query, {
|
|
@@ -2,35 +2,49 @@
|
|
|
2
2
|
* The shared run bodies a compiled pack verb closes over.
|
|
3
3
|
*
|
|
4
4
|
* Each factory returns a `VerbSpec.run` closure. Reads are plain async: the list
|
|
5
|
-
* body
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* body admits the caller's filter values against the graph's vocabulary, then
|
|
6
|
+
* runs ONE generated SELECT that carries the filters, the search and the page's
|
|
7
|
+
* bounds; the lookup body resolves names → IRIs and fetches per the declared
|
|
8
|
+
* source, gated by the resolved disclosure level; the sample body draws N random
|
|
9
|
+
* entities at the highest level. All store access is through the runtime facade
|
|
10
|
+
* (lazy), so these factories carry no heavy static import.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import { cliRecovery, PragmaError } from "../error/index.js";
|
|
13
14
|
import type { PragmaRuntime } from "../runtime/index.js";
|
|
15
|
+
import { encodeCursor, pageFingerprint, readCursor } from "./cursor.js";
|
|
14
16
|
import { resolvePackDetail } from "./disclosure.js";
|
|
17
|
+
import { readLimit } from "./paging.js";
|
|
15
18
|
import type { SampleOutput } from "./renderPack.js";
|
|
16
19
|
import {
|
|
17
20
|
type LookupOutput,
|
|
21
|
+
type LookupScope,
|
|
18
22
|
listEntityNames,
|
|
19
23
|
resolveLookup,
|
|
20
24
|
} from "./resolveEntity.js";
|
|
21
25
|
import { parseSampleCount, pickRandom } from "./sample.js";
|
|
26
|
+
import { buildListQuery } from "./sparql/buildListQuery.js";
|
|
22
27
|
import {
|
|
23
|
-
applyPackFilters,
|
|
24
28
|
type FilterVocabularies,
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
resolveFilterPredicates,
|
|
30
|
+
} from "./sparql/filterValues.js";
|
|
27
31
|
import { runSelect } from "./sparql/runSelect.js";
|
|
28
|
-
import
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
import { readSearchTerm } from "./sparql/searchTerm.js";
|
|
33
|
+
import {
|
|
34
|
+
resolveReadScope,
|
|
35
|
+
scopeIris,
|
|
36
|
+
scopeLabel,
|
|
37
|
+
type TierScope,
|
|
38
|
+
} from "./tierScope.js";
|
|
39
|
+
import {
|
|
40
|
+
ENTITY_VARIABLE,
|
|
41
|
+
type PackAppliedFilter,
|
|
42
|
+
type PackFilter,
|
|
43
|
+
type PackList,
|
|
44
|
+
type PackLookup,
|
|
45
|
+
type PackPage,
|
|
46
|
+
type PackTierScope,
|
|
47
|
+
type StorySource,
|
|
34
48
|
} from "./types.js";
|
|
35
49
|
|
|
36
50
|
/** The highest canonical level — sample fetches everything for shape discovery. */
|
|
@@ -55,37 +69,152 @@ function listRecovery(noun: string) {
|
|
|
55
69
|
/** Facts a list-shaped run body needs beyond its `shape`. */
|
|
56
70
|
export interface ListRunMeta {
|
|
57
71
|
readonly source: StorySource;
|
|
72
|
+
/**
|
|
73
|
+
* The noun's declared tier hierarchy, when its entities are tiered: the list
|
|
74
|
+
* is then narrowed to a tier scope, compiled into the same query the filters
|
|
75
|
+
* are. Absent leaves the read exactly as it was.
|
|
76
|
+
*/
|
|
77
|
+
readonly tierScope?: PackTierScope;
|
|
58
78
|
}
|
|
59
79
|
|
|
60
80
|
/**
|
|
61
81
|
* Build the run body for a list-shaped verb (`list` or an extra verb).
|
|
62
82
|
*
|
|
63
|
-
* Zero rows is a plain empty list — a SUCCESS, not an error. It returns
|
|
64
|
-
* (JSON stays `[]`, exit 0); the formatter turns the emptiness into
|
|
65
|
-
* message (a pack's `emptyRecovery` becomes that message's hint).
|
|
66
|
-
* results through EMPTY_RESULTS would map to exit 1 and break the
|
|
67
|
-
* `ok:true` list contract, so the run body never throws on emptiness.
|
|
83
|
+
* Zero rows is a plain empty list — a SUCCESS, not an error. It returns an
|
|
84
|
+
* empty page (JSON stays `[]`, exit 0); the formatter turns the emptiness into
|
|
85
|
+
* a non-blank message (a pack's `emptyRecovery` becomes that message's hint).
|
|
86
|
+
* Routing zero results through EMPTY_RESULTS would map to exit 1 and break the
|
|
87
|
+
* uniform `ok:true` list contract, so the run body never throws on emptiness.
|
|
88
|
+
* An empty PAGE is the same calm answer: a caller who walked past the last row
|
|
89
|
+
* asked a legitimate question and got a legitimate nothing.
|
|
90
|
+
*
|
|
91
|
+
* `limit + 1` rows are asked for and at most `limit` returned. That extra row
|
|
92
|
+
* is the whole "are there more" answer — a `COUNT` over the filtered
|
|
93
|
+
* population would be a second full evaluation of the same query to learn one
|
|
94
|
+
* boolean.
|
|
68
95
|
*/
|
|
69
96
|
export function makeListRun(
|
|
70
97
|
shape: PackList,
|
|
71
98
|
meta: ListRunMeta,
|
|
72
|
-
): (params: Record<string, unknown>, rt: PragmaRuntime) => Promise<
|
|
99
|
+
): (params: Record<string, unknown>, rt: PragmaRuntime) => Promise<PackPage> {
|
|
73
100
|
return async (params, rt) => {
|
|
74
|
-
const rows = await runSelect(rt, shape.query, meta.source);
|
|
75
101
|
const vocabularies = await readFilterVocabularies(
|
|
76
102
|
rt,
|
|
77
103
|
shape.filters,
|
|
78
104
|
params,
|
|
79
105
|
meta.source,
|
|
80
106
|
);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
shape.search,
|
|
107
|
+
const predicates = resolveFilterPredicates(
|
|
108
|
+
shape.filters,
|
|
84
109
|
params,
|
|
110
|
+
vocabularies,
|
|
111
|
+
meta.source.label,
|
|
112
|
+
);
|
|
113
|
+
const search = readSearchTerm(shape.search, params);
|
|
114
|
+
const scope = await resolveReadScope(
|
|
115
|
+
rt,
|
|
116
|
+
meta.tierScope,
|
|
117
|
+
params.tier,
|
|
118
|
+
meta.source,
|
|
119
|
+
);
|
|
120
|
+
// The scope as the query builder takes it, or nothing: a story with no
|
|
121
|
+
// declared hierarchy, and a read that asked for every tier, both compile
|
|
122
|
+
// the query they always did.
|
|
123
|
+
const tiers = scope ? scopeIris(scope) : [];
|
|
124
|
+
const listScope =
|
|
125
|
+
meta.tierScope && tiers.length > 0
|
|
126
|
+
? { entity: ENTITY_VARIABLE, via: meta.tierScope.via, tiers }
|
|
127
|
+
: undefined;
|
|
128
|
+
const limit = readLimit(params.limit);
|
|
129
|
+
// The cursor is spendable only on the read that issued it, and the read IS
|
|
130
|
+
// the author query plus the admitted arguments — the tier scope among them.
|
|
131
|
+
// A cursor minted under one scope and spent under another would page
|
|
132
|
+
// through a population it was not cut from, which is the same defect the
|
|
133
|
+
// fingerprint exists to prevent for filters.
|
|
134
|
+
const fingerprint = pageFingerprint([
|
|
135
|
+
shape.query,
|
|
136
|
+
JSON.stringify(predicates),
|
|
137
|
+
JSON.stringify(search ?? null),
|
|
138
|
+
// Only when there IS a scope: the fingerprint names the read, and an
|
|
139
|
+
// unscoped read has no scope to name. Adding an empty one unconditionally
|
|
140
|
+
// would re-hash every cursor every unscoped story has ever issued, for a
|
|
141
|
+
// component that says nothing about them.
|
|
142
|
+
...(listScope ? [JSON.stringify(tiers)] : []),
|
|
143
|
+
]);
|
|
144
|
+
const offset = readCursor(params.after, fingerprint);
|
|
145
|
+
const rows = await runSelect(
|
|
146
|
+
rt,
|
|
147
|
+
buildListQuery({
|
|
148
|
+
query: shape.query,
|
|
149
|
+
predicates,
|
|
150
|
+
...(search ? { search } : {}),
|
|
151
|
+
...(listScope ? { scope: listScope } : {}),
|
|
152
|
+
window: { limit: limit + 1, offset },
|
|
153
|
+
label: meta.source.label,
|
|
154
|
+
}),
|
|
155
|
+
meta.source,
|
|
85
156
|
);
|
|
157
|
+
const hasMore = rows.length > limit;
|
|
158
|
+
const applied = appliedFilters(shape, params, search?.term, scope);
|
|
159
|
+
return {
|
|
160
|
+
rows: hasMore ? rows.slice(0, limit) : rows,
|
|
161
|
+
...(hasMore
|
|
162
|
+
? { nextAfter: encodeCursor(offset + limit, fingerprint) }
|
|
163
|
+
: {}),
|
|
164
|
+
...(applied.length > 0 ? { filters: applied } : {}),
|
|
165
|
+
...(scope?.kind === "tiers"
|
|
166
|
+
? { scope: { tiers: scope.tiers.map((tier) => tier.local) } }
|
|
167
|
+
: {}),
|
|
168
|
+
limit,
|
|
169
|
+
};
|
|
86
170
|
};
|
|
87
171
|
}
|
|
88
172
|
|
|
173
|
+
/**
|
|
174
|
+
* The filters this read was narrowed by, in the spelling the caller used.
|
|
175
|
+
*
|
|
176
|
+
* Read off the DECLARED filters and the search term rather than off the
|
|
177
|
+
* compiled predicates: what an empty answer has to name is the argument a
|
|
178
|
+
* reader typed (`--search zzzznotreal`), not the query variable it bound to.
|
|
179
|
+
* A repeated flag accumulates into an array; it is joined here so the renderer
|
|
180
|
+
* never has to know that.
|
|
181
|
+
*
|
|
182
|
+
* A per-call `--tier` counts as one of them, and a CONFIGURED or default scope
|
|
183
|
+
* does not. The distinction is the one this list exists to draw: these are the
|
|
184
|
+
* arguments a reader TYPED, so an empty answer can hand them back as flags to
|
|
185
|
+
* edit. A scope nobody asked for is reported by the page's own `scope` instead,
|
|
186
|
+
* which every format states in its own register.
|
|
187
|
+
*
|
|
188
|
+
* @param shape - The story's list shape (its declared filters).
|
|
189
|
+
* @param params - The arguments this invocation supplied.
|
|
190
|
+
* @param search - The normalised search term, when the caller supplied one.
|
|
191
|
+
* @param scope - The tier scope in force, when the noun is tiered.
|
|
192
|
+
* @returns One entry per filter in force, in declared order, search last.
|
|
193
|
+
*/
|
|
194
|
+
function appliedFilters(
|
|
195
|
+
shape: PackList,
|
|
196
|
+
params: Record<string, unknown>,
|
|
197
|
+
search: string | undefined,
|
|
198
|
+
scope: TierScope | undefined,
|
|
199
|
+
): PackAppliedFilter[] {
|
|
200
|
+
const applied: PackAppliedFilter[] = [];
|
|
201
|
+
for (const filter of shape.filters ?? []) {
|
|
202
|
+
const provided = params[filter.param];
|
|
203
|
+
if (provided === undefined) continue;
|
|
204
|
+
const value = Array.isArray(provided)
|
|
205
|
+
? provided.map(String).join(", ")
|
|
206
|
+
: String(provided);
|
|
207
|
+
if (value === "") continue;
|
|
208
|
+
applied.push({ param: filter.param, value });
|
|
209
|
+
}
|
|
210
|
+
if (search !== undefined) applied.push({ param: "search", value: search });
|
|
211
|
+
if (scope !== undefined && typeof params.tier === "string") {
|
|
212
|
+
const value = params.tier.trim();
|
|
213
|
+
if (value !== "") applied.push({ param: "tier", value });
|
|
214
|
+
}
|
|
215
|
+
return applied;
|
|
216
|
+
}
|
|
217
|
+
|
|
89
218
|
/**
|
|
90
219
|
* Read the declared vocabulary of each value-free filter the caller actually
|
|
91
220
|
* used.
|
|
@@ -130,12 +259,22 @@ async function readFilterVocabularies(
|
|
|
130
259
|
return resolved;
|
|
131
260
|
}
|
|
132
261
|
|
|
133
|
-
/**
|
|
262
|
+
/**
|
|
263
|
+
* Build the run body for a lookup verb (variadic names → resolved entities).
|
|
264
|
+
*
|
|
265
|
+
* A TIERED noun's lookup carries the scope too, and it is the same scope its
|
|
266
|
+
* list carries — one rule, so `block list` and `block lookup` cannot disagree
|
|
267
|
+
* about which Button a reader means. What differs is what the scope DOES: a
|
|
268
|
+
* list omits what is out of scope, a lookup prefers what is in it and falls
|
|
269
|
+
* back rather than answering "no such block" about a block that exists
|
|
270
|
+
* ({@link ./resolveEntity.applyScope}).
|
|
271
|
+
*/
|
|
134
272
|
export function makeLookupRun(
|
|
135
273
|
lookup: PackLookup,
|
|
136
274
|
noun: string,
|
|
137
275
|
source: StorySource,
|
|
138
276
|
prefixes: Readonly<Record<string, string>>,
|
|
277
|
+
tierScope?: PackTierScope,
|
|
139
278
|
): (
|
|
140
279
|
params: Record<string, unknown>,
|
|
141
280
|
rt: PragmaRuntime,
|
|
@@ -143,6 +282,7 @@ export function makeLookupRun(
|
|
|
143
282
|
return async (params, rt) => {
|
|
144
283
|
const names = readNames(params);
|
|
145
284
|
const level = await resolvePackDetail(rt, lookup.disclosure);
|
|
285
|
+
const scope = await resolveReadScope(rt, tierScope, params.tier, source);
|
|
146
286
|
const output = await resolveLookup(
|
|
147
287
|
rt,
|
|
148
288
|
lookup,
|
|
@@ -151,6 +291,7 @@ export function makeLookupRun(
|
|
|
151
291
|
source,
|
|
152
292
|
prefixes,
|
|
153
293
|
level,
|
|
294
|
+
lookupScope(tierScope, scope),
|
|
154
295
|
);
|
|
155
296
|
// A total miss (single or all-miss) exits non-zero; a partial batch renders
|
|
156
297
|
// the results it found and notes the misses.
|
|
@@ -169,7 +310,36 @@ export function makeLookupRun(
|
|
|
169
310
|
};
|
|
170
311
|
}
|
|
171
312
|
|
|
172
|
-
/**
|
|
313
|
+
/**
|
|
314
|
+
* The resolver's view of a scope, or `undefined` when there is nothing to
|
|
315
|
+
* narrow by.
|
|
316
|
+
*
|
|
317
|
+
* `all` collapses to `undefined` rather than to an empty tier set: a resolver
|
|
318
|
+
* handed an empty set would treat every match as out of scope and report a
|
|
319
|
+
* fallback for a read that asked for every tier.
|
|
320
|
+
*/
|
|
321
|
+
function lookupScope(
|
|
322
|
+
declaration: PackTierScope | undefined,
|
|
323
|
+
scope: TierScope | undefined,
|
|
324
|
+
): LookupScope | undefined {
|
|
325
|
+
if (!declaration || scope === undefined || scope.kind === "all") {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
const label = scopeLabel(scope);
|
|
329
|
+
if (label === undefined) return undefined;
|
|
330
|
+
return { via: declaration.via, tiers: scopeIris(scope), label };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Build the run body for a sample verb (N random entities at the highest level).
|
|
335
|
+
*
|
|
336
|
+
* UNSCOPED, deliberately, and it is the one read of a tiered noun that is. A
|
|
337
|
+
* sample is a shape probe — "show me what a block record looks like" — drawn at
|
|
338
|
+
* the highest disclosure level for exactly that reason, and the tier a specimen
|
|
339
|
+
* happens to belong to is not part of the shape. Narrowing it would also make
|
|
340
|
+
* the draw pool depend on a setting, so two agents reading the same store would
|
|
341
|
+
* see different exemplars for reasons neither asked about.
|
|
342
|
+
*/
|
|
173
343
|
export function makeSampleRun(
|
|
174
344
|
lookup: PackLookup,
|
|
175
345
|
noun: string,
|