@canonical/pragma-cli 0.38.0 → 0.40.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 +1 -1
- package/dist/package.json +14 -14
- package/dist/pragma.conf.js +273 -176
- package/dist/pragma.conf.js.map +1 -1
- package/dist/src/capabilities/capabilities/capabilities.verb.js +2 -0
- package/dist/src/capabilities/capabilities/capabilities.verb.js.map +1 -1
- package/dist/src/capabilities/capabilities/catalog.js +47 -51
- package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
- package/dist/src/capabilities/capabilities/index.js +2 -0
- package/dist/src/capabilities/capabilities/index.js.map +1 -1
- package/dist/src/capabilities/capabilities/types.js +5 -5
- package/dist/src/capabilities/colophon/colophon.render.js +7 -4
- package/dist/src/capabilities/colophon/colophon.render.js.map +1 -1
- package/dist/src/capabilities/colophon/colophon.verb.js +1 -0
- package/dist/src/capabilities/colophon/colophon.verb.js.map +1 -1
- package/dist/src/capabilities/config/get.verb.js +2 -0
- package/dist/src/capabilities/config/get.verb.js.map +1 -1
- package/dist/src/capabilities/config/runField.js +2 -2
- package/dist/src/capabilities/config/runField.js.map +1 -1
- package/dist/src/capabilities/config/set.verb.js +2 -0
- package/dist/src/capabilities/config/set.verb.js.map +1 -1
- package/dist/src/capabilities/config/show.verb.js +1 -0
- package/dist/src/capabilities/config/show.verb.js.map +1 -1
- package/dist/src/capabilities/config/unset.verb.js +2 -0
- package/dist/src/capabilities/config/unset.verb.js.map +1 -1
- package/dist/src/capabilities/create/constants.js +2 -2
- package/dist/src/capabilities/create/create.verb.js +14 -4
- package/dist/src/capabilities/create/create.verb.js.map +1 -1
- package/dist/src/capabilities/distribution.js +3 -1
- package/dist/src/capabilities/distribution.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +10 -2
- package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
- package/dist/src/capabilities/doctor/doctor.verb.js +2 -0
- package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
- package/dist/src/capabilities/graph/connect.verb.js +2 -0
- package/dist/src/capabilities/graph/connect.verb.js.map +1 -1
- package/dist/src/capabilities/graph/inspect.render.js +7 -1
- package/dist/src/capabilities/graph/inspect.render.js.map +1 -1
- package/dist/src/capabilities/graph/inspect.verb.js +2 -0
- package/dist/src/capabilities/graph/inspect.verb.js.map +1 -1
- package/dist/src/capabilities/graph/query.render.js +37 -5
- package/dist/src/capabilities/graph/query.render.js.map +1 -1
- package/dist/src/capabilities/graph/query.verb.js +2 -0
- package/dist/src/capabilities/graph/query.verb.js.map +1 -1
- package/dist/src/capabilities/graph/queryText.js +28 -0
- package/dist/src/capabilities/graph/queryText.js.map +1 -1
- package/dist/src/capabilities/graph/runConnect.js +3 -3
- package/dist/src/capabilities/graph/runConnect.js.map +1 -1
- package/dist/src/capabilities/graph/runQuery.js +8 -6
- package/dist/src/capabilities/graph/runQuery.js.map +1 -1
- package/dist/src/capabilities/info/info.verb.js +2 -0
- package/dist/src/capabilities/info/info.verb.js.map +1 -1
- package/dist/src/capabilities/meta/mcp.verb.js +1 -0
- package/dist/src/capabilities/meta/mcp.verb.js.map +1 -1
- package/dist/src/capabilities/ontology/verbs.js +7 -10
- package/dist/src/capabilities/ontology/verbs.js.map +1 -1
- package/dist/src/capabilities/prompt/verbs.js +5 -4
- package/dist/src/capabilities/prompt/verbs.js.map +1 -1
- package/dist/src/capabilities/resources/provider.js +6 -3
- package/dist/src/capabilities/resources/provider.js.map +1 -1
- package/dist/src/capabilities/setup/setup.verb.js +13 -5
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
- package/dist/src/capabilities/shared/calls.js +16 -0
- package/dist/src/capabilities/shared/calls.js.map +1 -0
- package/dist/src/capabilities/skill/render.js +7 -5
- package/dist/src/capabilities/skill/render.js.map +1 -1
- package/dist/src/capabilities/skill/verbs.js +5 -4
- package/dist/src/capabilities/skill/verbs.js.map +1 -1
- package/dist/src/capabilities/sources/collectStatus.js +6 -1
- package/dist/src/capabilities/sources/collectStatus.js.map +1 -1
- package/dist/src/capabilities/sources/index.js +4 -3
- package/dist/src/capabilities/sources/index.js.map +1 -1
- package/dist/src/capabilities/sources/reset.render.js +37 -0
- package/dist/src/capabilities/sources/reset.render.js.map +1 -0
- package/dist/src/capabilities/sources/reset.verb.js +51 -0
- package/dist/src/capabilities/sources/reset.verb.js.map +1 -0
- package/dist/src/capabilities/sources/runReset.js +59 -0
- package/dist/src/capabilities/sources/runReset.js.map +1 -0
- package/dist/src/capabilities/sources/runUpdate.js +7 -6
- package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
- package/dist/src/capabilities/sources/status.render.js +24 -8
- package/dist/src/capabilities/sources/status.render.js.map +1 -1
- package/dist/src/capabilities/sources/status.verb.js +1 -0
- package/dist/src/capabilities/sources/status.verb.js.map +1 -1
- package/dist/src/capabilities/sources/update.verb.js +2 -0
- package/dist/src/capabilities/sources/update.verb.js.map +1 -1
- package/dist/src/capabilities/upgrade/upgrade.verb.js +1 -0
- package/dist/src/capabilities/upgrade/upgrade.verb.js.map +1 -1
- package/dist/src/capabilities/version/version.verb.js +1 -0
- package/dist/src/capabilities/version/version.verb.js.map +1 -1
- package/dist/src/constants.js +4 -3
- package/dist/src/constants.js.map +1 -1
- package/dist/src/kernel/error/index.js +1 -1
- package/dist/src/kernel/error/index.js.map +1 -1
- package/dist/src/kernel/error/recovery.js +24 -42
- package/dist/src/kernel/error/recovery.js.map +1 -1
- package/dist/src/kernel/packs/collect.js +123 -9
- package/dist/src/kernel/packs/collect.js.map +1 -1
- package/dist/src/kernel/packs/compile.js +74 -15
- package/dist/src/kernel/packs/compile.js.map +1 -1
- package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js +3 -3
- package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -1
- package/dist/src/kernel/packs/iri.js +2 -4
- package/dist/src/kernel/packs/iri.js.map +1 -1
- package/dist/src/kernel/packs/renderPack.js +90 -50
- package/dist/src/kernel/packs/renderPack.js.map +1 -1
- package/dist/src/kernel/packs/resolveEntity.js +202 -90
- package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
- package/dist/src/kernel/packs/runBodies.js +89 -30
- package/dist/src/kernel/packs/runBodies.js.map +1 -1
- package/dist/src/kernel/packs/schema.js +66 -12
- package/dist/src/kernel/packs/schema.js.map +1 -1
- package/dist/src/kernel/packs/sparql/buildListQuery.js +80 -13
- package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -1
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js +41 -13
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
- package/dist/src/kernel/packs/sparql/filterValues.js +32 -23
- package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -1
- package/dist/src/kernel/packs/sparql/runSelect.js +3 -5
- package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
- package/dist/src/kernel/packs/storyRules.js +31 -4
- package/dist/src/kernel/packs/storyRules.js.map +1 -1
- package/dist/src/kernel/packs/tierScope.js +3 -3
- package/dist/src/kernel/packs/tierScope.js.map +1 -1
- package/dist/src/kernel/packs/types.js +4 -0
- package/dist/src/kernel/packs/types.js.map +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js +4 -0
- package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
- package/dist/src/kernel/project/cli/dispatch.js +32 -8
- package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
- package/dist/src/kernel/project/cli/suggestNames.js +44 -1
- package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
- package/dist/src/kernel/project/cli/verbHelp.js +17 -18
- package/dist/src/kernel/project/cli/verbHelp.js.map +1 -1
- package/dist/src/kernel/project/mcp/buildServer.js +11 -4
- package/dist/src/kernel/project/mcp/buildServer.js.map +1 -1
- package/dist/src/kernel/project/mcp/envelope.js +3 -2
- package/dist/src/kernel/project/mcp/envelope.js.map +1 -1
- package/dist/src/kernel/project/mcp/instructions.js +101 -31
- package/dist/src/kernel/project/mcp/instructions.js.map +1 -1
- package/dist/src/kernel/project/mcp/registerVerb.js +59 -23
- package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
- package/dist/src/kernel/render/renderers.js +3 -3
- package/dist/src/kernel/render/turtle.js +21 -4
- package/dist/src/kernel/render/turtle.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/build.js +54 -1
- package/dist/src/kernel/runtime/graphpack/build.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/manifest.js +14 -0
- package/dist/src/kernel/runtime/graphpack/manifest.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/read.js +3 -5
- package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/schemas.js +1 -0
- package/dist/src/kernel/runtime/graphpack/schemas.js.map +1 -1
- package/dist/src/kernel/runtime/loadSession.js +8 -1
- package/dist/src/kernel/runtime/loadSession.js.map +1 -1
- package/dist/src/kernel/runtime/packVersion.js +75 -0
- package/dist/src/kernel/runtime/packVersion.js.map +1 -0
- package/dist/src/kernel/runtime/readEntity.js +43 -4
- package/dist/src/kernel/runtime/readEntity.js.map +1 -1
- package/dist/src/kernel/runtime/resolveSources.js +61 -9
- package/dist/src/kernel/runtime/resolveSources.js.map +1 -1
- package/dist/src/kernel/runtime/storeReadiness.js +3 -2
- package/dist/src/kernel/runtime/storeReadiness.js.map +1 -1
- package/dist/src/kernel/spec/call.js +167 -0
- package/dist/src/kernel/spec/call.js.map +1 -0
- package/dist/src/kernel/spec/emitReference.js +9 -13
- package/dist/src/kernel/spec/emitReference.js.map +1 -1
- package/dist/src/kernel/spec/emitSurface.js +8 -1
- package/dist/src/kernel/spec/emitSurface.js.map +1 -1
- package/dist/src/kernel/spec/guidance.js +63 -0
- package/dist/src/kernel/spec/guidance.js.map +1 -0
- package/dist/src/kernel/spec/wireType.js +36 -0
- package/dist/src/kernel/spec/wireType.js.map +1 -0
- package/package.json +14 -14
- package/pragma.conf.ts +294 -177
- package/src/capabilities/block.parity.test.ts +1 -1
- package/src/capabilities/callRule.test.ts +147 -0
- package/src/capabilities/capabilities/capabilities.test.ts +47 -19
- package/src/capabilities/capabilities/capabilities.verb.ts +2 -0
- package/src/capabilities/capabilities/catalog.ts +60 -55
- package/src/capabilities/capabilities/index.ts +2 -0
- package/src/capabilities/capabilities/types.ts +16 -19
- package/src/capabilities/colophon/colophon.render.ts +9 -5
- package/src/capabilities/colophon/colophon.verb.ts +1 -0
- package/src/capabilities/config/get.verb.ts +2 -0
- package/src/capabilities/config/runField.ts +3 -3
- package/src/capabilities/config/set.verb.ts +3 -0
- package/src/capabilities/config/show.verb.ts +2 -0
- package/src/capabilities/config/unset.verb.ts +2 -0
- package/src/capabilities/create/constants.ts +2 -2
- package/src/capabilities/create/create.verb.ts +16 -0
- package/src/capabilities/create/createGrammar.test.ts +1 -1
- package/src/capabilities/distribution.test.ts +6 -3
- package/src/capabilities/distribution.ts +6 -0
- package/src/capabilities/doctor/checks/checkPackageRefs.ts +14 -2
- package/src/capabilities/doctor/doctor.test.ts +38 -1
- package/src/capabilities/doctor/doctor.verb.ts +2 -0
- package/src/capabilities/emptyRecovery.test.ts +5 -0
- package/src/capabilities/graph/connect.verb.ts +2 -0
- package/src/capabilities/graph/inspect.render.ts +9 -1
- package/src/capabilities/graph/inspect.test.ts +73 -0
- package/src/capabilities/graph/inspect.verb.ts +2 -0
- package/src/capabilities/graph/query.render.test.ts +35 -0
- package/src/capabilities/graph/query.render.ts +45 -7
- package/src/capabilities/graph/query.shipped.exec.test.ts +44 -0
- package/src/capabilities/graph/query.verb.ts +2 -0
- package/src/capabilities/graph/queryText.test.ts +55 -1
- package/src/capabilities/graph/queryText.ts +47 -0
- package/src/capabilities/graph/runConnect.ts +4 -5
- package/src/capabilities/graph/runQuery.ts +23 -9
- package/src/capabilities/info/info.verb.ts +2 -0
- package/src/capabilities/links.shipped.exec.test.ts +398 -0
- package/src/capabilities/listBudget.shipped.exec.test.ts +2 -2
- package/src/capabilities/listLookup.shipped.exec.test.ts +122 -13
- package/src/capabilities/listQuery.shipped.exec.test.ts +39 -16
- package/src/capabilities/lookupBudget.shipped.exec.test.ts +208 -0
- package/src/capabilities/meta/mcp.verb.ts +2 -0
- package/src/capabilities/ontology/ontology.test.ts +24 -0
- package/src/capabilities/ontology/verbs.ts +16 -12
- package/src/capabilities/prompt/verbs.ts +10 -4
- package/src/capabilities/resources/provider.ts +7 -3
- package/src/capabilities/resources/resources.test.ts +2 -1
- package/src/capabilities/setup/setup.verb.ts +16 -0
- package/src/capabilities/shared/calls.ts +20 -0
- package/src/capabilities/skill/render.ts +9 -6
- package/src/capabilities/skill/verbs.ts +8 -4
- package/src/capabilities/sources/collectStatus.ts +6 -1
- package/src/capabilities/sources/index.ts +4 -3
- package/src/capabilities/sources/reset.render.test.ts +33 -0
- package/src/capabilities/sources/reset.render.ts +43 -0
- package/src/capabilities/sources/reset.verb.ts +59 -0
- package/src/capabilities/sources/runReset.ts +65 -0
- package/src/capabilities/sources/runUpdate.ts +12 -11
- package/src/capabilities/sources/sources.test.ts +158 -3
- package/src/capabilities/sources/status.render.test.ts +63 -1
- package/src/capabilities/sources/status.render.ts +25 -8
- package/src/capabilities/sources/status.verb.ts +2 -0
- package/src/capabilities/sources/types.ts +26 -0
- package/src/capabilities/sources/update.verb.ts +3 -0
- package/src/capabilities/surface.test.ts +2 -2
- package/src/capabilities/token.parity.test.ts +3 -2
- package/src/capabilities/tokenGraph.shipped.exec.test.ts +14 -2
- package/src/capabilities/upgrade/upgrade.verb.ts +1 -0
- package/src/capabilities/variable.parity.test.ts +2 -1
- package/src/capabilities/version/version.verb.ts +1 -0
- package/src/capabilities/wireContract.test.ts +110 -0
- package/src/constants.ts +4 -3
- package/src/kernel/copy.test.ts +1 -1
- package/src/kernel/error/PragmaError.test.ts +7 -3
- package/src/kernel/error/index.ts +1 -1
- package/src/kernel/error/recovery.test.ts +52 -18
- package/src/kernel/error/recovery.ts +25 -46
- package/src/kernel/error/types.ts +1 -1
- package/src/kernel/packs/collect.test.ts +79 -0
- package/src/kernel/packs/collect.ts +151 -11
- package/src/kernel/packs/compile.test.ts +107 -10
- package/src/kernel/packs/compile.ts +100 -11
- package/src/kernel/packs/filteredEmpty.test.ts +22 -5
- package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +5 -6
- package/src/kernel/packs/graphqlSource.test.ts +42 -0
- package/src/kernel/packs/iri.ts +5 -4
- package/src/kernel/packs/listNotice.test.ts +1 -1
- package/src/kernel/packs/nounLink.test.ts +385 -0
- package/src/kernel/packs/renderPack.ts +137 -57
- package/src/kernel/packs/resolveEntity.test.ts +170 -1
- package/src/kernel/packs/resolveEntity.ts +281 -101
- package/src/kernel/packs/runBodies.ts +133 -31
- package/src/kernel/packs/schema.ts +77 -12
- package/src/kernel/packs/sparql/buildListQuery.test.ts +78 -1
- package/src/kernel/packs/sparql/buildListQuery.ts +106 -16
- package/src/kernel/packs/sparql/buildLookupQuery.ts +44 -13
- package/src/kernel/packs/sparql/filterValues.test.ts +36 -4
- package/src/kernel/packs/sparql/filterValues.ts +50 -31
- package/src/kernel/packs/sparql/runSelect.ts +4 -6
- package/src/kernel/packs/storyRules.ts +37 -4
- package/src/kernel/packs/tierScope.test.ts +120 -6
- package/src/kernel/packs/tierScope.ts +5 -7
- package/src/kernel/packs/toolDescription.test.ts +59 -74
- package/src/kernel/packs/types.ts +93 -11
- package/src/kernel/project/cli/buildProgram.ts +4 -0
- package/src/kernel/project/cli/dispatch.ts +34 -8
- package/src/kernel/project/cli/suggest.test.ts +77 -0
- package/src/kernel/project/cli/suggestNames.ts +47 -1
- package/src/kernel/project/cli/verbHelp.ts +16 -19
- package/src/kernel/project/mcp/buildServer.ts +15 -4
- package/src/kernel/project/mcp/envelope.ts +3 -2
- package/src/kernel/project/mcp/instructions.ts +123 -35
- package/src/kernel/project/mcp/prompts/prompts.test.ts +136 -20
- package/src/kernel/project/mcp/registerVerb.test.ts +28 -0
- package/src/kernel/project/mcp/registerVerb.ts +67 -29
- package/src/kernel/render/__snapshots__/renderers.test.ts.snap +1 -1
- package/src/kernel/render/renderers.test.ts +2 -2
- package/src/kernel/render/renderers.ts +3 -3
- package/src/kernel/render/turtle.ts +24 -4
- package/src/kernel/runtime/graphpack/build.ts +58 -1
- package/src/kernel/runtime/graphpack/graphpack.test.ts +20 -0
- package/src/kernel/runtime/graphpack/manifest.ts +15 -0
- package/src/kernel/runtime/graphpack/read.ts +4 -6
- package/src/kernel/runtime/graphpack/schemas.ts +1 -0
- package/src/kernel/runtime/graphpack/types.ts +13 -0
- package/src/kernel/runtime/loadSession.ts +8 -1
- package/src/kernel/runtime/packVersion.test.ts +61 -0
- package/src/kernel/runtime/packVersion.ts +84 -0
- package/src/kernel/runtime/readEntity.ts +61 -6
- package/src/kernel/runtime/resolveSources.test.ts +90 -7
- package/src/kernel/runtime/resolveSources.ts +91 -10
- package/src/kernel/runtime/storeReadiness.ts +4 -4
- package/src/kernel/runtime/types.ts +12 -0
- package/src/kernel/spec/call.test.ts +95 -0
- package/src/kernel/spec/call.ts +186 -0
- package/src/kernel/spec/emitReference.ts +9 -13
- package/src/kernel/spec/emitSurface.ts +9 -1
- package/src/kernel/spec/guidance.test.ts +49 -0
- package/src/kernel/spec/guidance.ts +68 -0
- package/src/kernel/spec/index.ts +4 -0
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/types.ts +64 -7
- package/src/kernel/spec/wireType.ts +53 -0
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +2 -1
- package/src/testing/behavioral/journeys.firstInstall.test.ts +72 -2
- package/src/testing/behavioral/parity.test.ts +40 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +1 -1
- package/src/testing/eval/cases/assembly.ts +4 -3
- package/src/testing/eval/cases/stable.ts +2 -3
- package/src/testing/helpers/callSchema.ts +40 -0
- package/src/testing/helpers/parity.ts +35 -2
- package/src/testing/setupCallChecking.ts +30 -0
- package/dist/src/capabilities/capabilities/hints.js +0 -223
- package/dist/src/capabilities/capabilities/hints.js.map +0 -1
- package/src/capabilities/capabilities/hints.ts +0 -266
|
@@ -17,9 +17,12 @@
|
|
|
17
17
|
* defect class is not standard-specific, since every pair shares this
|
|
18
18
|
* machinery. Three guarantees per noun:
|
|
19
19
|
*
|
|
20
|
-
* - every
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* - every IDENTIFIER `list` publishes resolves through `lookup`, in every form
|
|
21
|
+
* a row is printed in — the name, the prefixed IRI the plain and condensed
|
|
22
|
+
* formats print, and the absolute IRI JSON carries (executed through the
|
|
23
|
+
* real verb bodies, so glob/IRI argument-shape dispatch is exercised with
|
|
24
|
+
* the very strings an agent would paste). One case per form, so a failure
|
|
25
|
+
* names the spelling that broke;
|
|
23
26
|
* - every published name is in the addressable population (`listEntityNames`
|
|
24
27
|
* — the ONE pool that feeds miss-suggestions, glob expansion AND `sample`'s
|
|
25
28
|
* draw), so a suggestion can never offer a name the resolve cannot answer;
|
|
@@ -44,6 +47,7 @@ import {
|
|
|
44
47
|
type PackPage,
|
|
45
48
|
} from "../kernel/packs/types.js";
|
|
46
49
|
import { verbKey } from "../kernel/packs/uniqueness.js";
|
|
50
|
+
import { compactUri, DEFAULT_PREFIX_MAP } from "../kernel/render/index.js";
|
|
47
51
|
import { bootRuntime } from "../kernel/runtime/boot.js";
|
|
48
52
|
import type { PragmaRuntime } from "../kernel/runtime/types.js";
|
|
49
53
|
import type { CapabilityModule, VerbSpec } from "../kernel/spec/types.js";
|
|
@@ -96,7 +100,19 @@ function verbOf(
|
|
|
96
100
|
}
|
|
97
101
|
|
|
98
102
|
/**
|
|
99
|
-
*
|
|
103
|
+
* The forms a list row's identifier is printed in, by the format that prints
|
|
104
|
+
* it. Whatever a reader copies out of a list has to be an address.
|
|
105
|
+
*/
|
|
106
|
+
const FORMS = ["name", "prefixed IRI", "absolute IRI"] as const;
|
|
107
|
+
type Form = (typeof FORMS)[number];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Run `<noun> list` and return every identifier it publishes, per form,
|
|
111
|
+
* verbatim, deduped.
|
|
112
|
+
*
|
|
113
|
+
* The prefixed IRI is compacted against the map the distribution's stories are
|
|
114
|
+
* compiled with (`distribution.ts`), so it is the string the plain and
|
|
115
|
+
* condensed renderers print — not a second opinion about how to shorten it.
|
|
100
116
|
*
|
|
101
117
|
* WALKS THE PAGES, and it has to. This file's promise is the round trip over the
|
|
102
118
|
* WHOLE corpus rather than a sample, and that used to come free: every declared
|
|
@@ -111,13 +127,15 @@ function verbOf(
|
|
|
111
127
|
* a large corpus, and an unbounded `while` over a broken cursor would hang the
|
|
112
128
|
* suite instead of failing it.
|
|
113
129
|
*/
|
|
114
|
-
async function
|
|
130
|
+
async function publishedIdentifiers(
|
|
115
131
|
rt: PragmaRuntime,
|
|
116
132
|
module: CapabilityModule,
|
|
117
133
|
noun: string,
|
|
118
|
-
): Promise<string[]
|
|
134
|
+
): Promise<Record<Form, string[]>> {
|
|
119
135
|
const verb = verbOf(module, noun, "list");
|
|
120
136
|
const names = new Set<string>();
|
|
137
|
+
const prefixed = new Set<string>();
|
|
138
|
+
const absolute = new Set<string>();
|
|
121
139
|
let after: string | undefined;
|
|
122
140
|
let pages = 0;
|
|
123
141
|
do {
|
|
@@ -128,6 +146,10 @@ async function publishedNames(
|
|
|
128
146
|
for (const row of page.rows) {
|
|
129
147
|
const name = row.name ?? "";
|
|
130
148
|
if (name !== "") names.add(name);
|
|
149
|
+
const uri = row.uri ?? "";
|
|
150
|
+
if (uri === "") continue;
|
|
151
|
+
absolute.add(uri);
|
|
152
|
+
prefixed.add(compactUri(uri, DEFAULT_PREFIX_MAP));
|
|
131
153
|
}
|
|
132
154
|
after = page.nextAfter;
|
|
133
155
|
pages += 1;
|
|
@@ -137,7 +159,20 @@ async function publishedNames(
|
|
|
137
159
|
);
|
|
138
160
|
}
|
|
139
161
|
} while (after !== undefined);
|
|
140
|
-
return
|
|
162
|
+
return {
|
|
163
|
+
name: [...names],
|
|
164
|
+
"prefixed IRI": [...prefixed],
|
|
165
|
+
"absolute IRI": [...absolute],
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Every NAME `<noun> list` publishes — the form the pool guarantees read. */
|
|
170
|
+
async function publishedNames(
|
|
171
|
+
rt: PragmaRuntime,
|
|
172
|
+
module: CapabilityModule,
|
|
173
|
+
noun: string,
|
|
174
|
+
): Promise<string[]> {
|
|
175
|
+
return (await publishedIdentifiers(rt, module, noun)).name;
|
|
141
176
|
}
|
|
142
177
|
|
|
143
178
|
/**
|
|
@@ -197,11 +232,11 @@ describe("every list-published name resolves through lookup, whole corpus (PROTE
|
|
|
197
232
|
expect(NOUNS).toContain("block");
|
|
198
233
|
});
|
|
199
234
|
|
|
200
|
-
it.each(NOUNS.
|
|
201
|
-
"%s: every published
|
|
202
|
-
async (noun) => {
|
|
235
|
+
it.each(NOUNS.flatMap((noun) => FORMS.map((form) => [noun, form] as const)))(
|
|
236
|
+
"%s: every published %s round-trips, and its corpus is not silently empty",
|
|
237
|
+
async (noun, form) => {
|
|
203
238
|
const module = storyModules.get(noun) as CapabilityModule;
|
|
204
|
-
const names = await
|
|
239
|
+
const names = (await publishedIdentifiers(rt, module, noun))[form];
|
|
205
240
|
if (EMPTY_CORPUS_TODAY.includes(noun)) {
|
|
206
241
|
// Asserted empty, not skipped: this is what expires the allowlist.
|
|
207
242
|
expect(
|
|
@@ -210,16 +245,18 @@ describe("every list-published name resolves through lookup, whole corpus (PROTE
|
|
|
210
245
|
"delete it from EMPTY_CORPUS_TODAY so a later regression to zero rows cannot hide behind the entry",
|
|
211
246
|
).toEqual([]);
|
|
212
247
|
} else {
|
|
248
|
+
// Held for every form: each shipped list projects its IRI column, and
|
|
249
|
+
// an IRI case that passed over zero IRIs would prove nothing.
|
|
213
250
|
expect(
|
|
214
251
|
names.length,
|
|
215
|
-
`${noun} list published no
|
|
252
|
+
`${noun} list published no ${form} at all from the shipped pack`,
|
|
216
253
|
).toBeGreaterThan(0);
|
|
217
254
|
}
|
|
218
255
|
|
|
219
256
|
const errors = await lookupErrors(rt, module, noun, names);
|
|
220
257
|
expect(
|
|
221
258
|
errors,
|
|
222
|
-
`${noun} list published ${errors.length}
|
|
259
|
+
`${noun} list published ${errors.length} ${form}(s) its own lookup cannot resolve — ` +
|
|
223
260
|
"the two-step grammar the tool descriptions document is broken for every agent following it",
|
|
224
261
|
).toEqual([]);
|
|
225
262
|
},
|
|
@@ -305,6 +342,78 @@ describe("every list-published name resolves through lookup, whole corpus (PROTE
|
|
|
305
342
|
);
|
|
306
343
|
});
|
|
307
344
|
|
|
345
|
+
describe("an IRI's local name is one step from an address (shipped pack)", () => {
|
|
346
|
+
/** A published block IRI of the `tier.component.name` shape, and its local name. */
|
|
347
|
+
async function sampleIri(): Promise<{ prefixed: string; local: string }> {
|
|
348
|
+
const module = storyModules.get("block") as CapabilityModule;
|
|
349
|
+
const { "prefixed IRI": prefixed } = await publishedIdentifiers(
|
|
350
|
+
rt,
|
|
351
|
+
module,
|
|
352
|
+
"block",
|
|
353
|
+
);
|
|
354
|
+
const chosen = prefixed.find((iri) => iri.includes(".component.")) ?? "";
|
|
355
|
+
expect(chosen).not.toBe("");
|
|
356
|
+
return { prefixed: chosen, local: chosen.replace(/^[^:]*:/, "") };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
it("a wildcard over a local name reaches its entity", async () => {
|
|
360
|
+
const { prefixed, local } = await sampleIri();
|
|
361
|
+
const module = storyModules.get("block") as CapabilityModule;
|
|
362
|
+
const output = (await verbOf(module, "block", "lookup").run(
|
|
363
|
+
{ name: [`*.${local.split(".").slice(1).join(".")}`] },
|
|
364
|
+
rt,
|
|
365
|
+
)) as { results: { uri: string }[]; errors: unknown[] };
|
|
366
|
+
|
|
367
|
+
expect(output.errors).toEqual([]);
|
|
368
|
+
expect(
|
|
369
|
+
output.results.map((entity) =>
|
|
370
|
+
compactUri(entity.uri, DEFAULT_PREFIX_MAP),
|
|
371
|
+
),
|
|
372
|
+
).toContain(prefixed);
|
|
373
|
+
}, 60_000);
|
|
374
|
+
|
|
375
|
+
it("a bare local name misses, and the miss leads with the prefixed form", async () => {
|
|
376
|
+
const { prefixed, local } = await sampleIri();
|
|
377
|
+
const module = storyModules.get("block") as CapabilityModule;
|
|
378
|
+
const reason = await Promise.resolve(
|
|
379
|
+
verbOf(module, "block", "lookup").run({ name: [local] }, rt),
|
|
380
|
+
).catch((error: unknown) => error);
|
|
381
|
+
|
|
382
|
+
expect(reason).toMatchObject({ code: "ENTITY_NOT_FOUND" });
|
|
383
|
+
expect((reason as { suggestions: string[] }).suggestions.at(0)).toBe(
|
|
384
|
+
prefixed,
|
|
385
|
+
);
|
|
386
|
+
}, 60_000);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe("every story example answers from the shipped pack", () => {
|
|
390
|
+
// An example is copied verbatim by the agents it is shown to, so a name the
|
|
391
|
+
// graph has since renamed is a miss handed out in advance.
|
|
392
|
+
const examples = [...storyModules.values()].flatMap((module) =>
|
|
393
|
+
module.verbs
|
|
394
|
+
.filter((verb) => verb.example !== undefined)
|
|
395
|
+
.map((verb) => [verbKey(verb.path), verb] as const),
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
it.each(examples)(
|
|
399
|
+
"%s",
|
|
400
|
+
async (_label, verb) => {
|
|
401
|
+
const answer = (await verb.run({ ...verb.example }, rt)) as {
|
|
402
|
+
rows?: unknown[];
|
|
403
|
+
results?: unknown[];
|
|
404
|
+
errors?: unknown[];
|
|
405
|
+
};
|
|
406
|
+
expect(answer.errors ?? []).toEqual([]);
|
|
407
|
+
const named = verb.example?.name;
|
|
408
|
+
if (Array.isArray(named)) {
|
|
409
|
+
expect(answer.results?.length).toBeGreaterThanOrEqual(named.length);
|
|
410
|
+
}
|
|
411
|
+
if (answer.rows) expect(answer.rows.length).toBeGreaterThan(0);
|
|
412
|
+
},
|
|
413
|
+
60_000,
|
|
414
|
+
);
|
|
415
|
+
});
|
|
416
|
+
|
|
308
417
|
describe("negative control: the pre-#1047 shape fails these assertions", () => {
|
|
309
418
|
// The exact divergence that shipped: a list that COALESCEs an OPTIONAL
|
|
310
419
|
// display name with an IRI-derived one, over a lookup that requires the
|
|
@@ -34,6 +34,7 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
|
34
34
|
import { MAX_LISTED_OPTIONS } from "../kernel/error/validOptions.js";
|
|
35
35
|
import { compileStoryModule } from "../kernel/packs/compile.js";
|
|
36
36
|
import { MAX_LIST_WINDOW } from "../kernel/packs/paging.js";
|
|
37
|
+
import { listEntityNames } from "../kernel/packs/resolveEntity.js";
|
|
37
38
|
import {
|
|
38
39
|
distributionSource,
|
|
39
40
|
type PackFilter,
|
|
@@ -47,7 +48,7 @@ import type { PragmaRuntime } from "../kernel/runtime/types.js";
|
|
|
47
48
|
import { kebabCase } from "../kernel/spec/emitSurface.js";
|
|
48
49
|
import type { VerbSpec } from "../kernel/spec/types.js";
|
|
49
50
|
import { TEST_FLAGS } from "../testing/helpers/projectCli.js";
|
|
50
|
-
import { declaredStories } from "./distribution.js";
|
|
51
|
+
import { declaredLookups, declaredStories } from "./distribution.js";
|
|
51
52
|
|
|
52
53
|
const SOURCE = distributionSource("pragma.conf.ts");
|
|
53
54
|
|
|
@@ -65,7 +66,7 @@ interface Body {
|
|
|
65
66
|
*/
|
|
66
67
|
const BODIES: readonly Body[] = [...declaredStories].flatMap(
|
|
67
68
|
([noun, story]) => {
|
|
68
|
-
const module = compileStoryModule(story, SOURCE, {});
|
|
69
|
+
const module = compileStoryModule(story, SOURCE, {}, declaredLookups);
|
|
69
70
|
const find = (verb: string): VerbSpec => {
|
|
70
71
|
const found = module.verbs.find(
|
|
71
72
|
(v) => verbKey(v.path) === `${noun} ${verb}`,
|
|
@@ -119,9 +120,14 @@ async function population(body: Body): Promise<readonly PackRow[]> {
|
|
|
119
120
|
return (await page(body, { limit: MAX_LIST_WINDOW })).rows;
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* The values a value-free filter admits: its declared vocabulary, or — for a
|
|
125
|
+
* filter naming a noun — every name that noun's lookup resolves.
|
|
126
|
+
*/
|
|
123
127
|
async function vocabulary(filter: PackFilter): Promise<readonly string[]> {
|
|
124
128
|
if (filter.values) return filter.values;
|
|
129
|
+
const named = filter.noun ? declaredLookups(filter.noun) : undefined;
|
|
130
|
+
if (named) return listEntityNames(rt, named, SOURCE);
|
|
125
131
|
const declared = filter.vocabulary;
|
|
126
132
|
if (!declared) throw new Error(`filter --${filter.param} declares neither`);
|
|
127
133
|
const result = await rt.query.sparql(declared.query);
|
|
@@ -275,9 +281,15 @@ describe("the compiled query agrees with the retired row predicate (PROTECTED)",
|
|
|
275
281
|
[filter.param]: value,
|
|
276
282
|
limit: MAX_LIST_WINDOW,
|
|
277
283
|
});
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
284
|
+
const expected = rowPredicate(whole, filter, value);
|
|
285
|
+
// A `via` filter constrains an entity the cell only PRINTS one name
|
|
286
|
+
// of, so a name the cell does not print may still answer; whatever
|
|
287
|
+
// the cell does print must answer exactly.
|
|
288
|
+
if (filter.via === undefined || expected.length > 0) {
|
|
289
|
+
expect(JSON.stringify(answered.rows)).toBe(
|
|
290
|
+
JSON.stringify(expected),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
281
293
|
}
|
|
282
294
|
}
|
|
283
295
|
},
|
|
@@ -351,13 +363,21 @@ describe("a refusal and a calm empty answer stay distinguishable (PROTECTED)", (
|
|
|
351
363
|
"%s: a value the vocabulary does not admit is INVALID_INPUT, naming the ones it does",
|
|
352
364
|
async (_label, body) => {
|
|
353
365
|
for (const filter of body.shape.filters ?? []) {
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
page(body, { [filter.param]: "zzz-definitely-not-a-value" }),
|
|
357
|
-
).rejects.toMatchObject({
|
|
358
|
-
code: "INVALID_INPUT",
|
|
359
|
-
validOptions: [...admissible].sort(),
|
|
366
|
+
const refusal = page(body, {
|
|
367
|
+
[filter.param]: "zzz-definitely-not-a-value",
|
|
360
368
|
});
|
|
369
|
+
// A noun has too many names to list: its miss says so by name.
|
|
370
|
+
await expect(refusal).rejects.toMatchObject(
|
|
371
|
+
filter.noun
|
|
372
|
+
? {
|
|
373
|
+
code: "INVALID_INPUT",
|
|
374
|
+
message: `No ${filter.noun} is named "zzz-definitely-not-a-value".`,
|
|
375
|
+
}
|
|
376
|
+
: {
|
|
377
|
+
code: "INVALID_INPUT",
|
|
378
|
+
validOptions: [...(await vocabulary(filter))].sort(),
|
|
379
|
+
},
|
|
380
|
+
);
|
|
361
381
|
}
|
|
362
382
|
},
|
|
363
383
|
60_000,
|
|
@@ -374,6 +394,8 @@ describe("a refusal and a calm empty answer stay distinguishable (PROTECTED)", (
|
|
|
374
394
|
for (const body of NARROWABLE) {
|
|
375
395
|
const whole = await population(body);
|
|
376
396
|
for (const filter of body.shape.filters ?? []) {
|
|
397
|
+
// The cell is no oracle for emptiness under a `via` filter (above).
|
|
398
|
+
if (filter.via !== undefined) continue;
|
|
377
399
|
for (const value of sweep(await vocabulary(filter))) {
|
|
378
400
|
if (rowPredicate(whole, filter, value).length === 0) {
|
|
379
401
|
unpopulated.push({ body, param: filter.param, value });
|
|
@@ -389,11 +411,12 @@ describe("a refusal and a calm empty answer stay distinguishable (PROTECTED)", (
|
|
|
389
411
|
// And the calm sentence rides the same seam it always did — naming the
|
|
390
412
|
// filter that narrowed to nothing, since a filtered empty answer is a
|
|
391
413
|
// statement about the filter and not about the population.
|
|
414
|
+
// In the flag's own spelling: a story's `channelOf` param is typed
|
|
415
|
+
// `--channel-of`, and an answer that named the param would be naming
|
|
416
|
+
// something no caller can pass. A value with a space in it is quoted.
|
|
417
|
+
const shown = /\s/.test(value) ? `'${value}'` : value;
|
|
392
418
|
expect(body.verb.output.formatters.notice?.(answered as never)).toContain(
|
|
393
|
-
|
|
394
|
-
// `--channel-of`, and an answer that named the param would be naming
|
|
395
|
-
// something no caller can pass.
|
|
396
|
-
`matches \`--${kebabCase(param)} ${value}\`.`,
|
|
419
|
+
`matches \`--${kebabCase(param)} ${shown}\`.`,
|
|
397
420
|
);
|
|
398
421
|
}
|
|
399
422
|
}, 120_000);
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The response-size budget for a LOOKUP and for `graph inspect`, at every
|
|
3
|
+
* disclosure level, measured over the SHIPPED pack (PROTECTED).
|
|
4
|
+
*
|
|
5
|
+
* The list budget beside this file bounds what a browse costs. A read of one
|
|
6
|
+
* entity had no bound at all, and two recorded answers are why it has one now:
|
|
7
|
+
* `token_lookup { name: ["color.*"] }` answered with 473 entities and 460 KB,
|
|
8
|
+
* and `graph_inspect` of one block with 41 KB — neither with any way to be
|
|
9
|
+
* asked for less, since most lookups declared no `detail`.
|
|
10
|
+
*
|
|
11
|
+
* So three things are held here, each over the whole shipped corpus and each
|
|
12
|
+
* derived from the configuration rather than listed:
|
|
13
|
+
*
|
|
14
|
+
* - the LARGEST single entity a lookup answers with, per level — `summary` is
|
|
15
|
+
* the cheap level by measurement rather than by name;
|
|
16
|
+
* - the widest possible pattern (`*`), which the expansion cap has to hold to
|
|
17
|
+
* a bounded answer on every noun;
|
|
18
|
+
* - the largest `graph inspect` of a block, per level.
|
|
19
|
+
*
|
|
20
|
+
* WHERE IT IS ENFORCED, and why bytes of the re-serialised JSON: exactly as the
|
|
21
|
+
* list budget does it, for the reasons given there.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
25
|
+
import { DETAIL_LEVELS, type DetailLevel } from "../constants.js";
|
|
26
|
+
import { MAX_LIST_WINDOW } from "../kernel/packs/paging.js";
|
|
27
|
+
import {
|
|
28
|
+
GLOB_EXPANSION_CAP,
|
|
29
|
+
type LookupOutput,
|
|
30
|
+
} from "../kernel/packs/resolveEntity.js";
|
|
31
|
+
import type { PackPage } from "../kernel/packs/types.js";
|
|
32
|
+
import { verbKey } from "../kernel/packs/uniqueness.js";
|
|
33
|
+
import { bootRuntime } from "../kernel/runtime/boot.js";
|
|
34
|
+
import type { PragmaRuntime } from "../kernel/runtime/types.js";
|
|
35
|
+
import type { CapabilityModule, VerbSpec } from "../kernel/spec/types.js";
|
|
36
|
+
import { TEST_FLAGS } from "../testing/helpers/projectCli.js";
|
|
37
|
+
import { declaredStories, storyModules } from "./distribution.js";
|
|
38
|
+
import { graphModule } from "./graph/index.js";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The ceiling on ONE looked-up entity, in bytes, per level.
|
|
42
|
+
*
|
|
43
|
+
* **Derived from measurement, 2026-09-17**, as the largest entity of any noun
|
|
44
|
+
* at that level: 2,425 bytes at `summary` (a code standard), 25,282 at
|
|
45
|
+
* `standard` (a concept, whose Markdown body arrives there) and 70,097 at
|
|
46
|
+
* `detailed` (a block). Each ceiling is the measurement × 1.5, rounded — the
|
|
47
|
+
* list budget's shape of derivation — so ordinary upstream growth in a pack
|
|
48
|
+
* does not turn this red on a bump, and a lookup whose `summary` quietly grows
|
|
49
|
+
* an expand does. Per noun, `summary` measured: token 548 (4,174 at
|
|
50
|
+
* `detailed`), variable 393 (4,854), tier 101 (3,847), modifier 103 (203),
|
|
51
|
+
* concept 763 (25,302), block 2,312 (70,097), standard 2,425 (6,622).
|
|
52
|
+
*/
|
|
53
|
+
const ENTITY_BUDGET_BYTES: Readonly<Record<DetailLevel, number>> = {
|
|
54
|
+
summary: 3_700,
|
|
55
|
+
standard: 38_000,
|
|
56
|
+
detailed: 105_000,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The ceiling on the answer to the widest pattern a noun can be handed.
|
|
61
|
+
*
|
|
62
|
+
* It moves with {@link GLOB_EXPANSION_CAP} and with how fat a noun's entities
|
|
63
|
+
* are. Measured 2026-09-17: `block` at 22,592 bytes (`summary`) and 206,809
|
|
64
|
+
* (`detailed`), × 1.5, rounded. Full table in BUDGETS.md.
|
|
65
|
+
*/
|
|
66
|
+
const GLOB_BUDGET_BYTES: Readonly<Record<"summary" | "detailed", number>> = {
|
|
67
|
+
summary: 34_000,
|
|
68
|
+
detailed: 310_000,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The ceiling on ONE `graph inspect` of a block, in bytes, per level.
|
|
73
|
+
*
|
|
74
|
+
* **Derived from measurement, 2026-09-17**, over every block the shipped pack
|
|
75
|
+
* lists: 5,785 bytes at `summary`, 20,551 at `standard`, 72,258 at `detailed`;
|
|
76
|
+
* × 1.5, rounded. `detailed` is the uncut neighbourhood by design, so its
|
|
77
|
+
* ceiling records a size rather than enforcing a cut; `summary` and `standard`
|
|
78
|
+
* are the levels the outbound cap holds (the global Button read 28,713 at
|
|
79
|
+
* `standard` before it, 18,173 after).
|
|
80
|
+
*/
|
|
81
|
+
const INSPECT_BUDGET_BYTES: Readonly<Record<DetailLevel, number>> = {
|
|
82
|
+
summary: 8_700,
|
|
83
|
+
standard: 31_000,
|
|
84
|
+
detailed: 108_000,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** Every noun with a lookup, derived from the config. */
|
|
88
|
+
const NOUNS: readonly string[] = [...declaredStories]
|
|
89
|
+
.filter(([, story]) => story.lookup !== undefined)
|
|
90
|
+
.map(([noun]) => noun);
|
|
91
|
+
|
|
92
|
+
let rt: PragmaRuntime;
|
|
93
|
+
beforeAll(async () => {
|
|
94
|
+
rt = bootRuntime(TEST_FLAGS);
|
|
95
|
+
await rt.store.get();
|
|
96
|
+
}, 60_000);
|
|
97
|
+
afterAll(async () => {
|
|
98
|
+
(await rt.store.get()).store.dispose();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
/** The runtime one call at `detail` sees — what `--detail`/`detail` seed. */
|
|
102
|
+
const at = (detail: DetailLevel): PragmaRuntime => ({
|
|
103
|
+
...rt,
|
|
104
|
+
globalFlags: { ...rt.globalFlags, detail },
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/** Bytes of a value as either machine surface re-serialises it. */
|
|
108
|
+
const bytesOf = (value: unknown): number =>
|
|
109
|
+
Buffer.byteLength(JSON.stringify(value));
|
|
110
|
+
|
|
111
|
+
function verbOf(noun: string, verb: string): VerbSpec {
|
|
112
|
+
const module = storyModules.get(noun) as CapabilityModule;
|
|
113
|
+
const found = module.verbs.find((v) => verbKey(v.path) === `${noun} ${verb}`);
|
|
114
|
+
if (!found) throw new Error(`no compiled "${noun} ${verb}" verb`);
|
|
115
|
+
return found;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The argument that lifts a tiered noun's scope, and nothing for the rest: the
|
|
120
|
+
* budget is over the whole corpus, not over the default scope's part of it.
|
|
121
|
+
*/
|
|
122
|
+
const everyTier = (noun: string): Record<string, unknown> =>
|
|
123
|
+
declaredStories.get(noun)?.tierScope ? { tier: "all" } : {};
|
|
124
|
+
|
|
125
|
+
/** Every entity a noun's lookup can be handed, by IRI (none when it has no list). */
|
|
126
|
+
async function population(noun: string): Promise<string[]> {
|
|
127
|
+
if (declaredStories.get(noun)?.list === undefined) return [];
|
|
128
|
+
const page = (await verbOf(noun, "list").run(
|
|
129
|
+
{ limit: MAX_LIST_WINDOW, ...everyTier(noun) },
|
|
130
|
+
rt,
|
|
131
|
+
)) as PackPage;
|
|
132
|
+
return [...new Set(page.rows.map((row) => row.uri ?? ""))].filter(Boolean);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
describe("lookup response budget, shipped pack (PROTECTED)", () => {
|
|
136
|
+
it("derives every lookup from the config", () => {
|
|
137
|
+
expect(NOUNS).toContain("token");
|
|
138
|
+
expect(NOUNS.length).toBeGreaterThanOrEqual(7);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it.each(
|
|
142
|
+
NOUNS.flatMap((noun) =>
|
|
143
|
+
DETAIL_LEVELS.map((level) => [noun, level] as const),
|
|
144
|
+
),
|
|
145
|
+
)(
|
|
146
|
+
"%s lookup at %s: the largest entity is inside the budget",
|
|
147
|
+
async (noun, level) => {
|
|
148
|
+
const names = await population(noun);
|
|
149
|
+
expect(names.length).toBeGreaterThan(0);
|
|
150
|
+
const output = (await verbOf(noun, "lookup").run(
|
|
151
|
+
{ name: names, ...everyTier(noun) },
|
|
152
|
+
at(level),
|
|
153
|
+
)) as LookupOutput;
|
|
154
|
+
const largest = Math.max(...output.results.map(bytesOf));
|
|
155
|
+
expect(output.errors).toEqual([]);
|
|
156
|
+
expect(largest).toBeLessThanOrEqual(ENTITY_BUDGET_BYTES[level]);
|
|
157
|
+
},
|
|
158
|
+
120_000,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
it.each(
|
|
162
|
+
NOUNS.flatMap((noun) =>
|
|
163
|
+
(["summary", "detailed"] as const).map((level) => [noun, level] as const),
|
|
164
|
+
),
|
|
165
|
+
)(
|
|
166
|
+
"%s lookup '*' at %s: the widest pattern is cut, and inside the budget",
|
|
167
|
+
async (noun, level) => {
|
|
168
|
+
const verb = verbOf(noun, "lookup");
|
|
169
|
+
const output = (await verb.run(
|
|
170
|
+
{ name: ["*"], ...everyTier(noun) },
|
|
171
|
+
at(level),
|
|
172
|
+
)) as LookupOutput;
|
|
173
|
+
const bytes = bytesOf(JSON.parse(verb.output.formatters.json(output)));
|
|
174
|
+
// Cut exactly when there was more than the cap to cut.
|
|
175
|
+
const entities = (await population(noun)).length;
|
|
176
|
+
expect(output.truncated === true).toBe(entities > GLOB_EXPANSION_CAP);
|
|
177
|
+
expect(output.results.length).toBe(
|
|
178
|
+
Math.min(entities, GLOB_EXPANSION_CAP),
|
|
179
|
+
);
|
|
180
|
+
expect(bytes).toBeLessThanOrEqual(GLOB_BUDGET_BYTES[level]);
|
|
181
|
+
},
|
|
182
|
+
120_000,
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("graph inspect response budget, shipped pack (PROTECTED)", () => {
|
|
187
|
+
const inspect = graphModule.verbs.find(
|
|
188
|
+
(v) => verbKey(v.path) === "graph inspect",
|
|
189
|
+
) as VerbSpec;
|
|
190
|
+
|
|
191
|
+
it.each(DETAIL_LEVELS.map((level) => [level] as const))(
|
|
192
|
+
"at %s: the largest block neighbourhood is inside the budget",
|
|
193
|
+
async (level) => {
|
|
194
|
+
const blocks = await population("block");
|
|
195
|
+
expect(blocks.length).toBeGreaterThan(0);
|
|
196
|
+
let largest = 0;
|
|
197
|
+
for (const uri of blocks) {
|
|
198
|
+
const result = await inspect.run({ uri }, at(level));
|
|
199
|
+
largest = Math.max(
|
|
200
|
+
largest,
|
|
201
|
+
bytesOf(JSON.parse(inspect.output.formatters.json(result as never))),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
expect(largest).toBeLessThanOrEqual(INSPECT_BUDGET_BYTES[level]);
|
|
205
|
+
},
|
|
206
|
+
120_000,
|
|
207
|
+
);
|
|
208
|
+
});
|
|
@@ -16,6 +16,8 @@ import type { VerbSpec } from "../../kernel/spec/index.js";
|
|
|
16
16
|
export const mcpVerb: VerbSpec<Record<string, unknown>, void> = {
|
|
17
17
|
path: ["mcp", "serve"],
|
|
18
18
|
summary: "Start the MCP server over stdio.",
|
|
19
|
+
useWhen:
|
|
20
|
+
"when an MCP client needs to launch this server; people rarely run it by hand",
|
|
19
21
|
params: [],
|
|
20
22
|
output: {
|
|
21
23
|
formatters: {
|
|
@@ -126,6 +126,30 @@ describe("ontology lookup (primary by-name read)", () => {
|
|
|
126
126
|
/prefix/i,
|
|
127
127
|
);
|
|
128
128
|
});
|
|
129
|
+
|
|
130
|
+
// Each refusal ends in the same next call, in both spellings.
|
|
131
|
+
const LIST_RECOVERY = {
|
|
132
|
+
cli: "pragma ontology list",
|
|
133
|
+
mcp: { tool: "ontology_list", params: {} },
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
it("an unbound namespace URI recovers to the namespace list", async () => {
|
|
137
|
+
await expect(
|
|
138
|
+
lookupVerb.run({ prefix: "https://nope.example/" }, rt),
|
|
139
|
+
).rejects.toMatchObject({
|
|
140
|
+
code: "INVALID_INPUT",
|
|
141
|
+
recovery: LIST_RECOVERY,
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("a class the namespace does not define recovers to the namespace list", async () => {
|
|
146
|
+
await expect(
|
|
147
|
+
lookupVerb.run({ prefix: "ds", class: "NoSuchClass" }, rt),
|
|
148
|
+
).rejects.toMatchObject({
|
|
149
|
+
code: "ENTITY_NOT_FOUND",
|
|
150
|
+
recovery: LIST_RECOVERY,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
129
153
|
});
|
|
130
154
|
|
|
131
155
|
describe("ontology_lookup honours detail over MCP (B5)", () => {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { BIN_NAME } from "../../constants.js";
|
|
25
|
-
import {
|
|
25
|
+
import { callRecovery, PragmaError } from "../../kernel/error/index.js";
|
|
26
26
|
import { resolvePackDetail } from "../../kernel/packs/disclosure.js";
|
|
27
27
|
import type { PragmaRuntime } from "../../kernel/runtime/index.js";
|
|
28
28
|
import { asVerb } from "../../kernel/spec/asVerb.js";
|
|
@@ -51,12 +51,9 @@ function resolvePrefix(
|
|
|
51
51
|
if (entry) return { prefix: entry[0], namespace: entry[1] };
|
|
52
52
|
throw PragmaError.invalidInput("namespace", input, {
|
|
53
53
|
validOptions: Object.values(prefixes),
|
|
54
|
-
recovery:
|
|
55
|
-
"ontology list",
|
|
54
|
+
recovery: callRecovery(
|
|
55
|
+
{ verb: "ontology list" },
|
|
56
56
|
"List loaded ontology namespaces.",
|
|
57
|
-
{
|
|
58
|
-
tool: "ontology_list",
|
|
59
|
-
},
|
|
60
57
|
),
|
|
61
58
|
});
|
|
62
59
|
}
|
|
@@ -64,9 +61,10 @@ function resolvePrefix(
|
|
|
64
61
|
if (namespace === undefined) {
|
|
65
62
|
throw PragmaError.invalidInput("prefix", input, {
|
|
66
63
|
validOptions: Object.keys(prefixes),
|
|
67
|
-
recovery:
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
recovery: callRecovery(
|
|
65
|
+
{ verb: "ontology list" },
|
|
66
|
+
"List loaded ontologies.",
|
|
67
|
+
),
|
|
70
68
|
});
|
|
71
69
|
}
|
|
72
70
|
return { prefix: input, namespace };
|
|
@@ -75,6 +73,8 @@ function resolvePrefix(
|
|
|
75
73
|
const listVerb: VerbSpec<Record<string, unknown>, OntologySummary[]> = {
|
|
76
74
|
path: ["ontology", "list"],
|
|
77
75
|
summary: "List loaded ontology namespaces with class and property counts.",
|
|
76
|
+
useWhen:
|
|
77
|
+
"when asked which vocabularies (namespaces and their prefixes) the data uses — the first step before writing a raw query",
|
|
78
78
|
params: [],
|
|
79
79
|
output: { formatters: ontologyListFormatters },
|
|
80
80
|
examples: [{ cmd: `${BIN_NAME} ontology list` }],
|
|
@@ -192,9 +192,10 @@ async function runByName(
|
|
|
192
192
|
|
|
193
193
|
if (classes.length === 0 && properties.length === 0) {
|
|
194
194
|
throw PragmaError.notFound("ontology", String(params.prefix), {
|
|
195
|
-
recovery:
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
recovery: callRecovery(
|
|
196
|
+
{ verb: "ontology list" },
|
|
197
|
+
"List loaded ontologies.",
|
|
198
|
+
),
|
|
198
199
|
});
|
|
199
200
|
}
|
|
200
201
|
|
|
@@ -215,6 +216,9 @@ export const ontologyListVerb = asVerb(listVerb);
|
|
|
215
216
|
export const ontologyLookupVerb = asVerb({
|
|
216
217
|
path: ["ontology", "lookup"],
|
|
217
218
|
summary: "Look up a namespace's classes (hierarchy + counts) and properties.",
|
|
219
|
+
useWhen:
|
|
220
|
+
"before writing a raw query, to find the real class and property names under one prefix",
|
|
221
|
+
example: { prefix: "ds" },
|
|
218
222
|
params: BY_NAME_PARAMS,
|
|
219
223
|
output: { formatters: ontologyShowFormatters },
|
|
220
224
|
examples: [
|