@warpgogol/werkstatt-shared 0.13.0 → 0.14.1
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 +26 -16
- package/README.uk.md +24 -14
- package/package.json +27 -69
- package/src/checks/result-helpers.ts +21 -4
- package/src/checks/tests/remediation-auto-populate.test.ts +83 -0
- package/src/integration/funnel.ts +78 -1
- package/src/integration/index.ts +19 -2
- package/src/integration/lagebild-ingress.ts +169 -0
- package/src/integration/orchestration.ts +7 -13
- package/src/integration/port-barrel.ts +9 -28
- package/src/integration/tests/funnel-bridge.test.ts +7 -7
- package/src/integration/tests/lagebild-ingress.test.ts +118 -0
- package/src/integration/vitest.config.ts +7 -0
- package/src/observability/typed-refs.ts +1 -1
- package/src/ontology/analytics/matomo-fleet.registry.yaml +1 -1
- package/src/ontology/archetypes/index.json +12 -9
- package/src/ontology/archetypes/index.yaml +26 -10
- package/src/ontology/blueprints/website-local.yaml +11 -11
- package/src/ontology/blueprints/website-service.yaml +20 -20
- package/src/ontology/capabilities/lead.submit.yaml +2 -2
- package/src/ontology/enums.ts +2 -2
- package/src/ontology/external-surfaces/jsonld-types.yaml +14 -14
- package/src/ontology/external-surfaces/url-schema.yaml +1 -1
- package/src/ontology/index.ts +2 -0
- package/src/ontology/manifest.ts +19 -0
- package/src/ontology/schemas/index.ts +7 -2
- package/src/ontology/schemas/system/integrations.ts +1 -1
- package/src/ontology/schemas/system/manifest.ts +38 -1
- package/src/ontology/schemas/system/tests/growth.test.ts +22 -0
- package/src/ontology/schemas/system/tests/integrations.test.ts +12 -0
- package/src/ontology/schemas/system/tests/manifest.test.ts +22 -0
- package/src/ontology/schemas/system/tests/page-output.test.ts +13 -0
- package/src/ontology/schemas/system/tests/release.test.ts +22 -0
- package/src/ontology/schemas/system/tests/text.test.ts +18 -0
- package/src/ontology/schemas/system/tests/verification.test.ts +12 -0
- package/src/ontology/schemas/system.ts +7 -2
- package/src/ontology/schemas/tests/biome.test.ts +19 -0
- package/src/ontology/schemas/tests/capability.test.ts +22 -0
- package/src/ontology/schemas/tests/constellation.test.ts +22 -0
- package/src/ontology/schemas/tests/dns-records.test.ts +13 -0
- package/src/ontology/schemas/tests/manifest-resolver.test.ts +13 -0
- package/src/ontology/schemas/tests/page-entry.test.ts +22 -0
- package/src/ontology/schemas/tests/section-archetype.test.ts +12 -0
- package/src/ontology/schemas/tests/site-family.test.ts +18 -0
- package/src/ontology/schemas/tests/system.test.ts +20 -0
- package/src/ontology/shared-section-props/common.ts +11 -0
- package/src/ontology/shared-section-props/visual-header.ts +1 -1
- package/src/share/agent/api-catalog.ts +5 -0
- package/src/share/agent/ard-catalog.ts +14 -0
- package/src/share/agent/index.ts +2 -0
- package/src/share/agent/manifest.ts +25 -2
- package/src/share/agent/mcp-card.ts +4 -0
- package/src/share/agent/openapi.ts +78 -0
- package/src/share/agent/search.test.ts +188 -0
- package/src/share/agent/search.ts +164 -0
- package/src/share/agent/tests/api-catalog.test.ts +8 -0
- package/src/share/agent/tests/ard-catalog.test.ts +8 -0
- package/src/share/agent/tests/capability.test.ts +14 -0
- package/src/share/agent/tests/fleet-catalog.test.ts +15 -0
- package/src/share/agent/tests/knowledge.test.ts +20 -0
- package/src/share/agent/tests/manifest.test.ts +9 -0
- package/src/share/agent/tests/mcp-card.test.ts +8 -0
- package/src/share/agent/tests/omit-empty.test.ts +33 -0
- package/src/share/agent/tests/openapi.test.ts +8 -0
- package/src/share/agent/tests/proof.test.ts +15 -0
- package/src/share/env.d.ts +0 -4
- package/src/share/knowledge/tests/claim.test.ts +11 -0
- package/src/share/knowledge/tests/derivation.test.ts +30 -0
- package/src/share/knowledge/tests/freshness.test.ts +18 -0
- package/src/share/knowledge/tests/ledger.test.ts +35 -0
- package/src/share/knowledge/tests/plan.test.ts +19 -0
- package/src/share/knowledge/tests/source.test.ts +19 -0
- package/src/share/remediation/remediation-catalog.ts +196 -0
- package/src/share/schemas/effects.ts +16 -0
- package/src/share/schemas/media.ts +4 -0
- package/src/share/schemas/section-shell.ts +2 -1
- package/src/share/schemas/tests/claim-records.test.ts +52 -0
- package/src/share/schemas/tests/claims.test.ts +23 -0
- package/src/share/schemas/tests/effects.test.ts +116 -0
- package/src/share/schemas/tests/features.test.ts +61 -0
- package/src/share/schemas/tests/horizontal-align.test.ts +16 -0
- package/src/share/schemas/tests/icon-color.test.ts +16 -0
- package/src/share/schemas/tests/live-photo.test.ts +40 -0
- package/src/share/schemas/tests/material-credit.test.ts +51 -0
- package/src/share/schemas/tests/media.test.ts +26 -0
- package/src/share/schemas/tests/navigation.test.ts +58 -0
- package/src/share/schemas/tests/page-base.test.ts +19 -0
- package/src/share/schemas/tests/participant.test.ts +26 -0
- package/src/share/schemas/tests/person.test.ts +12 -0
- package/src/share/schemas/tests/print.test.ts +30 -0
- package/src/share/schemas/tests/section-background.test.ts +31 -0
- package/src/share/schemas/tests/section-body.test.ts +16 -0
- package/src/share/schemas/tests/section-cards.test.ts +17 -0
- package/src/share/schemas/tests/section-cta.test.ts +41 -0
- package/src/share/schemas/tests/section-header.test.ts +31 -0
- package/src/share/schemas/tests/section-image.test.ts +24 -0
- package/src/share/schemas/tests/section-motion.test.ts +29 -0
- package/src/share/schemas/tests/section-shell.test.ts +42 -0
- package/src/share/schemas/tests/section-stats.test.ts +17 -0
- package/src/share/schemas/tests/site-background.test.ts +33 -0
- package/src/share/schemas/tests/standard-list-item.test.ts +16 -0
- package/src/share/scripts/external-link-qr.ts +9 -1
- package/src/share/scripts/gsap-counter.ts +2 -0
- package/src/share/scripts/inline-number-animation.ts +2 -0
- package/src/share/scripts/live-photos.ts +2 -0
- package/src/share/scripts/tests/external-link-qr.test.ts +8 -0
- package/src/share/scripts/tests/external-links.test.ts +25 -0
- package/src/share/scripts/tests/gsap-counter.test.ts +8 -0
- package/src/share/scripts/tests/gsap-mountain-journey.test.ts +8 -0
- package/src/share/scripts/tests/gsap-parallax.test.ts +8 -0
- package/src/share/scripts/tests/gsap-reveal.test.ts +8 -0
- package/src/share/scripts/tests/gsap-stagger.test.ts +8 -0
- package/src/share/scripts/tests/inline-number-animation.test.ts +8 -0
- package/src/share/scripts/tests/lenis.test.ts +8 -0
- package/src/share/scripts/tests/live-photos.test.ts +8 -0
- package/src/share/scripts/tests/lordicon.test.ts +8 -0
- package/src/share/scripts/tests/orchestrator.test.ts +8 -0
- package/src/share/scripts/tests/scheduler.test.ts +23 -0
- package/src/share/scripts/video-player.test.ts +210 -0
- package/src/share/scripts/video-player.ts +73 -13
- package/src/share/semantic/extract.ts +25 -1
- package/src/share/semantic/jsonld/tests/article.test.ts +89 -0
- package/src/share/semantic/jsonld/tests/breadcrumb.test.ts +62 -0
- package/src/share/semantic/jsonld/tests/collection-list.test.ts +39 -0
- package/src/share/semantic/jsonld/tests/faq.test.ts +60 -0
- package/src/share/semantic/jsonld/tests/initiative.test.ts +56 -0
- package/src/share/semantic/jsonld/tests/organization.test.ts +123 -0
- package/src/share/semantic/jsonld/tests/person.test.ts +89 -0
- package/src/share/semantic/jsonld/tests/service.test.ts +65 -0
- package/src/share/semantic/jsonld/tests/shared.test.ts +41 -0
- package/src/share/semantic/jsonld/tests/video.test.ts +84 -0
- package/src/share/semantic/jsonld/tests/webpage.test.ts +93 -0
- package/src/share/semantic/jsonld/tests/website.test.ts +27 -0
- package/src/share/semantic/jsonld/types.ts +1 -0
- package/src/share/semantic/models.ts +1 -0
- package/src/share/semantic/tests/ai.test.ts +55 -0
- package/src/share/semantic/tests/block-extraction.test.ts +34 -0
- package/src/share/semantic/tests/breadcrumbs.test.ts +127 -0
- package/src/share/semantic/tests/build-page.test.ts +122 -0
- package/src/share/semantic/tests/business-projection.test.ts +188 -0
- package/src/share/semantic/tests/derived-prices-loader.test.ts +46 -0
- package/src/share/semantic/tests/extract.test.ts +126 -0
- package/src/share/semantic/tests/feed.test.ts +113 -0
- package/src/share/semantic/tests/helpers.ts +32 -0
- package/src/share/semantic/tests/ids.test.ts +118 -0
- package/src/share/semantic/tests/image-sitemap.test.ts +109 -0
- package/src/share/semantic/tests/jsonld.test.ts +61 -0
- package/src/share/semantic/tests/llms-policy.test.ts +60 -0
- package/src/share/semantic/tests/llms.test.ts +118 -0
- package/src/share/semantic/tests/markdown-hygiene.test.ts +72 -0
- package/src/share/semantic/tests/markdown-page.test.ts +97 -0
- package/src/share/semantic/tests/markdown-twin-provenance.test.ts +152 -0
- package/src/share/semantic/tests/organization-profile.test.ts +107 -0
- package/src/share/semantic/tests/output-projection.test.ts +77 -0
- package/src/share/semantic/tests/page-markdown.test.ts +98 -0
- package/src/share/semantic/tests/page-utils.test.ts +88 -0
- package/src/share/semantic/tests/price-marker-resolver.test.ts +88 -0
- package/src/share/semantic/tests/robots.test.ts +70 -0
- package/src/share/semantic/tests/split-sentences.test.ts +57 -0
- package/src/share/semantic/tests/update-stamp.test.ts +102 -0
- package/src/share/tests/manifest.test.ts +51 -0
- package/src/share/tests/openapi.test.ts +30 -0
- package/src/share/types/qrcode-browser.d.ts +18 -0
- package/src/share/utility-registry.yaml +16 -2
- package/src/integration/crm-buffer.ts +0 -491
- package/src/integration-adapter-supabase-crm/adapter.ts +0 -437
- package/src/integration-adapter-supabase-crm/client.ts +0 -605
- package/src/integration-adapter-supabase-crm/index.ts +0 -28
- package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +0 -482
- package/src/integration-adapter-supabase-crm/supabase-rest.ts +0 -89
- package/src/integration-adapter-supabase-crm/tenant-registry.ts +0 -334
- package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +0 -83
- package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +0 -409
- package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +0 -288
- package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +0 -80
- package/src/integration-adapter-supabase-crm/vitest.config.ts +0 -8
- package/src/integration-adapter-supabase-crm/worker.ts +0 -223
- package/src/ontology/archetypes/components/currency-selector.yaml +0 -19
|
@@ -155,6 +155,17 @@ export const EFFECT_STACK_ITEM_SCHEMA = {
|
|
|
155
155
|
skewX: { type: "number", minimum: -15, maximum: 15 },
|
|
156
156
|
},
|
|
157
157
|
},
|
|
158
|
+
{
|
|
159
|
+
type: "object",
|
|
160
|
+
additionalProperties: false,
|
|
161
|
+
required: ["kind", "enabled"],
|
|
162
|
+
properties: {
|
|
163
|
+
kind: { const: "lighten" },
|
|
164
|
+
enabled: { type: "boolean" },
|
|
165
|
+
opacity: { type: "number", minimum: 0, maximum: 1 },
|
|
166
|
+
color: EFFECT_COLOR_SCHEMA,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
158
169
|
],
|
|
159
170
|
};
|
|
160
171
|
|
|
@@ -88,7 +88,7 @@ export const SECTION_VISUAL_FRAGMENT: JsonSchemaFragment = {
|
|
|
88
88
|
},
|
|
89
89
|
},
|
|
90
90
|
},
|
|
91
|
-
density: { enum: ["compact", "normal", "spacious"] },
|
|
91
|
+
density: { enum: ["compact", "normal", "spacious", "flush"] },
|
|
92
92
|
tone: { enum: ["default", "warning", "success", "muted"] },
|
|
93
93
|
containerVariant: { enum: ["default", "narrow", "full"] },
|
|
94
94
|
motion: {
|
|
@@ -12,6 +12,7 @@ No I/O — the kernel command loads the manifest and passes it here.
|
|
|
12
12
|
</MODULE_CONTRACT>
|
|
13
13
|
<CHANGE_SUMMARY>
|
|
14
14
|
<item>RFC-0783: initial API Catalog linkset+json projection.</item>
|
|
15
|
+
<item>RFC-0954: add search endpoint link when interfaces.search is non-null.</item>
|
|
15
16
|
</CHANGE_SUMMARY>
|
|
16
17
|
*/
|
|
17
18
|
|
|
@@ -71,6 +72,10 @@ export function buildApiCatalog(manifest: AgentSurfaceManifest): ApiCatalog {
|
|
|
71
72
|
entry["service"] = [{ href: manifest.interfaces.mcp.url, type: "application/json" }];
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
if (manifest.interfaces.search) {
|
|
76
|
+
entry["search"] = [{ href: manifest.interfaces.search.url, type: "application/json" }];
|
|
77
|
+
}
|
|
78
|
+
|
|
74
79
|
entry["service-doc"] = [{ href: manifest.interfaces.llms, type: "text/plain" }];
|
|
75
80
|
|
|
76
81
|
return { linkset: [entry] };
|
|
@@ -105,6 +105,20 @@ export function buildArdCatalog(
|
|
|
105
105
|
],
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
// Semantic search endpoint entry
|
|
109
|
+
if (manifest.interfaces.search) {
|
|
110
|
+
entries.push({
|
|
111
|
+
identifier: `urn:air:${domain}:api:search`,
|
|
112
|
+
displayName: "Semantic Search",
|
|
113
|
+
type: "application/json",
|
|
114
|
+
url: manifest.interfaces.search.url,
|
|
115
|
+
representativeQueries: [
|
|
116
|
+
"Search this site for relevant content",
|
|
117
|
+
"Find pages matching a natural language query",
|
|
118
|
+
],
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
108
122
|
// Knowledge domain entries
|
|
109
123
|
for (const ref of manifest.knowledge) {
|
|
110
124
|
entries.push({
|
package/src/share/agent/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ here (knowledge, capability, openapi, proof) and re-export them from this barrel
|
|
|
14
14
|
<item>RFC-0292: re-export fleet-catalog module.</item>
|
|
15
15
|
<item>RFC-0783: re-export api-catalog and mcp-card modules.</item>
|
|
16
16
|
<item>RFC-0786: re-export dns-aid module.</item>
|
|
17
|
+
<item>RFC-0954: re-export search module.</item>
|
|
17
18
|
</CHANGE_SUMMARY>
|
|
18
19
|
*/
|
|
19
20
|
|
|
@@ -28,3 +29,4 @@ export * from "./api-catalog.ts";
|
|
|
28
29
|
export * from "./mcp-card.ts";
|
|
29
30
|
export * from "./dns-aid.ts";
|
|
30
31
|
export * from "./ard-catalog.ts";
|
|
32
|
+
export * from "./search.ts";
|
|
@@ -14,6 +14,7 @@ two runs on unchanged input are byte-identical (AS-7).
|
|
|
14
14
|
</MODULE_CONTRACT>
|
|
15
15
|
<CHANGE_SUMMARY>
|
|
16
16
|
<item>RFC-0286: initial manifest contract and pure builder.</item>
|
|
17
|
+
<item>RFC-0954: add search interface to AgentSurfaceManifest.interfaces.</item>
|
|
17
18
|
</CHANGE_SUMMARY>
|
|
18
19
|
*/
|
|
19
20
|
|
|
@@ -54,6 +55,7 @@ export interface AgentSurfaceManifest {
|
|
|
54
55
|
twins: { pattern: string } | null;
|
|
55
56
|
openapi: string | null;
|
|
56
57
|
mcp: { url: string; protocolVersion: string } | null;
|
|
58
|
+
search: { url: string; model: string; dimensions: number } | null;
|
|
57
59
|
};
|
|
58
60
|
/** Null until agent.surface.sign signs it (RFC-0308) or when no key material exists. */
|
|
59
61
|
proof: AgentSurfaceProof | null;
|
|
@@ -68,6 +70,7 @@ export interface AgentSurfaceManifestInput {
|
|
|
68
70
|
hasTwins?: boolean;
|
|
69
71
|
openapiUrl?: string | null;
|
|
70
72
|
mcp?: { url: string; protocolVersion: string } | null;
|
|
73
|
+
search?: { url: string; model: string; dimensions: number } | null;
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
// ---------------------------------------------------------------------------
|
|
@@ -91,10 +94,29 @@ export function canonicalJson(value: unknown): string {
|
|
|
91
94
|
return JSON.stringify(sortKeysDeep(value));
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
/**
|
|
98
|
+
* sha256 hex over the canonical JSON of `doc` with `contentHash`, `proof`, and any
|
|
99
|
+
* additional `excludeFields` removed. All signed manifest hash functions MUST use
|
|
100
|
+
* this helper to ensure the `proof` field is always excluded — a missing exclusion
|
|
101
|
+
* causes contentHash drift after signing (AGS-SEARCH-07 pattern).
|
|
102
|
+
*/
|
|
103
|
+
export function computeSignedContentHash(
|
|
104
|
+
doc: Record<string, unknown>,
|
|
105
|
+
excludeFields: string[] = [],
|
|
106
|
+
): string {
|
|
107
|
+
const exclusions = new Set(["contentHash", "proof", ...excludeFields]);
|
|
108
|
+
const rest: Record<string, unknown> = {};
|
|
109
|
+
for (const [key, value] of Object.entries(doc)) {
|
|
110
|
+
if (!exclusions.has(key)) {
|
|
111
|
+
rest[key] = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return createHash("sha256").update(canonicalJson(rest), "utf8").digest("hex");
|
|
115
|
+
}
|
|
116
|
+
|
|
94
117
|
/** sha256 hex over the canonical JSON of `doc` with `contentHash` and `proof` removed. */
|
|
95
118
|
export function computeAgentManifestContentHash(doc: Record<string, unknown>): string {
|
|
96
|
-
|
|
97
|
-
return createHash("sha256").update(canonicalJson(rest), "utf8").digest("hex");
|
|
119
|
+
return computeSignedContentHash(doc);
|
|
98
120
|
}
|
|
99
121
|
|
|
100
122
|
// ---------------------------------------------------------------------------
|
|
@@ -118,6 +140,7 @@ export function buildAgentSurfaceManifest(input: AgentSurfaceManifestInput): Age
|
|
|
118
140
|
twins: input.hasTwins ? { pattern: "/**.md" } : null,
|
|
119
141
|
openapi: input.openapiUrl ?? null,
|
|
120
142
|
mcp: input.mcp ?? null,
|
|
143
|
+
search: input.search ?? null,
|
|
121
144
|
},
|
|
122
145
|
proof: null,
|
|
123
146
|
};
|
|
@@ -12,6 +12,7 @@ No I/O — the kernel command loads the manifest and passes it here.
|
|
|
12
12
|
</MODULE_CONTRACT>
|
|
13
13
|
<CHANGE_SUMMARY>
|
|
14
14
|
<item>RFC-0783: initial MCP Server Card projection.</item>
|
|
15
|
+
<item>RFC-0954: include search tool in serverInfo description when interfaces.search is non-null.</item>
|
|
15
16
|
</CHANGE_SUMMARY>
|
|
16
17
|
*/
|
|
17
18
|
|
|
@@ -48,6 +49,9 @@ export function buildMcpServerCard(manifest: AgentSurfaceManifest): McpServerCar
|
|
|
48
49
|
serverInfo: {
|
|
49
50
|
name: `${manifest.site}-agent-gate`,
|
|
50
51
|
version: manifest.surfaceVersion,
|
|
52
|
+
...(manifest.interfaces.search
|
|
53
|
+
? { description: "Agent gate with MCP tools and semantic search" }
|
|
54
|
+
: {}),
|
|
51
55
|
},
|
|
52
56
|
transport: {
|
|
53
57
|
type: "streamable-http",
|
|
@@ -15,6 +15,7 @@ copied verbatim (AGO-04 enforces this).
|
|
|
15
15
|
</MODULE_CONTRACT>
|
|
16
16
|
<CHANGE_SUMMARY>
|
|
17
17
|
<item>RFC-0289: initial OpenAPI projection.</item>
|
|
18
|
+
<item>RFC-0954: add /api/agent/search path with dynamic lang enum from manifest.languages.supported.</item>
|
|
18
19
|
</CHANGE_SUMMARY>
|
|
19
20
|
*/
|
|
20
21
|
|
|
@@ -33,6 +34,13 @@ export interface OpenApiOperation {
|
|
|
33
34
|
operationId: string;
|
|
34
35
|
summary?: string;
|
|
35
36
|
tags: string[];
|
|
37
|
+
parameters?: Array<{
|
|
38
|
+
name: string;
|
|
39
|
+
in: string;
|
|
40
|
+
required?: boolean;
|
|
41
|
+
schema: Record<string, unknown>;
|
|
42
|
+
description?: string;
|
|
43
|
+
}>;
|
|
36
44
|
requestBody?: {
|
|
37
45
|
required: true;
|
|
38
46
|
content: { "application/json": { schema: OpenApiSchemaObject } };
|
|
@@ -136,6 +144,76 @@ export function formatAgentOpenApi(
|
|
|
136
144
|
};
|
|
137
145
|
}
|
|
138
146
|
|
|
147
|
+
if (manifest.interfaces.search) {
|
|
148
|
+
const searchSchema: OpenApiSchemaObject = {
|
|
149
|
+
type: "object",
|
|
150
|
+
required: ["query", "results", "tookMs"],
|
|
151
|
+
properties: {
|
|
152
|
+
query: { type: "string" },
|
|
153
|
+
results: {
|
|
154
|
+
type: "array",
|
|
155
|
+
items: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
chunk: { type: "object" },
|
|
159
|
+
score: { type: "number" },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
tookMs: { type: "number" },
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
componentSchemas["search-response"] = searchSchema;
|
|
167
|
+
const langEnum = manifest.languages.supported;
|
|
168
|
+
paths[manifest.interfaces.search.url] = {
|
|
169
|
+
get: {
|
|
170
|
+
operationId: "search.query",
|
|
171
|
+
summary: "Semantic search over site content",
|
|
172
|
+
tags: ["search"],
|
|
173
|
+
responses: {
|
|
174
|
+
"200": {
|
|
175
|
+
description: "Search results",
|
|
176
|
+
content: {
|
|
177
|
+
"application/json": { schema: { $ref: "#/components/schemas/search-response" } },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
"400": { description: "Invalid query" },
|
|
181
|
+
"503": { description: "Search index not available" },
|
|
182
|
+
},
|
|
183
|
+
parameters: [
|
|
184
|
+
{
|
|
185
|
+
name: "q",
|
|
186
|
+
in: "query",
|
|
187
|
+
required: true,
|
|
188
|
+
schema: { type: "string", maxLength: 1000 },
|
|
189
|
+
description: "Natural language search query",
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: "lang",
|
|
193
|
+
in: "query",
|
|
194
|
+
required: false,
|
|
195
|
+
schema: { type: "string", enum: langEnum },
|
|
196
|
+
description: "Filter by language",
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: "type",
|
|
200
|
+
in: "query",
|
|
201
|
+
required: false,
|
|
202
|
+
schema: { type: "string", enum: ["page", "knowledge", "faq", "prose"] },
|
|
203
|
+
description: "Filter by content type",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "topK",
|
|
207
|
+
in: "query",
|
|
208
|
+
required: false,
|
|
209
|
+
schema: { type: "integer", default: 5, maximum: 20 },
|
|
210
|
+
description: "Number of results to return",
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
139
217
|
const doc: OpenApiDocument = {
|
|
140
218
|
openapi: "3.1.0",
|
|
141
219
|
info: {
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
buildSearchManifest,
|
|
4
|
+
computeSearchManifestContentHash,
|
|
5
|
+
SEARCH_EMBEDDING_MODEL,
|
|
6
|
+
SEARCH_EMBEDDING_DIMENSIONS,
|
|
7
|
+
SEARCH_MANIFEST_SCHEMA_VERSION,
|
|
8
|
+
SEARCH_CHUNK_TEXT_MAX_LENGTH,
|
|
9
|
+
isNonKnowledgeFile,
|
|
10
|
+
NON_KNOWLEDGE_FILES,
|
|
11
|
+
type SearchChunk,
|
|
12
|
+
} from "./search.ts";
|
|
13
|
+
|
|
14
|
+
const baseChunk: SearchChunk = {
|
|
15
|
+
id: "de:/about:block-intro",
|
|
16
|
+
url: "/about",
|
|
17
|
+
lang: "de",
|
|
18
|
+
type: "page",
|
|
19
|
+
blockId: "block-intro",
|
|
20
|
+
heading: "About",
|
|
21
|
+
text: "About the company",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
describe("buildSearchManifest", () => {
|
|
25
|
+
it("produces a manifest with correct schema version and model", () => {
|
|
26
|
+
const manifest = buildSearchManifest({
|
|
27
|
+
site: "test-site",
|
|
28
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
29
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
30
|
+
generatedAt: "2026-08-27T00:00:00Z",
|
|
31
|
+
chunks: [baseChunk],
|
|
32
|
+
});
|
|
33
|
+
expect(manifest.schema).toBe(SEARCH_MANIFEST_SCHEMA_VERSION);
|
|
34
|
+
expect(manifest.model).toBe(SEARCH_EMBEDDING_MODEL);
|
|
35
|
+
expect(manifest.dimensions).toBe(SEARCH_EMBEDDING_DIMENSIONS);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("sorts chunks by id for deterministic output", () => {
|
|
39
|
+
const chunkB: SearchChunk = { ...baseChunk, id: "de:/about:block-zzz" };
|
|
40
|
+
const chunkA: SearchChunk = { ...baseChunk, id: "de:/about:block-aaa" };
|
|
41
|
+
const manifest = buildSearchManifest({
|
|
42
|
+
site: "test-site",
|
|
43
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
44
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
45
|
+
generatedAt: "2026-08-27T00:00:00Z",
|
|
46
|
+
chunks: [chunkB, chunkA],
|
|
47
|
+
});
|
|
48
|
+
expect(manifest.chunks[0].id).toBe("de:/about:block-aaa");
|
|
49
|
+
expect(manifest.chunks[1].id).toBe("de:/about:block-zzz");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("is deterministic — same input produces same contentHash regardless of chunk order", () => {
|
|
53
|
+
const chunkA: SearchChunk = { ...baseChunk, id: "aaa" };
|
|
54
|
+
const chunkB: SearchChunk = { ...baseChunk, id: "bbb" };
|
|
55
|
+
const m1 = buildSearchManifest({
|
|
56
|
+
site: "test-site",
|
|
57
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
58
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
59
|
+
generatedAt: "2026-08-27T00:00:00Z",
|
|
60
|
+
chunks: [chunkA, chunkB],
|
|
61
|
+
});
|
|
62
|
+
const m2 = buildSearchManifest({
|
|
63
|
+
site: "test-site",
|
|
64
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
65
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
66
|
+
generatedAt: "2026-08-28T00:00:00Z",
|
|
67
|
+
chunks: [chunkB, chunkA],
|
|
68
|
+
});
|
|
69
|
+
expect(m1.contentHash).toBe(m2.contentHash);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("excludes generatedAt from contentHash", () => {
|
|
73
|
+
const m1 = buildSearchManifest({
|
|
74
|
+
site: "test-site",
|
|
75
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
76
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
77
|
+
generatedAt: "2026-08-27T00:00:00Z",
|
|
78
|
+
chunks: [baseChunk],
|
|
79
|
+
});
|
|
80
|
+
const m2 = buildSearchManifest({
|
|
81
|
+
site: "test-site",
|
|
82
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
83
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
84
|
+
generatedAt: "2026-08-28T00:00:00Z",
|
|
85
|
+
chunks: [baseChunk],
|
|
86
|
+
});
|
|
87
|
+
expect(m1.contentHash).toBe(m2.contentHash);
|
|
88
|
+
expect(m1.generatedAt).not.toBe(m2.generatedAt);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("produces different contentHash when chunks change", () => {
|
|
92
|
+
const m1 = buildSearchManifest({
|
|
93
|
+
site: "test-site",
|
|
94
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
95
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
96
|
+
generatedAt: "2026-08-27T00:00:00Z",
|
|
97
|
+
chunks: [baseChunk],
|
|
98
|
+
});
|
|
99
|
+
const m2 = buildSearchManifest({
|
|
100
|
+
site: "test-site",
|
|
101
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
102
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
103
|
+
generatedAt: "2026-08-27T00:00:00Z",
|
|
104
|
+
chunks: [{ ...baseChunk, text: "Different text" }],
|
|
105
|
+
});
|
|
106
|
+
expect(m1.contentHash).not.toBe(m2.contentHash);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("produces different contentHash when site changes", () => {
|
|
110
|
+
const m1 = buildSearchManifest({
|
|
111
|
+
site: "site-a",
|
|
112
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
113
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
114
|
+
generatedAt: "2026-08-27T00:00:00Z",
|
|
115
|
+
chunks: [baseChunk],
|
|
116
|
+
});
|
|
117
|
+
const m2 = buildSearchManifest({
|
|
118
|
+
site: "site-b",
|
|
119
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
120
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
121
|
+
generatedAt: "2026-08-27T00:00:00Z",
|
|
122
|
+
chunks: [baseChunk],
|
|
123
|
+
});
|
|
124
|
+
expect(m1.contentHash).not.toBe(m2.contentHash);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("computeSearchManifestContentHash", () => {
|
|
129
|
+
it("matches the hash produced by buildSearchManifest", () => {
|
|
130
|
+
const manifest = buildSearchManifest({
|
|
131
|
+
site: "test-site",
|
|
132
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
133
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
134
|
+
generatedAt: "2026-08-27T00:00:00Z",
|
|
135
|
+
chunks: [baseChunk],
|
|
136
|
+
});
|
|
137
|
+
const recomputed = computeSearchManifestContentHash(manifest);
|
|
138
|
+
expect(recomputed).toBe(manifest.contentHash);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("excludes proof from contentHash", () => {
|
|
142
|
+
const manifest = buildSearchManifest({
|
|
143
|
+
site: "test-site",
|
|
144
|
+
model: SEARCH_EMBEDDING_MODEL,
|
|
145
|
+
dimensions: SEARCH_EMBEDDING_DIMENSIONS,
|
|
146
|
+
generatedAt: "2026-08-27T00:00:00Z",
|
|
147
|
+
chunks: [baseChunk],
|
|
148
|
+
});
|
|
149
|
+
const withProof = {
|
|
150
|
+
...manifest,
|
|
151
|
+
proof: {
|
|
152
|
+
type: "Ed25519Signature2020",
|
|
153
|
+
created: "2026-08-27T00:00:00Z",
|
|
154
|
+
verificationMethod: "did:web:example.com#key-v1",
|
|
155
|
+
proofPurpose: "assertionMethod",
|
|
156
|
+
proofValue: "zFakeProofValue",
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
expect(computeSearchManifestContentHash(withProof)).toBe(manifest.contentHash);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("constants", () => {
|
|
164
|
+
it("SEARCH_CHUNK_TEXT_MAX_LENGTH is 2000", () => {
|
|
165
|
+
expect(SEARCH_CHUNK_TEXT_MAX_LENGTH).toBe(2000);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("isNonKnowledgeFile", () => {
|
|
170
|
+
it("returns true for search-manifest.json", () => {
|
|
171
|
+
expect(isNonKnowledgeFile("search-manifest.json")).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("returns false for knowledge domain files", () => {
|
|
175
|
+
expect(isNonKnowledgeFile("company.json")).toBe(false);
|
|
176
|
+
expect(isNonKnowledgeFile("offer.json")).toBe(false);
|
|
177
|
+
expect(isNonKnowledgeFile("faq.json")).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("returns false for arbitrary filenames", () => {
|
|
181
|
+
expect(isNonKnowledgeFile("foo.json")).toBe(false);
|
|
182
|
+
expect(isNonKnowledgeFile("readme.md")).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("NON_KNOWLEDGE_FILES contains search-manifest.json", () => {
|
|
186
|
+
expect(NON_KNOWLEDGE_FILES).toContain("search-manifest.json");
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>
|
|
4
|
+
RFC-0954: shared types and pure helpers for the agent semantic search capability.
|
|
5
|
+
Defines the search manifest shape (build-time output), query/response contracts
|
|
6
|
+
(runtime API), and pinned embedding model constants. Pure, deterministic — no I/O,
|
|
7
|
+
no timestamps in the hash payload.
|
|
8
|
+
</purpose>
|
|
9
|
+
<non-goals>
|
|
10
|
+
<item>Do not read files or embed text — callers (kernel commands) load content and
|
|
11
|
+
call buildSearchManifest with already-extracted chunks.</item>
|
|
12
|
+
<item>Do not call Workers AI or Vectorize — runtime logic lives in agent-gate/astro.ts.</item>
|
|
13
|
+
</non-goals>
|
|
14
|
+
</MODULE_CONTRACT>
|
|
15
|
+
<CHANGE_SUMMARY>
|
|
16
|
+
<item>RFC-0954: initial search manifest contract, query/response types, and pure builder.</item>
|
|
17
|
+
<item>m000101: add NON_KNOWLEDGE_FILES and isNonKnowledgeFile for shared non-knowledge-file exclusion.</item>
|
|
18
|
+
</CHANGE_SUMMARY>
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { computeSignedContentHash } from "./manifest.ts";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Pinned embedding model constants (RFC-0954 §Embedding model)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/** Cloudflare Workers AI embedding model for semantic search (multilingual, 1024 dims). */
|
|
28
|
+
export const SEARCH_EMBEDDING_MODEL = "@cf/baai/bge-m3";
|
|
29
|
+
|
|
30
|
+
/** Vector dimensions for the pinned embedding model. */
|
|
31
|
+
export const SEARCH_EMBEDDING_DIMENSIONS = 1024;
|
|
32
|
+
|
|
33
|
+
/** Maximum number of results per query. */
|
|
34
|
+
export const SEARCH_MAX_TOP_K = 20;
|
|
35
|
+
|
|
36
|
+
/** Default number of results per query. */
|
|
37
|
+
export const SEARCH_DEFAULT_TOP_K = 5;
|
|
38
|
+
|
|
39
|
+
/** Maximum character length for a chunk's `text` field (truncated before embedding). */
|
|
40
|
+
export const SEARCH_CHUNK_TEXT_MAX_LENGTH = 2000;
|
|
41
|
+
|
|
42
|
+
/** Schema version for the search manifest. */
|
|
43
|
+
export const SEARCH_MANIFEST_SCHEMA_VERSION = "1";
|
|
44
|
+
|
|
45
|
+
/** Static asset path for the generated search manifest. */
|
|
46
|
+
export const SEARCH_MANIFEST_PATH = "public/api/agent/v1/search-manifest.json";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Files that live in the knowledge directory (`public/api/agent/v1/`) but are NOT
|
|
50
|
+
* knowledge envelopes. Used by `agent.knowledge.validate`, `agent.manifest.generate`,
|
|
51
|
+
* `agent.surface.validate`, and `agent.search.manifest.generate` to skip non-knowledge
|
|
52
|
+
* artifacts when iterating knowledge domain files.
|
|
53
|
+
*/
|
|
54
|
+
export const NON_KNOWLEDGE_FILES = ["search-manifest.json"] as const;
|
|
55
|
+
|
|
56
|
+
/** Returns true if `filename` is a non-knowledge file that lives in the knowledge directory. */
|
|
57
|
+
export function isNonKnowledgeFile(filename: string): boolean {
|
|
58
|
+
return (NON_KNOWLEDGE_FILES as readonly string[]).includes(filename);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Search manifest (build-time output)
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
/** Content type of a search chunk. */
|
|
66
|
+
export type SearchChunkType = "page" | "knowledge" | "faq" | "prose";
|
|
67
|
+
|
|
68
|
+
/** A single searchable content unit — one per block, knowledge entry, or FAQ entry. */
|
|
69
|
+
export interface SearchChunk {
|
|
70
|
+
/** Deterministic ID: "{lang}:{urlPath}:{blockId}" or "{lang}:knowledge:{domain}" etc. */
|
|
71
|
+
id: string;
|
|
72
|
+
/** Full URL path to the page or resource (relative to site root). */
|
|
73
|
+
url: string;
|
|
74
|
+
/** ISO 639-1 language code (e.g. "de", "uk"). */
|
|
75
|
+
lang: string;
|
|
76
|
+
/** Content type — determines metadata filtering. */
|
|
77
|
+
type: SearchChunkType;
|
|
78
|
+
/** Block ID within the page (null for knowledge/faq chunks). */
|
|
79
|
+
blockId: string | null;
|
|
80
|
+
/** Heading text extracted from the block (optional). */
|
|
81
|
+
heading: string | null;
|
|
82
|
+
/** Body text extracted from the block, truncated to SEARCH_CHUNK_TEXT_MAX_LENGTH. */
|
|
83
|
+
text: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Static search manifest — build-time output written to public/api/agent/v1/search-manifest.json. */
|
|
87
|
+
export interface SearchManifest {
|
|
88
|
+
schema: string;
|
|
89
|
+
site: string;
|
|
90
|
+
model: string;
|
|
91
|
+
dimensions: number;
|
|
92
|
+
/** sha256 hex over sorted-key JSON of this document minus contentHash + generatedAt. */
|
|
93
|
+
contentHash: string;
|
|
94
|
+
/** ISO timestamp — present in output but excluded from contentHash. */
|
|
95
|
+
generatedAt: string;
|
|
96
|
+
chunks: SearchChunk[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Query / response (runtime API contracts)
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
/** Query parameters for the search endpoint. */
|
|
104
|
+
export interface SearchQuery {
|
|
105
|
+
/** Natural language search query (max 1000 chars). */
|
|
106
|
+
q: string;
|
|
107
|
+
/** Filter by language (optional). */
|
|
108
|
+
lang?: string;
|
|
109
|
+
/** Filter by content type (optional). */
|
|
110
|
+
type?: SearchChunkType;
|
|
111
|
+
/** Number of results to return (default 5, max 20). */
|
|
112
|
+
topK?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** A single search result — chunk + similarity score. */
|
|
116
|
+
export interface SearchResult {
|
|
117
|
+
chunk: SearchChunk;
|
|
118
|
+
/** Cosine similarity score (0..1, higher = more similar). */
|
|
119
|
+
score: number;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Response from the search endpoint. */
|
|
123
|
+
export interface SearchResponse {
|
|
124
|
+
query: string;
|
|
125
|
+
results: SearchResult[];
|
|
126
|
+
/** Server-side processing time in milliseconds. */
|
|
127
|
+
tookMs: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Pure helpers
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Pure: assemble + hash the search manifest. Deterministic — sorts chunks by id,
|
|
136
|
+
* excludes generatedAt from the content hash. Two runs on unchanged input are
|
|
137
|
+
* byte-identical (excluding the timestamp).
|
|
138
|
+
*/
|
|
139
|
+
export function buildSearchManifest(input: {
|
|
140
|
+
site: string;
|
|
141
|
+
model: string;
|
|
142
|
+
dimensions: number;
|
|
143
|
+
generatedAt: string;
|
|
144
|
+
chunks: SearchChunk[];
|
|
145
|
+
}): SearchManifest {
|
|
146
|
+
const sortedChunks = [...input.chunks].sort((a, b) => a.id.localeCompare(b.id));
|
|
147
|
+
const base: Omit<SearchManifest, "contentHash"> = {
|
|
148
|
+
schema: SEARCH_MANIFEST_SCHEMA_VERSION,
|
|
149
|
+
site: input.site,
|
|
150
|
+
model: input.model,
|
|
151
|
+
dimensions: input.dimensions,
|
|
152
|
+
generatedAt: input.generatedAt,
|
|
153
|
+
chunks: sortedChunks,
|
|
154
|
+
};
|
|
155
|
+
const contentHash = computeSearchManifestContentHash(base);
|
|
156
|
+
return { ...base, contentHash };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** sha256 hex over the canonical JSON of the manifest minus contentHash + generatedAt + proof. */
|
|
160
|
+
export function computeSearchManifestContentHash(
|
|
161
|
+
doc: Omit<SearchManifest, "contentHash"> | Record<string, unknown>,
|
|
162
|
+
): string {
|
|
163
|
+
return computeSignedContentHash(doc as Record<string, unknown>, ["generatedAt"]);
|
|
164
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { resolveActiveCapabilities, capabilityToActionRef } from "../capability.ts";
|
|
3
|
+
|
|
4
|
+
describe("resolveActiveCapabilities", () => {
|
|
5
|
+
it("is a function", () => {
|
|
6
|
+
expect(typeof resolveActiveCapabilities).toBe("function");
|
|
7
|
+
});
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
describe("capabilityToActionRef", () => {
|
|
11
|
+
it("is a function", () => {
|
|
12
|
+
expect(typeof capabilityToActionRef).toBe("function");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { FLEET_AGENT_CATALOG_SCHEMA, buildFleetAgentCatalog } from "../fleet-catalog.ts";
|
|
3
|
+
|
|
4
|
+
describe("FLEET_AGENT_CATALOG_SCHEMA", () => {
|
|
5
|
+
it("is a string constant", () => {
|
|
6
|
+
expect(typeof FLEET_AGENT_CATALOG_SCHEMA).toBe("string");
|
|
7
|
+
expect(FLEET_AGENT_CATALOG_SCHEMA).toContain("fleet.agent-catalog");
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("buildFleetAgentCatalog", () => {
|
|
12
|
+
it("is a function", () => {
|
|
13
|
+
expect(typeof buildFleetAgentCatalog).toBe("function");
|
|
14
|
+
});
|
|
15
|
+
});
|