@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,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Patterns stripped from stringified errors before emission on
|
|
3
|
+
* `_ggui:contract-error`. The default set covers the common credential
|
|
4
|
+
* shapes that show up in stack traces by accident (URLs with token query
|
|
5
|
+
* params, Bearer headers captured by retry libraries, env-var dumps).
|
|
6
|
+
*
|
|
7
|
+
* Each regex replaces the matched span with `[REDACTED]`. Replacement is
|
|
8
|
+
* conservative — we'd rather over-redact than leak. Operators who need
|
|
9
|
+
* finer control inject a custom sanitizer via
|
|
10
|
+
* `createSessionChannelServer({ sanitizeCausedBy })`.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_CREDENTIAL_PATTERNS: readonly RegExp[];
|
|
13
|
+
/** Default max length for a sanitized `causedBy` string. */
|
|
14
|
+
export declare const DEFAULT_CAUSED_BY_MAX_LENGTH = 2048;
|
|
15
|
+
/** Truncation marker appended when `causedBy` exceeds the max length. */
|
|
16
|
+
export declare const TRUNCATION_MARKER = "\n\u2026[truncated]";
|
|
17
|
+
/**
|
|
18
|
+
* Sanitize a stringified error (typically `err.stack`) before it's
|
|
19
|
+
* written to `ContractErrorPayload.error.causedBy`.
|
|
20
|
+
*
|
|
21
|
+
* Applies every pattern in {@link DEFAULT_CREDENTIAL_PATTERNS} in order,
|
|
22
|
+
* replacing matches with `[REDACTED]`. If the result exceeds `maxLength`,
|
|
23
|
+
* the tail is replaced with {@link TRUNCATION_MARKER}.
|
|
24
|
+
*
|
|
25
|
+
* The function is pure and deterministic — the same `raw` produces the
|
|
26
|
+
* same output across runs. Safe to call in hot paths; patterns are
|
|
27
|
+
* pre-compiled module-level regexes.
|
|
28
|
+
*
|
|
29
|
+
* @param raw - The stringified error (usually `err.stack`).
|
|
30
|
+
* @param maxLength - Maximum length of returned string. Default 2048.
|
|
31
|
+
* @param patterns - Override the default credential-pattern set. Useful
|
|
32
|
+
* for tests or operators who want stricter behavior. Passing an empty
|
|
33
|
+
* array disables pattern replacement (truncation still applies).
|
|
34
|
+
*/
|
|
35
|
+
export declare function sanitizeCausedBy(raw: string, maxLength?: number, patterns?: readonly RegExp[]): string;
|
|
36
|
+
/**
|
|
37
|
+
* Signature of the sanitizer hook operators can inject into
|
|
38
|
+
* `createSessionChannelServer`. Receives the raw stringified error
|
|
39
|
+
* (typically `err.stack`) and MUST return a safe-to-emit string.
|
|
40
|
+
*
|
|
41
|
+
* Defaults to {@link sanitizeCausedBy} when no override is supplied. A
|
|
42
|
+
* pass-through function that returns `raw` unchanged is valid but
|
|
43
|
+
* discouraged — it re-enables the leak this module was added to close.
|
|
44
|
+
*/
|
|
45
|
+
export type SanitizeCausedBy = (raw: string) => string;
|
|
46
|
+
//# sourceMappingURL=sanitize-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize-error.d.ts","sourceRoot":"","sources":["../../src/validation/sanitize-error.ts"],"names":[],"mappings":"AA0BA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,MAAM,EAWxD,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,yEAAyE;AACzE,eAAO,MAAM,iBAAiB,wBAAmB,CAAC;AAElD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,GAAE,MAAqC,EAChD,QAAQ,GAAE,SAAS,MAAM,EAAgC,GACxD,MAAM,CAgBR;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// packages/protocol/src/validation/sanitize-error.ts
|
|
2
|
+
//
|
|
3
|
+
// Credential-leak sanitizer for `ContractErrorPayload.error.causedBy`.
|
|
4
|
+
//
|
|
5
|
+
// The `causedBy` field on a contract-error envelope carries the
|
|
6
|
+
// stringified original error — typically `Error.stack`. Stacks frequently
|
|
7
|
+
// contain URLs with query-param tokens (`?token=...`, `?api_key=...`),
|
|
8
|
+
// Authorization header values captured by over-eager logging libraries,
|
|
9
|
+
// and env-var dumps. The envelope flows on the reserved
|
|
10
|
+
// `_ggui:contract-error` channel with `replay: 'all'`, so anything that
|
|
11
|
+
// lands in `causedBy` persists in the session ring buffer and is visible
|
|
12
|
+
// in operator tools (SessionInspector activity panels). If an operator
|
|
13
|
+
// shares a bug report with that data, the credential leaks externally.
|
|
14
|
+
//
|
|
15
|
+
// This module's default posture is "sanitize before emission": the
|
|
16
|
+
// session-channel router pipes every `err.stack` through
|
|
17
|
+
// {@link sanitizeCausedBy} before populating `causedBy`. Operators who
|
|
18
|
+
// need stricter sanitization can inject their own function via
|
|
19
|
+
// `createSessionChannelServer({ sanitizeCausedBy })`.
|
|
20
|
+
//
|
|
21
|
+
// Scope: this is a DEFENSE-IN-DEPTH belt, not the sole line. Libraries
|
|
22
|
+
// that handle secrets should already avoid embedding them in error
|
|
23
|
+
// messages. This sanitizer exists because stack traces are produced
|
|
24
|
+
// opportunistically by code that didn't think about leaking and we'd
|
|
25
|
+
// rather redact conservatively than ship raw stacks.
|
|
26
|
+
/**
|
|
27
|
+
* Patterns stripped from stringified errors before emission on
|
|
28
|
+
* `_ggui:contract-error`. The default set covers the common credential
|
|
29
|
+
* shapes that show up in stack traces by accident (URLs with token query
|
|
30
|
+
* params, Bearer headers captured by retry libraries, env-var dumps).
|
|
31
|
+
*
|
|
32
|
+
* Each regex replaces the matched span with `[REDACTED]`. Replacement is
|
|
33
|
+
* conservative — we'd rather over-redact than leak. Operators who need
|
|
34
|
+
* finer control inject a custom sanitizer via
|
|
35
|
+
* `createSessionChannelServer({ sanitizeCausedBy })`.
|
|
36
|
+
*/
|
|
37
|
+
export const DEFAULT_CREDENTIAL_PATTERNS = [
|
|
38
|
+
// Bearer / Basic header values. `Bearer <token>` / `Authorization: Basic <base64>`.
|
|
39
|
+
/Bearer\s+[A-Za-z0-9._\-~+/]+=*/gi,
|
|
40
|
+
/Basic\s+[A-Za-z0-9+/]+=*/gi,
|
|
41
|
+
/Authorization:\s*\S+/gi,
|
|
42
|
+
// Query-param-style secrets — matches `?token=...` / `&api_key=...` etc.
|
|
43
|
+
// Up to the next `&`, space, quote, or end-of-string.
|
|
44
|
+
/([?&](?:token|api[_-]?key|access[_-]?token|refresh[_-]?token|password|secret|session[_-]?id)=)[^&\s"']+/gi,
|
|
45
|
+
// Env-var-style `KEY=value` where KEY is obviously secret-ish and value
|
|
46
|
+
// is non-empty. Bounds the value to the next whitespace/quote.
|
|
47
|
+
/\b(AWS_[A-Z_]+|ANTHROPIC_API_KEY|OPENAI_API_KEY|GOOGLE_API_KEY|OPENROUTER_API_KEY|COGNITO_[A-Z_]+|DATABASE_URL|DB_PASSWORD|REDIS_URL)=\S+/gi,
|
|
48
|
+
];
|
|
49
|
+
/** Default max length for a sanitized `causedBy` string. */
|
|
50
|
+
export const DEFAULT_CAUSED_BY_MAX_LENGTH = 2048;
|
|
51
|
+
/** Truncation marker appended when `causedBy` exceeds the max length. */
|
|
52
|
+
export const TRUNCATION_MARKER = '\n…[truncated]';
|
|
53
|
+
/**
|
|
54
|
+
* Sanitize a stringified error (typically `err.stack`) before it's
|
|
55
|
+
* written to `ContractErrorPayload.error.causedBy`.
|
|
56
|
+
*
|
|
57
|
+
* Applies every pattern in {@link DEFAULT_CREDENTIAL_PATTERNS} in order,
|
|
58
|
+
* replacing matches with `[REDACTED]`. If the result exceeds `maxLength`,
|
|
59
|
+
* the tail is replaced with {@link TRUNCATION_MARKER}.
|
|
60
|
+
*
|
|
61
|
+
* The function is pure and deterministic — the same `raw` produces the
|
|
62
|
+
* same output across runs. Safe to call in hot paths; patterns are
|
|
63
|
+
* pre-compiled module-level regexes.
|
|
64
|
+
*
|
|
65
|
+
* @param raw - The stringified error (usually `err.stack`).
|
|
66
|
+
* @param maxLength - Maximum length of returned string. Default 2048.
|
|
67
|
+
* @param patterns - Override the default credential-pattern set. Useful
|
|
68
|
+
* for tests or operators who want stricter behavior. Passing an empty
|
|
69
|
+
* array disables pattern replacement (truncation still applies).
|
|
70
|
+
*/
|
|
71
|
+
export function sanitizeCausedBy(raw, maxLength = DEFAULT_CAUSED_BY_MAX_LENGTH, patterns = DEFAULT_CREDENTIAL_PATTERNS) {
|
|
72
|
+
let out = raw;
|
|
73
|
+
for (const p of patterns) {
|
|
74
|
+
// Special-case the query-param pattern: we want to keep the key name
|
|
75
|
+
// so callers can still see WHERE the secret was, just not what it
|
|
76
|
+
// was. Every other pattern is replaced whole.
|
|
77
|
+
if (p.source.includes('token|api')) {
|
|
78
|
+
out = out.replace(p, '$1[REDACTED]');
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
out = out.replace(p, '[REDACTED]');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (out.length > maxLength) {
|
|
85
|
+
out = out.slice(0, maxLength) + TRUNCATION_MARKER;
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol-level schema-compatibility invariants for `DataContract`.
|
|
3
|
+
*
|
|
4
|
+
* Ships one stable error code:
|
|
5
|
+
*
|
|
6
|
+
* - `CTR_SCHEMA_INCOMPAT` — an `actionSpec[*].schema` is not a
|
|
7
|
+
* subset of the referenced `agentCapabilities.tools[*].inputSchema`,
|
|
8
|
+
* OR a `streamSpec[*].schema` is not a superset of the
|
|
9
|
+
* referenced `agentCapabilities.tools[*].outputSchema`. Direction is
|
|
10
|
+
* fixed by the data flow: action payloads travel UI → tool, so
|
|
11
|
+
* the action's accepted values must fit the tool's accepted
|
|
12
|
+
* inputs; stream payloads travel tool → channel, so the
|
|
13
|
+
* channel's accepted values must cover the tool's possible
|
|
14
|
+
* outputs.
|
|
15
|
+
*
|
|
16
|
+
* **Scope vs server-level F4.** This invariant runs PURELY against
|
|
17
|
+
* the contract's own catalog — `actionSpec[*].schema` /
|
|
18
|
+
* `streamSpec[*].schema` vs `agentCapabilities.tools[*].inputSchema` /
|
|
19
|
+
* `outputSchema`, all of which are author-declared JSON Schemas
|
|
20
|
+
* already on the contract. No tool registry, no zod conversion, no
|
|
21
|
+
* server state. The server-level F4 check (`checkStackItemSchemaCompat`
|
|
22
|
+
* in `@ggui-ai/mcp-server`) compares the same `actionSpec` /
|
|
23
|
+
* `streamSpec` schemas against the SERVER-REGISTERED tools' actual
|
|
24
|
+
* zod schemas; it covers the operator-side "did the deployed tool
|
|
25
|
+
* change schema since the contract was authored?" failure mode. Both
|
|
26
|
+
* checks compose:
|
|
27
|
+
*
|
|
28
|
+
* - Protocol-level CTR_SCHEMA_INCOMPAT: author-visible bug.
|
|
29
|
+
* "Your contract's action.schema doesn't fit the inputSchema
|
|
30
|
+
* you yourself declared on this tool entry."
|
|
31
|
+
* - Server-level SchemaCompatError: operator-visible bug. "The
|
|
32
|
+
* deployed tool's actual inputSchema doesn't match what the
|
|
33
|
+
* contract declares."
|
|
34
|
+
*
|
|
35
|
+
* Skipped silently when the referenced agentTool has no declared
|
|
36
|
+
* `inputSchema`/`outputSchema` (the catalog entry is incomplete; the
|
|
37
|
+
* check has no anchor and degrades to "no opinion"). Skipped when
|
|
38
|
+
* the action/channel has no `schema` (void-payload entries — nothing
|
|
39
|
+
* to compare).
|
|
40
|
+
*
|
|
41
|
+
* Companion to `cross-references` and `name-invariants` — together
|
|
42
|
+
* they ship companion rule registries to cross-references and
|
|
43
|
+
* name-invariants under the unified `lintContract` API.
|
|
44
|
+
*/
|
|
45
|
+
import type { AgentCapabilitiesSpec, DataContract } from '../types/data-contract';
|
|
46
|
+
import type { ContractViolation } from './contract-validator';
|
|
47
|
+
/**
|
|
48
|
+
* Stable error code for protocol-level schema-compatibility
|
|
49
|
+
* violations on action ⊆ inputSchema or channel ⊇ outputSchema.
|
|
50
|
+
*/
|
|
51
|
+
export declare const CTR_SCHEMA_INCOMPAT = "CTR_SCHEMA_INCOMPAT";
|
|
52
|
+
/**
|
|
53
|
+
* Discriminator on the side of the contract the violation came
|
|
54
|
+
* from. Surfaced on the violation so consumers can render the
|
|
55
|
+
* action vs. stream cases differently without parsing the field
|
|
56
|
+
* path.
|
|
57
|
+
*/
|
|
58
|
+
export type SchemaCompatSide = 'action' | 'stream';
|
|
59
|
+
/**
|
|
60
|
+
* Schema-compat invariant violation. Carries the stable error code
|
|
61
|
+
* plus side / specName / toolName so consumers can pivot rendering
|
|
62
|
+
* on the action vs stream case without parsing the field path.
|
|
63
|
+
*
|
|
64
|
+
* The granular `isSchemaSubset` violation list is NOT carried on the
|
|
65
|
+
* violation (the rich `SubsetViolation` shape doesn't fit the
|
|
66
|
+
* `ContractViolation extends JsonObject` constraint). The message
|
|
67
|
+
* includes the first mismatch's reason + path; callers needing the
|
|
68
|
+
* full list can re-run {@link checkSchemaCompat} subcomponents
|
|
69
|
+
* directly.
|
|
70
|
+
*/
|
|
71
|
+
export interface SchemaCompatViolation extends ContractViolation {
|
|
72
|
+
code: typeof CTR_SCHEMA_INCOMPAT;
|
|
73
|
+
/** Which side of the contract was checked. */
|
|
74
|
+
side: SchemaCompatSide;
|
|
75
|
+
/** The action / channel name on the contract. */
|
|
76
|
+
specName: string;
|
|
77
|
+
/** The agentCapabilities.tools key resolved to perform the check. */
|
|
78
|
+
toolName: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Validate every `actionSpec[*].schema` is a subset of the
|
|
82
|
+
* referenced `agentCapabilities.tools[nextStep].inputSchema`.
|
|
83
|
+
*
|
|
84
|
+
* Skips entries where:
|
|
85
|
+
* - no `nextStep` is declared (pure event signal — the agent owns
|
|
86
|
+
* dispatch; nothing to compare),
|
|
87
|
+
* - the referenced agentTool has no declared `inputSchema` (the
|
|
88
|
+
* catalog entry is incomplete; the check has no anchor),
|
|
89
|
+
* - the referenced agentTool is missing entirely (a separate
|
|
90
|
+
* invariant `CTR_REF_NEXT_STEP` covers that).
|
|
91
|
+
*
|
|
92
|
+
* When the action has no `schema`, the wire shape is modeled as
|
|
93
|
+
* `{type: 'object', properties: {}, additionalProperties: false}` —
|
|
94
|
+
* "void payload." This matches the F4 convention so the protocol-
|
|
95
|
+
* level check stays compatible with the server-level posture.
|
|
96
|
+
*/
|
|
97
|
+
export declare function checkActionSchemaCompat(actionSpec: DataContract['actionSpec'] | undefined, agentCapabilities: AgentCapabilitiesSpec | undefined): SchemaCompatViolation[];
|
|
98
|
+
/**
|
|
99
|
+
* Validate every `streamSpec[*].schema` is a SUPERSET of the
|
|
100
|
+
* referenced `agentCapabilities.tools[source.tool].outputSchema`. Direction
|
|
101
|
+
* inverts: streams travel tool → channel, so the channel schema
|
|
102
|
+
* must accept everything the tool can return.
|
|
103
|
+
*
|
|
104
|
+
* Skips entries where:
|
|
105
|
+
* - no `source` is declared,
|
|
106
|
+
* - the referenced agentTool has no declared `outputSchema`,
|
|
107
|
+
* - the referenced agentTool is missing entirely
|
|
108
|
+
* (`CTR_REF_STREAM_SOURCE` covers that),
|
|
109
|
+
* - the channel has no `schema` (declarative validation is
|
|
110
|
+
* impossible without the channel's accepted shape).
|
|
111
|
+
*/
|
|
112
|
+
export declare function checkStreamSchemaCompat(streamSpec: DataContract['streamSpec'] | undefined, agentCapabilities: AgentCapabilitiesSpec | undefined): SchemaCompatViolation[];
|
|
113
|
+
/**
|
|
114
|
+
* Run every protocol-level schema-compat invariant. Aggregates action
|
|
115
|
+
* + stream violations; order is stable (action checks first).
|
|
116
|
+
*/
|
|
117
|
+
export declare function checkSchemaCompat(contract: DataContract): SchemaCompatViolation[];
|
|
118
|
+
/**
|
|
119
|
+
* Throwable form of {@link checkSchemaCompat}. Use at protocol
|
|
120
|
+
* boundaries where a schema-compat violation is a contract bug the
|
|
121
|
+
* author must fix.
|
|
122
|
+
*/
|
|
123
|
+
export declare class SchemaCompatInvariantError extends Error {
|
|
124
|
+
readonly code: "schema_compat_incompat";
|
|
125
|
+
readonly violations: readonly SchemaCompatViolation[];
|
|
126
|
+
constructor(violations: readonly SchemaCompatViolation[]);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Throw-on-violation wrapper around {@link checkSchemaCompat}.
|
|
130
|
+
* No-op when the contract's schemas align with its own
|
|
131
|
+
* agentCapabilities catalog.
|
|
132
|
+
*
|
|
133
|
+
* Slots alongside `assertCrossReferences` + `assertNameInvariants`
|
|
134
|
+
* at push time. Different scope from the server-level
|
|
135
|
+
* `SchemaCompatError` thrown by `checkStackItemSchemaCompat` in
|
|
136
|
+
* `@ggui-ai/mcp-server`: this check uses ONLY the contract's own
|
|
137
|
+
* catalog; the server-level check uses the runtime tool registry.
|
|
138
|
+
*/
|
|
139
|
+
export declare function assertSchemaCompat(contract: DataContract): void;
|
|
140
|
+
//# sourceMappingURL=schema-compat-invariants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-compat-invariants.d.ts","sourceRoot":"","sources":["../../src/validation/schema-compat-invariants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAGV,qBAAqB,EACrB,YAAY,EAGb,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAQ9D;;;GAGG;AACH,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEnD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,qBAAsB,SAAQ,iBAAiB;IAC9D,IAAI,EAAE,OAAO,mBAAmB,CAAC;IACjC,8CAA8C;IAC9C,IAAI,EAAE,gBAAgB,CAAC;IACvB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;CAClB;AASD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,GAAG,SAAS,EAClD,iBAAiB,EAAE,qBAAqB,GAAG,SAAS,GACnD,qBAAqB,EAAE,CAqCzB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,GAAG,SAAS,EAClD,iBAAiB,EAAE,qBAAqB,GAAG,SAAS,GACnD,qBAAqB,EAAE,CAuCzB;AAQD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,GACrB,qBAAqB,EAAE,CAKzB;AAED;;;;GAIG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,QAAQ,CAAC,IAAI,EAAG,wBAAwB,CAAU;IAClD,QAAQ,CAAC,UAAU,EAAE,SAAS,qBAAqB,EAAE,CAAC;gBAE1C,UAAU,EAAE,SAAS,qBAAqB,EAAE;CAQzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAK/D"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol-level schema-compatibility invariants for `DataContract`.
|
|
3
|
+
*
|
|
4
|
+
* Ships one stable error code:
|
|
5
|
+
*
|
|
6
|
+
* - `CTR_SCHEMA_INCOMPAT` — an `actionSpec[*].schema` is not a
|
|
7
|
+
* subset of the referenced `agentCapabilities.tools[*].inputSchema`,
|
|
8
|
+
* OR a `streamSpec[*].schema` is not a superset of the
|
|
9
|
+
* referenced `agentCapabilities.tools[*].outputSchema`. Direction is
|
|
10
|
+
* fixed by the data flow: action payloads travel UI → tool, so
|
|
11
|
+
* the action's accepted values must fit the tool's accepted
|
|
12
|
+
* inputs; stream payloads travel tool → channel, so the
|
|
13
|
+
* channel's accepted values must cover the tool's possible
|
|
14
|
+
* outputs.
|
|
15
|
+
*
|
|
16
|
+
* **Scope vs server-level F4.** This invariant runs PURELY against
|
|
17
|
+
* the contract's own catalog — `actionSpec[*].schema` /
|
|
18
|
+
* `streamSpec[*].schema` vs `agentCapabilities.tools[*].inputSchema` /
|
|
19
|
+
* `outputSchema`, all of which are author-declared JSON Schemas
|
|
20
|
+
* already on the contract. No tool registry, no zod conversion, no
|
|
21
|
+
* server state. The server-level F4 check (`checkStackItemSchemaCompat`
|
|
22
|
+
* in `@ggui-ai/mcp-server`) compares the same `actionSpec` /
|
|
23
|
+
* `streamSpec` schemas against the SERVER-REGISTERED tools' actual
|
|
24
|
+
* zod schemas; it covers the operator-side "did the deployed tool
|
|
25
|
+
* change schema since the contract was authored?" failure mode. Both
|
|
26
|
+
* checks compose:
|
|
27
|
+
*
|
|
28
|
+
* - Protocol-level CTR_SCHEMA_INCOMPAT: author-visible bug.
|
|
29
|
+
* "Your contract's action.schema doesn't fit the inputSchema
|
|
30
|
+
* you yourself declared on this tool entry."
|
|
31
|
+
* - Server-level SchemaCompatError: operator-visible bug. "The
|
|
32
|
+
* deployed tool's actual inputSchema doesn't match what the
|
|
33
|
+
* contract declares."
|
|
34
|
+
*
|
|
35
|
+
* Skipped silently when the referenced agentTool has no declared
|
|
36
|
+
* `inputSchema`/`outputSchema` (the catalog entry is incomplete; the
|
|
37
|
+
* check has no anchor and degrades to "no opinion"). Skipped when
|
|
38
|
+
* the action/channel has no `schema` (void-payload entries — nothing
|
|
39
|
+
* to compare).
|
|
40
|
+
*
|
|
41
|
+
* Companion to `cross-references` and `name-invariants` — together
|
|
42
|
+
* they ship companion rule registries to cross-references and
|
|
43
|
+
* name-invariants under the unified `lintContract` API.
|
|
44
|
+
*/
|
|
45
|
+
import { isSchemaSubset } from './schema-subset.js';
|
|
46
|
+
// `SubsetViolation` is used as the parameter type for the local
|
|
47
|
+
// `describe` helper below; it is intentionally NOT carried on the
|
|
48
|
+
// public `SchemaCompatViolation` (the rich shape doesn't fit
|
|
49
|
+
// `ContractViolation extends JsonObject`).
|
|
50
|
+
/**
|
|
51
|
+
* Stable error code for protocol-level schema-compatibility
|
|
52
|
+
* violations on action ⊆ inputSchema or channel ⊇ outputSchema.
|
|
53
|
+
*/
|
|
54
|
+
export const CTR_SCHEMA_INCOMPAT = 'CTR_SCHEMA_INCOMPAT';
|
|
55
|
+
function buildToolMap(agentCapabilities) {
|
|
56
|
+
if (!agentCapabilities)
|
|
57
|
+
return new Map();
|
|
58
|
+
return new Map(Object.entries(agentCapabilities.tools));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Validate every `actionSpec[*].schema` is a subset of the
|
|
62
|
+
* referenced `agentCapabilities.tools[nextStep].inputSchema`.
|
|
63
|
+
*
|
|
64
|
+
* Skips entries where:
|
|
65
|
+
* - no `nextStep` is declared (pure event signal — the agent owns
|
|
66
|
+
* dispatch; nothing to compare),
|
|
67
|
+
* - the referenced agentTool has no declared `inputSchema` (the
|
|
68
|
+
* catalog entry is incomplete; the check has no anchor),
|
|
69
|
+
* - the referenced agentTool is missing entirely (a separate
|
|
70
|
+
* invariant `CTR_REF_NEXT_STEP` covers that).
|
|
71
|
+
*
|
|
72
|
+
* When the action has no `schema`, the wire shape is modeled as
|
|
73
|
+
* `{type: 'object', properties: {}, additionalProperties: false}` —
|
|
74
|
+
* "void payload." This matches the F4 convention so the protocol-
|
|
75
|
+
* level check stays compatible with the server-level posture.
|
|
76
|
+
*/
|
|
77
|
+
export function checkActionSchemaCompat(actionSpec, agentCapabilities) {
|
|
78
|
+
if (!actionSpec)
|
|
79
|
+
return [];
|
|
80
|
+
const tools = buildToolMap(agentCapabilities);
|
|
81
|
+
const violations = [];
|
|
82
|
+
for (const [actionName, entry] of Object.entries(actionSpec)) {
|
|
83
|
+
if (!entry || typeof entry !== 'object')
|
|
84
|
+
continue;
|
|
85
|
+
const toolName = entry.nextStep;
|
|
86
|
+
if (typeof toolName !== 'string' || toolName.length === 0)
|
|
87
|
+
continue;
|
|
88
|
+
const tool = tools.get(toolName);
|
|
89
|
+
if (!tool)
|
|
90
|
+
continue; // CTR_REF_NEXT_STEP covers this
|
|
91
|
+
const toolInput = tool.inputSchema;
|
|
92
|
+
if (!toolInput)
|
|
93
|
+
continue; // catalog entry incomplete — no anchor
|
|
94
|
+
const actionSchema = entry.schema ?? {
|
|
95
|
+
type: 'object',
|
|
96
|
+
properties: {},
|
|
97
|
+
additionalProperties: false,
|
|
98
|
+
};
|
|
99
|
+
const result = isSchemaSubset(toolInput, actionSchema);
|
|
100
|
+
if (result.compatible)
|
|
101
|
+
continue;
|
|
102
|
+
violations.push({
|
|
103
|
+
code: CTR_SCHEMA_INCOMPAT,
|
|
104
|
+
side: 'action',
|
|
105
|
+
specName: actionName,
|
|
106
|
+
toolName,
|
|
107
|
+
field: `actionSpec.${actionName}.schema`,
|
|
108
|
+
message: `actionSpec.${actionName}.schema is not a subset of agentCapabilities.tools.${toolName}.inputSchema — values the action accepts would be rejected by the tool. ${describe(result.violations)}`,
|
|
109
|
+
expected: `subset of agentCapabilities.tools.${toolName}.inputSchema`,
|
|
110
|
+
received: 'incompatible',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return violations;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Validate every `streamSpec[*].schema` is a SUPERSET of the
|
|
117
|
+
* referenced `agentCapabilities.tools[source.tool].outputSchema`. Direction
|
|
118
|
+
* inverts: streams travel tool → channel, so the channel schema
|
|
119
|
+
* must accept everything the tool can return.
|
|
120
|
+
*
|
|
121
|
+
* Skips entries where:
|
|
122
|
+
* - no `source` is declared,
|
|
123
|
+
* - the referenced agentTool has no declared `outputSchema`,
|
|
124
|
+
* - the referenced agentTool is missing entirely
|
|
125
|
+
* (`CTR_REF_STREAM_SOURCE` covers that),
|
|
126
|
+
* - the channel has no `schema` (declarative validation is
|
|
127
|
+
* impossible without the channel's accepted shape).
|
|
128
|
+
*/
|
|
129
|
+
export function checkStreamSchemaCompat(streamSpec, agentCapabilities) {
|
|
130
|
+
if (!streamSpec)
|
|
131
|
+
return [];
|
|
132
|
+
const tools = buildToolMap(agentCapabilities);
|
|
133
|
+
const violations = [];
|
|
134
|
+
for (const [channelName, entry] of Object.entries(streamSpec)) {
|
|
135
|
+
if (!entry || typeof entry !== 'object')
|
|
136
|
+
continue;
|
|
137
|
+
const source = entry.source;
|
|
138
|
+
if (!source)
|
|
139
|
+
continue;
|
|
140
|
+
const toolName = source.tool;
|
|
141
|
+
if (typeof toolName !== 'string' || toolName.length === 0)
|
|
142
|
+
continue;
|
|
143
|
+
const tool = tools.get(toolName);
|
|
144
|
+
if (!tool)
|
|
145
|
+
continue; // CTR_REF_STREAM_SOURCE covers this
|
|
146
|
+
const toolOutput = tool.outputSchema;
|
|
147
|
+
if (!toolOutput)
|
|
148
|
+
continue;
|
|
149
|
+
const channelSchema = entry.schema;
|
|
150
|
+
if (!channelSchema)
|
|
151
|
+
continue;
|
|
152
|
+
// Direction: every tool-returned value must be accepted by the
|
|
153
|
+
// channel schema. Channel is the superset / permissive side,
|
|
154
|
+
// tool-return is the restricted side.
|
|
155
|
+
const result = isSchemaSubset(channelSchema, toolOutput);
|
|
156
|
+
if (result.compatible)
|
|
157
|
+
continue;
|
|
158
|
+
violations.push({
|
|
159
|
+
code: CTR_SCHEMA_INCOMPAT,
|
|
160
|
+
side: 'stream',
|
|
161
|
+
specName: channelName,
|
|
162
|
+
toolName,
|
|
163
|
+
field: `streamSpec.${channelName}.schema`,
|
|
164
|
+
message: `streamSpec.${channelName}.schema does not accept all values agentCapabilities.tools.${toolName}.outputSchema produces — some tool outputs would fail channel validation. ${describe(result.violations)}`,
|
|
165
|
+
expected: `superset of agentCapabilities.tools.${toolName}.outputSchema`,
|
|
166
|
+
received: 'incompatible',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return violations;
|
|
170
|
+
}
|
|
171
|
+
function describe(violations) {
|
|
172
|
+
if (violations.length === 0)
|
|
173
|
+
return '';
|
|
174
|
+
const first = violations[0];
|
|
175
|
+
return `First mismatch: ${first.reason} at ${first.path || '<root>'}.`;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Run every protocol-level schema-compat invariant. Aggregates action
|
|
179
|
+
* + stream violations; order is stable (action checks first).
|
|
180
|
+
*/
|
|
181
|
+
export function checkSchemaCompat(contract) {
|
|
182
|
+
return [
|
|
183
|
+
...checkActionSchemaCompat(contract.actionSpec, contract.agentCapabilities),
|
|
184
|
+
...checkStreamSchemaCompat(contract.streamSpec, contract.agentCapabilities),
|
|
185
|
+
];
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Throwable form of {@link checkSchemaCompat}. Use at protocol
|
|
189
|
+
* boundaries where a schema-compat violation is a contract bug the
|
|
190
|
+
* author must fix.
|
|
191
|
+
*/
|
|
192
|
+
export class SchemaCompatInvariantError extends Error {
|
|
193
|
+
code = 'schema_compat_incompat';
|
|
194
|
+
violations;
|
|
195
|
+
constructor(violations) {
|
|
196
|
+
const summary = violations
|
|
197
|
+
.map((v) => `[${v.code}] ${v.message}`)
|
|
198
|
+
.join(' | ');
|
|
199
|
+
super(`Contract schema-compat invariants failed: ${summary}`);
|
|
200
|
+
this.name = 'SchemaCompatInvariantError';
|
|
201
|
+
this.violations = violations;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Throw-on-violation wrapper around {@link checkSchemaCompat}.
|
|
206
|
+
* No-op when the contract's schemas align with its own
|
|
207
|
+
* agentCapabilities catalog.
|
|
208
|
+
*
|
|
209
|
+
* Slots alongside `assertCrossReferences` + `assertNameInvariants`
|
|
210
|
+
* at push time. Different scope from the server-level
|
|
211
|
+
* `SchemaCompatError` thrown by `checkStackItemSchemaCompat` in
|
|
212
|
+
* `@ggui-ai/mcp-server`: this check uses ONLY the contract's own
|
|
213
|
+
* catalog; the server-level check uses the runtime tool registry.
|
|
214
|
+
*/
|
|
215
|
+
export function assertSchemaCompat(contract) {
|
|
216
|
+
const violations = checkSchemaCompat(contract);
|
|
217
|
+
if (violations.length > 0) {
|
|
218
|
+
throw new SchemaCompatInvariantError(violations);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer B (inner JSON Schema meta-validation) for a `DataContract`.
|
|
3
|
+
*
|
|
4
|
+
* Walks the six inner JSON Schema fields the agent authors:
|
|
5
|
+
* 1. `propsSpec.properties[*].schema`
|
|
6
|
+
* 2. `actionSpec[*].schema` (optional per entry)
|
|
7
|
+
* 3. `streamSpec[*].schema`
|
|
8
|
+
* 4. `contextSpec[*].schema`
|
|
9
|
+
* 5. `agentCapabilities.tools[*].inputSchema` (optional per entry)
|
|
10
|
+
* 6. `agentCapabilities.tools[*].outputSchema` (optional per entry)
|
|
11
|
+
*
|
|
12
|
+
* For each present schema, runs `compileForValidation()` from
|
|
13
|
+
* `ajv-runtime`. Ajv's `strict: true` mode throws on malformed JSON
|
|
14
|
+
* Schemas at compile-time — unknown keywords, missing `items` on
|
|
15
|
+
* array nodes, properties values that are not schemas, etc. This is
|
|
16
|
+
* the meta-validation pass: it asserts the contract's own type
|
|
17
|
+
* descriptions are well-formed BEFORE any runtime data flows.
|
|
18
|
+
*
|
|
19
|
+
* Failures collect into a single throw with every malformed field
|
|
20
|
+
* named — agents fix all of them in one round rather than retry-
|
|
21
|
+
* per-field.
|
|
22
|
+
*
|
|
23
|
+
* Designed to slot at:
|
|
24
|
+
* - `ggui_handshake` entry — validates `blueprintDraft.contract`
|
|
25
|
+
* before the negotiator runs.
|
|
26
|
+
* - `ggui_push` entry — validates the `effectiveContract` (either
|
|
27
|
+
* synth-amended or override) before any state mutation.
|
|
28
|
+
*
|
|
29
|
+
* Both call sites already run cross-reference and name-invariant
|
|
30
|
+
* assertions for the same author-recoverable failure class; this
|
|
31
|
+
* sits alongside them.
|
|
32
|
+
*/
|
|
33
|
+
import type { DataContract } from '../types/data-contract';
|
|
34
|
+
export interface SchemaMetaViolation {
|
|
35
|
+
/** Path to the malformed schema field (e.g., `propsSpec.properties.todos.schema`). */
|
|
36
|
+
field: string;
|
|
37
|
+
/** Ajv's compile-error message (truncated to fit the wire). */
|
|
38
|
+
message: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Typed error for contract schema meta-validation failures. Mirrors
|
|
42
|
+
* the shape of `CrossReferenceError` / `ContractViolationError` for
|
|
43
|
+
* symmetry — push/handshake catch and surface a structured
|
|
44
|
+
* `contract_schema_invalid` error to the agent.
|
|
45
|
+
*/
|
|
46
|
+
export declare class ContractSchemaMetaError extends Error {
|
|
47
|
+
readonly code: "contract_schema_invalid";
|
|
48
|
+
readonly violations: readonly SchemaMetaViolation[];
|
|
49
|
+
readonly hint: string;
|
|
50
|
+
constructor(violations: readonly SchemaMetaViolation[]);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Walks the contract's six inner JSON Schema fields and asserts
|
|
54
|
+
* each compiles cleanly under Ajv's strict mode. Throws
|
|
55
|
+
* {@link ContractSchemaMetaError} on the first push attempt that
|
|
56
|
+
* smuggles a malformed schema; collects every offender in one pass
|
|
57
|
+
* before throwing so the agent sees the full list.
|
|
58
|
+
*/
|
|
59
|
+
export declare function assertContractSchemasValid(contract: DataContract): void;
|
|
60
|
+
//# sourceMappingURL=schema-meta-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-meta-validation.d.ts","sourceRoot":"","sources":["../../src/validation/schema-meta-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAG3D,MAAM,WAAW,mBAAmB;IAClC,sFAAsF;IACtF,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,IAAI,EAAG,yBAAyB,CAAU;IACnD,QAAQ,CAAC,UAAU,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,UAAU,EAAE,SAAS,mBAAmB,EAAE;CAWvD;AAmCD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAuDvE"}
|