@ggui-ai/protocol 0.1.0-rc.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/LICENSE +201 -0
- package/README.md +46 -0
- package/dist/bridge/invoke-agent.d.ts +65 -0
- package/dist/bridge/invoke-agent.d.ts.map +1 -0
- package/dist/bridge/invoke-agent.js +113 -0
- package/dist/envelope-adapters.d.ts +24 -0
- package/dist/envelope-adapters.d.ts.map +1 -0
- package/dist/envelope-adapters.js +14 -0
- package/dist/envelopes/builders.d.ts +145 -0
- package/dist/envelopes/builders.d.ts.map +1 -0
- package/dist/envelopes/builders.js +113 -0
- package/dist/errors/unknown-permission-name.d.ts +12 -0
- package/dist/errors/unknown-permission-name.d.ts.map +1 -0
- package/dist/errors/unknown-permission-name.js +29 -0
- package/dist/errors/version-mismatch.d.ts +55 -0
- package/dist/errors/version-mismatch.d.ts.map +1 -0
- package/dist/errors/version-mismatch.js +52 -0
- package/dist/gadgets/resolve-contract-gadgets.d.ts +93 -0
- package/dist/gadgets/resolve-contract-gadgets.d.ts.map +1 -0
- package/dist/gadgets/resolve-contract-gadgets.js +119 -0
- package/dist/gadgets/stdlib-gadgets.d.ts +43 -0
- package/dist/gadgets/stdlib-gadgets.d.ts.map +1 -0
- package/dist/gadgets/stdlib-gadgets.js +161 -0
- package/dist/iframe-bridge.d.ts +63 -0
- package/dist/iframe-bridge.d.ts.map +1 -0
- package/dist/iframe-bridge.js +166 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +79 -0
- package/dist/integrations/mcp-apps.d.ts +1218 -0
- package/dist/integrations/mcp-apps.d.ts.map +1 -0
- package/dist/integrations/mcp-apps.js +427 -0
- package/dist/navigation/index.d.ts +3 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +1 -0
- package/dist/navigation/stack-navigation.d.ts +55 -0
- package/dist/navigation/stack-navigation.d.ts.map +1 -0
- package/dist/navigation/stack-navigation.js +80 -0
- package/dist/recommended-prompts.d.ts +56 -0
- package/dist/recommended-prompts.d.ts.map +1 -0
- package/dist/recommended-prompts.js +55 -0
- package/dist/registry/blueprint-key.d.ts +9 -0
- package/dist/registry/blueprint-key.d.ts.map +1 -0
- package/dist/registry/blueprint-key.js +28 -0
- package/dist/registry/canonicalize-contract.d.ts +35 -0
- package/dist/registry/canonicalize-contract.d.ts.map +1 -0
- package/dist/registry/canonicalize-contract.js +166 -0
- package/dist/registry/summarize-contract.d.ts +46 -0
- package/dist/registry/summarize-contract.d.ts.map +1 -0
- package/dist/registry/summarize-contract.js +63 -0
- package/dist/schema-learning/derive-contract.d.ts +67 -0
- package/dist/schema-learning/derive-contract.d.ts.map +1 -0
- package/dist/schema-learning/derive-contract.js +117 -0
- package/dist/schema-learning/merge.d.ts +32 -0
- package/dist/schema-learning/merge.d.ts.map +1 -0
- package/dist/schema-learning/merge.js +146 -0
- package/dist/schemas/blueprint.d.ts +32 -0
- package/dist/schemas/blueprint.d.ts.map +1 -0
- package/dist/schemas/blueprint.js +92 -0
- package/dist/schemas/data-contract.d.ts +750 -0
- package/dist/schemas/data-contract.d.ts.map +1 -0
- package/dist/schemas/data-contract.js +663 -0
- package/dist/schemas/gadget-name-grammar.d.ts +29 -0
- package/dist/schemas/gadget-name-grammar.d.ts.map +1 -0
- package/dist/schemas/gadget-name-grammar.js +28 -0
- package/dist/schemas/handshake-suggestion.d.ts +46 -0
- package/dist/schemas/handshake-suggestion.d.ts.map +1 -0
- package/dist/schemas/handshake-suggestion.js +107 -0
- package/dist/schemas/invoke.d.ts +337 -0
- package/dist/schemas/invoke.d.ts.map +1 -0
- package/dist/schemas/invoke.js +169 -0
- package/dist/schemas/mcp.d.ts +301 -0
- package/dist/schemas/mcp.d.ts.map +1 -0
- package/dist/schemas/mcp.js +373 -0
- package/dist/schemas/ops-blueprint.d.ts +176 -0
- package/dist/schemas/ops-blueprint.d.ts.map +1 -0
- package/dist/schemas/ops-blueprint.js +259 -0
- package/dist/schemas/sync-check.d.ts +11 -0
- package/dist/schemas/sync-check.d.ts.map +1 -0
- package/dist/schemas/sync-check.js +60 -0
- package/dist/screen-blueprints/define.d.ts +22 -0
- package/dist/screen-blueprints/define.d.ts.map +1 -0
- package/dist/screen-blueprints/define.js +3 -0
- package/dist/screen-blueprints/index.d.ts +4 -0
- package/dist/screen-blueprints/index.d.ts.map +1 -0
- package/dist/screen-blueprints/index.js +3 -0
- package/dist/screen-blueprints/match.d.ts +35 -0
- package/dist/screen-blueprints/match.d.ts.map +1 -0
- package/dist/screen-blueprints/match.js +51 -0
- package/dist/screen-blueprints/types.d.ts +164 -0
- package/dist/screen-blueprints/types.d.ts.map +1 -0
- package/dist/screen-blueprints/types.js +1 -0
- package/dist/stream/stream-parser.d.ts +62 -0
- package/dist/stream/stream-parser.d.ts.map +1 -0
- package/dist/stream/stream-parser.js +199 -0
- package/dist/transport/websocket.d.ts +178 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +1 -0
- package/dist/types/app-config.d.ts +61 -0
- package/dist/types/app-config.d.ts.map +1 -0
- package/dist/types/app-config.js +1 -0
- package/dist/types/auth.d.ts +61 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +1 -0
- package/dist/types/blueprint.d.ts +206 -0
- package/dist/types/blueprint.d.ts.map +1 -0
- package/dist/types/blueprint.js +1 -0
- package/dist/types/canvas-lifecycle.d.ts +105 -0
- package/dist/types/canvas-lifecycle.d.ts.map +1 -0
- package/dist/types/canvas-lifecycle.js +38 -0
- package/dist/types/capabilities.d.ts +40 -0
- package/dist/types/capabilities.d.ts.map +1 -0
- package/dist/types/capabilities.js +19 -0
- package/dist/types/contract-inference.d.ts +401 -0
- package/dist/types/contract-inference.d.ts.map +1 -0
- package/dist/types/contract-inference.js +44 -0
- package/dist/types/credential.d.ts +41 -0
- package/dist/types/credential.d.ts.map +1 -0
- package/dist/types/credential.js +32 -0
- package/dist/types/data-bindings.d.ts +322 -0
- package/dist/types/data-bindings.d.ts.map +1 -0
- package/dist/types/data-bindings.js +29 -0
- package/dist/types/data-contract.d.ts +1296 -0
- package/dist/types/data-contract.d.ts.map +1 -0
- package/dist/types/data-contract.js +111 -0
- package/dist/types/events.d.ts +182 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +8 -0
- package/dist/types/feedback.d.ts +24 -0
- package/dist/types/feedback.d.ts.map +1 -0
- package/dist/types/feedback.js +7 -0
- package/dist/types/gadget.d.ts +121 -0
- package/dist/types/gadget.d.ts.map +1 -0
- package/dist/types/gadget.js +24 -0
- package/dist/types/handshake-suggestion.d.ts +264 -0
- package/dist/types/handshake-suggestion.d.ts.map +1 -0
- package/dist/types/handshake-suggestion.js +70 -0
- package/dist/types/host-context.d.ts +163 -0
- package/dist/types/host-context.d.ts.map +1 -0
- package/dist/types/host-context.js +142 -0
- package/dist/types/interface-context.d.ts +105 -0
- package/dist/types/interface-context.d.ts.map +1 -0
- package/dist/types/interface-context.js +115 -0
- package/dist/types/invoke.d.ts +28 -0
- package/dist/types/invoke.d.ts.map +1 -0
- package/dist/types/invoke.js +7 -0
- package/dist/types/live-channel.d.ts +613 -0
- package/dist/types/live-channel.d.ts.map +1 -0
- package/dist/types/live-channel.js +1 -0
- package/dist/types/llm.d.ts +61 -0
- package/dist/types/llm.d.ts.map +1 -0
- package/dist/types/llm.js +186 -0
- package/dist/types/mcp-proxy.d.ts +67 -0
- package/dist/types/mcp-proxy.d.ts.map +1 -0
- package/dist/types/mcp-proxy.js +46 -0
- package/dist/types/mcp.d.ts +637 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +30 -0
- package/dist/types/openrouter-models.d.ts +22 -0
- package/dist/types/openrouter-models.d.ts.map +1 -0
- package/dist/types/openrouter-models.js +4843 -0
- package/dist/types/region.d.ts +26 -0
- package/dist/types/region.d.ts.map +1 -0
- package/dist/types/region.js +36 -0
- package/dist/types/session.d.ts +419 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +1 -0
- package/dist/types/thread.d.ts +207 -0
- package/dist/types/thread.d.ts.map +1 -0
- package/dist/types/thread.js +57 -0
- package/dist/types/ui-generator.d.ts +100 -0
- package/dist/types/ui-generator.d.ts.map +1 -0
- package/dist/types/ui-generator.js +53 -0
- package/dist/validation/ajv-runtime.d.ts +140 -0
- package/dist/validation/ajv-runtime.d.ts.map +1 -0
- package/dist/validation/ajv-runtime.js +452 -0
- package/dist/validation/content-hash.d.ts +3 -0
- package/dist/validation/content-hash.d.ts.map +1 -0
- package/dist/validation/content-hash.js +21 -0
- package/dist/validation/contract-validator.d.ts +244 -0
- package/dist/validation/contract-validator.d.ts.map +1 -0
- package/dist/validation/contract-validator.js +711 -0
- package/dist/validation/cross-references.d.ts +105 -0
- package/dist/validation/cross-references.d.ts.map +1 -0
- package/dist/validation/cross-references.js +164 -0
- package/dist/validation/hygiene-rules.d.ts +250 -0
- package/dist/validation/hygiene-rules.d.ts.map +1 -0
- package/dist/validation/hygiene-rules.js +564 -0
- package/dist/validation/lint-contract.d.ts +130 -0
- package/dist/validation/lint-contract.d.ts.map +1 -0
- package/dist/validation/lint-contract.js +225 -0
- package/dist/validation/name-invariants.d.ts +117 -0
- package/dist/validation/name-invariants.d.ts.map +1 -0
- package/dist/validation/name-invariants.js +172 -0
- package/dist/validation/reserved-channels.d.ts +156 -0
- package/dist/validation/reserved-channels.d.ts.map +1 -0
- package/dist/validation/reserved-channels.js +356 -0
- package/dist/validation/resolve-stream-channel.d.ts +78 -0
- package/dist/validation/resolve-stream-channel.d.ts.map +1 -0
- package/dist/validation/resolve-stream-channel.js +64 -0
- package/dist/validation/sanitize-error.d.ts +46 -0
- package/dist/validation/sanitize-error.d.ts.map +1 -0
- package/dist/validation/sanitize-error.js +88 -0
- package/dist/validation/schema-compat-invariants.d.ts +140 -0
- package/dist/validation/schema-compat-invariants.d.ts.map +1 -0
- package/dist/validation/schema-compat-invariants.js +220 -0
- package/dist/validation/schema-meta-validation.d.ts +60 -0
- package/dist/validation/schema-meta-validation.d.ts.map +1 -0
- package/dist/validation/schema-meta-validation.js +131 -0
- package/dist/validation/schema-subset.d.ts +165 -0
- package/dist/validation/schema-subset.d.ts.map +1 -0
- package/dist/validation/schema-subset.js +295 -0
- package/dist/validation/ui-security.d.ts +54 -0
- package/dist/validation/ui-security.d.ts.map +1 -0
- package/dist/validation/ui-security.js +138 -0
- package/dist/validation/zod-to-json-schema.d.ts +63 -0
- package/dist/validation/zod-to-json-schema.d.ts.map +1 -0
- package/dist/validation/zod-to-json-schema.js +126 -0
- package/dist/version.d.ts +1458 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +1459 -0
- package/package.json +113 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ajv-backed JSON Schema validation runtime for ggui contracts.
|
|
3
|
+
*
|
|
4
|
+
* Owns layers B (inner JSON Schema meta-validation) and C (runtime
|
|
5
|
+
* data validation across propsSpec / actionSpec / streamSpec /
|
|
6
|
+
* contextSpec) of the six-layer model. The outer A-layers — protocol
|
|
7
|
+
* wrappers (DataContract envelope, PropsSpec, PropEntry, ActionEntry,
|
|
8
|
+
* etc.) — stay on zod where TS inference + structural strict-mode
|
|
9
|
+
* already do their job.
|
|
10
|
+
*
|
|
11
|
+
* Why Ajv:
|
|
12
|
+
* - Canonical JSON Schema validator (303M weekly downloads).
|
|
13
|
+
* - Single source of truth: same compiled validator powers all four
|
|
14
|
+
* runtime spec surfaces, so closed-shape semantics never diverge
|
|
15
|
+
* between props vs action vs stream vs context.
|
|
16
|
+
* - Compile-time meta-validation: `strict: true` rejects malformed
|
|
17
|
+
* JSON Schemas at `compile()` — agents discover bugs at
|
|
18
|
+
* handshake/push, not at first data flow.
|
|
19
|
+
*
|
|
20
|
+
* Closed-shape (load-bearing):
|
|
21
|
+
* JSON Schema's default is `additionalProperties: true` (extras
|
|
22
|
+
* allowed). Our "propsSpec IS the contract" promise needs
|
|
23
|
+
* closed-shape at EVERY depth. Rather than tax agents with
|
|
24
|
+
* `additionalProperties: false` at every object node, we inject it
|
|
25
|
+
* recursively via {@link injectClosedShape} before Ajv compiles.
|
|
26
|
+
* An author who explicitly sets `additionalProperties` (boolean or
|
|
27
|
+
* schema) keeps that intent — escape hatch for the rare case where
|
|
28
|
+
* open extension is intentional.
|
|
29
|
+
*
|
|
30
|
+
* Tolerated metadata keywords:
|
|
31
|
+
* - `example` (singular, OpenAPI-ish; JSON Schema standard is
|
|
32
|
+
* `examples` array). Treated as informational.
|
|
33
|
+
* - `nullable` (OpenAPI 3.0 shorthand). Tolerated; the canonical
|
|
34
|
+
* way to express nullability is `type: [<original>, 'null']`.
|
|
35
|
+
*
|
|
36
|
+
* Both are registered as no-op keywords so Ajv strict mode doesn't
|
|
37
|
+
* reject schemas that carry them.
|
|
38
|
+
*/
|
|
39
|
+
import Ajv from 'ajv';
|
|
40
|
+
import addFormats from 'ajv-formats';
|
|
41
|
+
import standaloneCode from 'ajv/dist/standalone/index.js';
|
|
42
|
+
import equalImport from 'ajv/dist/runtime/equal.js';
|
|
43
|
+
import ucs2lengthImport from 'ajv/dist/runtime/ucs2length.js';
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a runtime-helper module's exported function across the
|
|
46
|
+
* CJS↔ESM interop gap: a plain `module.exports = fn` CJS module
|
|
47
|
+
* surfaces the function directly, while a `__esModule`-flagged one
|
|
48
|
+
* (Ajv's `dist/runtime/*`) surfaces it under `.default`.
|
|
49
|
+
*/
|
|
50
|
+
function resolveHelperFn(imported) {
|
|
51
|
+
if (typeof imported === 'function') {
|
|
52
|
+
return imported;
|
|
53
|
+
}
|
|
54
|
+
const inner = imported?.default;
|
|
55
|
+
if (typeof inner === 'function') {
|
|
56
|
+
return inner;
|
|
57
|
+
}
|
|
58
|
+
throw new Error('ajv-runtime: could not resolve a runtime-helper function');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Source text of the two Ajv runtime helpers a standalone validator for
|
|
62
|
+
* our closed contract schemas can reach: `equal` (fast-deep-equal —
|
|
63
|
+
* emitted for `uniqueItems` and object-valued `enum`/`const`) and
|
|
64
|
+
* `ucs2length` (emitted for string `minLength`/`maxLength`). Ajv
|
|
65
|
+
* references both by bare specifier; {@link compileValidatorModule}
|
|
66
|
+
* inlines this source so the emitted module is fully self-contained —
|
|
67
|
+
* no bare-specifier imports the CSP-sandboxed renderer iframe would
|
|
68
|
+
* fail to resolve. Captured once at module init; `toString()` on a
|
|
69
|
+
* pure function is deterministic.
|
|
70
|
+
*/
|
|
71
|
+
const FAST_DEEP_EQUAL_SOURCE = resolveHelperFn(equalImport).toString();
|
|
72
|
+
const UCS2LENGTH_SOURCE = resolveHelperFn(ucs2lengthImport).toString();
|
|
73
|
+
/**
|
|
74
|
+
* Singleton Ajv instance. Configured once with:
|
|
75
|
+
* - `strict: true` — rejects unknown keywords + malformed schemas
|
|
76
|
+
* at compile-time (layer B meta-validation as a side effect).
|
|
77
|
+
* - `allErrors: true` — collect all violations per validation, not
|
|
78
|
+
* just the first. The agent sees the full picture in one round.
|
|
79
|
+
* - `useDefaults: false` — don't mutate input by filling defaults.
|
|
80
|
+
* Contract validation is read-only.
|
|
81
|
+
* - `coerceTypes: false` — strict types. `"5"` is not a number.
|
|
82
|
+
* - `removeAdditional: false` — extras MUST error, not be silently
|
|
83
|
+
* stripped. The closed-shape promise depends on this.
|
|
84
|
+
*/
|
|
85
|
+
const AJV_OPTIONS = {
|
|
86
|
+
strict: true,
|
|
87
|
+
allErrors: true,
|
|
88
|
+
useDefaults: false,
|
|
89
|
+
coerceTypes: false,
|
|
90
|
+
removeAdditional: false,
|
|
91
|
+
verbose: true,
|
|
92
|
+
};
|
|
93
|
+
const ajv = new Ajv({ ...AJV_OPTIONS });
|
|
94
|
+
addFormats(ajv);
|
|
95
|
+
if (!ajv.getKeyword('example'))
|
|
96
|
+
ajv.addKeyword({ keyword: 'example' });
|
|
97
|
+
if (!ajv.getKeyword('nullable'))
|
|
98
|
+
ajv.addKeyword({ keyword: 'nullable' });
|
|
99
|
+
/**
|
|
100
|
+
* Dedicated Ajv instance for {@link compileValidatorModule}. Same
|
|
101
|
+
* options as the singleton plus `code.source`/`code.esm` so Ajv emits
|
|
102
|
+
* the validator as ESM source text instead of a live function.
|
|
103
|
+
*
|
|
104
|
+
* Why a second instance: `code.source` makes every compiled validator
|
|
105
|
+
* carry its generated source — a cost the runtime-validation singleton
|
|
106
|
+
* doesn't need. Keeping standalone emission isolated leaves the hot
|
|
107
|
+
* `compileForValidation` path unchanged.
|
|
108
|
+
*/
|
|
109
|
+
const standaloneAjv = new Ajv({
|
|
110
|
+
...AJV_OPTIONS,
|
|
111
|
+
code: { source: true, esm: true },
|
|
112
|
+
});
|
|
113
|
+
addFormats(standaloneAjv);
|
|
114
|
+
if (!standaloneAjv.getKeyword('example')) {
|
|
115
|
+
standaloneAjv.addKeyword({ keyword: 'example' });
|
|
116
|
+
}
|
|
117
|
+
if (!standaloneAjv.getKeyword('nullable')) {
|
|
118
|
+
standaloneAjv.addKeyword({ keyword: 'nullable' });
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Recursively walk a JSON Schema and inject
|
|
122
|
+
* `additionalProperties: false` at every object node. Authors who
|
|
123
|
+
* explicitly set `additionalProperties` keep that intent (boolean
|
|
124
|
+
* preserved; schema recursed into).
|
|
125
|
+
*
|
|
126
|
+
* Walks:
|
|
127
|
+
* - `properties` (each entry)
|
|
128
|
+
* - `items` (array element schema)
|
|
129
|
+
* - `additionalProperties` (when it's a schema)
|
|
130
|
+
* - `oneOf` / `anyOf` (each branch)
|
|
131
|
+
*
|
|
132
|
+
* Returns a new schema tree; never mutates the input.
|
|
133
|
+
*/
|
|
134
|
+
export function injectClosedShape(schema) {
|
|
135
|
+
const isObjectNode = schema.type === 'object' || schema.properties !== undefined;
|
|
136
|
+
if (isObjectNode) {
|
|
137
|
+
const out = { ...schema };
|
|
138
|
+
if (out.properties) {
|
|
139
|
+
const newProps = {};
|
|
140
|
+
for (const [k, v] of Object.entries(out.properties)) {
|
|
141
|
+
newProps[k] = injectClosedShape(v);
|
|
142
|
+
}
|
|
143
|
+
out.properties = newProps;
|
|
144
|
+
}
|
|
145
|
+
if (out.additionalProperties === undefined) {
|
|
146
|
+
out.additionalProperties = false;
|
|
147
|
+
}
|
|
148
|
+
else if (typeof out.additionalProperties !== 'boolean') {
|
|
149
|
+
out.additionalProperties = injectClosedShape(out.additionalProperties);
|
|
150
|
+
}
|
|
151
|
+
if (out.oneOf)
|
|
152
|
+
out.oneOf = out.oneOf.map(injectClosedShape);
|
|
153
|
+
if (out.anyOf)
|
|
154
|
+
out.anyOf = out.anyOf.map(injectClosedShape);
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
if (schema.type === 'array' && schema.items) {
|
|
158
|
+
return { ...schema, items: injectClosedShape(schema.items) };
|
|
159
|
+
}
|
|
160
|
+
if (schema.oneOf || schema.anyOf) {
|
|
161
|
+
const out = { ...schema };
|
|
162
|
+
if (out.oneOf)
|
|
163
|
+
out.oneOf = out.oneOf.map(injectClosedShape);
|
|
164
|
+
if (out.anyOf)
|
|
165
|
+
out.anyOf = out.anyOf.map(injectClosedShape);
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
return schema;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Compile a JSON Schema into an Ajv {@link ValidateFunction}, with
|
|
172
|
+
* closed-shape injected at every object node. Throws if the schema
|
|
173
|
+
* is malformed under Ajv strict mode — this is layer B meta-
|
|
174
|
+
* validation as a free side effect. Dedicated meta-validation call
|
|
175
|
+
* sites (handshake / push) wrap this in a structured error.
|
|
176
|
+
*
|
|
177
|
+
* Not cached. Compilation is fast and contracts are small; caching
|
|
178
|
+
* adds a memory cost without a measured win. Revisit if profiling
|
|
179
|
+
* shows compile dominating.
|
|
180
|
+
*/
|
|
181
|
+
export function compileForValidation(schema) {
|
|
182
|
+
const injected = injectClosedShape(schema);
|
|
183
|
+
return ajv.compile(injected);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Compile a JSON Schema into a standalone, **fully self-contained ESM
|
|
187
|
+
* validator module** — source text, never a live function. Closed-shape
|
|
188
|
+
* is injected first, exactly as {@link compileForValidation} does, so
|
|
189
|
+
* the emitted validator enforces the same semantics.
|
|
190
|
+
*
|
|
191
|
+
* Why this exists: the renderer iframe runs under a strict CSP with no
|
|
192
|
+
* `'unsafe-eval'`, so `ajv.compile()` (which builds the validator via
|
|
193
|
+
* `new Function`) throws `EvalError` there. Codegen has to happen
|
|
194
|
+
* where `eval` is legal — the server, at push time, where the contract
|
|
195
|
+
* schema is already fixed. The iframe then loads this module source
|
|
196
|
+
* via a `blob:` dynamic import (governed by `script-src`, not
|
|
197
|
+
* `unsafe-eval`) and only ever *runs* the validator.
|
|
198
|
+
*
|
|
199
|
+
* The returned module `export default`s the validator function (and
|
|
200
|
+
* also names it `validate`). Ajv standalone references its runtime
|
|
201
|
+
* helpers by bare specifier (`ajv/dist/runtime/*`) — the
|
|
202
|
+
* CSP-sandboxed iframe has no bundler to resolve those, so this
|
|
203
|
+
* function **inlines** every helper a closed-contract validator can
|
|
204
|
+
* reach (in practice only `equal` / fast-deep-equal, for `uniqueItems`
|
|
205
|
+
* and object-valued `enum`/`const`). The result has zero imports. A
|
|
206
|
+
* survivor check throws if any un-inlined bare import remains, so a new
|
|
207
|
+
* Ajv helper surfaces as a loud server-side failure, never as silent
|
|
208
|
+
* iframe breakage.
|
|
209
|
+
*
|
|
210
|
+
* Throws if the schema is malformed under Ajv strict mode — same
|
|
211
|
+
* layer-B meta-validation side effect as {@link compileForValidation}.
|
|
212
|
+
*/
|
|
213
|
+
export function compileValidatorModule(schema) {
|
|
214
|
+
const injected = injectClosedShape(schema);
|
|
215
|
+
const validate = standaloneAjv.compile(injected);
|
|
216
|
+
return inlineRuntimeHelpers(standaloneCode(standaloneAjv, validate));
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Inlinable Ajv runtime helpers, keyed by bare specifier. Ajv standalone
|
|
220
|
+
* references these by `import`; the CSP-sandboxed iframe has no bundler
|
|
221
|
+
* to resolve a bare specifier, so {@link inlineRuntimeHelpers} replaces
|
|
222
|
+
* each `import` with the helper's source inline.
|
|
223
|
+
*/
|
|
224
|
+
const RUNTIME_HELPER_SOURCES = {
|
|
225
|
+
'ajv/dist/runtime/equal': FAST_DEEP_EQUAL_SOURCE,
|
|
226
|
+
'ajv/dist/runtime/ucs2length': UCS2LENGTH_SOURCE,
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* Make an Ajv standalone module fully self-contained: normalize any
|
|
230
|
+
* CJS `require` of a runtime helper to ESM `import`, inline every
|
|
231
|
+
* helper we support, and assert nothing un-inlined survives.
|
|
232
|
+
*/
|
|
233
|
+
function inlineRuntimeHelpers(source) {
|
|
234
|
+
// Ajv standalone may emit CJS `require(...)` for runtime helpers even
|
|
235
|
+
// under `code.esm`. Normalize to ESM `import` first so one inliner
|
|
236
|
+
// pass below handles both emission styles.
|
|
237
|
+
let out = source
|
|
238
|
+
.replace(/const (\w+) = require\("([^"]+)"\)\.default;/g, 'import $1 from "$2";')
|
|
239
|
+
.replace(/const (\w+) = require\("([^"]+)"\);/g, 'import * as $1 from "$2";');
|
|
240
|
+
// Inline each supported runtime helper — replacing the `import` with
|
|
241
|
+
// an inline `const` keeps the emitted module free of bare specifiers.
|
|
242
|
+
// Function form of `.replace` so a `$` in the helper source is never
|
|
243
|
+
// treated as a capture-group reference.
|
|
244
|
+
for (const [specifier, helperSource] of Object.entries(RUNTIME_HELPER_SOURCES)) {
|
|
245
|
+
const importRe = new RegExp(`import (\\w+) from "${specifier.replace(/[/]/g, '\\/')}";`, 'g');
|
|
246
|
+
out = out.replace(importRe, (_match, binding) => `const ${binding} = ${helperSource};`);
|
|
247
|
+
}
|
|
248
|
+
// Survivor check: a remaining `ajv/dist/runtime/*` import means Ajv
|
|
249
|
+
// emitted a helper we don't inline. Fail loud here (server-side,
|
|
250
|
+
// caught by tests / push) rather than shipping a module the iframe
|
|
251
|
+
// cannot load. Scoped to the `ajv/dist/runtime/` prefix — the only
|
|
252
|
+
// specifiers Ajv standalone emits — so an embedded contract-schema
|
|
253
|
+
// string can't false-trip it.
|
|
254
|
+
const leftover = /import\s+[\w*\s{},]+from\s*"(ajv\/dist\/runtime\/[^"]+)"/.exec(out);
|
|
255
|
+
if (leftover) {
|
|
256
|
+
throw new Error(`compileValidatorModule: emitted module has an un-inlined import of "${leftover[1]}". Add it to RUNTIME_HELPER_SOURCES.`);
|
|
257
|
+
}
|
|
258
|
+
return out;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Convert Ajv error objects into our {@link ContractViolation} shape.
|
|
262
|
+
*
|
|
263
|
+
* Path translation: Ajv `instancePath: '/todos/0/done'` →
|
|
264
|
+
* `field: 'todos[0].done'`. Numeric segments become bracket indices,
|
|
265
|
+
* named segments become dot-separated. Empty instancePath collapses
|
|
266
|
+
* to `''` (root-level error).
|
|
267
|
+
*
|
|
268
|
+
* Per-keyword mapping (see {@link mapOne}):
|
|
269
|
+
* - `additionalProperties` — extra-key violation; `field` includes
|
|
270
|
+
* the offending key, `expected: '<declared key>'`.
|
|
271
|
+
* - `required` — missing-key violation; `field` includes the
|
|
272
|
+
* missing key, `expected: 'present'`, `received: 'undefined'`.
|
|
273
|
+
* - `type` — type mismatch; `expected` is the JSON Schema type,
|
|
274
|
+
* `received` reads from the violating value.
|
|
275
|
+
* - `enum` / `const` — `expected` is the allowed value(s);
|
|
276
|
+
* `received` is the offending value.
|
|
277
|
+
* - `pattern` — `expected` is the regex; `received` is the
|
|
278
|
+
* offending string.
|
|
279
|
+
* - other keywords — fall through to Ajv's message verbatim.
|
|
280
|
+
*/
|
|
281
|
+
export function mapAjvErrorsToViolations(errors, data) {
|
|
282
|
+
if (!errors)
|
|
283
|
+
return [];
|
|
284
|
+
return errors.map(err => mapOne(err, data));
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Re-anchor a list of Ajv-mapped violations under a stable field
|
|
288
|
+
* prefix. Used by the four spec validators to lift Ajv's root-
|
|
289
|
+
* relative paths into the caller's namespace:
|
|
290
|
+
* - propsSpec: no prefix (paths already prop-relative).
|
|
291
|
+
* - actionSpec: `<actionName>.data`.
|
|
292
|
+
* - streamSpec: `<channelName>.payload`.
|
|
293
|
+
* - contextSpec: `<slotName>.value`.
|
|
294
|
+
*
|
|
295
|
+
* Empty `field` (root-level violation) collapses to the prefix
|
|
296
|
+
* itself; sub-fields dot-join.
|
|
297
|
+
*/
|
|
298
|
+
export function prefixViolations(violations, prefix) {
|
|
299
|
+
if (!prefix)
|
|
300
|
+
return violations;
|
|
301
|
+
return violations.map(v => ({
|
|
302
|
+
...v,
|
|
303
|
+
field: v.field ? `${prefix}.${v.field}` : prefix,
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
function mapOne(err, root) {
|
|
307
|
+
const path = pathFromInstancePath(err.instancePath);
|
|
308
|
+
switch (err.keyword) {
|
|
309
|
+
case 'additionalProperties': {
|
|
310
|
+
const params = err.params;
|
|
311
|
+
const extra = params.additionalProperty ?? '<unknown>';
|
|
312
|
+
const fieldPath = path ? `${path}.${extra}` : extra;
|
|
313
|
+
const parentSchema = err.parentSchema;
|
|
314
|
+
const declaredKeys = parentSchema?.properties
|
|
315
|
+
? Object.keys(parentSchema.properties)
|
|
316
|
+
: [];
|
|
317
|
+
const value = resolveAtPath(root, `${err.instancePath}/${extra}`);
|
|
318
|
+
const declaredHint = declaredKeys.length > 0
|
|
319
|
+
? ` Declared keys: [${declaredKeys.join(', ')}].`
|
|
320
|
+
: ' Declared keys: [(none)].';
|
|
321
|
+
return {
|
|
322
|
+
field: fieldPath,
|
|
323
|
+
message: `Undeclared field '${extra}'${path ? ` at '${path}'` : ''}.${declaredHint}`,
|
|
324
|
+
expected: '<declared key>',
|
|
325
|
+
received: jsonTypeOf(value),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
case 'required': {
|
|
329
|
+
const params = err.params;
|
|
330
|
+
const missing = params.missingProperty ?? '<unknown>';
|
|
331
|
+
const fieldPath = path ? `${path}.${missing}` : missing;
|
|
332
|
+
return {
|
|
333
|
+
field: fieldPath,
|
|
334
|
+
message: `Required field '${missing}' missing${path ? ` at '${path}'` : ''}`,
|
|
335
|
+
expected: 'present',
|
|
336
|
+
received: 'undefined',
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
case 'type': {
|
|
340
|
+
const params = err.params;
|
|
341
|
+
const expected = Array.isArray(params.type)
|
|
342
|
+
? params.type.join('|')
|
|
343
|
+
: params.type ?? 'unknown';
|
|
344
|
+
const received = jsonTypeOf(resolveAtPath(root, err.instancePath));
|
|
345
|
+
return {
|
|
346
|
+
field: path,
|
|
347
|
+
message: err.message ?? `Type mismatch at '${path || '<root>'}'`,
|
|
348
|
+
expected,
|
|
349
|
+
received,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
case 'enum': {
|
|
353
|
+
const params = err.params;
|
|
354
|
+
const allowed = params.allowedValues ?? [];
|
|
355
|
+
const value = resolveAtPath(root, err.instancePath);
|
|
356
|
+
return {
|
|
357
|
+
field: path,
|
|
358
|
+
message: err.message ?? `Enum mismatch at '${path || '<root>'}'`,
|
|
359
|
+
expected: allowed.map(v => JSON.stringify(v)).join('|'),
|
|
360
|
+
received: JSON.stringify(value),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
case 'const': {
|
|
364
|
+
const params = err.params;
|
|
365
|
+
const value = resolveAtPath(root, err.instancePath);
|
|
366
|
+
return {
|
|
367
|
+
field: path,
|
|
368
|
+
message: err.message ?? `Const mismatch at '${path || '<root>'}'`,
|
|
369
|
+
expected: JSON.stringify(params.allowedValue),
|
|
370
|
+
received: JSON.stringify(value),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
case 'pattern': {
|
|
374
|
+
const params = err.params;
|
|
375
|
+
const value = resolveAtPath(root, err.instancePath);
|
|
376
|
+
return {
|
|
377
|
+
field: path,
|
|
378
|
+
message: err.message ?? `Pattern mismatch at '${path || '<root>'}'`,
|
|
379
|
+
expected: params.pattern ?? '<pattern>',
|
|
380
|
+
received: typeof value === 'string' ? value : JSON.stringify(value),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
default: {
|
|
384
|
+
return {
|
|
385
|
+
field: path,
|
|
386
|
+
message: err.message ?? `Validation failed${path ? ` at '${path}'` : ''} (${err.keyword})`,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Convert Ajv slash-style instance path to our bracket-dot field path.
|
|
393
|
+
*
|
|
394
|
+
* Examples:
|
|
395
|
+
* `''` → `''`
|
|
396
|
+
* `'/todos'` → `'todos'`
|
|
397
|
+
* `'/todos/0'` → `'todos[0]'`
|
|
398
|
+
* `'/todos/0/done'` → `'todos[0].done'`
|
|
399
|
+
* `'/users/alice/age'` → `'users.alice.age'`
|
|
400
|
+
*
|
|
401
|
+
* Numeric segments become bracket indices; everything else dot-joins.
|
|
402
|
+
* Ajv pre-decodes `~0`/`~1` JSON Pointer escapes, so a key with `/`
|
|
403
|
+
* still arrives slash-free.
|
|
404
|
+
*/
|
|
405
|
+
function pathFromInstancePath(instancePath) {
|
|
406
|
+
if (!instancePath)
|
|
407
|
+
return '';
|
|
408
|
+
const parts = instancePath.slice(1).split('/');
|
|
409
|
+
let out = '';
|
|
410
|
+
for (const part of parts) {
|
|
411
|
+
if (/^\d+$/.test(part)) {
|
|
412
|
+
out += `[${part}]`;
|
|
413
|
+
}
|
|
414
|
+
else if (out === '') {
|
|
415
|
+
out = part;
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
out += `.${part}`;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return out;
|
|
422
|
+
}
|
|
423
|
+
function resolveAtPath(root, instancePath) {
|
|
424
|
+
if (!instancePath)
|
|
425
|
+
return root;
|
|
426
|
+
const parts = instancePath.slice(1).split('/');
|
|
427
|
+
let cur = root;
|
|
428
|
+
for (const part of parts) {
|
|
429
|
+
if (cur === null || cur === undefined)
|
|
430
|
+
return undefined;
|
|
431
|
+
if (Array.isArray(cur)) {
|
|
432
|
+
const idx = Number(part);
|
|
433
|
+
if (!Number.isInteger(idx))
|
|
434
|
+
return undefined;
|
|
435
|
+
cur = cur[idx];
|
|
436
|
+
}
|
|
437
|
+
else if (typeof cur === 'object') {
|
|
438
|
+
cur = cur[part];
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
return undefined;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return cur;
|
|
445
|
+
}
|
|
446
|
+
function jsonTypeOf(value) {
|
|
447
|
+
if (value === null)
|
|
448
|
+
return 'null';
|
|
449
|
+
if (Array.isArray(value))
|
|
450
|
+
return 'array';
|
|
451
|
+
return typeof value;
|
|
452
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-hash.d.ts","sourceRoot":"","sources":["../../src/validation/content-hash.ts"],"names":[],"mappings":"AAmBA,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAExD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// packages/protocol/src/validation/content-hash.ts
|
|
2
|
+
//
|
|
3
|
+
// SHA-256 content hashing for compiled UI assets — the canonical identity
|
|
4
|
+
// function for cached + registered UIs.
|
|
5
|
+
//
|
|
6
|
+
// Why this lives in its own module (not `ui-security.ts`): `createHash`
|
|
7
|
+
// only ships in Node's `node:crypto` builtin. Re-exporting it from the
|
|
8
|
+
// protocol's root barrel drags `node:crypto` into every downstream
|
|
9
|
+
// bundler's module graph — including browser apps like Studio, where
|
|
10
|
+
// webpack refuses to resolve the `node:` scheme and the whole page fails
|
|
11
|
+
// to compile. Keeping this in a server-only subpath lets the root barrel
|
|
12
|
+
// stay browser-safe.
|
|
13
|
+
//
|
|
14
|
+
// Consumers (all server-side): `core/src/validation/ui-compiler.ts`,
|
|
15
|
+
// `cloud/amplify/functions/rest-api/cli-api/ui-register-handler.ts`.
|
|
16
|
+
// Import as `@ggui-ai/protocol/content-hash` — never from the barrel.
|
|
17
|
+
import { createHash } from 'node:crypto';
|
|
18
|
+
/** SHA-256 content hash (16-char hex) — canonical identity for a compiled UI. */
|
|
19
|
+
export function contentHash(compiledCode) {
|
|
20
|
+
return createHash('sha256').update(compiledCode).digest('hex').slice(0, 16);
|
|
21
|
+
}
|